summaryrefslogtreecommitdiff
path: root/apps/web/app/(auth)/forgot-password
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-07 01:42:57 -0800
committerFuwn <[email protected]>2026-02-07 01:42:57 -0800
commit5c5b1993edd890a80870ee05607ac5f088191d4e (patch)
treea721b76bcd49ba10826c53efc87302c7a689512f /apps/web/app/(auth)/forgot-password
downloadasa.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.tsx101
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>
+ </>
+ )
+}