diff --git a/app/(app)/alerts/create-alert.tsx b/app/(app)/alerts/create-alert.tsx index d79a320d..1387bf84 100644 --- a/app/(app)/alerts/create-alert.tsx +++ b/app/(app)/alerts/create-alert.tsx @@ -7,7 +7,7 @@ import { createAlert, testRunAlert } from "@/app/actions/alerts"; import type { SerpResult } from "@/lib/alerts/valueserp"; // People-tracking templates (name / reputation / impersonation / legal) are -// withheld from the v1 launch picker pending a trust-and-safety policy. +// withheld from the v1 picker pending a trust-and-safety policy. const PICKABLE = ALERT_CATEGORIES.filter((c) => !c.gated); const RECENCIES: { value: Recency; label: string }[] = [ @@ -17,13 +17,7 @@ const RECENCIES: { value: Recency; label: string }[] = [ { value: "any", label: "Any time" }, ]; -export function CreateAlert({ - remainingSlots, - allowHourly, -}: { - remainingSlots: number; - allowHourly: boolean; -}) { +export function CreateAlert({ remainingSlots }: { remainingSlots: number }) { const router = useRouter(); const [pending, startTransition] = useTransition(); const [categoryKey, setCategoryKey] = useState(PICKABLE[0].key); @@ -49,18 +43,13 @@ export function CreateAlert({ function runTest() { setError(null); setNotice(null); - if (!term.trim()) { - setError(`${category.inputLabel} is required.`); - return; - } + if (!term.trim()) return setError(`${category.inputLabel} is required.`); startTransition(async () => { const res = await testRunAlert({ category: categoryKey, term, customQuery: term }); - if (!res.ok) { - setError(res.error); - return; - } + if (!res.ok) return setError(res.error); setPreview(res.results); - if (res.results.length === 0) setNotice("No current results — you'll be emailed when something new appears."); + if (res.results.length === 0) + setNotice("No current results — you'll be emailed when something new appears."); }); } @@ -68,22 +57,10 @@ export function CreateAlert({ e.preventDefault(); setError(null); setNotice(null); - if (!term.trim()) { - setError(`${category.inputLabel} is required.`); - return; - } + if (!term.trim()) return setError(`${category.inputLabel} is required.`); startTransition(async () => { - const res = await createAlert({ - category: categoryKey, - term, - customQuery: term, - recency, - frequency, - }); - if (!res.ok) { - setError(res.error); - return; - } + const res = await createAlert({ category: categoryKey, term, customQuery: term, recency, frequency }); + if (!res.ok) return setError(res.error); setTerm(""); setPreview(null); setNotice("Alert created. First check runs shortly."); @@ -135,11 +112,9 @@ export function CreateAlert({ value={frequency} onChange={(e) => setFrequency(e.target.value as "daily" | "hourly")} aria-label="Check frequency" - disabled={!allowHourly} - title={allowHourly ? "" : "Hourly checks are a paid feature"} > - + @@ -166,13 +141,7 @@ export function CreateAlert({ Current results preview (you'll only be emailed NEW ones)

{preview.map((r) => ( - + {r.title || r.url} {r.url} diff --git a/app/(app)/alerts/page.tsx b/app/(app)/alerts/page.tsx index ca4ec2f8..a63a18f5 100644 --- a/app/(app)/alerts/page.tsx +++ b/app/(app)/alerts/page.tsx @@ -27,8 +27,12 @@ type AlertRow = { function fmt(ts: string | null): string { if (!ts) return "not yet"; - const d = new Date(ts); - return d.toLocaleString(undefined, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }); + return new Date(ts).toLocaleString(undefined, { + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit", + }); } export default async function AlertsPage({ @@ -42,10 +46,9 @@ export default async function AlertsPage({ const { data: { user }, } = await supabase.auth.getUser(); - if (!user) redirect("/login"); + if (!user) redirect("/login?next=/alerts"); - // Post-signup: create the alert carried in the magic-link redirect, then - // strip the params so a refresh can't double-create. + // Post-signup magic-link redirect: create the pending alert, then clean the URL. if (newCat && getCategory(newCat)) { await createAlert({ category: newCat, term: term ?? "", customQuery: term ?? "" }); redirect("/alerts"); @@ -84,15 +87,10 @@ export default async function AlertsPage({
{activeCount}/{cap} active {Math.max(0, budget - used)} checks left this month - {plan === "free" && ( - - Upgrade - - )}
- +

diff --git a/lib/alerts/limits.ts b/lib/alerts/limits.ts index 4b63211c..e278f381 100644 --- a/lib/alerts/limits.ts +++ b/lib/alerts/limits.ts @@ -25,9 +25,11 @@ export const SERP_CALLS_PER_MONTH: Record = { team: 1_000_000, }; -// Only paid plans may run hourly; free is daily. -export function allowedFrequencies(plan: Plan): Array<"daily" | "hourly"> { - return plan === "free" ? ["daily"] : ["daily", "hourly"]; +// Hourly is currently free for everyone (no paywall on frequency yet — the +// per-account monthly SERP-call budget is what bounds cost). Revisit if/when +// hourly becomes a paid lever. +export function allowedFrequencies(_plan: Plan): Array<"daily" | "hourly"> { + return ["daily", "hourly"]; } // Results requested per poll and surfaced per email. Capping at 10 keeps SERP