@braintwopoint0/playback-commons 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui/index.js CHANGED
@@ -2866,6 +2866,733 @@ function NewsletterForm2({
2866
2866
  }
2867
2867
  );
2868
2868
  }
2869
+
2870
+ // src/ui/site-footer.tsx
2871
+ import Link3 from "next/link";
2872
+ import { jsx as jsx38, jsxs as jsxs22 } from "react/jsx-runtime";
2873
+ var INK_MUTED = "rgba(214,213,201,0.64)";
2874
+ var INK_SUBTLE = "rgba(214,213,201,0.44)";
2875
+ var LINE = "rgba(214,213,201,0.08)";
2876
+ function FooterLinkItem2({ link }) {
2877
+ const classes = "text-[14px] text-[rgba(214,213,201,0.64)] hover:text-[var(--timberwolf)] transition-colors rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--timberwolf)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--night)]";
2878
+ if (link.external) {
2879
+ return /* @__PURE__ */ jsx38(
2880
+ "a",
2881
+ {
2882
+ href: link.href,
2883
+ target: "_blank",
2884
+ rel: "noopener noreferrer",
2885
+ className: classes,
2886
+ children: link.label
2887
+ }
2888
+ );
2889
+ }
2890
+ return /* @__PURE__ */ jsx38(Link3, { href: link.href, className: classes, children: link.label });
2891
+ }
2892
+ function SiteFooter({
2893
+ columns,
2894
+ siteName = "PLAYBACK",
2895
+ newsletterEndpoint = "/api/newsletter/subscribe",
2896
+ newsletterSource = "footer",
2897
+ newsletterLabel = "Newsletter",
2898
+ newsletterTitle = "Updates from the Network.",
2899
+ newsletterSubtitle = "Only when relevant. No spam."
2900
+ }) {
2901
+ return /* @__PURE__ */ jsxs22(
2902
+ "footer",
2903
+ {
2904
+ id: "footer",
2905
+ className: "mt-24 border-t bg-[var(--night)]",
2906
+ style: { borderTopColor: LINE },
2907
+ "aria-labelledby": "site-footer-heading",
2908
+ children: [
2909
+ /* @__PURE__ */ jsxs22("h2", { id: "site-footer-heading", className: "sr-only", children: [
2910
+ siteName,
2911
+ " site footer"
2912
+ ] }),
2913
+ /* @__PURE__ */ jsxs22("div", { className: "mx-auto max-w-[1400px] px-6 sm:px-10", children: [
2914
+ /* @__PURE__ */ jsxs22("div", { className: "flex flex-col gap-6 pt-16 pb-10 md:flex-row md:items-start md:justify-between md:gap-10 md:pt-20", children: [
2915
+ /* @__PURE__ */ jsxs22("div", { className: "max-w-[36ch]", children: [
2916
+ /* @__PURE__ */ jsx38(
2917
+ "p",
2918
+ {
2919
+ className: "text-[11px] uppercase tracking-[0.22em] font-semibold",
2920
+ style: { color: INK_SUBTLE },
2921
+ children: newsletterLabel
2922
+ }
2923
+ ),
2924
+ /* @__PURE__ */ jsx38("p", { className: "mt-3 text-[17px] md:text-[19px] leading-[1.35] tracking-[-0.01em] text-[var(--timberwolf)]", children: newsletterTitle }),
2925
+ /* @__PURE__ */ jsx38(
2926
+ "p",
2927
+ {
2928
+ className: "mt-2 text-[13px] leading-[1.5]",
2929
+ style: { color: INK_MUTED },
2930
+ children: newsletterSubtitle
2931
+ }
2932
+ )
2933
+ ] }),
2934
+ /* @__PURE__ */ jsx38("div", { className: "w-full md:max-w-md md:pt-1", children: /* @__PURE__ */ jsx38(
2935
+ NewsletterForm2,
2936
+ {
2937
+ endpoint: newsletterEndpoint,
2938
+ source: newsletterSource
2939
+ }
2940
+ ) })
2941
+ ] }),
2942
+ /* @__PURE__ */ jsx38("div", { className: "border-t py-14", style: { borderTopColor: LINE }, children: /* @__PURE__ */ jsx38("div", { className: "grid grid-cols-2 gap-x-6 gap-y-10 md:grid-cols-4", children: columns.map((col) => /* @__PURE__ */ jsxs22("nav", { "aria-label": col.title, children: [
2943
+ /* @__PURE__ */ jsx38(
2944
+ "p",
2945
+ {
2946
+ className: "text-[12px] uppercase tracking-[0.14em] mb-4",
2947
+ style: { color: INK_SUBTLE },
2948
+ children: col.title
2949
+ }
2950
+ ),
2951
+ /* @__PURE__ */ jsx38("ul", { className: "flex flex-col gap-3", children: col.links.map((link) => /* @__PURE__ */ jsx38("li", { children: /* @__PURE__ */ jsx38(FooterLinkItem2, { link }) }, `${col.title}-${link.label}`)) })
2952
+ ] }, col.title)) }) }),
2953
+ /* @__PURE__ */ jsx38(FooterCreditsBar, {})
2954
+ ] })
2955
+ ]
2956
+ }
2957
+ );
2958
+ }
2959
+
2960
+ // src/ui/sign-in-form.tsx
2961
+ import * as React28 from "react";
2962
+ import Link4 from "next/link";
2963
+ import {
2964
+ AlertCircle,
2965
+ Eye,
2966
+ EyeOff,
2967
+ Loader2,
2968
+ Lock,
2969
+ Mail
2970
+ } from "lucide-react";
2971
+
2972
+ // src/auth/shared.ts
2973
+ function validateEmail(email) {
2974
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2975
+ return emailRegex.test(email);
2976
+ }
2977
+ function validatePassword(password) {
2978
+ const errors = [];
2979
+ if (password.length < 8) {
2980
+ errors.push("Password must be at least 8 characters long");
2981
+ }
2982
+ if (!/(?=.*[a-z])/.test(password)) {
2983
+ errors.push("Password must contain at least one lowercase letter");
2984
+ }
2985
+ if (!/(?=.*[A-Z])/.test(password)) {
2986
+ errors.push("Password must contain at least one uppercase letter");
2987
+ }
2988
+ if (!/(?=.*\d)/.test(password)) {
2989
+ errors.push("Password must contain at least one number");
2990
+ }
2991
+ return {
2992
+ isValid: errors.length === 0,
2993
+ errors
2994
+ };
2995
+ }
2996
+ function getAuthErrorMessage(error) {
2997
+ if (!error) return "An unknown error occurred";
2998
+ const message = error.message || error.error_description || error.toString();
2999
+ const errorMappings = {
3000
+ "Invalid login credentials": "Invalid email or password. Please check your credentials and try again.",
3001
+ "Email not confirmed": "Please check your email and click the confirmation link before signing in.",
3002
+ "User already registered": "An account with this email address already exists. Please sign in instead.",
3003
+ "Password should be at least 6 characters": "Password must be at least 6 characters long.",
3004
+ "Signup requires a valid password": "Please provide a valid password.",
3005
+ "Invalid email": "Please provide a valid email address.",
3006
+ "Email rate limit exceeded": "Too many emails sent. Please wait before requesting another.",
3007
+ 'duplicate key value violates unique constraint "profiles_username_key"': "This username is already taken. Please choose a different username."
3008
+ };
3009
+ return errorMappings[message] || message;
3010
+ }
3011
+
3012
+ // src/supabase/client.ts
3013
+ import { createBrowserClient } from "@supabase/ssr";
3014
+ function createClient() {
3015
+ const cookieDomain = process.env.NEXT_PUBLIC_COOKIE_DOMAIN;
3016
+ return createBrowserClient(
3017
+ process.env.NEXT_PUBLIC_SUPABASE_URL,
3018
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
3019
+ {
3020
+ ...cookieDomain && {
3021
+ cookieOptions: { domain: cookieDomain }
3022
+ }
3023
+ }
3024
+ );
3025
+ }
3026
+
3027
+ // src/ui/sign-in-form.tsx
3028
+ import { Fragment as Fragment6, jsx as jsx39, jsxs as jsxs23 } from "react/jsx-runtime";
3029
+ var COOLDOWN_THRESHOLD = 3;
3030
+ var COOLDOWN_DURATION_MS = 3e4;
3031
+ function SignInForm({
3032
+ onSuccess,
3033
+ forgotPasswordHref,
3034
+ signUpHref,
3035
+ initialError,
3036
+ emailPlaceholder = "you@example.com",
3037
+ title = "Welcome back",
3038
+ subtitle = "Sign in to access your account"
3039
+ }) {
3040
+ const [email, setEmail] = React28.useState("");
3041
+ const [password, setPassword] = React28.useState("");
3042
+ const [showPassword, setShowPassword] = React28.useState(false);
3043
+ const [error, setError] = React28.useState(initialError ?? "");
3044
+ const [loading, setLoading] = React28.useState(false);
3045
+ const [consecutiveErrors, setConsecutiveErrors] = React28.useState(0);
3046
+ const [cooldownUntil, setCooldownUntil] = React28.useState(null);
3047
+ const [supabase] = React28.useState(() => createClient());
3048
+ React28.useEffect(() => {
3049
+ if (initialError) setError(initialError);
3050
+ }, [initialError]);
3051
+ const handleSubmit = async (e) => {
3052
+ e.preventDefault();
3053
+ setError("");
3054
+ if (cooldownUntil && Date.now() < cooldownUntil) {
3055
+ const secondsLeft = Math.ceil((cooldownUntil - Date.now()) / 1e3);
3056
+ setError(`Too many attempts. Please wait ${secondsLeft} seconds.`);
3057
+ return;
3058
+ }
3059
+ if (!email || !password) {
3060
+ setError("Please fill in all fields");
3061
+ return;
3062
+ }
3063
+ if (!validateEmail(email)) {
3064
+ setError("Please enter a valid email address");
3065
+ return;
3066
+ }
3067
+ setLoading(true);
3068
+ try {
3069
+ const { error: signInError } = await supabase.auth.signInWithPassword({
3070
+ email,
3071
+ password
3072
+ });
3073
+ if (signInError) {
3074
+ setError(getAuthErrorMessage(signInError));
3075
+ const newCount = consecutiveErrors + 1;
3076
+ setConsecutiveErrors(newCount);
3077
+ if (newCount >= COOLDOWN_THRESHOLD) {
3078
+ setCooldownUntil(Date.now() + COOLDOWN_DURATION_MS);
3079
+ setConsecutiveErrors(0);
3080
+ }
3081
+ } else {
3082
+ setConsecutiveErrors(0);
3083
+ setCooldownUntil(null);
3084
+ onSuccess?.(email);
3085
+ }
3086
+ } catch {
3087
+ setError("An unexpected error occurred. Please try again.");
3088
+ } finally {
3089
+ setLoading(false);
3090
+ }
3091
+ };
3092
+ return /* @__PURE__ */ jsxs23("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6", children: [
3093
+ /* @__PURE__ */ jsxs23("div", { className: "text-center", children: [
3094
+ /* @__PURE__ */ jsx39("h1", { className: "text-2xl font-bold text-[var(--timberwolf)] mb-2", children: title }),
3095
+ /* @__PURE__ */ jsx39("p", { className: "text-sm text-muted-foreground", children: subtitle })
3096
+ ] }),
3097
+ /* @__PURE__ */ jsxs23("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
3098
+ error && /* @__PURE__ */ jsx39(
3099
+ "div",
3100
+ {
3101
+ id: "signin-error",
3102
+ role: "alert",
3103
+ "aria-live": "polite",
3104
+ className: "bg-red-900/20 border border-red-700/30 rounded-lg p-3",
3105
+ children: /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2 text-red-400 text-sm", children: [
3106
+ /* @__PURE__ */ jsx39(AlertCircle, { className: "h-4 w-4 flex-shrink-0" }),
3107
+ /* @__PURE__ */ jsx39("span", { children: error })
3108
+ ] })
3109
+ }
3110
+ ),
3111
+ /* @__PURE__ */ jsxs23("div", { className: "space-y-2", children: [
3112
+ /* @__PURE__ */ jsx39(Label, { htmlFor: "email", className: "text-[var(--timberwolf)]", children: "Email" }),
3113
+ /* @__PURE__ */ jsxs23("div", { className: "relative", children: [
3114
+ /* @__PURE__ */ jsx39(
3115
+ Input,
3116
+ {
3117
+ id: "email",
3118
+ type: "email",
3119
+ placeholder: emailPlaceholder,
3120
+ value: email,
3121
+ onChange: (e) => setEmail(e.target.value),
3122
+ className: "h-11 pl-10",
3123
+ disabled: loading,
3124
+ autoComplete: "email",
3125
+ "aria-invalid": !!error,
3126
+ "aria-describedby": error ? "signin-error" : void 0
3127
+ }
3128
+ ),
3129
+ /* @__PURE__ */ jsx39(Mail, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" })
3130
+ ] })
3131
+ ] }),
3132
+ /* @__PURE__ */ jsxs23("div", { className: "space-y-2", children: [
3133
+ /* @__PURE__ */ jsx39(Label, { htmlFor: "password", className: "text-[var(--timberwolf)]", children: "Password" }),
3134
+ /* @__PURE__ */ jsxs23("div", { className: "relative", children: [
3135
+ /* @__PURE__ */ jsx39(
3136
+ Input,
3137
+ {
3138
+ id: "password",
3139
+ type: showPassword ? "text" : "password",
3140
+ placeholder: "Enter your password",
3141
+ value: password,
3142
+ onChange: (e) => setPassword(e.target.value),
3143
+ className: "h-11 pl-10 pr-10",
3144
+ disabled: loading,
3145
+ autoComplete: "current-password",
3146
+ "aria-invalid": !!error,
3147
+ "aria-describedby": error ? "signin-error" : void 0
3148
+ }
3149
+ ),
3150
+ /* @__PURE__ */ jsx39(Lock, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }),
3151
+ /* @__PURE__ */ jsx39(
3152
+ "button",
3153
+ {
3154
+ type: "button",
3155
+ onClick: () => setShowPassword((prev) => !prev),
3156
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-[var(--timberwolf)]",
3157
+ disabled: loading,
3158
+ "aria-label": showPassword ? "Hide password" : "Show password",
3159
+ "aria-pressed": showPassword,
3160
+ children: showPassword ? /* @__PURE__ */ jsx39(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx39(Eye, { className: "h-4 w-4" })
3161
+ }
3162
+ )
3163
+ ] })
3164
+ ] }),
3165
+ forgotPasswordHref ? /* @__PURE__ */ jsx39("div", { className: "flex justify-end -mt-1", children: /* @__PURE__ */ jsx39(
3166
+ Link4,
3167
+ {
3168
+ href: forgotPasswordHref,
3169
+ className: "text-xs text-muted-foreground hover:text-[var(--timberwolf)] transition-colors",
3170
+ children: "Forgot your password?"
3171
+ }
3172
+ ) }) : null,
3173
+ /* @__PURE__ */ jsx39(
3174
+ Button,
3175
+ {
3176
+ type: "submit",
3177
+ className: "w-full h-11 bg-[var(--timberwolf)] text-[var(--night)] hover:bg-[var(--ash-grey)]",
3178
+ disabled: loading,
3179
+ children: loading ? /* @__PURE__ */ jsxs23(Fragment6, { children: [
3180
+ /* @__PURE__ */ jsx39(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }),
3181
+ "Signing in..."
3182
+ ] }) : "Sign in"
3183
+ }
3184
+ )
3185
+ ] }),
3186
+ signUpHref ? /* @__PURE__ */ jsx39("div", { className: "text-center text-sm", children: /* @__PURE__ */ jsxs23("p", { className: "text-muted-foreground", children: [
3187
+ "Don't have an account?",
3188
+ " ",
3189
+ /* @__PURE__ */ jsx39(
3190
+ Link4,
3191
+ {
3192
+ href: signUpHref,
3193
+ className: "text-[var(--timberwolf)] hover:underline",
3194
+ children: "Sign up"
3195
+ }
3196
+ )
3197
+ ] }) }) : null
3198
+ ] });
3199
+ }
3200
+
3201
+ // src/ui/forgot-password-form.tsx
3202
+ import * as React29 from "react";
3203
+ import Link5 from "next/link";
3204
+ import {
3205
+ AlertCircle as AlertCircle2,
3206
+ ArrowLeft,
3207
+ CheckCircle,
3208
+ Loader2 as Loader22,
3209
+ Mail as Mail2
3210
+ } from "lucide-react";
3211
+ import { Fragment as Fragment7, jsx as jsx40, jsxs as jsxs24 } from "react/jsx-runtime";
3212
+ var RESEND_COOLDOWN_MS = 3e4;
3213
+ var COOLDOWN_STORAGE_KEY = "playback:pwreset-cooldown-until";
3214
+ function ForgotPasswordForm({
3215
+ redirectTo,
3216
+ loginHref = "/auth/login",
3217
+ title = "Forgot password?",
3218
+ subtitle = "Enter your email and we'll send you a reset link."
3219
+ }) {
3220
+ const [email, setEmail] = React29.useState("");
3221
+ const [error, setError] = React29.useState("");
3222
+ const [loading, setLoading] = React29.useState(false);
3223
+ const [emailSent, setEmailSent] = React29.useState(false);
3224
+ const [cooldownUntil, setCooldownUntilState] = React29.useState(
3225
+ null
3226
+ );
3227
+ const [now, setNow] = React29.useState(() => Date.now());
3228
+ const [supabase] = React29.useState(() => createClient());
3229
+ const setCooldownUntil = (value) => {
3230
+ setCooldownUntilState(value);
3231
+ if (typeof window === "undefined") return;
3232
+ if (value === null) {
3233
+ window.sessionStorage.removeItem(COOLDOWN_STORAGE_KEY);
3234
+ } else {
3235
+ window.sessionStorage.setItem(COOLDOWN_STORAGE_KEY, String(value));
3236
+ }
3237
+ };
3238
+ React29.useEffect(() => {
3239
+ if (typeof window === "undefined") return;
3240
+ const stored = window.sessionStorage.getItem(COOLDOWN_STORAGE_KEY);
3241
+ if (!stored) return;
3242
+ const parsed = Number(stored);
3243
+ if (!Number.isFinite(parsed) || parsed <= Date.now()) {
3244
+ window.sessionStorage.removeItem(COOLDOWN_STORAGE_KEY);
3245
+ return;
3246
+ }
3247
+ setCooldownUntilState(parsed);
3248
+ }, []);
3249
+ React29.useEffect(() => {
3250
+ if (!cooldownUntil) return;
3251
+ const id = setInterval(() => setNow(Date.now()), 1e3);
3252
+ return () => clearInterval(id);
3253
+ }, [cooldownUntil]);
3254
+ const cooldownSecondsLeft = cooldownUntil && cooldownUntil > now ? Math.ceil((cooldownUntil - now) / 1e3) : 0;
3255
+ const handleSubmit = async (e) => {
3256
+ e.preventDefault();
3257
+ setError("");
3258
+ if (cooldownSecondsLeft > 0) {
3259
+ setError(`Please wait ${cooldownSecondsLeft} seconds before resending.`);
3260
+ return;
3261
+ }
3262
+ if (!email) {
3263
+ setError("Please enter your email address");
3264
+ return;
3265
+ }
3266
+ if (!validateEmail(email)) {
3267
+ setError("Please enter a valid email address");
3268
+ return;
3269
+ }
3270
+ setLoading(true);
3271
+ try {
3272
+ const { error: resetError } = await supabase.auth.resetPasswordForEmail(
3273
+ email,
3274
+ { redirectTo }
3275
+ );
3276
+ if (resetError) {
3277
+ setError(getAuthErrorMessage(resetError));
3278
+ } else {
3279
+ setEmailSent(true);
3280
+ setCooldownUntil(Date.now() + RESEND_COOLDOWN_MS);
3281
+ }
3282
+ } catch {
3283
+ setError("An unexpected error occurred. Please try again.");
3284
+ } finally {
3285
+ setLoading(false);
3286
+ }
3287
+ };
3288
+ if (emailSent) {
3289
+ return /* @__PURE__ */ jsxs24("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6 text-center", children: [
3290
+ /* @__PURE__ */ jsx40("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx40("div", { className: "h-12 w-12 rounded-full bg-[var(--timberwolf)]/10 border border-[var(--timberwolf)]/20 flex items-center justify-center", children: /* @__PURE__ */ jsx40(CheckCircle, { className: "h-6 w-6 text-[var(--timberwolf)]" }) }) }),
3291
+ /* @__PURE__ */ jsxs24("div", { className: "space-y-2", children: [
3292
+ /* @__PURE__ */ jsx40("h1", { className: "text-2xl font-bold text-[var(--timberwolf)]", children: "Check your email" }),
3293
+ /* @__PURE__ */ jsxs24("p", { className: "text-sm text-muted-foreground", children: [
3294
+ "We sent a reset link to",
3295
+ " ",
3296
+ /* @__PURE__ */ jsx40("span", { className: "text-[var(--timberwolf)] font-medium", children: email }),
3297
+ ". If it's not in your inbox, check spam."
3298
+ ] })
3299
+ ] }),
3300
+ /* @__PURE__ */ jsxs24("div", { className: "space-y-3 pt-2", children: [
3301
+ /* @__PURE__ */ jsx40(
3302
+ Button,
3303
+ {
3304
+ onClick: () => {
3305
+ setEmailSent(false);
3306
+ setEmail("");
3307
+ setError("");
3308
+ },
3309
+ variant: "outline",
3310
+ className: "w-full h-11",
3311
+ disabled: cooldownSecondsLeft > 0,
3312
+ children: cooldownSecondsLeft > 0 ? `Send another email in ${cooldownSecondsLeft}s` : "Send another email"
3313
+ }
3314
+ ),
3315
+ /* @__PURE__ */ jsx40(Link5, { href: loginHref, className: "block", children: /* @__PURE__ */ jsxs24(Button, { className: "w-full h-11 bg-[var(--timberwolf)] text-[var(--night)] hover:bg-[var(--ash-grey)]", children: [
3316
+ /* @__PURE__ */ jsx40(ArrowLeft, { className: "h-4 w-4 mr-2" }),
3317
+ "Back to sign in"
3318
+ ] }) })
3319
+ ] })
3320
+ ] });
3321
+ }
3322
+ return /* @__PURE__ */ jsxs24("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6", children: [
3323
+ /* @__PURE__ */ jsxs24("div", { className: "text-center", children: [
3324
+ /* @__PURE__ */ jsx40("h1", { className: "text-2xl font-bold text-[var(--timberwolf)] mb-2", children: title }),
3325
+ /* @__PURE__ */ jsx40("p", { className: "text-sm text-muted-foreground", children: subtitle })
3326
+ ] }),
3327
+ /* @__PURE__ */ jsxs24("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
3328
+ error && /* @__PURE__ */ jsx40(
3329
+ "div",
3330
+ {
3331
+ id: "forgot-error",
3332
+ role: "alert",
3333
+ "aria-live": "polite",
3334
+ className: "bg-red-900/20 border border-red-700/30 rounded-lg p-3",
3335
+ children: /* @__PURE__ */ jsxs24("div", { className: "flex items-center gap-2 text-red-400 text-sm", children: [
3336
+ /* @__PURE__ */ jsx40(AlertCircle2, { className: "h-4 w-4 flex-shrink-0" }),
3337
+ /* @__PURE__ */ jsx40("span", { children: error })
3338
+ ] })
3339
+ }
3340
+ ),
3341
+ /* @__PURE__ */ jsxs24("div", { className: "space-y-2", children: [
3342
+ /* @__PURE__ */ jsx40(Label, { htmlFor: "email", className: "text-[var(--timberwolf)]", children: "Email" }),
3343
+ /* @__PURE__ */ jsxs24("div", { className: "relative", children: [
3344
+ /* @__PURE__ */ jsx40(
3345
+ Input,
3346
+ {
3347
+ id: "email",
3348
+ type: "email",
3349
+ placeholder: "you@example.com",
3350
+ value: email,
3351
+ onChange: (e) => setEmail(e.target.value),
3352
+ className: "h-11 pl-10",
3353
+ disabled: loading,
3354
+ autoComplete: "email",
3355
+ autoFocus: true,
3356
+ "aria-invalid": !!error,
3357
+ "aria-describedby": error ? "forgot-error" : void 0
3358
+ }
3359
+ ),
3360
+ /* @__PURE__ */ jsx40(Mail2, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" })
3361
+ ] })
3362
+ ] }),
3363
+ /* @__PURE__ */ jsx40(
3364
+ Button,
3365
+ {
3366
+ type: "submit",
3367
+ className: "w-full h-11 bg-[var(--timberwolf)] text-[var(--night)] hover:bg-[var(--ash-grey)]",
3368
+ disabled: loading || cooldownSecondsLeft > 0,
3369
+ children: loading ? /* @__PURE__ */ jsxs24(Fragment7, { children: [
3370
+ /* @__PURE__ */ jsx40(Loader22, { className: "h-4 w-4 mr-2 animate-spin" }),
3371
+ "Sending email..."
3372
+ ] }) : cooldownSecondsLeft > 0 ? `Try again in ${cooldownSecondsLeft}s` : "Send reset link"
3373
+ }
3374
+ )
3375
+ ] }),
3376
+ /* @__PURE__ */ jsx40("div", { className: "text-center text-sm", children: /* @__PURE__ */ jsxs24(
3377
+ Link5,
3378
+ {
3379
+ href: loginHref,
3380
+ className: "inline-flex items-center gap-1.5 text-muted-foreground hover:text-[var(--timberwolf)] transition-colors",
3381
+ children: [
3382
+ /* @__PURE__ */ jsx40(ArrowLeft, { className: "h-3.5 w-3.5" }),
3383
+ "Back to sign in"
3384
+ ]
3385
+ }
3386
+ ) })
3387
+ ] });
3388
+ }
3389
+
3390
+ // src/ui/reset-password-form.tsx
3391
+ import * as React30 from "react";
3392
+ import Link6 from "next/link";
3393
+ import { useRouter } from "next/navigation";
3394
+ import {
3395
+ AlertCircle as AlertCircle3,
3396
+ CheckCircle as CheckCircle2,
3397
+ Eye as Eye2,
3398
+ EyeOff as EyeOff2,
3399
+ Loader2 as Loader23,
3400
+ Lock as Lock2
3401
+ } from "lucide-react";
3402
+ import { Fragment as Fragment8, jsx as jsx41, jsxs as jsxs25 } from "react/jsx-runtime";
3403
+ function ResetPasswordForm({
3404
+ loginHref = "/auth/login",
3405
+ initialError,
3406
+ redirectDelayMs = 3e3,
3407
+ title = "Reset password",
3408
+ subtitle = "Enter a new password for your account."
3409
+ }) {
3410
+ const [password, setPassword] = React30.useState("");
3411
+ const [confirmPassword, setConfirmPassword] = React30.useState("");
3412
+ const [showPassword, setShowPassword] = React30.useState(false);
3413
+ const [showConfirmPassword, setShowConfirmPassword] = React30.useState(false);
3414
+ const [error, setError] = React30.useState(initialError ?? "");
3415
+ const [loading, setLoading] = React30.useState(false);
3416
+ const [passwordReset, setPasswordReset] = React30.useState(false);
3417
+ const router = useRouter();
3418
+ const [supabase] = React30.useState(() => createClient());
3419
+ React30.useEffect(() => {
3420
+ if (initialError) setError(initialError);
3421
+ }, [initialError]);
3422
+ React30.useEffect(() => {
3423
+ if (!passwordReset) return;
3424
+ const id = setTimeout(() => {
3425
+ router.push(loginHref);
3426
+ }, redirectDelayMs);
3427
+ return () => clearTimeout(id);
3428
+ }, [passwordReset, router, loginHref, redirectDelayMs]);
3429
+ const handleSubmit = async (e) => {
3430
+ e.preventDefault();
3431
+ setError("");
3432
+ if (!password || !confirmPassword) {
3433
+ setError("Please fill in both fields");
3434
+ return;
3435
+ }
3436
+ const { isValid, errors } = validatePassword(password);
3437
+ if (!isValid) {
3438
+ setError(errors[0]);
3439
+ return;
3440
+ }
3441
+ if (password !== confirmPassword) {
3442
+ setError("Passwords do not match");
3443
+ return;
3444
+ }
3445
+ setLoading(true);
3446
+ try {
3447
+ const { error: updateError } = await supabase.auth.updateUser({
3448
+ password
3449
+ });
3450
+ if (updateError) {
3451
+ setError(getAuthErrorMessage(updateError));
3452
+ } else {
3453
+ try {
3454
+ await supabase.auth.signOut({ scope: "global" });
3455
+ } catch (signOutError) {
3456
+ console.warn(
3457
+ "[ResetPasswordForm] global sign-out failed after successful update",
3458
+ signOutError
3459
+ );
3460
+ }
3461
+ setPasswordReset(true);
3462
+ }
3463
+ } catch {
3464
+ setError("An unexpected error occurred. Please try again.");
3465
+ } finally {
3466
+ setLoading(false);
3467
+ }
3468
+ };
3469
+ if (passwordReset) {
3470
+ return /* @__PURE__ */ jsxs25("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6 text-center", children: [
3471
+ /* @__PURE__ */ jsx41("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx41("div", { className: "h-12 w-12 rounded-full bg-[var(--timberwolf)]/10 border border-[var(--timberwolf)]/20 flex items-center justify-center", children: /* @__PURE__ */ jsx41(CheckCircle2, { className: "h-6 w-6 text-[var(--timberwolf)]" }) }) }),
3472
+ /* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
3473
+ /* @__PURE__ */ jsx41("h1", { className: "text-2xl font-bold text-[var(--timberwolf)]", children: "Password updated" }),
3474
+ /* @__PURE__ */ jsx41("p", { className: "text-sm text-muted-foreground", children: "Redirecting you to sign in\u2026" })
3475
+ ] }),
3476
+ /* @__PURE__ */ jsx41(Link6, { href: loginHref, className: "block pt-2", children: /* @__PURE__ */ jsx41(Button, { className: "w-full h-11 bg-[var(--timberwolf)] text-[var(--night)] hover:bg-[var(--ash-grey)]", children: "Continue to sign in" }) })
3477
+ ] });
3478
+ }
3479
+ return /* @__PURE__ */ jsxs25("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6", children: [
3480
+ /* @__PURE__ */ jsxs25("div", { className: "text-center", children: [
3481
+ /* @__PURE__ */ jsx41("h1", { className: "text-2xl font-bold text-[var(--timberwolf)] mb-2", children: title }),
3482
+ /* @__PURE__ */ jsx41("p", { className: "text-sm text-muted-foreground", children: subtitle })
3483
+ ] }),
3484
+ /* @__PURE__ */ jsxs25("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
3485
+ error && /* @__PURE__ */ jsx41(
3486
+ "div",
3487
+ {
3488
+ id: "reset-error",
3489
+ role: "alert",
3490
+ "aria-live": "polite",
3491
+ className: "bg-red-900/20 border border-red-700/30 rounded-lg p-3",
3492
+ children: /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2 text-red-400 text-sm", children: [
3493
+ /* @__PURE__ */ jsx41(AlertCircle3, { className: "h-4 w-4 flex-shrink-0" }),
3494
+ /* @__PURE__ */ jsx41("span", { children: error })
3495
+ ] })
3496
+ }
3497
+ ),
3498
+ /* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
3499
+ /* @__PURE__ */ jsx41(Label, { htmlFor: "password", className: "text-[var(--timberwolf)]", children: "New password" }),
3500
+ /* @__PURE__ */ jsxs25("div", { className: "relative", children: [
3501
+ /* @__PURE__ */ jsx41(
3502
+ Input,
3503
+ {
3504
+ id: "password",
3505
+ type: showPassword ? "text" : "password",
3506
+ placeholder: "Enter a new password",
3507
+ value: password,
3508
+ onChange: (e) => setPassword(e.target.value),
3509
+ className: "h-11 pl-10 pr-10",
3510
+ disabled: loading,
3511
+ autoComplete: "new-password",
3512
+ autoFocus: true,
3513
+ "aria-invalid": !!error,
3514
+ "aria-describedby": error ? "reset-error" : void 0
3515
+ }
3516
+ ),
3517
+ /* @__PURE__ */ jsx41(Lock2, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }),
3518
+ /* @__PURE__ */ jsx41(
3519
+ "button",
3520
+ {
3521
+ type: "button",
3522
+ onClick: () => setShowPassword((prev) => !prev),
3523
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-[var(--timberwolf)]",
3524
+ disabled: loading,
3525
+ "aria-label": showPassword ? "Hide password" : "Show password",
3526
+ "aria-pressed": showPassword,
3527
+ children: showPassword ? /* @__PURE__ */ jsx41(EyeOff2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx41(Eye2, { className: "h-4 w-4" })
3528
+ }
3529
+ )
3530
+ ] })
3531
+ ] }),
3532
+ /* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
3533
+ /* @__PURE__ */ jsx41(
3534
+ Label,
3535
+ {
3536
+ htmlFor: "confirmPassword",
3537
+ className: "text-[var(--timberwolf)]",
3538
+ children: "Confirm new password"
3539
+ }
3540
+ ),
3541
+ /* @__PURE__ */ jsxs25("div", { className: "relative", children: [
3542
+ /* @__PURE__ */ jsx41(
3543
+ Input,
3544
+ {
3545
+ id: "confirmPassword",
3546
+ type: showConfirmPassword ? "text" : "password",
3547
+ placeholder: "Re-enter your new password",
3548
+ value: confirmPassword,
3549
+ onChange: (e) => setConfirmPassword(e.target.value),
3550
+ className: "h-11 pl-10 pr-10",
3551
+ disabled: loading,
3552
+ autoComplete: "new-password",
3553
+ "aria-invalid": !!error,
3554
+ "aria-describedby": error ? "reset-error" : void 0
3555
+ }
3556
+ ),
3557
+ /* @__PURE__ */ jsx41(Lock2, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }),
3558
+ /* @__PURE__ */ jsx41(
3559
+ "button",
3560
+ {
3561
+ type: "button",
3562
+ onClick: () => setShowConfirmPassword((prev) => !prev),
3563
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-[var(--timberwolf)]",
3564
+ disabled: loading,
3565
+ "aria-label": showConfirmPassword ? "Hide password" : "Show password",
3566
+ "aria-pressed": showConfirmPassword,
3567
+ children: showConfirmPassword ? /* @__PURE__ */ jsx41(EyeOff2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx41(Eye2, { className: "h-4 w-4" })
3568
+ }
3569
+ )
3570
+ ] })
3571
+ ] }),
3572
+ /* @__PURE__ */ jsx41("p", { className: "text-xs text-muted-foreground", children: "At least 8 characters with an uppercase letter, a lowercase letter, and a number." }),
3573
+ /* @__PURE__ */ jsx41(
3574
+ Button,
3575
+ {
3576
+ type: "submit",
3577
+ className: "w-full h-11 bg-[var(--timberwolf)] text-[var(--night)] hover:bg-[var(--ash-grey)]",
3578
+ disabled: loading,
3579
+ children: loading ? /* @__PURE__ */ jsxs25(Fragment8, { children: [
3580
+ /* @__PURE__ */ jsx41(Loader23, { className: "h-4 w-4 mr-2 animate-spin" }),
3581
+ "Updating password..."
3582
+ ] }) : "Update password"
3583
+ }
3584
+ )
3585
+ ] }),
3586
+ /* @__PURE__ */ jsx41("div", { className: "text-center text-sm", children: /* @__PURE__ */ jsx41(
3587
+ Link6,
3588
+ {
3589
+ href: loginHref,
3590
+ className: "text-muted-foreground hover:text-[var(--timberwolf)] transition-colors",
3591
+ children: "Back to sign in"
3592
+ }
3593
+ ) })
3594
+ ] });
3595
+ }
2869
3596
  export {
2870
3597
  AnimatedTooltip,
2871
3598
  Avatar,
@@ -2909,6 +3636,7 @@ export {
2909
3636
  FlipWords,
2910
3637
  Footer,
2911
3638
  FooterCreditsBar,
3639
+ ForgotPasswordForm,
2912
3640
  HeroHighlight,
2913
3641
  Highlight,
2914
3642
  HoverCard,
@@ -2924,6 +3652,7 @@ export {
2924
3652
  PopoverAnchor,
2925
3653
  PopoverContent,
2926
3654
  PopoverTrigger,
3655
+ ResetPasswordForm,
2927
3656
  SearchBar,
2928
3657
  SectionCard,
2929
3658
  Select,
@@ -2947,6 +3676,8 @@ export {
2947
3676
  SheetPortal,
2948
3677
  SheetTitle,
2949
3678
  SheetTrigger,
3679
+ SignInForm,
3680
+ SiteFooter,
2950
3681
  Skeleton,
2951
3682
  StatsGrid,
2952
3683
  Switch,