@braintwopoint0/playback-commons 0.2.4 → 0.2.6
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/api/index.d.ts +34 -0
- package/dist/api/index.js +26 -0
- package/dist/api/index.js.map +1 -0
- package/dist/ui/index.js +97 -114
- package/dist/ui/index.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
type CorsOptions = {
|
|
2
|
+
/** Comma-separated method list. Default: 'POST, OPTIONS'. */
|
|
3
|
+
methods?: string;
|
|
4
|
+
/** Comma-separated header allowlist. Default: 'Content-Type'. */
|
|
5
|
+
headers?: string;
|
|
6
|
+
/** Preflight cache duration in seconds. Default: 86400 (24h). */
|
|
7
|
+
maxAge?: number;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Build CORS response headers.
|
|
11
|
+
*
|
|
12
|
+
* Always returns `Vary: Origin` so caches don't serve a CORS-allowed response
|
|
13
|
+
* to a different origin. Only emits `Access-Control-*` headers when the request
|
|
14
|
+
* Origin is in the allowlist — exact-match, no wildcards, so a typo'd subdomain
|
|
15
|
+
* or rogue preview URL can't post into a production endpoint.
|
|
16
|
+
*
|
|
17
|
+
* Same-origin requests have no Origin header on POST in some browsers and never
|
|
18
|
+
* preflight, so an absent or unknown origin returning the empty CORS set is the
|
|
19
|
+
* correct behavior.
|
|
20
|
+
*/
|
|
21
|
+
declare function corsHeaders(origin: string | null, allowed: ReadonlySet<string>, opts?: CorsOptions): Record<string, string>;
|
|
22
|
+
/**
|
|
23
|
+
* Build a preflight (OPTIONS) Response. Use directly as the route's OPTIONS export:
|
|
24
|
+
*
|
|
25
|
+
* export const OPTIONS = (req: NextRequest) => corsPreflight(req, ALLOWED_ORIGINS)
|
|
26
|
+
*
|
|
27
|
+
* Returns 204 No Content with the CORS headers built from the request's Origin
|
|
28
|
+
* against the allowlist. If the origin isn't allowed, the response still returns
|
|
29
|
+
* 204 but without the `Access-Control-Allow-*` headers — which is what the browser
|
|
30
|
+
* needs to see in order to block the subsequent real request.
|
|
31
|
+
*/
|
|
32
|
+
declare function corsPreflight(req: Request, allowed: ReadonlySet<string>, opts?: CorsOptions): Response;
|
|
33
|
+
|
|
34
|
+
export { type CorsOptions, corsHeaders, corsPreflight };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/api/index.ts
|
|
2
|
+
var DEFAULT_METHODS = "POST, OPTIONS";
|
|
3
|
+
var DEFAULT_HEADERS = "Content-Type";
|
|
4
|
+
var DEFAULT_MAX_AGE = 86400;
|
|
5
|
+
function corsHeaders(origin, allowed, opts = {}) {
|
|
6
|
+
const headers = { Vary: "Origin" };
|
|
7
|
+
if (origin && allowed.has(origin)) {
|
|
8
|
+
headers["Access-Control-Allow-Origin"] = origin;
|
|
9
|
+
headers["Access-Control-Allow-Methods"] = opts.methods ?? DEFAULT_METHODS;
|
|
10
|
+
headers["Access-Control-Allow-Headers"] = opts.headers ?? DEFAULT_HEADERS;
|
|
11
|
+
headers["Access-Control-Max-Age"] = String(opts.maxAge ?? DEFAULT_MAX_AGE);
|
|
12
|
+
}
|
|
13
|
+
return headers;
|
|
14
|
+
}
|
|
15
|
+
function corsPreflight(req, allowed, opts) {
|
|
16
|
+
const origin = req.headers.get("origin");
|
|
17
|
+
return new Response(null, {
|
|
18
|
+
status: 204,
|
|
19
|
+
headers: corsHeaders(origin, allowed, opts)
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
corsHeaders,
|
|
24
|
+
corsPreflight
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/api/index.ts"],"sourcesContent":["// CORS primitives for cross-origin API routes between PLAYBACK / PLAYHUB / future\n// PLAYBACK-suite apps.\n//\n// The mechanism (header construction, preflight response) lives here. The policy\n// — i.e. which origins are allowed to call a given endpoint — is intentionally\n// per-route, since \"who can hit my newsletter endpoint\" is a different decision\n// from \"who can hit my admin endpoint\".\n\nexport type CorsOptions = {\n /** Comma-separated method list. Default: 'POST, OPTIONS'. */\n methods?: string\n /** Comma-separated header allowlist. Default: 'Content-Type'. */\n headers?: string\n /** Preflight cache duration in seconds. Default: 86400 (24h). */\n maxAge?: number\n}\n\nconst DEFAULT_METHODS = 'POST, OPTIONS'\nconst DEFAULT_HEADERS = 'Content-Type'\nconst DEFAULT_MAX_AGE = 86400\n\n/**\n * Build CORS response headers.\n *\n * Always returns `Vary: Origin` so caches don't serve a CORS-allowed response\n * to a different origin. Only emits `Access-Control-*` headers when the request\n * Origin is in the allowlist — exact-match, no wildcards, so a typo'd subdomain\n * or rogue preview URL can't post into a production endpoint.\n *\n * Same-origin requests have no Origin header on POST in some browsers and never\n * preflight, so an absent or unknown origin returning the empty CORS set is the\n * correct behavior.\n */\nexport function corsHeaders(\n origin: string | null,\n allowed: ReadonlySet<string>,\n opts: CorsOptions = {}\n): Record<string, string> {\n const headers: Record<string, string> = { Vary: 'Origin' }\n if (origin && allowed.has(origin)) {\n headers['Access-Control-Allow-Origin'] = origin\n headers['Access-Control-Allow-Methods'] = opts.methods ?? DEFAULT_METHODS\n headers['Access-Control-Allow-Headers'] = opts.headers ?? DEFAULT_HEADERS\n headers['Access-Control-Max-Age'] = String(opts.maxAge ?? DEFAULT_MAX_AGE)\n }\n return headers\n}\n\n/**\n * Build a preflight (OPTIONS) Response. Use directly as the route's OPTIONS export:\n *\n * export const OPTIONS = (req: NextRequest) => corsPreflight(req, ALLOWED_ORIGINS)\n *\n * Returns 204 No Content with the CORS headers built from the request's Origin\n * against the allowlist. If the origin isn't allowed, the response still returns\n * 204 but without the `Access-Control-Allow-*` headers — which is what the browser\n * needs to see in order to block the subsequent real request.\n */\nexport function corsPreflight(\n req: Request,\n allowed: ReadonlySet<string>,\n opts?: CorsOptions\n): Response {\n const origin = req.headers.get('origin')\n return new Response(null, {\n status: 204,\n headers: corsHeaders(origin, allowed, opts),\n })\n}\n"],"mappings":";AAiBA,IAAM,kBAAkB;AACxB,IAAM,kBAAkB;AACxB,IAAM,kBAAkB;AAcjB,SAAS,YACd,QACA,SACA,OAAoB,CAAC,GACG;AACxB,QAAM,UAAkC,EAAE,MAAM,SAAS;AACzD,MAAI,UAAU,QAAQ,IAAI,MAAM,GAAG;AACjC,YAAQ,6BAA6B,IAAI;AACzC,YAAQ,8BAA8B,IAAI,KAAK,WAAW;AAC1D,YAAQ,8BAA8B,IAAI,KAAK,WAAW;AAC1D,YAAQ,wBAAwB,IAAI,OAAO,KAAK,UAAU,eAAe;AAAA,EAC3E;AACA,SAAO;AACT;AAYO,SAAS,cACd,KACA,SACA,MACU;AACV,QAAM,SAAS,IAAI,QAAQ,IAAI,QAAQ;AACvC,SAAO,IAAI,SAAS,MAAM;AAAA,IACxB,QAAQ;AAAA,IACR,SAAS,YAAY,QAAQ,SAAS,IAAI;AAAA,EAC5C,CAAC;AACH;","names":[]}
|
package/dist/ui/index.js
CHANGED
|
@@ -2744,7 +2744,7 @@ function NewsletterForm2({
|
|
|
2744
2744
|
const trimmed = email.trim();
|
|
2745
2745
|
const ok = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed);
|
|
2746
2746
|
if (!ok) {
|
|
2747
|
-
setState("
|
|
2747
|
+
setState("invalid_format");
|
|
2748
2748
|
return;
|
|
2749
2749
|
}
|
|
2750
2750
|
inFlight.current = true;
|
|
@@ -2765,11 +2765,13 @@ function NewsletterForm2({
|
|
|
2765
2765
|
setEmail("");
|
|
2766
2766
|
} else if (res.status === 429) {
|
|
2767
2767
|
setState("rate_limited");
|
|
2768
|
+
} else if (res.status === 400) {
|
|
2769
|
+
setState("invalid_format");
|
|
2768
2770
|
} else {
|
|
2769
|
-
setState("
|
|
2771
|
+
setState("server_error");
|
|
2770
2772
|
}
|
|
2771
2773
|
} catch {
|
|
2772
|
-
setState("
|
|
2774
|
+
setState("server_error");
|
|
2773
2775
|
} finally {
|
|
2774
2776
|
inFlight.current = false;
|
|
2775
2777
|
}
|
|
@@ -2820,16 +2822,17 @@ function NewsletterForm2({
|
|
|
2820
2822
|
value: email,
|
|
2821
2823
|
onChange: (e) => {
|
|
2822
2824
|
setEmail(e.target.value);
|
|
2823
|
-
if (state === "
|
|
2825
|
+
if (state === "invalid_format" || state === "server_error" || state === "rate_limited")
|
|
2826
|
+
setState("idle");
|
|
2824
2827
|
},
|
|
2825
2828
|
placeholder,
|
|
2826
|
-
"aria-invalid": state === "
|
|
2829
|
+
"aria-invalid": state === "invalid_format",
|
|
2827
2830
|
"aria-describedby": feedbackId,
|
|
2828
2831
|
className: cn(
|
|
2829
2832
|
"w-full sm:flex-1 h-12 sm:h-11 rounded-full bg-[var(--surface-1,#0f1512)] border px-5 text-[16px] sm:text-[14px] text-[var(--timberwolf)] placeholder:text-[rgba(214,213,201,0.44)]",
|
|
2830
2833
|
"focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--timberwolf)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--night)]",
|
|
2831
2834
|
"disabled:opacity-60",
|
|
2832
|
-
state === "
|
|
2835
|
+
state === "invalid_format" || state === "server_error" ? "border-[rgba(237,106,106,0.5)]" : "border-[rgba(214,213,201,0.08)]"
|
|
2833
2836
|
)
|
|
2834
2837
|
}
|
|
2835
2838
|
),
|
|
@@ -2851,13 +2854,14 @@ function NewsletterForm2({
|
|
|
2851
2854
|
className: cn(
|
|
2852
2855
|
"mt-2 -ml-0.5 text-[13px] min-h-[1.25rem]",
|
|
2853
2856
|
state === "sent" && "text-[var(--timberwolf)]",
|
|
2854
|
-
(state === "
|
|
2857
|
+
(state === "invalid_format" || state === "server_error" || state === "rate_limited") && "text-[rgb(237,106,106)]",
|
|
2855
2858
|
(state === "idle" || state === "sending") && "text-[rgba(214,213,201,0.44)]"
|
|
2856
2859
|
),
|
|
2857
|
-
role: state === "
|
|
2860
|
+
role: state === "invalid_format" || state === "server_error" || state === "rate_limited" ? "alert" : void 0,
|
|
2858
2861
|
children: [
|
|
2859
2862
|
state === "sent" && "Thanks \u2014 we\u2019ll be in touch.",
|
|
2860
|
-
state === "
|
|
2863
|
+
state === "invalid_format" && "Please enter a valid email address.",
|
|
2864
|
+
state === "server_error" && "Could not subscribe right now. Please try again in a moment.",
|
|
2861
2865
|
state === "rate_limited" && "Too many attempts. Try again in a minute."
|
|
2862
2866
|
]
|
|
2863
2867
|
}
|
|
@@ -2969,45 +2973,6 @@ import {
|
|
|
2969
2973
|
Mail
|
|
2970
2974
|
} from "lucide-react";
|
|
2971
2975
|
|
|
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
2976
|
// src/auth/shared.ts
|
|
3012
2977
|
function validateEmail(email) {
|
|
3013
2978
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -3048,8 +3013,23 @@ function getAuthErrorMessage(error) {
|
|
|
3048
3013
|
return errorMappings[message] || message;
|
|
3049
3014
|
}
|
|
3050
3015
|
|
|
3016
|
+
// src/supabase/client.ts
|
|
3017
|
+
import { createBrowserClient } from "@supabase/ssr";
|
|
3018
|
+
function createClient() {
|
|
3019
|
+
const cookieDomain = process.env.NEXT_PUBLIC_COOKIE_DOMAIN;
|
|
3020
|
+
return createBrowserClient(
|
|
3021
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
|
3022
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
|
3023
|
+
{
|
|
3024
|
+
...cookieDomain && {
|
|
3025
|
+
cookieOptions: { domain: cookieDomain }
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
);
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3051
3031
|
// src/ui/sign-in-form.tsx
|
|
3052
|
-
import { Fragment as Fragment6, jsx as
|
|
3032
|
+
import { Fragment as Fragment6, jsx as jsx39, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
3053
3033
|
var COOLDOWN_THRESHOLD = 3;
|
|
3054
3034
|
var COOLDOWN_DURATION_MS = 3e4;
|
|
3055
3035
|
function SignInForm({
|
|
@@ -3068,7 +3048,7 @@ function SignInForm({
|
|
|
3068
3048
|
const [loading, setLoading] = React28.useState(false);
|
|
3069
3049
|
const [consecutiveErrors, setConsecutiveErrors] = React28.useState(0);
|
|
3070
3050
|
const [cooldownUntil, setCooldownUntil] = React28.useState(null);
|
|
3071
|
-
const
|
|
3051
|
+
const [supabase] = React28.useState(() => createClient());
|
|
3072
3052
|
React28.useEffect(() => {
|
|
3073
3053
|
if (initialError) setError(initialError);
|
|
3074
3054
|
}, [initialError]);
|
|
@@ -3090,7 +3070,10 @@ function SignInForm({
|
|
|
3090
3070
|
}
|
|
3091
3071
|
setLoading(true);
|
|
3092
3072
|
try {
|
|
3093
|
-
const { error: signInError } = await
|
|
3073
|
+
const { error: signInError } = await supabase.auth.signInWithPassword({
|
|
3074
|
+
email,
|
|
3075
|
+
password
|
|
3076
|
+
});
|
|
3094
3077
|
if (signInError) {
|
|
3095
3078
|
setError(getAuthErrorMessage(signInError));
|
|
3096
3079
|
const newCount = consecutiveErrors + 1;
|
|
@@ -3112,11 +3095,11 @@ function SignInForm({
|
|
|
3112
3095
|
};
|
|
3113
3096
|
return /* @__PURE__ */ jsxs23("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6", children: [
|
|
3114
3097
|
/* @__PURE__ */ jsxs23("div", { className: "text-center", children: [
|
|
3115
|
-
/* @__PURE__ */
|
|
3116
|
-
/* @__PURE__ */
|
|
3098
|
+
/* @__PURE__ */ jsx39("h1", { className: "text-2xl font-bold text-[var(--timberwolf)] mb-2", children: title }),
|
|
3099
|
+
/* @__PURE__ */ jsx39("p", { className: "text-sm text-muted-foreground", children: subtitle })
|
|
3117
3100
|
] }),
|
|
3118
3101
|
/* @__PURE__ */ jsxs23("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
|
|
3119
|
-
error && /* @__PURE__ */
|
|
3102
|
+
error && /* @__PURE__ */ jsx39(
|
|
3120
3103
|
"div",
|
|
3121
3104
|
{
|
|
3122
3105
|
id: "signin-error",
|
|
@@ -3124,15 +3107,15 @@ function SignInForm({
|
|
|
3124
3107
|
"aria-live": "polite",
|
|
3125
3108
|
className: "bg-red-900/20 border border-red-700/30 rounded-lg p-3",
|
|
3126
3109
|
children: /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2 text-red-400 text-sm", children: [
|
|
3127
|
-
/* @__PURE__ */
|
|
3128
|
-
/* @__PURE__ */
|
|
3110
|
+
/* @__PURE__ */ jsx39(AlertCircle, { className: "h-4 w-4 flex-shrink-0" }),
|
|
3111
|
+
/* @__PURE__ */ jsx39("span", { children: error })
|
|
3129
3112
|
] })
|
|
3130
3113
|
}
|
|
3131
3114
|
),
|
|
3132
3115
|
/* @__PURE__ */ jsxs23("div", { className: "space-y-2", children: [
|
|
3133
|
-
/* @__PURE__ */
|
|
3116
|
+
/* @__PURE__ */ jsx39(Label, { htmlFor: "email", className: "text-[var(--timberwolf)]", children: "Email" }),
|
|
3134
3117
|
/* @__PURE__ */ jsxs23("div", { className: "relative", children: [
|
|
3135
|
-
/* @__PURE__ */
|
|
3118
|
+
/* @__PURE__ */ jsx39(
|
|
3136
3119
|
Input,
|
|
3137
3120
|
{
|
|
3138
3121
|
id: "email",
|
|
@@ -3147,13 +3130,13 @@ function SignInForm({
|
|
|
3147
3130
|
"aria-describedby": error ? "signin-error" : void 0
|
|
3148
3131
|
}
|
|
3149
3132
|
),
|
|
3150
|
-
/* @__PURE__ */
|
|
3133
|
+
/* @__PURE__ */ jsx39(Mail, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" })
|
|
3151
3134
|
] })
|
|
3152
3135
|
] }),
|
|
3153
3136
|
/* @__PURE__ */ jsxs23("div", { className: "space-y-2", children: [
|
|
3154
|
-
/* @__PURE__ */
|
|
3137
|
+
/* @__PURE__ */ jsx39(Label, { htmlFor: "password", className: "text-[var(--timberwolf)]", children: "Password" }),
|
|
3155
3138
|
/* @__PURE__ */ jsxs23("div", { className: "relative", children: [
|
|
3156
|
-
/* @__PURE__ */
|
|
3139
|
+
/* @__PURE__ */ jsx39(
|
|
3157
3140
|
Input,
|
|
3158
3141
|
{
|
|
3159
3142
|
id: "password",
|
|
@@ -3168,8 +3151,8 @@ function SignInForm({
|
|
|
3168
3151
|
"aria-describedby": error ? "signin-error" : void 0
|
|
3169
3152
|
}
|
|
3170
3153
|
),
|
|
3171
|
-
/* @__PURE__ */
|
|
3172
|
-
/* @__PURE__ */
|
|
3154
|
+
/* @__PURE__ */ jsx39(Lock, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
3155
|
+
/* @__PURE__ */ jsx39(
|
|
3173
3156
|
"button",
|
|
3174
3157
|
{
|
|
3175
3158
|
type: "button",
|
|
@@ -3178,12 +3161,12 @@ function SignInForm({
|
|
|
3178
3161
|
disabled: loading,
|
|
3179
3162
|
"aria-label": showPassword ? "Hide password" : "Show password",
|
|
3180
3163
|
"aria-pressed": showPassword,
|
|
3181
|
-
children: showPassword ? /* @__PURE__ */
|
|
3164
|
+
children: showPassword ? /* @__PURE__ */ jsx39(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx39(Eye, { className: "h-4 w-4" })
|
|
3182
3165
|
}
|
|
3183
3166
|
)
|
|
3184
3167
|
] })
|
|
3185
3168
|
] }),
|
|
3186
|
-
forgotPasswordHref ? /* @__PURE__ */
|
|
3169
|
+
forgotPasswordHref ? /* @__PURE__ */ jsx39("div", { className: "flex justify-end -mt-1", children: /* @__PURE__ */ jsx39(
|
|
3187
3170
|
Link4,
|
|
3188
3171
|
{
|
|
3189
3172
|
href: forgotPasswordHref,
|
|
@@ -3191,23 +3174,23 @@ function SignInForm({
|
|
|
3191
3174
|
children: "Forgot your password?"
|
|
3192
3175
|
}
|
|
3193
3176
|
) }) : null,
|
|
3194
|
-
/* @__PURE__ */
|
|
3177
|
+
/* @__PURE__ */ jsx39(
|
|
3195
3178
|
Button,
|
|
3196
3179
|
{
|
|
3197
3180
|
type: "submit",
|
|
3198
3181
|
className: "w-full h-11 bg-[var(--timberwolf)] text-[var(--night)] hover:bg-[var(--ash-grey)]",
|
|
3199
3182
|
disabled: loading,
|
|
3200
3183
|
children: loading ? /* @__PURE__ */ jsxs23(Fragment6, { children: [
|
|
3201
|
-
/* @__PURE__ */
|
|
3184
|
+
/* @__PURE__ */ jsx39(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }),
|
|
3202
3185
|
"Signing in..."
|
|
3203
3186
|
] }) : "Sign in"
|
|
3204
3187
|
}
|
|
3205
3188
|
)
|
|
3206
3189
|
] }),
|
|
3207
|
-
signUpHref ? /* @__PURE__ */
|
|
3190
|
+
signUpHref ? /* @__PURE__ */ jsx39("div", { className: "text-center text-sm", children: /* @__PURE__ */ jsxs23("p", { className: "text-muted-foreground", children: [
|
|
3208
3191
|
"Don't have an account?",
|
|
3209
3192
|
" ",
|
|
3210
|
-
/* @__PURE__ */
|
|
3193
|
+
/* @__PURE__ */ jsx39(
|
|
3211
3194
|
Link4,
|
|
3212
3195
|
{
|
|
3213
3196
|
href: signUpHref,
|
|
@@ -3229,7 +3212,7 @@ import {
|
|
|
3229
3212
|
Loader2 as Loader22,
|
|
3230
3213
|
Mail as Mail2
|
|
3231
3214
|
} from "lucide-react";
|
|
3232
|
-
import { Fragment as Fragment7, jsx as
|
|
3215
|
+
import { Fragment as Fragment7, jsx as jsx40, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
3233
3216
|
var RESEND_COOLDOWN_MS = 3e4;
|
|
3234
3217
|
var COOLDOWN_STORAGE_KEY = "playback:pwreset-cooldown-until";
|
|
3235
3218
|
function ForgotPasswordForm({
|
|
@@ -3308,18 +3291,18 @@ function ForgotPasswordForm({
|
|
|
3308
3291
|
};
|
|
3309
3292
|
if (emailSent) {
|
|
3310
3293
|
return /* @__PURE__ */ jsxs24("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6 text-center", children: [
|
|
3311
|
-
/* @__PURE__ */
|
|
3294
|
+
/* @__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)]" }) }) }),
|
|
3312
3295
|
/* @__PURE__ */ jsxs24("div", { className: "space-y-2", children: [
|
|
3313
|
-
/* @__PURE__ */
|
|
3296
|
+
/* @__PURE__ */ jsx40("h1", { className: "text-2xl font-bold text-[var(--timberwolf)]", children: "Check your email" }),
|
|
3314
3297
|
/* @__PURE__ */ jsxs24("p", { className: "text-sm text-muted-foreground", children: [
|
|
3315
3298
|
"We sent a reset link to",
|
|
3316
3299
|
" ",
|
|
3317
|
-
/* @__PURE__ */
|
|
3300
|
+
/* @__PURE__ */ jsx40("span", { className: "text-[var(--timberwolf)] font-medium", children: email }),
|
|
3318
3301
|
". If it's not in your inbox, check spam."
|
|
3319
3302
|
] })
|
|
3320
3303
|
] }),
|
|
3321
3304
|
/* @__PURE__ */ jsxs24("div", { className: "space-y-3 pt-2", children: [
|
|
3322
|
-
/* @__PURE__ */
|
|
3305
|
+
/* @__PURE__ */ jsx40(
|
|
3323
3306
|
Button,
|
|
3324
3307
|
{
|
|
3325
3308
|
onClick: () => {
|
|
@@ -3333,8 +3316,8 @@ function ForgotPasswordForm({
|
|
|
3333
3316
|
children: cooldownSecondsLeft > 0 ? `Send another email in ${cooldownSecondsLeft}s` : "Send another email"
|
|
3334
3317
|
}
|
|
3335
3318
|
),
|
|
3336
|
-
/* @__PURE__ */
|
|
3337
|
-
/* @__PURE__ */
|
|
3319
|
+
/* @__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: [
|
|
3320
|
+
/* @__PURE__ */ jsx40(ArrowLeft, { className: "h-4 w-4 mr-2" }),
|
|
3338
3321
|
"Back to sign in"
|
|
3339
3322
|
] }) })
|
|
3340
3323
|
] })
|
|
@@ -3342,11 +3325,11 @@ function ForgotPasswordForm({
|
|
|
3342
3325
|
}
|
|
3343
3326
|
return /* @__PURE__ */ jsxs24("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6", children: [
|
|
3344
3327
|
/* @__PURE__ */ jsxs24("div", { className: "text-center", children: [
|
|
3345
|
-
/* @__PURE__ */
|
|
3346
|
-
/* @__PURE__ */
|
|
3328
|
+
/* @__PURE__ */ jsx40("h1", { className: "text-2xl font-bold text-[var(--timberwolf)] mb-2", children: title }),
|
|
3329
|
+
/* @__PURE__ */ jsx40("p", { className: "text-sm text-muted-foreground", children: subtitle })
|
|
3347
3330
|
] }),
|
|
3348
3331
|
/* @__PURE__ */ jsxs24("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
|
|
3349
|
-
error && /* @__PURE__ */
|
|
3332
|
+
error && /* @__PURE__ */ jsx40(
|
|
3350
3333
|
"div",
|
|
3351
3334
|
{
|
|
3352
3335
|
id: "forgot-error",
|
|
@@ -3354,15 +3337,15 @@ function ForgotPasswordForm({
|
|
|
3354
3337
|
"aria-live": "polite",
|
|
3355
3338
|
className: "bg-red-900/20 border border-red-700/30 rounded-lg p-3",
|
|
3356
3339
|
children: /* @__PURE__ */ jsxs24("div", { className: "flex items-center gap-2 text-red-400 text-sm", children: [
|
|
3357
|
-
/* @__PURE__ */
|
|
3358
|
-
/* @__PURE__ */
|
|
3340
|
+
/* @__PURE__ */ jsx40(AlertCircle2, { className: "h-4 w-4 flex-shrink-0" }),
|
|
3341
|
+
/* @__PURE__ */ jsx40("span", { children: error })
|
|
3359
3342
|
] })
|
|
3360
3343
|
}
|
|
3361
3344
|
),
|
|
3362
3345
|
/* @__PURE__ */ jsxs24("div", { className: "space-y-2", children: [
|
|
3363
|
-
/* @__PURE__ */
|
|
3346
|
+
/* @__PURE__ */ jsx40(Label, { htmlFor: "email", className: "text-[var(--timberwolf)]", children: "Email" }),
|
|
3364
3347
|
/* @__PURE__ */ jsxs24("div", { className: "relative", children: [
|
|
3365
|
-
/* @__PURE__ */
|
|
3348
|
+
/* @__PURE__ */ jsx40(
|
|
3366
3349
|
Input,
|
|
3367
3350
|
{
|
|
3368
3351
|
id: "email",
|
|
@@ -3378,29 +3361,29 @@ function ForgotPasswordForm({
|
|
|
3378
3361
|
"aria-describedby": error ? "forgot-error" : void 0
|
|
3379
3362
|
}
|
|
3380
3363
|
),
|
|
3381
|
-
/* @__PURE__ */
|
|
3364
|
+
/* @__PURE__ */ jsx40(Mail2, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" })
|
|
3382
3365
|
] })
|
|
3383
3366
|
] }),
|
|
3384
|
-
/* @__PURE__ */
|
|
3367
|
+
/* @__PURE__ */ jsx40(
|
|
3385
3368
|
Button,
|
|
3386
3369
|
{
|
|
3387
3370
|
type: "submit",
|
|
3388
3371
|
className: "w-full h-11 bg-[var(--timberwolf)] text-[var(--night)] hover:bg-[var(--ash-grey)]",
|
|
3389
3372
|
disabled: loading || cooldownSecondsLeft > 0,
|
|
3390
3373
|
children: loading ? /* @__PURE__ */ jsxs24(Fragment7, { children: [
|
|
3391
|
-
/* @__PURE__ */
|
|
3374
|
+
/* @__PURE__ */ jsx40(Loader22, { className: "h-4 w-4 mr-2 animate-spin" }),
|
|
3392
3375
|
"Sending email..."
|
|
3393
3376
|
] }) : cooldownSecondsLeft > 0 ? `Try again in ${cooldownSecondsLeft}s` : "Send reset link"
|
|
3394
3377
|
}
|
|
3395
3378
|
)
|
|
3396
3379
|
] }),
|
|
3397
|
-
/* @__PURE__ */
|
|
3380
|
+
/* @__PURE__ */ jsx40("div", { className: "text-center text-sm", children: /* @__PURE__ */ jsxs24(
|
|
3398
3381
|
Link5,
|
|
3399
3382
|
{
|
|
3400
3383
|
href: loginHref,
|
|
3401
3384
|
className: "inline-flex items-center gap-1.5 text-muted-foreground hover:text-[var(--timberwolf)] transition-colors",
|
|
3402
3385
|
children: [
|
|
3403
|
-
/* @__PURE__ */
|
|
3386
|
+
/* @__PURE__ */ jsx40(ArrowLeft, { className: "h-3.5 w-3.5" }),
|
|
3404
3387
|
"Back to sign in"
|
|
3405
3388
|
]
|
|
3406
3389
|
}
|
|
@@ -3411,7 +3394,7 @@ function ForgotPasswordForm({
|
|
|
3411
3394
|
// src/ui/reset-password-form.tsx
|
|
3412
3395
|
import * as React30 from "react";
|
|
3413
3396
|
import Link6 from "next/link";
|
|
3414
|
-
import { useRouter
|
|
3397
|
+
import { useRouter } from "next/navigation";
|
|
3415
3398
|
import {
|
|
3416
3399
|
AlertCircle as AlertCircle3,
|
|
3417
3400
|
CheckCircle as CheckCircle2,
|
|
@@ -3420,7 +3403,7 @@ import {
|
|
|
3420
3403
|
Loader2 as Loader23,
|
|
3421
3404
|
Lock as Lock2
|
|
3422
3405
|
} from "lucide-react";
|
|
3423
|
-
import { Fragment as Fragment8, jsx as
|
|
3406
|
+
import { Fragment as Fragment8, jsx as jsx41, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
3424
3407
|
function ResetPasswordForm({
|
|
3425
3408
|
loginHref = "/auth/login",
|
|
3426
3409
|
initialError,
|
|
@@ -3435,7 +3418,7 @@ function ResetPasswordForm({
|
|
|
3435
3418
|
const [error, setError] = React30.useState(initialError ?? "");
|
|
3436
3419
|
const [loading, setLoading] = React30.useState(false);
|
|
3437
3420
|
const [passwordReset, setPasswordReset] = React30.useState(false);
|
|
3438
|
-
const router =
|
|
3421
|
+
const router = useRouter();
|
|
3439
3422
|
const [supabase] = React30.useState(() => createClient());
|
|
3440
3423
|
React30.useEffect(() => {
|
|
3441
3424
|
if (initialError) setError(initialError);
|
|
@@ -3489,21 +3472,21 @@ function ResetPasswordForm({
|
|
|
3489
3472
|
};
|
|
3490
3473
|
if (passwordReset) {
|
|
3491
3474
|
return /* @__PURE__ */ jsxs25("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6 text-center", children: [
|
|
3492
|
-
/* @__PURE__ */
|
|
3475
|
+
/* @__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)]" }) }) }),
|
|
3493
3476
|
/* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
|
|
3494
|
-
/* @__PURE__ */
|
|
3495
|
-
/* @__PURE__ */
|
|
3477
|
+
/* @__PURE__ */ jsx41("h1", { className: "text-2xl font-bold text-[var(--timberwolf)]", children: "Password updated" }),
|
|
3478
|
+
/* @__PURE__ */ jsx41("p", { className: "text-sm text-muted-foreground", children: "Redirecting you to sign in\u2026" })
|
|
3496
3479
|
] }),
|
|
3497
|
-
/* @__PURE__ */
|
|
3480
|
+
/* @__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" }) })
|
|
3498
3481
|
] });
|
|
3499
3482
|
}
|
|
3500
3483
|
return /* @__PURE__ */ jsxs25("div", { className: "bg-card border border-border rounded-xl p-8 space-y-6", children: [
|
|
3501
3484
|
/* @__PURE__ */ jsxs25("div", { className: "text-center", children: [
|
|
3502
|
-
/* @__PURE__ */
|
|
3503
|
-
/* @__PURE__ */
|
|
3485
|
+
/* @__PURE__ */ jsx41("h1", { className: "text-2xl font-bold text-[var(--timberwolf)] mb-2", children: title }),
|
|
3486
|
+
/* @__PURE__ */ jsx41("p", { className: "text-sm text-muted-foreground", children: subtitle })
|
|
3504
3487
|
] }),
|
|
3505
3488
|
/* @__PURE__ */ jsxs25("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
|
|
3506
|
-
error && /* @__PURE__ */
|
|
3489
|
+
error && /* @__PURE__ */ jsx41(
|
|
3507
3490
|
"div",
|
|
3508
3491
|
{
|
|
3509
3492
|
id: "reset-error",
|
|
@@ -3511,15 +3494,15 @@ function ResetPasswordForm({
|
|
|
3511
3494
|
"aria-live": "polite",
|
|
3512
3495
|
className: "bg-red-900/20 border border-red-700/30 rounded-lg p-3",
|
|
3513
3496
|
children: /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2 text-red-400 text-sm", children: [
|
|
3514
|
-
/* @__PURE__ */
|
|
3515
|
-
/* @__PURE__ */
|
|
3497
|
+
/* @__PURE__ */ jsx41(AlertCircle3, { className: "h-4 w-4 flex-shrink-0" }),
|
|
3498
|
+
/* @__PURE__ */ jsx41("span", { children: error })
|
|
3516
3499
|
] })
|
|
3517
3500
|
}
|
|
3518
3501
|
),
|
|
3519
3502
|
/* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
|
|
3520
|
-
/* @__PURE__ */
|
|
3503
|
+
/* @__PURE__ */ jsx41(Label, { htmlFor: "password", className: "text-[var(--timberwolf)]", children: "New password" }),
|
|
3521
3504
|
/* @__PURE__ */ jsxs25("div", { className: "relative", children: [
|
|
3522
|
-
/* @__PURE__ */
|
|
3505
|
+
/* @__PURE__ */ jsx41(
|
|
3523
3506
|
Input,
|
|
3524
3507
|
{
|
|
3525
3508
|
id: "password",
|
|
@@ -3535,8 +3518,8 @@ function ResetPasswordForm({
|
|
|
3535
3518
|
"aria-describedby": error ? "reset-error" : void 0
|
|
3536
3519
|
}
|
|
3537
3520
|
),
|
|
3538
|
-
/* @__PURE__ */
|
|
3539
|
-
/* @__PURE__ */
|
|
3521
|
+
/* @__PURE__ */ jsx41(Lock2, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
3522
|
+
/* @__PURE__ */ jsx41(
|
|
3540
3523
|
"button",
|
|
3541
3524
|
{
|
|
3542
3525
|
type: "button",
|
|
@@ -3545,13 +3528,13 @@ function ResetPasswordForm({
|
|
|
3545
3528
|
disabled: loading,
|
|
3546
3529
|
"aria-label": showPassword ? "Hide password" : "Show password",
|
|
3547
3530
|
"aria-pressed": showPassword,
|
|
3548
|
-
children: showPassword ? /* @__PURE__ */
|
|
3531
|
+
children: showPassword ? /* @__PURE__ */ jsx41(EyeOff2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx41(Eye2, { className: "h-4 w-4" })
|
|
3549
3532
|
}
|
|
3550
3533
|
)
|
|
3551
3534
|
] })
|
|
3552
3535
|
] }),
|
|
3553
3536
|
/* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
|
|
3554
|
-
/* @__PURE__ */
|
|
3537
|
+
/* @__PURE__ */ jsx41(
|
|
3555
3538
|
Label,
|
|
3556
3539
|
{
|
|
3557
3540
|
htmlFor: "confirmPassword",
|
|
@@ -3560,7 +3543,7 @@ function ResetPasswordForm({
|
|
|
3560
3543
|
}
|
|
3561
3544
|
),
|
|
3562
3545
|
/* @__PURE__ */ jsxs25("div", { className: "relative", children: [
|
|
3563
|
-
/* @__PURE__ */
|
|
3546
|
+
/* @__PURE__ */ jsx41(
|
|
3564
3547
|
Input,
|
|
3565
3548
|
{
|
|
3566
3549
|
id: "confirmPassword",
|
|
@@ -3575,8 +3558,8 @@ function ResetPasswordForm({
|
|
|
3575
3558
|
"aria-describedby": error ? "reset-error" : void 0
|
|
3576
3559
|
}
|
|
3577
3560
|
),
|
|
3578
|
-
/* @__PURE__ */
|
|
3579
|
-
/* @__PURE__ */
|
|
3561
|
+
/* @__PURE__ */ jsx41(Lock2, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }),
|
|
3562
|
+
/* @__PURE__ */ jsx41(
|
|
3580
3563
|
"button",
|
|
3581
3564
|
{
|
|
3582
3565
|
type: "button",
|
|
@@ -3585,26 +3568,26 @@ function ResetPasswordForm({
|
|
|
3585
3568
|
disabled: loading,
|
|
3586
3569
|
"aria-label": showConfirmPassword ? "Hide password" : "Show password",
|
|
3587
3570
|
"aria-pressed": showConfirmPassword,
|
|
3588
|
-
children: showConfirmPassword ? /* @__PURE__ */
|
|
3571
|
+
children: showConfirmPassword ? /* @__PURE__ */ jsx41(EyeOff2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx41(Eye2, { className: "h-4 w-4" })
|
|
3589
3572
|
}
|
|
3590
3573
|
)
|
|
3591
3574
|
] })
|
|
3592
3575
|
] }),
|
|
3593
|
-
/* @__PURE__ */
|
|
3594
|
-
/* @__PURE__ */
|
|
3576
|
+
/* @__PURE__ */ jsx41("p", { className: "text-xs text-muted-foreground", children: "At least 8 characters with an uppercase letter, a lowercase letter, and a number." }),
|
|
3577
|
+
/* @__PURE__ */ jsx41(
|
|
3595
3578
|
Button,
|
|
3596
3579
|
{
|
|
3597
3580
|
type: "submit",
|
|
3598
3581
|
className: "w-full h-11 bg-[var(--timberwolf)] text-[var(--night)] hover:bg-[var(--ash-grey)]",
|
|
3599
3582
|
disabled: loading,
|
|
3600
3583
|
children: loading ? /* @__PURE__ */ jsxs25(Fragment8, { children: [
|
|
3601
|
-
/* @__PURE__ */
|
|
3584
|
+
/* @__PURE__ */ jsx41(Loader23, { className: "h-4 w-4 mr-2 animate-spin" }),
|
|
3602
3585
|
"Updating password..."
|
|
3603
3586
|
] }) : "Update password"
|
|
3604
3587
|
}
|
|
3605
3588
|
)
|
|
3606
3589
|
] }),
|
|
3607
|
-
/* @__PURE__ */
|
|
3590
|
+
/* @__PURE__ */ jsx41("div", { className: "text-center text-sm", children: /* @__PURE__ */ jsx41(
|
|
3608
3591
|
Link6,
|
|
3609
3592
|
{
|
|
3610
3593
|
href: loginHref,
|