@braintwopoint0/playback-commons 0.2.3 → 0.2.4

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,754 @@ 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/context.tsx
2973
+ import {
2974
+ createContext as createContext2,
2975
+ useContext as useContext2,
2976
+ useEffect as useEffect6,
2977
+ useState as useState14,
2978
+ useMemo as useMemo2,
2979
+ useCallback as useCallback2,
2980
+ useRef as useRef6
2981
+ } from "react";
2982
+ import { useRouter } from "next/navigation";
2983
+
2984
+ // src/supabase/client.ts
2985
+ import { createBrowserClient } from "@supabase/ssr";
2986
+ function createClient() {
2987
+ const cookieDomain = process.env.NEXT_PUBLIC_COOKIE_DOMAIN;
2988
+ return createBrowserClient(
2989
+ process.env.NEXT_PUBLIC_SUPABASE_URL,
2990
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
2991
+ {
2992
+ ...cookieDomain && {
2993
+ cookieOptions: { domain: cookieDomain }
2994
+ }
2995
+ }
2996
+ );
2997
+ }
2998
+
2999
+ // src/auth/context.tsx
3000
+ import { jsx as jsx39 } from "react/jsx-runtime";
3001
+ var AuthContext = createContext2(void 0);
3002
+ var CACHE_TIMEOUT = 5 * 60 * 1e3;
3003
+ function useAuth() {
3004
+ const context = useContext2(AuthContext);
3005
+ if (context === void 0) {
3006
+ throw new Error("useAuth must be used within an AuthProvider");
3007
+ }
3008
+ return context;
3009
+ }
3010
+
3011
+ // src/auth/shared.ts
3012
+ function validateEmail(email) {
3013
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3014
+ return emailRegex.test(email);
3015
+ }
3016
+ function validatePassword(password) {
3017
+ const errors = [];
3018
+ if (password.length < 8) {
3019
+ errors.push("Password must be at least 8 characters long");
3020
+ }
3021
+ if (!/(?=.*[a-z])/.test(password)) {
3022
+ errors.push("Password must contain at least one lowercase letter");
3023
+ }
3024
+ if (!/(?=.*[A-Z])/.test(password)) {
3025
+ errors.push("Password must contain at least one uppercase letter");
3026
+ }
3027
+ if (!/(?=.*\d)/.test(password)) {
3028
+ errors.push("Password must contain at least one number");
3029
+ }
3030
+ return {
3031
+ isValid: errors.length === 0,
3032
+ errors
3033
+ };
3034
+ }
3035
+ function getAuthErrorMessage(error) {
3036
+ if (!error) return "An unknown error occurred";
3037
+ const message = error.message || error.error_description || error.toString();
3038
+ const errorMappings = {
3039
+ "Invalid login credentials": "Invalid email or password. Please check your credentials and try again.",
3040
+ "Email not confirmed": "Please check your email and click the confirmation link before signing in.",
3041
+ "User already registered": "An account with this email address already exists. Please sign in instead.",
3042
+ "Password should be at least 6 characters": "Password must be at least 6 characters long.",
3043
+ "Signup requires a valid password": "Please provide a valid password.",
3044
+ "Invalid email": "Please provide a valid email address.",
3045
+ "Email rate limit exceeded": "Too many emails sent. Please wait before requesting another.",
3046
+ 'duplicate key value violates unique constraint "profiles_username_key"': "This username is already taken. Please choose a different username."
3047
+ };
3048
+ return errorMappings[message] || message;
3049
+ }
3050
+
3051
+ // src/ui/sign-in-form.tsx
3052
+ import { Fragment as Fragment6, jsx as jsx40, jsxs as jsxs23 } from "react/jsx-runtime";
3053
+ var COOLDOWN_THRESHOLD = 3;
3054
+ var COOLDOWN_DURATION_MS = 3e4;
3055
+ function SignInForm({
3056
+ onSuccess,
3057
+ forgotPasswordHref,
3058
+ signUpHref,
3059
+ initialError,
3060
+ emailPlaceholder = "you@example.com",
3061
+ title = "Welcome back",
3062
+ subtitle = "Sign in to access your account"
3063
+ }) {
3064
+ const [email, setEmail] = React28.useState("");
3065
+ const [password, setPassword] = React28.useState("");
3066
+ const [showPassword, setShowPassword] = React28.useState(false);
3067
+ const [error, setError] = React28.useState(initialError ?? "");
3068
+ const [loading, setLoading] = React28.useState(false);
3069
+ const [consecutiveErrors, setConsecutiveErrors] = React28.useState(0);
3070
+ const [cooldownUntil, setCooldownUntil] = React28.useState(null);
3071
+ const { signIn } = useAuth();
3072
+ React28.useEffect(() => {
3073
+ if (initialError) setError(initialError);
3074
+ }, [initialError]);
3075
+ const handleSubmit = async (e) => {
3076
+ e.preventDefault();
3077
+ setError("");
3078
+ if (cooldownUntil && Date.now() < cooldownUntil) {
3079
+ const secondsLeft = Math.ceil((cooldownUntil - Date.now()) / 1e3);
3080
+ setError(`Too many attempts. Please wait ${secondsLeft} seconds.`);
3081
+ return;
3082
+ }
3083
+ if (!email || !password) {
3084
+ setError("Please fill in all fields");
3085
+ return;
3086
+ }
3087
+ if (!validateEmail(email)) {
3088
+ setError("Please enter a valid email address");
3089
+ return;
3090
+ }
3091
+ setLoading(true);
3092
+ try {
3093
+ const { error: signInError } = await signIn(email, password);
3094
+ if (signInError) {
3095
+ setError(getAuthErrorMessage(signInError));
3096
+ const newCount = consecutiveErrors + 1;
3097
+ setConsecutiveErrors(newCount);
3098
+ if (newCount >= COOLDOWN_THRESHOLD) {
3099
+ setCooldownUntil(Date.now() + COOLDOWN_DURATION_MS);
3100
+ setConsecutiveErrors(0);
3101
+ }
3102
+ } else {
3103
+ setConsecutiveErrors(0);
3104
+ setCooldownUntil(null);
3105
+ onSuccess?.(email);
3106
+ }
3107
+ } catch {
3108
+ setError("An unexpected error occurred. Please try again.");
3109
+ } finally {
3110
+ setLoading(false);
3111
+ }
3112
+ };
3113
+ return /* @__PURE__ */ jsxs23("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6", children: [
3114
+ /* @__PURE__ */ jsxs23("div", { className: "text-center", children: [
3115
+ /* @__PURE__ */ jsx40("h1", { className: "text-2xl font-bold text-[var(--timberwolf)] mb-2", children: title }),
3116
+ /* @__PURE__ */ jsx40("p", { className: "text-sm text-muted-foreground", children: subtitle })
3117
+ ] }),
3118
+ /* @__PURE__ */ jsxs23("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
3119
+ error && /* @__PURE__ */ jsx40(
3120
+ "div",
3121
+ {
3122
+ id: "signin-error",
3123
+ role: "alert",
3124
+ "aria-live": "polite",
3125
+ className: "bg-red-900/20 border border-red-700/30 rounded-lg p-3",
3126
+ children: /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2 text-red-400 text-sm", children: [
3127
+ /* @__PURE__ */ jsx40(AlertCircle, { className: "h-4 w-4 flex-shrink-0" }),
3128
+ /* @__PURE__ */ jsx40("span", { children: error })
3129
+ ] })
3130
+ }
3131
+ ),
3132
+ /* @__PURE__ */ jsxs23("div", { className: "space-y-2", children: [
3133
+ /* @__PURE__ */ jsx40(Label, { htmlFor: "email", className: "text-[var(--timberwolf)]", children: "Email" }),
3134
+ /* @__PURE__ */ jsxs23("div", { className: "relative", children: [
3135
+ /* @__PURE__ */ jsx40(
3136
+ Input,
3137
+ {
3138
+ id: "email",
3139
+ type: "email",
3140
+ placeholder: emailPlaceholder,
3141
+ value: email,
3142
+ onChange: (e) => setEmail(e.target.value),
3143
+ className: "h-11 pl-10",
3144
+ disabled: loading,
3145
+ autoComplete: "email",
3146
+ "aria-invalid": !!error,
3147
+ "aria-describedby": error ? "signin-error" : void 0
3148
+ }
3149
+ ),
3150
+ /* @__PURE__ */ jsx40(Mail, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" })
3151
+ ] })
3152
+ ] }),
3153
+ /* @__PURE__ */ jsxs23("div", { className: "space-y-2", children: [
3154
+ /* @__PURE__ */ jsx40(Label, { htmlFor: "password", className: "text-[var(--timberwolf)]", children: "Password" }),
3155
+ /* @__PURE__ */ jsxs23("div", { className: "relative", children: [
3156
+ /* @__PURE__ */ jsx40(
3157
+ Input,
3158
+ {
3159
+ id: "password",
3160
+ type: showPassword ? "text" : "password",
3161
+ placeholder: "Enter your password",
3162
+ value: password,
3163
+ onChange: (e) => setPassword(e.target.value),
3164
+ className: "h-11 pl-10 pr-10",
3165
+ disabled: loading,
3166
+ autoComplete: "current-password",
3167
+ "aria-invalid": !!error,
3168
+ "aria-describedby": error ? "signin-error" : void 0
3169
+ }
3170
+ ),
3171
+ /* @__PURE__ */ jsx40(Lock, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }),
3172
+ /* @__PURE__ */ jsx40(
3173
+ "button",
3174
+ {
3175
+ type: "button",
3176
+ onClick: () => setShowPassword((prev) => !prev),
3177
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-[var(--timberwolf)]",
3178
+ disabled: loading,
3179
+ "aria-label": showPassword ? "Hide password" : "Show password",
3180
+ "aria-pressed": showPassword,
3181
+ children: showPassword ? /* @__PURE__ */ jsx40(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx40(Eye, { className: "h-4 w-4" })
3182
+ }
3183
+ )
3184
+ ] })
3185
+ ] }),
3186
+ forgotPasswordHref ? /* @__PURE__ */ jsx40("div", { className: "flex justify-end -mt-1", children: /* @__PURE__ */ jsx40(
3187
+ Link4,
3188
+ {
3189
+ href: forgotPasswordHref,
3190
+ className: "text-xs text-muted-foreground hover:text-[var(--timberwolf)] transition-colors",
3191
+ children: "Forgot your password?"
3192
+ }
3193
+ ) }) : null,
3194
+ /* @__PURE__ */ jsx40(
3195
+ Button,
3196
+ {
3197
+ type: "submit",
3198
+ className: "w-full h-11 bg-[var(--timberwolf)] text-[var(--night)] hover:bg-[var(--ash-grey)]",
3199
+ disabled: loading,
3200
+ children: loading ? /* @__PURE__ */ jsxs23(Fragment6, { children: [
3201
+ /* @__PURE__ */ jsx40(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }),
3202
+ "Signing in..."
3203
+ ] }) : "Sign in"
3204
+ }
3205
+ )
3206
+ ] }),
3207
+ signUpHref ? /* @__PURE__ */ jsx40("div", { className: "text-center text-sm", children: /* @__PURE__ */ jsxs23("p", { className: "text-muted-foreground", children: [
3208
+ "Don't have an account?",
3209
+ " ",
3210
+ /* @__PURE__ */ jsx40(
3211
+ Link4,
3212
+ {
3213
+ href: signUpHref,
3214
+ className: "text-[var(--timberwolf)] hover:underline",
3215
+ children: "Sign up"
3216
+ }
3217
+ )
3218
+ ] }) }) : null
3219
+ ] });
3220
+ }
3221
+
3222
+ // src/ui/forgot-password-form.tsx
3223
+ import * as React29 from "react";
3224
+ import Link5 from "next/link";
3225
+ import {
3226
+ AlertCircle as AlertCircle2,
3227
+ ArrowLeft,
3228
+ CheckCircle,
3229
+ Loader2 as Loader22,
3230
+ Mail as Mail2
3231
+ } from "lucide-react";
3232
+ import { Fragment as Fragment7, jsx as jsx41, jsxs as jsxs24 } from "react/jsx-runtime";
3233
+ var RESEND_COOLDOWN_MS = 3e4;
3234
+ var COOLDOWN_STORAGE_KEY = "playback:pwreset-cooldown-until";
3235
+ function ForgotPasswordForm({
3236
+ redirectTo,
3237
+ loginHref = "/auth/login",
3238
+ title = "Forgot password?",
3239
+ subtitle = "Enter your email and we'll send you a reset link."
3240
+ }) {
3241
+ const [email, setEmail] = React29.useState("");
3242
+ const [error, setError] = React29.useState("");
3243
+ const [loading, setLoading] = React29.useState(false);
3244
+ const [emailSent, setEmailSent] = React29.useState(false);
3245
+ const [cooldownUntil, setCooldownUntilState] = React29.useState(
3246
+ null
3247
+ );
3248
+ const [now, setNow] = React29.useState(() => Date.now());
3249
+ const [supabase] = React29.useState(() => createClient());
3250
+ const setCooldownUntil = (value) => {
3251
+ setCooldownUntilState(value);
3252
+ if (typeof window === "undefined") return;
3253
+ if (value === null) {
3254
+ window.sessionStorage.removeItem(COOLDOWN_STORAGE_KEY);
3255
+ } else {
3256
+ window.sessionStorage.setItem(COOLDOWN_STORAGE_KEY, String(value));
3257
+ }
3258
+ };
3259
+ React29.useEffect(() => {
3260
+ if (typeof window === "undefined") return;
3261
+ const stored = window.sessionStorage.getItem(COOLDOWN_STORAGE_KEY);
3262
+ if (!stored) return;
3263
+ const parsed = Number(stored);
3264
+ if (!Number.isFinite(parsed) || parsed <= Date.now()) {
3265
+ window.sessionStorage.removeItem(COOLDOWN_STORAGE_KEY);
3266
+ return;
3267
+ }
3268
+ setCooldownUntilState(parsed);
3269
+ }, []);
3270
+ React29.useEffect(() => {
3271
+ if (!cooldownUntil) return;
3272
+ const id = setInterval(() => setNow(Date.now()), 1e3);
3273
+ return () => clearInterval(id);
3274
+ }, [cooldownUntil]);
3275
+ const cooldownSecondsLeft = cooldownUntil && cooldownUntil > now ? Math.ceil((cooldownUntil - now) / 1e3) : 0;
3276
+ const handleSubmit = async (e) => {
3277
+ e.preventDefault();
3278
+ setError("");
3279
+ if (cooldownSecondsLeft > 0) {
3280
+ setError(`Please wait ${cooldownSecondsLeft} seconds before resending.`);
3281
+ return;
3282
+ }
3283
+ if (!email) {
3284
+ setError("Please enter your email address");
3285
+ return;
3286
+ }
3287
+ if (!validateEmail(email)) {
3288
+ setError("Please enter a valid email address");
3289
+ return;
3290
+ }
3291
+ setLoading(true);
3292
+ try {
3293
+ const { error: resetError } = await supabase.auth.resetPasswordForEmail(
3294
+ email,
3295
+ { redirectTo }
3296
+ );
3297
+ if (resetError) {
3298
+ setError(getAuthErrorMessage(resetError));
3299
+ } else {
3300
+ setEmailSent(true);
3301
+ setCooldownUntil(Date.now() + RESEND_COOLDOWN_MS);
3302
+ }
3303
+ } catch {
3304
+ setError("An unexpected error occurred. Please try again.");
3305
+ } finally {
3306
+ setLoading(false);
3307
+ }
3308
+ };
3309
+ if (emailSent) {
3310
+ return /* @__PURE__ */ jsxs24("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6 text-center", children: [
3311
+ /* @__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(CheckCircle, { className: "h-6 w-6 text-[var(--timberwolf)]" }) }) }),
3312
+ /* @__PURE__ */ jsxs24("div", { className: "space-y-2", children: [
3313
+ /* @__PURE__ */ jsx41("h1", { className: "text-2xl font-bold text-[var(--timberwolf)]", children: "Check your email" }),
3314
+ /* @__PURE__ */ jsxs24("p", { className: "text-sm text-muted-foreground", children: [
3315
+ "We sent a reset link to",
3316
+ " ",
3317
+ /* @__PURE__ */ jsx41("span", { className: "text-[var(--timberwolf)] font-medium", children: email }),
3318
+ ". If it's not in your inbox, check spam."
3319
+ ] })
3320
+ ] }),
3321
+ /* @__PURE__ */ jsxs24("div", { className: "space-y-3 pt-2", children: [
3322
+ /* @__PURE__ */ jsx41(
3323
+ Button,
3324
+ {
3325
+ onClick: () => {
3326
+ setEmailSent(false);
3327
+ setEmail("");
3328
+ setError("");
3329
+ },
3330
+ variant: "outline",
3331
+ className: "w-full h-11",
3332
+ disabled: cooldownSecondsLeft > 0,
3333
+ children: cooldownSecondsLeft > 0 ? `Send another email in ${cooldownSecondsLeft}s` : "Send another email"
3334
+ }
3335
+ ),
3336
+ /* @__PURE__ */ jsx41(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: [
3337
+ /* @__PURE__ */ jsx41(ArrowLeft, { className: "h-4 w-4 mr-2" }),
3338
+ "Back to sign in"
3339
+ ] }) })
3340
+ ] })
3341
+ ] });
3342
+ }
3343
+ return /* @__PURE__ */ jsxs24("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6", children: [
3344
+ /* @__PURE__ */ jsxs24("div", { className: "text-center", children: [
3345
+ /* @__PURE__ */ jsx41("h1", { className: "text-2xl font-bold text-[var(--timberwolf)] mb-2", children: title }),
3346
+ /* @__PURE__ */ jsx41("p", { className: "text-sm text-muted-foreground", children: subtitle })
3347
+ ] }),
3348
+ /* @__PURE__ */ jsxs24("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
3349
+ error && /* @__PURE__ */ jsx41(
3350
+ "div",
3351
+ {
3352
+ id: "forgot-error",
3353
+ role: "alert",
3354
+ "aria-live": "polite",
3355
+ className: "bg-red-900/20 border border-red-700/30 rounded-lg p-3",
3356
+ children: /* @__PURE__ */ jsxs24("div", { className: "flex items-center gap-2 text-red-400 text-sm", children: [
3357
+ /* @__PURE__ */ jsx41(AlertCircle2, { className: "h-4 w-4 flex-shrink-0" }),
3358
+ /* @__PURE__ */ jsx41("span", { children: error })
3359
+ ] })
3360
+ }
3361
+ ),
3362
+ /* @__PURE__ */ jsxs24("div", { className: "space-y-2", children: [
3363
+ /* @__PURE__ */ jsx41(Label, { htmlFor: "email", className: "text-[var(--timberwolf)]", children: "Email" }),
3364
+ /* @__PURE__ */ jsxs24("div", { className: "relative", children: [
3365
+ /* @__PURE__ */ jsx41(
3366
+ Input,
3367
+ {
3368
+ id: "email",
3369
+ type: "email",
3370
+ placeholder: "you@example.com",
3371
+ value: email,
3372
+ onChange: (e) => setEmail(e.target.value),
3373
+ className: "h-11 pl-10",
3374
+ disabled: loading,
3375
+ autoComplete: "email",
3376
+ autoFocus: true,
3377
+ "aria-invalid": !!error,
3378
+ "aria-describedby": error ? "forgot-error" : void 0
3379
+ }
3380
+ ),
3381
+ /* @__PURE__ */ jsx41(Mail2, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" })
3382
+ ] })
3383
+ ] }),
3384
+ /* @__PURE__ */ jsx41(
3385
+ Button,
3386
+ {
3387
+ type: "submit",
3388
+ className: "w-full h-11 bg-[var(--timberwolf)] text-[var(--night)] hover:bg-[var(--ash-grey)]",
3389
+ disabled: loading || cooldownSecondsLeft > 0,
3390
+ children: loading ? /* @__PURE__ */ jsxs24(Fragment7, { children: [
3391
+ /* @__PURE__ */ jsx41(Loader22, { className: "h-4 w-4 mr-2 animate-spin" }),
3392
+ "Sending email..."
3393
+ ] }) : cooldownSecondsLeft > 0 ? `Try again in ${cooldownSecondsLeft}s` : "Send reset link"
3394
+ }
3395
+ )
3396
+ ] }),
3397
+ /* @__PURE__ */ jsx41("div", { className: "text-center text-sm", children: /* @__PURE__ */ jsxs24(
3398
+ Link5,
3399
+ {
3400
+ href: loginHref,
3401
+ className: "inline-flex items-center gap-1.5 text-muted-foreground hover:text-[var(--timberwolf)] transition-colors",
3402
+ children: [
3403
+ /* @__PURE__ */ jsx41(ArrowLeft, { className: "h-3.5 w-3.5" }),
3404
+ "Back to sign in"
3405
+ ]
3406
+ }
3407
+ ) })
3408
+ ] });
3409
+ }
3410
+
3411
+ // src/ui/reset-password-form.tsx
3412
+ import * as React30 from "react";
3413
+ import Link6 from "next/link";
3414
+ import { useRouter as useRouter2 } from "next/navigation";
3415
+ import {
3416
+ AlertCircle as AlertCircle3,
3417
+ CheckCircle as CheckCircle2,
3418
+ Eye as Eye2,
3419
+ EyeOff as EyeOff2,
3420
+ Loader2 as Loader23,
3421
+ Lock as Lock2
3422
+ } from "lucide-react";
3423
+ import { Fragment as Fragment8, jsx as jsx42, jsxs as jsxs25 } from "react/jsx-runtime";
3424
+ function ResetPasswordForm({
3425
+ loginHref = "/auth/login",
3426
+ initialError,
3427
+ redirectDelayMs = 3e3,
3428
+ title = "Reset password",
3429
+ subtitle = "Enter a new password for your account."
3430
+ }) {
3431
+ const [password, setPassword] = React30.useState("");
3432
+ const [confirmPassword, setConfirmPassword] = React30.useState("");
3433
+ const [showPassword, setShowPassword] = React30.useState(false);
3434
+ const [showConfirmPassword, setShowConfirmPassword] = React30.useState(false);
3435
+ const [error, setError] = React30.useState(initialError ?? "");
3436
+ const [loading, setLoading] = React30.useState(false);
3437
+ const [passwordReset, setPasswordReset] = React30.useState(false);
3438
+ const router = useRouter2();
3439
+ const [supabase] = React30.useState(() => createClient());
3440
+ React30.useEffect(() => {
3441
+ if (initialError) setError(initialError);
3442
+ }, [initialError]);
3443
+ React30.useEffect(() => {
3444
+ if (!passwordReset) return;
3445
+ const id = setTimeout(() => {
3446
+ router.push(loginHref);
3447
+ }, redirectDelayMs);
3448
+ return () => clearTimeout(id);
3449
+ }, [passwordReset, router, loginHref, redirectDelayMs]);
3450
+ const handleSubmit = async (e) => {
3451
+ e.preventDefault();
3452
+ setError("");
3453
+ if (!password || !confirmPassword) {
3454
+ setError("Please fill in both fields");
3455
+ return;
3456
+ }
3457
+ const { isValid, errors } = validatePassword(password);
3458
+ if (!isValid) {
3459
+ setError(errors[0]);
3460
+ return;
3461
+ }
3462
+ if (password !== confirmPassword) {
3463
+ setError("Passwords do not match");
3464
+ return;
3465
+ }
3466
+ setLoading(true);
3467
+ try {
3468
+ const { error: updateError } = await supabase.auth.updateUser({
3469
+ password
3470
+ });
3471
+ if (updateError) {
3472
+ setError(getAuthErrorMessage(updateError));
3473
+ } else {
3474
+ try {
3475
+ await supabase.auth.signOut({ scope: "global" });
3476
+ } catch (signOutError) {
3477
+ console.warn(
3478
+ "[ResetPasswordForm] global sign-out failed after successful update",
3479
+ signOutError
3480
+ );
3481
+ }
3482
+ setPasswordReset(true);
3483
+ }
3484
+ } catch {
3485
+ setError("An unexpected error occurred. Please try again.");
3486
+ } finally {
3487
+ setLoading(false);
3488
+ }
3489
+ };
3490
+ if (passwordReset) {
3491
+ return /* @__PURE__ */ jsxs25("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6 text-center", children: [
3492
+ /* @__PURE__ */ jsx42("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx42("div", { className: "h-12 w-12 rounded-full bg-[var(--timberwolf)]/10 border border-[var(--timberwolf)]/20 flex items-center justify-center", children: /* @__PURE__ */ jsx42(CheckCircle2, { className: "h-6 w-6 text-[var(--timberwolf)]" }) }) }),
3493
+ /* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
3494
+ /* @__PURE__ */ jsx42("h1", { className: "text-2xl font-bold text-[var(--timberwolf)]", children: "Password updated" }),
3495
+ /* @__PURE__ */ jsx42("p", { className: "text-sm text-muted-foreground", children: "Redirecting you to sign in\u2026" })
3496
+ ] }),
3497
+ /* @__PURE__ */ jsx42(Link6, { href: loginHref, className: "block pt-2", children: /* @__PURE__ */ jsx42(Button, { className: "w-full h-11 bg-[var(--timberwolf)] text-[var(--night)] hover:bg-[var(--ash-grey)]", children: "Continue to sign in" }) })
3498
+ ] });
3499
+ }
3500
+ return /* @__PURE__ */ jsxs25("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6", children: [
3501
+ /* @__PURE__ */ jsxs25("div", { className: "text-center", children: [
3502
+ /* @__PURE__ */ jsx42("h1", { className: "text-2xl font-bold text-[var(--timberwolf)] mb-2", children: title }),
3503
+ /* @__PURE__ */ jsx42("p", { className: "text-sm text-muted-foreground", children: subtitle })
3504
+ ] }),
3505
+ /* @__PURE__ */ jsxs25("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
3506
+ error && /* @__PURE__ */ jsx42(
3507
+ "div",
3508
+ {
3509
+ id: "reset-error",
3510
+ role: "alert",
3511
+ "aria-live": "polite",
3512
+ className: "bg-red-900/20 border border-red-700/30 rounded-lg p-3",
3513
+ children: /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2 text-red-400 text-sm", children: [
3514
+ /* @__PURE__ */ jsx42(AlertCircle3, { className: "h-4 w-4 flex-shrink-0" }),
3515
+ /* @__PURE__ */ jsx42("span", { children: error })
3516
+ ] })
3517
+ }
3518
+ ),
3519
+ /* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
3520
+ /* @__PURE__ */ jsx42(Label, { htmlFor: "password", className: "text-[var(--timberwolf)]", children: "New password" }),
3521
+ /* @__PURE__ */ jsxs25("div", { className: "relative", children: [
3522
+ /* @__PURE__ */ jsx42(
3523
+ Input,
3524
+ {
3525
+ id: "password",
3526
+ type: showPassword ? "text" : "password",
3527
+ placeholder: "Enter a new password",
3528
+ value: password,
3529
+ onChange: (e) => setPassword(e.target.value),
3530
+ className: "h-11 pl-10 pr-10",
3531
+ disabled: loading,
3532
+ autoComplete: "new-password",
3533
+ autoFocus: true,
3534
+ "aria-invalid": !!error,
3535
+ "aria-describedby": error ? "reset-error" : void 0
3536
+ }
3537
+ ),
3538
+ /* @__PURE__ */ jsx42(Lock2, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }),
3539
+ /* @__PURE__ */ jsx42(
3540
+ "button",
3541
+ {
3542
+ type: "button",
3543
+ onClick: () => setShowPassword((prev) => !prev),
3544
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-[var(--timberwolf)]",
3545
+ disabled: loading,
3546
+ "aria-label": showPassword ? "Hide password" : "Show password",
3547
+ "aria-pressed": showPassword,
3548
+ children: showPassword ? /* @__PURE__ */ jsx42(EyeOff2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx42(Eye2, { className: "h-4 w-4" })
3549
+ }
3550
+ )
3551
+ ] })
3552
+ ] }),
3553
+ /* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
3554
+ /* @__PURE__ */ jsx42(
3555
+ Label,
3556
+ {
3557
+ htmlFor: "confirmPassword",
3558
+ className: "text-[var(--timberwolf)]",
3559
+ children: "Confirm new password"
3560
+ }
3561
+ ),
3562
+ /* @__PURE__ */ jsxs25("div", { className: "relative", children: [
3563
+ /* @__PURE__ */ jsx42(
3564
+ Input,
3565
+ {
3566
+ id: "confirmPassword",
3567
+ type: showConfirmPassword ? "text" : "password",
3568
+ placeholder: "Re-enter your new password",
3569
+ value: confirmPassword,
3570
+ onChange: (e) => setConfirmPassword(e.target.value),
3571
+ className: "h-11 pl-10 pr-10",
3572
+ disabled: loading,
3573
+ autoComplete: "new-password",
3574
+ "aria-invalid": !!error,
3575
+ "aria-describedby": error ? "reset-error" : void 0
3576
+ }
3577
+ ),
3578
+ /* @__PURE__ */ jsx42(Lock2, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }),
3579
+ /* @__PURE__ */ jsx42(
3580
+ "button",
3581
+ {
3582
+ type: "button",
3583
+ onClick: () => setShowConfirmPassword((prev) => !prev),
3584
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-[var(--timberwolf)]",
3585
+ disabled: loading,
3586
+ "aria-label": showConfirmPassword ? "Hide password" : "Show password",
3587
+ "aria-pressed": showConfirmPassword,
3588
+ children: showConfirmPassword ? /* @__PURE__ */ jsx42(EyeOff2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx42(Eye2, { className: "h-4 w-4" })
3589
+ }
3590
+ )
3591
+ ] })
3592
+ ] }),
3593
+ /* @__PURE__ */ jsx42("p", { className: "text-xs text-muted-foreground", children: "At least 8 characters with an uppercase letter, a lowercase letter, and a number." }),
3594
+ /* @__PURE__ */ jsx42(
3595
+ Button,
3596
+ {
3597
+ type: "submit",
3598
+ className: "w-full h-11 bg-[var(--timberwolf)] text-[var(--night)] hover:bg-[var(--ash-grey)]",
3599
+ disabled: loading,
3600
+ children: loading ? /* @__PURE__ */ jsxs25(Fragment8, { children: [
3601
+ /* @__PURE__ */ jsx42(Loader23, { className: "h-4 w-4 mr-2 animate-spin" }),
3602
+ "Updating password..."
3603
+ ] }) : "Update password"
3604
+ }
3605
+ )
3606
+ ] }),
3607
+ /* @__PURE__ */ jsx42("div", { className: "text-center text-sm", children: /* @__PURE__ */ jsx42(
3608
+ Link6,
3609
+ {
3610
+ href: loginHref,
3611
+ className: "text-muted-foreground hover:text-[var(--timberwolf)] transition-colors",
3612
+ children: "Back to sign in"
3613
+ }
3614
+ ) })
3615
+ ] });
3616
+ }
2869
3617
  export {
2870
3618
  AnimatedTooltip,
2871
3619
  Avatar,
@@ -2909,6 +3657,7 @@ export {
2909
3657
  FlipWords,
2910
3658
  Footer,
2911
3659
  FooterCreditsBar,
3660
+ ForgotPasswordForm,
2912
3661
  HeroHighlight,
2913
3662
  Highlight,
2914
3663
  HoverCard,
@@ -2924,6 +3673,7 @@ export {
2924
3673
  PopoverAnchor,
2925
3674
  PopoverContent,
2926
3675
  PopoverTrigger,
3676
+ ResetPasswordForm,
2927
3677
  SearchBar,
2928
3678
  SectionCard,
2929
3679
  Select,
@@ -2947,6 +3697,8 @@ export {
2947
3697
  SheetPortal,
2948
3698
  SheetTitle,
2949
3699
  SheetTrigger,
3700
+ SignInForm,
3701
+ SiteFooter,
2950
3702
  Skeleton,
2951
3703
  StatsGrid,
2952
3704
  Switch,