diff options
| author | Fuwn <[email protected]> | 2026-02-07 01:42:57 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-07 01:42:57 -0800 |
| commit | 5c5b1993edd890a80870ee05607ac5f088191d4e (patch) | |
| tree | a721b76bcd49ba10826c53efc87302c7a689512f /apps/web/app/(auth)/forgot-password | |
| download | asa.news-5c5b1993edd890a80870ee05607ac5f088191d4e.tar.xz asa.news-5c5b1993edd890a80870ee05607ac5f088191d4e.zip | |
feat: asa.news RSS reader with developer tier, REST API, and webhooks
Full-stack RSS reader SaaS: Supabase + Next.js + Go worker.
Includes three subscription tiers (free/pro/developer), API key auth,
read-only REST API, webhook push notifications, Stripe billing with
proration, and PWA support.
Diffstat (limited to 'apps/web/app/(auth)/forgot-password')
| -rw-r--r-- | apps/web/app/(auth)/forgot-password/page.tsx | 101 |
1 files changed, 101 insertions, 0 deletions
diff --git a/apps/web/app/(auth)/forgot-password/page.tsx b/apps/web/app/(auth)/forgot-password/page.tsx new file mode 100644 index 0000000..748ba47 --- /dev/null +++ b/apps/web/app/(auth)/forgot-password/page.tsx @@ -0,0 +1,101 @@ +"use client" + +import { useState } from "react" +import Link from "next/link" +import { createSupabaseBrowserClient } from "@/lib/supabase/client" + +export default function ForgotPasswordPage() { + const [emailAddress, setEmailAddress] = useState("") + const [errorMessage, setErrorMessage] = useState<string | null>(null) + const [isSubmitting, setIsSubmitting] = useState(false) + const [isEmailSent, setIsEmailSent] = useState(false) + + async function handleResetRequest(event: React.FormEvent) { + event.preventDefault() + setIsSubmitting(true) + setErrorMessage(null) + + const supabaseClient = createSupabaseBrowserClient() + + const { error } = await supabaseClient.auth.resetPasswordForEmail( + emailAddress, + { + redirectTo: `${window.location.origin}/auth/callback?next=/reset-password`, + }, + ) + + if (error) { + setErrorMessage(error.message) + setIsSubmitting(false) + return + } + + setIsEmailSent(true) + } + + if (isEmailSent) { + return ( + <> + <div className="space-y-2"> + <h1 className="text-lg text-text-primary">check your email</h1> + <p className="text-text-secondary"> + we sent a password reset link to {emailAddress} + </p> + </div> + <Link + href="/sign-in" + className="block text-text-secondary transition-colors hover:text-text-primary" + > + back to sign in + </Link> + </> + ) + } + + return ( + <> + <div className="space-y-2"> + <h1 className="text-lg text-text-primary">forgot password</h1> + <p className="text-text-secondary"> + enter your email to receive a reset link + </p> + </div> + + <form onSubmit={handleResetRequest} className="space-y-4"> + <div className="space-y-2"> + <label htmlFor="email" className="text-text-secondary"> + email + </label> + <input + id="email" + type="email" + value={emailAddress} + onChange={(event) => setEmailAddress(event.target.value)} + required + className="w-full border border-border bg-background-secondary px-3 py-2 text-text-primary outline-none placeholder:text-text-dim focus:border-text-dim" + placeholder="[email protected]" + /> + </div> + + {errorMessage && ( + <p className="text-status-error">{errorMessage}</p> + )} + + <button + type="submit" + disabled={isSubmitting} + className="w-full border border-border bg-background-tertiary px-4 py-2 text-text-primary transition-colors hover:bg-border disabled:opacity-50" + > + {isSubmitting ? "sending reset link..." : "send reset link"} + </button> + </form> + + <Link + href="/sign-in" + className="block text-text-secondary transition-colors hover:text-text-primary" + > + back to sign in + </Link> + </> + ) +} |