diff --git a/app/(app)/alerts/alert-actions.tsx b/app/(marketing)/alerts/alert-actions.tsx similarity index 100% rename from app/(app)/alerts/alert-actions.tsx rename to app/(marketing)/alerts/alert-actions.tsx diff --git a/app/(app)/alerts/create-alert.tsx b/app/(marketing)/alerts/create-alert.tsx similarity index 83% rename from app/(app)/alerts/create-alert.tsx rename to app/(marketing)/alerts/create-alert.tsx index 1387bf84..e56c7c89 100644 --- a/app/(app)/alerts/create-alert.tsx +++ b/app/(marketing)/alerts/create-alert.tsx @@ -17,7 +17,19 @@ const RECENCIES: { value: Recency; label: string }[] = [ { value: "any", label: "Any time" }, ]; -export function CreateAlert({ remainingSlots }: { remainingSlots: number }) { +/** + * The alert builder. Anyone can build an alert; `authed` decides "Save": + * - authed: create it immediately (and offer an instant test run). + * - anonymous: bounce to /login carrying the built alert, so it's created + * right after sign-in. Saving always requires an account. + */ +export function CreateAlert({ + remainingSlots, + authed, +}: { + remainingSlots: number; + authed: boolean; +}) { const router = useRouter(); const [pending, startTransition] = useTransition(); const [categoryKey, setCategoryKey] = useState(PICKABLE[0].key); @@ -29,7 +41,7 @@ export function CreateAlert({ remainingSlots }: { remainingSlots: number }) { const [notice, setNotice] = useState(null); const category = useMemo(() => PICKABLE.find((c) => c.key === categoryKey)!, [categoryKey]); - const atCap = remainingSlots <= 0; + const atCap = authed && remainingSlots <= 0; function pickCategory(key: string) { const cat = PICKABLE.find((c) => c.key === key)!; @@ -58,6 +70,16 @@ export function CreateAlert({ remainingSlots }: { remainingSlots: number }) { setError(null); setNotice(null); if (!term.trim()) return setError(`${category.inputLabel} is required.`); + + if (!authed) { + // Require login to save: carry the built alert through the login round-trip. + const target = `/alerts?new=${encodeURIComponent(categoryKey)}&term=${encodeURIComponent( + term, + )}&recency=${recency}&frequency=${frequency}`; + router.push(`/login?redirect=${encodeURIComponent(target)}`); + return; + } + startTransition(async () => { const res = await createAlert({ category: categoryKey, term, customQuery: term, recency, frequency }); if (!res.ok) return setError(res.error); @@ -119,11 +141,13 @@ export function CreateAlert({ remainingSlots }: { remainingSlots: number }) {
- + {authed && ( + + )} {atCap && ( diff --git a/app/(app)/alerts/page.tsx b/app/(marketing)/alerts/page.tsx similarity index 62% rename from app/(app)/alerts/page.tsx rename to app/(marketing)/alerts/page.tsx index a63a18f5..1e7a6558 100644 --- a/app/(app)/alerts/page.tsx +++ b/app/(marketing)/alerts/page.tsx @@ -8,11 +8,15 @@ import { SERP_CALLS_PER_MONTH, planFromProfile, } from "@/lib/alerts/limits"; -import { getCategory } from "@/lib/alerts/categories"; +import { getCategory, type Recency } from "@/lib/alerts/categories"; import { CreateAlert } from "./create-alert"; import { AlertActions } from "./alert-actions"; -export const metadata = { title: "Alerts" }; +export const metadata = { + title: "Free web alerts — CrawlProof Alerts", + description: + "Free, near-realtime email alerts for anything Google can see: brand mentions, new backlinks, buying-intent searches, and more. Build one now — sign in to save it.", +}; type AlertRow = { id: string; @@ -35,25 +39,59 @@ function fmt(ts: string | null): string { }); } +const RECENCIES = new Set(["day", "week", "month", "any"]); + export default async function AlertsPage({ searchParams, }: { - searchParams: Promise<{ new?: string; term?: string }>; + searchParams: Promise<{ new?: string; term?: string; recency?: string; frequency?: string }>; }) { - const { new: newCat, term } = await searchParams; + const { new: newCat, term, recency, frequency } = await searchParams; const supabase = await createClient(); const { data: { user }, } = await supabase.auth.getUser(); - if (!user) redirect("/login?next=/alerts"); - // Post-signup magic-link redirect: create the pending alert, then clean the URL. + // ---------- Public (not signed in): build the alert, sign in to save ---------- + if (!user) { + return ( +
+
+

Know the moment the web mentions you

+

+ Free email alerts for brand mentions, new backlinks, competitor moves, and buying-intent + searches — powered by Google, verified by CrawlProof's crawler. Build one below; sign in + to save it. +

+
+
+ +
+

+ Already have an account?{" "} + + Log in + + . +

+
+ ); + } + + // Post-signin: create the alert carried through login, then clean the URL. if (newCat && getCategory(newCat)) { - await createAlert({ category: newCat, term: term ?? "", customQuery: term ?? "" }); + await createAlert({ + category: newCat, + term: term ?? "", + customQuery: term ?? "", + recency: recency && RECENCIES.has(recency as Recency) ? (recency as Recency) : undefined, + frequency: frequency === "hourly" || frequency === "daily" ? frequency : undefined, + }); redirect("/alerts"); } + // ---------- Signed in: full management dashboard ---------- const svc = serviceClient(); const { data: profile } = await svc .from("profiles") @@ -76,7 +114,7 @@ export default async function AlertsPage({ const used = (profile?.alert_serp_calls_used as number) ?? 0; return ( -
+

Alerts

@@ -90,7 +128,7 @@ export default async function AlertsPage({
- +

@@ -117,6 +155,6 @@ export default async function AlertsPage({

))}
-
+ ); }