@dravyn/auth-ui 0.1.1

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.
Files changed (52) hide show
  1. package/dist/chunk-35G4WYX3.mjs +154 -0
  2. package/dist/chunk-35G4WYX3.mjs.map +1 -0
  3. package/dist/chunk-6BBGRN4E.mjs +1 -0
  4. package/dist/chunk-6BBGRN4E.mjs.map +1 -0
  5. package/dist/chunk-FAI3ERB3.mjs +209 -0
  6. package/dist/chunk-FAI3ERB3.mjs.map +1 -0
  7. package/dist/chunk-FVXZZTKL.mjs +152 -0
  8. package/dist/chunk-FVXZZTKL.mjs.map +1 -0
  9. package/dist/chunk-Z2ICX4UY.mjs +132 -0
  10. package/dist/chunk-Z2ICX4UY.mjs.map +1 -0
  11. package/dist/index.css +235 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.mts +5 -0
  14. package/dist/index.d.ts +5 -0
  15. package/dist/index.js +678 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/index.mjs +22 -0
  18. package/dist/index.mjs.map +1 -0
  19. package/dist/screens/ForgotResetScreen.css +235 -0
  20. package/dist/screens/ForgotResetScreen.css.map +1 -0
  21. package/dist/screens/ForgotResetScreen.d.mts +17 -0
  22. package/dist/screens/ForgotResetScreen.d.ts +17 -0
  23. package/dist/screens/ForgotResetScreen.js +245 -0
  24. package/dist/screens/ForgotResetScreen.js.map +1 -0
  25. package/dist/screens/ForgotResetScreen.mjs +11 -0
  26. package/dist/screens/ForgotResetScreen.mjs.map +1 -0
  27. package/dist/screens/LoginScreen.css +235 -0
  28. package/dist/screens/LoginScreen.css.map +1 -0
  29. package/dist/screens/LoginScreen.d.mts +36 -0
  30. package/dist/screens/LoginScreen.d.ts +36 -0
  31. package/dist/screens/LoginScreen.js +167 -0
  32. package/dist/screens/LoginScreen.js.map +1 -0
  33. package/dist/screens/LoginScreen.mjs +9 -0
  34. package/dist/screens/LoginScreen.mjs.map +1 -0
  35. package/dist/screens/OTPScreen.css +235 -0
  36. package/dist/screens/OTPScreen.css.map +1 -0
  37. package/dist/screens/OTPScreen.d.mts +16 -0
  38. package/dist/screens/OTPScreen.d.ts +16 -0
  39. package/dist/screens/OTPScreen.js +187 -0
  40. package/dist/screens/OTPScreen.js.map +1 -0
  41. package/dist/screens/OTPScreen.mjs +9 -0
  42. package/dist/screens/OTPScreen.mjs.map +1 -0
  43. package/dist/screens/RegisterScreen.css +235 -0
  44. package/dist/screens/RegisterScreen.css.map +1 -0
  45. package/dist/screens/RegisterScreen.d.mts +21 -0
  46. package/dist/screens/RegisterScreen.d.ts +21 -0
  47. package/dist/screens/RegisterScreen.js +189 -0
  48. package/dist/screens/RegisterScreen.js.map +1 -0
  49. package/dist/screens/RegisterScreen.mjs +9 -0
  50. package/dist/screens/RegisterScreen.mjs.map +1 -0
  51. package/package.json +39 -0
  52. package/src/screens/auth.css +260 -0
@@ -0,0 +1,154 @@
1
+ // src/screens/RegisterScreen.tsx
2
+ import React from "react";
3
+ import { Button } from "@dravyn/ui";
4
+ import { Input } from "@dravyn/ui";
5
+ import { Alert } from "@dravyn/ui";
6
+ var RegisterScreen = ({
7
+ redirectTo = "/auth/verify",
8
+ onSuccess,
9
+ logo,
10
+ brandName = "Dravyn",
11
+ apiBase = process.env.NEXT_PUBLIC_AUTH_API_URL ?? "http://localhost:3001",
12
+ extraFields,
13
+ requireOTP = true
14
+ }) => {
15
+ const [form, setForm] = React.useState({
16
+ firstName: "",
17
+ lastName: "",
18
+ email: "",
19
+ password: "",
20
+ confirm: ""
21
+ });
22
+ const [showPass, setShowPass] = React.useState(false);
23
+ const [loading, setLoading] = React.useState(false);
24
+ const [error, setError] = React.useState("");
25
+ const [fieldErrs, setFieldErrs] = React.useState({});
26
+ const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value }));
27
+ const validate = () => {
28
+ const errs = {};
29
+ if (!form.firstName.trim()) errs.firstName = "First name is required.";
30
+ if (!form.lastName.trim()) errs.lastName = "Last name is required.";
31
+ if (!form.email.trim()) errs.email = "Email is required.";
32
+ else if (!/\S+@\S+\.\S+/.test(form.email)) errs.email = "Enter a valid email.";
33
+ if (!form.password) errs.password = "Password is required.";
34
+ else if (form.password.length < 8) errs.password = "Password must be at least 8 characters.";
35
+ if (form.confirm !== form.password) errs.confirm = "Passwords do not match.";
36
+ return errs;
37
+ };
38
+ const handleSubmit = async (e) => {
39
+ e.preventDefault();
40
+ const errs = validate();
41
+ if (Object.keys(errs).length) {
42
+ setFieldErrs(errs);
43
+ return;
44
+ }
45
+ setFieldErrs({});
46
+ setLoading(true);
47
+ setError("");
48
+ try {
49
+ const res = await fetch(`${apiBase}/auth/register`, {
50
+ method: "POST",
51
+ headers: { "Content-Type": "application/json" },
52
+ body: JSON.stringify({
53
+ firstName: form.firstName.trim(),
54
+ lastName: form.lastName.trim(),
55
+ email: form.email.trim().toLowerCase(),
56
+ password: form.password
57
+ })
58
+ });
59
+ const data = await res.json();
60
+ if (!res.ok) {
61
+ setError(data.message ?? "Registration failed.");
62
+ return;
63
+ }
64
+ if (requireOTP) {
65
+ sessionStorage.setItem("dravyn_pending_email", form.email.trim().toLowerCase());
66
+ window.location.href = redirectTo;
67
+ } else {
68
+ localStorage.setItem("dravyn_token", data.accessToken);
69
+ localStorage.setItem("dravyn_user", JSON.stringify(data.user));
70
+ if (onSuccess) onSuccess(data.accessToken, data.user);
71
+ else window.location.href = redirectTo;
72
+ }
73
+ } catch {
74
+ setError("Unable to connect. Check your internet connection.");
75
+ } finally {
76
+ setLoading(false);
77
+ }
78
+ };
79
+ const EyeIcon = () => /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }), /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "12", r: "3" }));
80
+ const EyeOffIcon = () => /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }), /* @__PURE__ */ React.createElement("path", { d: "M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68" }), /* @__PURE__ */ React.createElement("path", { d: "M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61" }), /* @__PURE__ */ React.createElement("line", { x1: "2", x2: "22", y1: "2", y2: "22" }));
81
+ return /* @__PURE__ */ React.createElement("div", { className: "dauth-root" }, /* @__PURE__ */ React.createElement("div", { className: "dauth-card" }, /* @__PURE__ */ React.createElement("div", { className: "dauth-brand" }, logo ?? /* @__PURE__ */ React.createElement("div", { className: "dauth-logo-default" }, /* @__PURE__ */ React.createElement("span", { className: "dauth-logo-dot" }), brandName)), /* @__PURE__ */ React.createElement("h1", { className: "dauth-title" }, "Create your account"), /* @__PURE__ */ React.createElement("p", { className: "dauth-sub" }, "Join ", brandName, " \u2014 it's free"), error && /* @__PURE__ */ React.createElement("div", { style: { marginBottom: 16 } }, /* @__PURE__ */ React.createElement(Alert, { variant: "danger" }, error)), /* @__PURE__ */ React.createElement("form", { onSubmit: handleSubmit, noValidate: true }, /* @__PURE__ */ React.createElement("div", { className: "dauth-fields" }, /* @__PURE__ */ React.createElement("div", { className: "dauth-row" }, /* @__PURE__ */ React.createElement(
82
+ Input,
83
+ {
84
+ label: "First name",
85
+ placeholder: "Jeremiah",
86
+ value: form.firstName,
87
+ onChange: set("firstName"),
88
+ error: fieldErrs.firstName,
89
+ required: true
90
+ }
91
+ ), /* @__PURE__ */ React.createElement(
92
+ Input,
93
+ {
94
+ label: "Last name",
95
+ placeholder: "Adeniyi",
96
+ value: form.lastName,
97
+ onChange: set("lastName"),
98
+ error: fieldErrs.lastName,
99
+ required: true
100
+ }
101
+ )), /* @__PURE__ */ React.createElement(
102
+ Input,
103
+ {
104
+ label: "Email address",
105
+ type: "email",
106
+ placeholder: "you@example.com",
107
+ value: form.email,
108
+ onChange: set("email"),
109
+ error: fieldErrs.email,
110
+ autoComplete: "email",
111
+ required: true
112
+ }
113
+ ), /* @__PURE__ */ React.createElement(
114
+ Input,
115
+ {
116
+ label: "Password",
117
+ type: showPass ? "text" : "password",
118
+ placeholder: "Min. 8 characters",
119
+ value: form.password,
120
+ onChange: set("password"),
121
+ error: fieldErrs.password,
122
+ hint: !fieldErrs.password ? "At least 8 characters." : void 0,
123
+ rightIcon: /* @__PURE__ */ React.createElement(
124
+ "button",
125
+ {
126
+ type: "button",
127
+ className: "dauth-eye",
128
+ onClick: () => setShowPass((v) => !v),
129
+ "aria-label": showPass ? "Hide" : "Show"
130
+ },
131
+ showPass ? /* @__PURE__ */ React.createElement(EyeOffIcon, null) : /* @__PURE__ */ React.createElement(EyeIcon, null)
132
+ ),
133
+ autoComplete: "new-password",
134
+ required: true
135
+ }
136
+ ), /* @__PURE__ */ React.createElement(
137
+ Input,
138
+ {
139
+ label: "Confirm password",
140
+ type: showPass ? "text" : "password",
141
+ placeholder: "Re-enter your password",
142
+ value: form.confirm,
143
+ onChange: set("confirm"),
144
+ error: fieldErrs.confirm,
145
+ autoComplete: "new-password",
146
+ required: true
147
+ }
148
+ ), extraFields), /* @__PURE__ */ React.createElement(Button, { type: "submit", variant: "primary", fullWidth: true, loading, style: { marginTop: 8 } }, "Create account")), /* @__PURE__ */ React.createElement("p", { className: "dauth-footer-text", style: { marginTop: 20 } }, "Already have an account?", " ", /* @__PURE__ */ React.createElement("a", { href: "/auth/login", className: "dauth-link" }, "Sign in")), /* @__PURE__ */ React.createElement("p", { className: "dauth-terms" }, "By creating an account you agree to our", " ", /* @__PURE__ */ React.createElement("a", { href: "/terms", className: "dauth-link" }, "Terms of Service"), " ", "and", " ", /* @__PURE__ */ React.createElement("a", { href: "/privacy", className: "dauth-link" }, "Privacy Policy"), ".")));
149
+ };
150
+
151
+ export {
152
+ RegisterScreen
153
+ };
154
+ //# sourceMappingURL=chunk-35G4WYX3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/screens/RegisterScreen.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\nimport { Button } from '@dravyn/ui';\nimport { Input } from '@dravyn/ui';\nimport { Alert } from '@dravyn/ui';\nimport './auth.css';\n\nexport interface RegisterScreenProps {\n redirectTo?: string;\n onSuccess?: (token: string, user: AuthUser) => void;\n logo?: React.ReactNode;\n brandName?: string;\n apiBase?: string;\n /** Extra fields to collect — rendered after the core fields */\n extraFields?: React.ReactNode;\n /** If true, redirect to OTP screen after register instead of auto-login */\n requireOTP?: boolean;\n}\n\ninterface AuthUser {\n id: string; email: string; name: string;\n}\n\nexport const RegisterScreen: React.FC<RegisterScreenProps> = ({\n redirectTo = '/auth/verify',\n onSuccess,\n logo,\n brandName = 'Dravyn',\n apiBase = process.env.NEXT_PUBLIC_AUTH_API_URL ?? 'http://localhost:3001',\n extraFields,\n requireOTP = true,\n}) => {\n const [form, setForm] = React.useState({\n firstName: '', lastName: '', email: '', password: '', confirm: '',\n });\n const [showPass, setShowPass] = React.useState(false);\n const [loading, setLoading] = React.useState(false);\n const [error, setError] = React.useState('');\n const [fieldErrs, setFieldErrs] = React.useState<Record<string, string>>({});\n\n const set = (k: keyof typeof form) => (e: React.ChangeEvent<HTMLInputElement>) =>\n setForm(f => ({ ...f, [k]: e.target.value }));\n\n const validate = () => {\n const errs: Record<string, string> = {};\n if (!form.firstName.trim()) errs.firstName = 'First name is required.';\n if (!form.lastName.trim()) errs.lastName = 'Last name is required.';\n if (!form.email.trim()) errs.email = 'Email is required.';\n else if (!/\\S+@\\S+\\.\\S+/.test(form.email)) errs.email = 'Enter a valid email.';\n if (!form.password) errs.password = 'Password is required.';\n else if (form.password.length < 8) errs.password = 'Password must be at least 8 characters.';\n if (form.confirm !== form.password) errs.confirm = 'Passwords do not match.';\n return errs;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n const errs = validate();\n if (Object.keys(errs).length) { setFieldErrs(errs); return; }\n setFieldErrs({});\n setLoading(true);\n setError('');\n\n try {\n const res = await fetch(`${apiBase}/auth/register`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n firstName: form.firstName.trim(),\n lastName: form.lastName.trim(),\n email: form.email.trim().toLowerCase(),\n password: form.password,\n }),\n });\n const data = await res.json();\n if (!res.ok) { setError(data.message ?? 'Registration failed.'); return; }\n\n if (requireOTP) {\n // Store email for OTP screen to use\n sessionStorage.setItem('dravyn_pending_email', form.email.trim().toLowerCase());\n window.location.href = redirectTo;\n } else {\n localStorage.setItem('dravyn_token', data.accessToken);\n localStorage.setItem('dravyn_user', JSON.stringify(data.user));\n if (onSuccess) onSuccess(data.accessToken, data.user);\n else window.location.href = redirectTo;\n }\n } catch {\n setError('Unable to connect. Check your internet connection.');\n } finally {\n setLoading(false);\n }\n };\n\n const EyeIcon = () => <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>;\n const EyeOffIcon = () => <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M9.88 9.88a3 3 0 1 0 4.24 4.24\"/><path d=\"M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68\"/><path d=\"M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61\"/><line x1=\"2\" x2=\"22\" y1=\"2\" y2=\"22\"/></svg>;\n\n return (\n <div className=\"dauth-root\">\n <div className=\"dauth-card\">\n <div className=\"dauth-brand\">\n {logo ?? <div className=\"dauth-logo-default\"><span className=\"dauth-logo-dot\" />{brandName}</div>}\n </div>\n\n <h1 className=\"dauth-title\">Create your account</h1>\n <p className=\"dauth-sub\">Join {brandName} — it's free</p>\n\n {error && <div style={{ marginBottom: 16 }}><Alert variant=\"danger\">{error}</Alert></div>}\n\n <form onSubmit={handleSubmit} noValidate>\n <div className=\"dauth-fields\">\n <div className=\"dauth-row\">\n <Input\n label=\"First name\"\n placeholder=\"Jeremiah\"\n value={form.firstName}\n onChange={set('firstName')}\n error={fieldErrs.firstName}\n required\n />\n <Input\n label=\"Last name\"\n placeholder=\"Adeniyi\"\n value={form.lastName}\n onChange={set('lastName')}\n error={fieldErrs.lastName}\n required\n />\n </div>\n\n <Input\n label=\"Email address\"\n type=\"email\"\n placeholder=\"you@example.com\"\n value={form.email}\n onChange={set('email')}\n error={fieldErrs.email}\n autoComplete=\"email\"\n required\n />\n\n <Input\n label=\"Password\"\n type={showPass ? 'text' : 'password'}\n placeholder=\"Min. 8 characters\"\n value={form.password}\n onChange={set('password')}\n error={fieldErrs.password}\n hint={!fieldErrs.password ? 'At least 8 characters.' : undefined}\n rightIcon={\n <button type=\"button\" className=\"dauth-eye\"\n onClick={() => setShowPass(v => !v)}\n aria-label={showPass ? 'Hide' : 'Show'}>\n {showPass ? <EyeOffIcon /> : <EyeIcon />}\n </button>\n }\n autoComplete=\"new-password\"\n required\n />\n\n <Input\n label=\"Confirm password\"\n type={showPass ? 'text' : 'password'}\n placeholder=\"Re-enter your password\"\n value={form.confirm}\n onChange={set('confirm')}\n error={fieldErrs.confirm}\n autoComplete=\"new-password\"\n required\n />\n\n {/* Slot for extra fields (university, department, etc.) */}\n {extraFields}\n </div>\n\n <Button type=\"submit\" variant=\"primary\" fullWidth loading={loading} style={{ marginTop: 8 }}>\n Create account\n </Button>\n </form>\n\n <p className=\"dauth-footer-text\" style={{ marginTop: 20 }}>\n Already have an account?{' '}\n <a href=\"/auth/login\" className=\"dauth-link\">Sign in</a>\n </p>\n\n <p className=\"dauth-terms\">\n By creating an account you agree to our{' '}\n <a href=\"/terms\" className=\"dauth-link\">Terms of Service</a>{' '}\n and{' '}\n <a href=\"/privacy\" className=\"dauth-link\">Privacy Policy</a>.\n </p>\n </div>\n </div>\n );\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-6BBGRN4E.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,209 @@
1
+ // src/screens/ForgotResetScreen.tsx
2
+ import React from "react";
3
+ import { Button } from "@dravyn/ui";
4
+ import { Input } from "@dravyn/ui";
5
+ import { Alert } from "@dravyn/ui";
6
+ var ForgotPasswordScreen = ({
7
+ logo,
8
+ brandName = "Dravyn",
9
+ apiBase = process.env.NEXT_PUBLIC_AUTH_API_URL ?? "http://localhost:3001"
10
+ }) => {
11
+ const [email, setEmail] = React.useState("");
12
+ const [loading, setLoading] = React.useState(false);
13
+ const [error, setError] = React.useState("");
14
+ const [sent, setSent] = React.useState(false);
15
+ const handleSubmit = async (e) => {
16
+ e.preventDefault();
17
+ if (!email.trim()) {
18
+ setError("Email address is required.");
19
+ return;
20
+ }
21
+ if (!/\S+@\S+\.\S+/.test(email)) {
22
+ setError("Enter a valid email address.");
23
+ return;
24
+ }
25
+ setLoading(true);
26
+ setError("");
27
+ try {
28
+ const res = await fetch(`${apiBase}/auth/forgot-password`, {
29
+ method: "POST",
30
+ headers: { "Content-Type": "application/json" },
31
+ body: JSON.stringify({ email: email.trim().toLowerCase() })
32
+ });
33
+ if (res.ok || res.status === 404) {
34
+ sessionStorage.setItem("dravyn_reset_email", email.trim().toLowerCase());
35
+ setSent(true);
36
+ } else {
37
+ const d = await res.json();
38
+ setError(d.message ?? "Something went wrong. Try again.");
39
+ }
40
+ } catch {
41
+ setError("Unable to connect.");
42
+ } finally {
43
+ setLoading(false);
44
+ }
45
+ };
46
+ if (sent) {
47
+ return /* @__PURE__ */ React.createElement("div", { className: "dauth-root" }, /* @__PURE__ */ React.createElement("div", { className: "dauth-card" }, /* @__PURE__ */ React.createElement("div", { className: "dauth-brand" }, logo ?? /* @__PURE__ */ React.createElement("div", { className: "dauth-logo-default" }, /* @__PURE__ */ React.createElement("span", { className: "dauth-logo-dot" }), brandName)), /* @__PURE__ */ React.createElement("div", { className: "dauth-otp-icon" }, "\u2713"), /* @__PURE__ */ React.createElement("h1", { className: "dauth-title" }, "Check your inbox"), /* @__PURE__ */ React.createElement("p", { className: "dauth-sub" }, "If ", /* @__PURE__ */ React.createElement("strong", { style: { color: "var(--dauth-text)" } }, email), " is registered, you'll receive a reset code shortly."), /* @__PURE__ */ React.createElement(
48
+ Button,
49
+ {
50
+ variant: "primary",
51
+ fullWidth: true,
52
+ style: { marginTop: 20 },
53
+ onClick: () => window.location.href = "/auth/reset-password"
54
+ },
55
+ "Enter reset code"
56
+ ), /* @__PURE__ */ React.createElement("p", { className: "dauth-footer-text" }, /* @__PURE__ */ React.createElement("a", { href: "/auth/login", className: "dauth-link" }, "Back to sign in"))));
57
+ }
58
+ return /* @__PURE__ */ React.createElement("div", { className: "dauth-root" }, /* @__PURE__ */ React.createElement("div", { className: "dauth-card" }, /* @__PURE__ */ React.createElement("div", { className: "dauth-brand" }, logo ?? /* @__PURE__ */ React.createElement("div", { className: "dauth-logo-default" }, /* @__PURE__ */ React.createElement("span", { className: "dauth-logo-dot" }), brandName)), /* @__PURE__ */ React.createElement("h1", { className: "dauth-title" }, "Reset your password"), /* @__PURE__ */ React.createElement("p", { className: "dauth-sub" }, "Enter your email and we'll send you a reset code."), error && /* @__PURE__ */ React.createElement("div", { style: { marginBottom: 14 } }, /* @__PURE__ */ React.createElement(Alert, { variant: "danger" }, error)), /* @__PURE__ */ React.createElement("form", { onSubmit: handleSubmit, noValidate: true }, /* @__PURE__ */ React.createElement("div", { className: "dauth-fields" }, /* @__PURE__ */ React.createElement(
59
+ Input,
60
+ {
61
+ label: "Email address",
62
+ type: "email",
63
+ placeholder: "you@example.com",
64
+ value: email,
65
+ onChange: (e) => setEmail(e.target.value),
66
+ autoComplete: "email",
67
+ required: true
68
+ }
69
+ )), /* @__PURE__ */ React.createElement(Button, { type: "submit", variant: "primary", fullWidth: true, loading, style: { marginTop: 8 } }, "Send reset code")), /* @__PURE__ */ React.createElement("p", { className: "dauth-footer-text" }, "Remember it?", " ", /* @__PURE__ */ React.createElement("a", { href: "/auth/login", className: "dauth-link" }, "Back to sign in"))));
70
+ };
71
+ var ResetPasswordScreen = ({
72
+ logo,
73
+ brandName = "Dravyn",
74
+ apiBase = process.env.NEXT_PUBLIC_AUTH_API_URL ?? "http://localhost:3001",
75
+ redirectTo = "/auth/login"
76
+ }) => {
77
+ const email = typeof sessionStorage !== "undefined" ? sessionStorage.getItem("dravyn_reset_email") ?? "" : "";
78
+ const [otp, setOtp] = React.useState(["", "", "", "", "", ""]);
79
+ const [password, setPassword] = React.useState("");
80
+ const [confirm, setConfirm] = React.useState("");
81
+ const [showPass, setShowPass] = React.useState(false);
82
+ const [loading, setLoading] = React.useState(false);
83
+ const [error, setError] = React.useState("");
84
+ const [done, setDone] = React.useState(false);
85
+ const inputRefs = React.useRef([]);
86
+ const handleOtpInput = (i, val) => {
87
+ if (val.length > 1) {
88
+ const digits = val.replace(/\D/g, "").slice(0, 6).split("");
89
+ const next2 = [...otp];
90
+ digits.forEach((d, idx) => {
91
+ if (idx < 6) next2[idx] = d;
92
+ });
93
+ setOtp(next2);
94
+ inputRefs.current[Math.min(digits.length, 5)]?.focus();
95
+ return;
96
+ }
97
+ const digit = val.replace(/\D/g, "");
98
+ const next = [...otp];
99
+ next[i] = digit;
100
+ setOtp(next);
101
+ if (digit && i < 5) inputRefs.current[i + 1]?.focus();
102
+ };
103
+ const handleSubmit = async (e) => {
104
+ e.preventDefault();
105
+ const code = otp.join("");
106
+ if (code.length < 6) {
107
+ setError("Enter the complete 6-digit code.");
108
+ return;
109
+ }
110
+ if (!password) {
111
+ setError("Enter a new password.");
112
+ return;
113
+ }
114
+ if (password.length < 8) {
115
+ setError("Password must be at least 8 characters.");
116
+ return;
117
+ }
118
+ if (password !== confirm) {
119
+ setError("Passwords do not match.");
120
+ return;
121
+ }
122
+ setLoading(true);
123
+ setError("");
124
+ try {
125
+ const res = await fetch(`${apiBase}/auth/reset-password`, {
126
+ method: "POST",
127
+ headers: { "Content-Type": "application/json" },
128
+ body: JSON.stringify({ email, otp: code, newPassword: password })
129
+ });
130
+ const data = await res.json();
131
+ if (!res.ok) {
132
+ setError(data.message ?? "Reset failed. Check the code and try again.");
133
+ return;
134
+ }
135
+ sessionStorage.removeItem("dravyn_reset_email");
136
+ setDone(true);
137
+ } catch {
138
+ setError("Unable to connect.");
139
+ } finally {
140
+ setLoading(false);
141
+ }
142
+ };
143
+ const EyeIcon = () => /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }), /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "12", r: "3" }));
144
+ const EyeOffIcon = () => /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }), /* @__PURE__ */ React.createElement("path", { d: "M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68" }), /* @__PURE__ */ React.createElement("path", { d: "M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61" }), /* @__PURE__ */ React.createElement("line", { x1: "2", x2: "22", y1: "2", y2: "22" }));
145
+ if (done) {
146
+ return /* @__PURE__ */ React.createElement("div", { className: "dauth-root" }, /* @__PURE__ */ React.createElement("div", { className: "dauth-card" }, /* @__PURE__ */ React.createElement("div", { className: "dauth-otp-icon", style: { color: "var(--dauth-green)" } }, "\u2713"), /* @__PURE__ */ React.createElement("h1", { className: "dauth-title" }, "Password reset!"), /* @__PURE__ */ React.createElement("p", { className: "dauth-sub" }, "Your password has been updated. You can now sign in."), /* @__PURE__ */ React.createElement(
147
+ Button,
148
+ {
149
+ variant: "primary",
150
+ fullWidth: true,
151
+ style: { marginTop: 20 },
152
+ onClick: () => window.location.href = redirectTo
153
+ },
154
+ "Sign in"
155
+ )));
156
+ }
157
+ return /* @__PURE__ */ React.createElement("div", { className: "dauth-root" }, /* @__PURE__ */ React.createElement("div", { className: "dauth-card" }, /* @__PURE__ */ React.createElement("div", { className: "dauth-brand" }, logo ?? /* @__PURE__ */ React.createElement("div", { className: "dauth-logo-default" }, /* @__PURE__ */ React.createElement("span", { className: "dauth-logo-dot" }), brandName)), /* @__PURE__ */ React.createElement("h1", { className: "dauth-title" }, "Set new password"), /* @__PURE__ */ React.createElement("p", { className: "dauth-sub" }, "Enter the code we sent you and choose a new password."), error && /* @__PURE__ */ React.createElement("div", { style: { marginBottom: 14 } }, /* @__PURE__ */ React.createElement(Alert, { variant: "danger" }, error)), /* @__PURE__ */ React.createElement("form", { onSubmit: handleSubmit, noValidate: true }, /* @__PURE__ */ React.createElement("div", { className: "dauth-fields" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "dauth-label" }, "Reset code"), /* @__PURE__ */ React.createElement("div", { className: "dauth-otp-boxes", style: { marginTop: 6 } }, otp.map((d, i) => /* @__PURE__ */ React.createElement(
158
+ "input",
159
+ {
160
+ key: i,
161
+ ref: (el) => {
162
+ inputRefs.current[i] = el;
163
+ },
164
+ className: `dauth-otp-box ${d ? "dauth-otp-box--filled" : ""}`,
165
+ type: "text",
166
+ inputMode: "numeric",
167
+ maxLength: 6,
168
+ value: d,
169
+ onChange: (e) => handleOtpInput(i, e.target.value),
170
+ onKeyDown: (e) => {
171
+ if (e.key === "Backspace" && !d && i > 0) inputRefs.current[i - 1]?.focus();
172
+ },
173
+ "aria-label": `Digit ${i + 1}`
174
+ }
175
+ )))), /* @__PURE__ */ React.createElement(
176
+ Input,
177
+ {
178
+ label: "New password",
179
+ type: showPass ? "text" : "password",
180
+ placeholder: "Min. 8 characters",
181
+ value: password,
182
+ onChange: (e) => setPassword(e.target.value),
183
+ rightIcon: /* @__PURE__ */ React.createElement(
184
+ "button",
185
+ {
186
+ type: "button",
187
+ className: "dauth-eye",
188
+ onClick: () => setShowPass((v) => !v)
189
+ },
190
+ showPass ? /* @__PURE__ */ React.createElement(EyeOffIcon, null) : /* @__PURE__ */ React.createElement(EyeIcon, null)
191
+ )
192
+ }
193
+ ), /* @__PURE__ */ React.createElement(
194
+ Input,
195
+ {
196
+ label: "Confirm new password",
197
+ type: showPass ? "text" : "password",
198
+ placeholder: "Re-enter password",
199
+ value: confirm,
200
+ onChange: (e) => setConfirm(e.target.value)
201
+ }
202
+ )), /* @__PURE__ */ React.createElement(Button, { type: "submit", variant: "primary", fullWidth: true, loading, style: { marginTop: 8 } }, "Reset password"))));
203
+ };
204
+
205
+ export {
206
+ ForgotPasswordScreen,
207
+ ResetPasswordScreen
208
+ };
209
+ //# sourceMappingURL=chunk-FAI3ERB3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/screens/ForgotResetScreen.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\nimport { Button } from '@dravyn/ui';\nimport { Input } from '@dravyn/ui';\nimport { Alert } from '@dravyn/ui';\nimport './auth.css';\n\n// ── ForgotPasswordScreen ──────────────────────────────────────────────────────\n\nexport interface ForgotPasswordScreenProps {\n logo?: React.ReactNode;\n brandName?: string;\n apiBase?: string;\n}\n\nexport const ForgotPasswordScreen: React.FC<ForgotPasswordScreenProps> = ({\n logo,\n brandName = 'Dravyn',\n apiBase = process.env.NEXT_PUBLIC_AUTH_API_URL ?? 'http://localhost:3001',\n}) => {\n const [email, setEmail] = React.useState('');\n const [loading, setLoading] = React.useState(false);\n const [error, setError] = React.useState('');\n const [sent, setSent] = React.useState(false);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!email.trim()) { setError('Email address is required.'); return; }\n if (!/\\S+@\\S+\\.\\S+/.test(email)) { setError('Enter a valid email address.'); return; }\n\n setLoading(true);\n setError('');\n try {\n const res = await fetch(`${apiBase}/auth/forgot-password`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: email.trim().toLowerCase() }),\n });\n // Always show success — don't reveal if email exists\n if (res.ok || res.status === 404) {\n sessionStorage.setItem('dravyn_reset_email', email.trim().toLowerCase());\n setSent(true);\n } else {\n const d = await res.json();\n setError(d.message ?? 'Something went wrong. Try again.');\n }\n } catch {\n setError('Unable to connect.');\n } finally {\n setLoading(false);\n }\n };\n\n if (sent) {\n return (\n <div className=\"dauth-root\">\n <div className=\"dauth-card\">\n <div className=\"dauth-brand\">\n {logo ?? <div className=\"dauth-logo-default\"><span className=\"dauth-logo-dot\" />{brandName}</div>}\n </div>\n <div className=\"dauth-otp-icon\">✓</div>\n <h1 className=\"dauth-title\">Check your inbox</h1>\n <p className=\"dauth-sub\">\n If <strong style={{ color: 'var(--dauth-text)' }}>{email}</strong> is\n registered, you'll receive a reset code shortly.\n </p>\n <Button\n variant=\"primary\"\n fullWidth\n style={{ marginTop: 20 }}\n onClick={() => window.location.href = '/auth/reset-password'}\n >\n Enter reset code\n </Button>\n <p className=\"dauth-footer-text\">\n <a href=\"/auth/login\" className=\"dauth-link\">Back to sign in</a>\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"dauth-root\">\n <div className=\"dauth-card\">\n <div className=\"dauth-brand\">\n {logo ?? <div className=\"dauth-logo-default\"><span className=\"dauth-logo-dot\" />{brandName}</div>}\n </div>\n\n <h1 className=\"dauth-title\">Reset your password</h1>\n <p className=\"dauth-sub\">\n Enter your email and we'll send you a reset code.\n </p>\n\n {error && <div style={{ marginBottom: 14 }}><Alert variant=\"danger\">{error}</Alert></div>}\n\n <form onSubmit={handleSubmit} noValidate>\n <div className=\"dauth-fields\">\n <Input\n label=\"Email address\"\n type=\"email\"\n placeholder=\"you@example.com\"\n value={email}\n onChange={e => setEmail(e.target.value)}\n autoComplete=\"email\"\n required\n />\n </div>\n <Button type=\"submit\" variant=\"primary\" fullWidth loading={loading} style={{ marginTop: 8 }}>\n Send reset code\n </Button>\n </form>\n\n <p className=\"dauth-footer-text\">\n Remember it?{' '}\n <a href=\"/auth/login\" className=\"dauth-link\">Back to sign in</a>\n </p>\n </div>\n </div>\n );\n};\n\n// ── ResetPasswordScreen ───────────────────────────────────────────────────────\n\nexport interface ResetPasswordScreenProps {\n logo?: React.ReactNode;\n brandName?: string;\n apiBase?: string;\n redirectTo?: string;\n}\n\nexport const ResetPasswordScreen: React.FC<ResetPasswordScreenProps> = ({\n logo,\n brandName = 'Dravyn',\n apiBase = process.env.NEXT_PUBLIC_AUTH_API_URL ?? 'http://localhost:3001',\n redirectTo = '/auth/login',\n}) => {\n const email = typeof sessionStorage !== 'undefined'\n ? sessionStorage.getItem('dravyn_reset_email') ?? '' : '';\n\n const [otp, setOtp] = React.useState(['', '', '', '', '', '']);\n const [password, setPassword] = React.useState('');\n const [confirm, setConfirm] = React.useState('');\n const [showPass, setShowPass] = React.useState(false);\n const [loading, setLoading] = React.useState(false);\n const [error, setError] = React.useState('');\n const [done, setDone] = React.useState(false);\n const inputRefs = React.useRef<(HTMLInputElement | null)[]>([]);\n\n const handleOtpInput = (i: number, val: string) => {\n if (val.length > 1) {\n const digits = val.replace(/\\D/g, '').slice(0, 6).split('');\n const next = [...otp];\n digits.forEach((d, idx) => { if (idx < 6) next[idx] = d; });\n setOtp(next);\n inputRefs.current[Math.min(digits.length, 5)]?.focus();\n return;\n }\n const digit = val.replace(/\\D/g, '');\n const next = [...otp]; next[i] = digit; setOtp(next);\n if (digit && i < 5) inputRefs.current[i + 1]?.focus();\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n const code = otp.join('');\n if (code.length < 6) { setError('Enter the complete 6-digit code.'); return; }\n if (!password) { setError('Enter a new password.'); return; }\n if (password.length < 8) { setError('Password must be at least 8 characters.'); return; }\n if (password !== confirm) { setError('Passwords do not match.'); return; }\n\n setLoading(true);\n setError('');\n try {\n const res = await fetch(`${apiBase}/auth/reset-password`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email, otp: code, newPassword: password }),\n });\n const data = await res.json();\n if (!res.ok) { setError(data.message ?? 'Reset failed. Check the code and try again.'); return; }\n sessionStorage.removeItem('dravyn_reset_email');\n setDone(true);\n } catch {\n setError('Unable to connect.');\n } finally {\n setLoading(false);\n }\n };\n\n const EyeIcon = () => <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>;\n const EyeOffIcon = () => <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M9.88 9.88a3 3 0 1 0 4.24 4.24\"/><path d=\"M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68\"/><path d=\"M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61\"/><line x1=\"2\" x2=\"22\" y1=\"2\" y2=\"22\"/></svg>;\n\n if (done) {\n return (\n <div className=\"dauth-root\">\n <div className=\"dauth-card\">\n <div className=\"dauth-otp-icon\" style={{ color: 'var(--dauth-green)' }}>✓</div>\n <h1 className=\"dauth-title\">Password reset!</h1>\n <p className=\"dauth-sub\">Your password has been updated. You can now sign in.</p>\n <Button variant=\"primary\" fullWidth style={{ marginTop: 20 }}\n onClick={() => window.location.href = redirectTo}>\n Sign in\n </Button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"dauth-root\">\n <div className=\"dauth-card\">\n <div className=\"dauth-brand\">\n {logo ?? <div className=\"dauth-logo-default\"><span className=\"dauth-logo-dot\" />{brandName}</div>}\n </div>\n\n <h1 className=\"dauth-title\">Set new password</h1>\n <p className=\"dauth-sub\">Enter the code we sent you and choose a new password.</p>\n\n {error && <div style={{ marginBottom: 14 }}><Alert variant=\"danger\">{error}</Alert></div>}\n\n <form onSubmit={handleSubmit} noValidate>\n {/* OTP row */}\n <div className=\"dauth-fields\">\n <div>\n <div className=\"dauth-label\">Reset code</div>\n <div className=\"dauth-otp-boxes\" style={{ marginTop: 6 }}>\n {otp.map((d, i) => (\n <input key={i}\n ref={el => { inputRefs.current[i] = el; }}\n className={`dauth-otp-box ${d ? 'dauth-otp-box--filled' : ''}`}\n type=\"text\" inputMode=\"numeric\" maxLength={6}\n value={d} onChange={e => handleOtpInput(i, e.target.value)}\n onKeyDown={e => { if (e.key === 'Backspace' && !d && i > 0) inputRefs.current[i-1]?.focus(); }}\n aria-label={`Digit ${i + 1}`}\n />\n ))}\n </div>\n </div>\n\n <Input\n label=\"New password\"\n type={showPass ? 'text' : 'password'}\n placeholder=\"Min. 8 characters\"\n value={password}\n onChange={e => setPassword(e.target.value)}\n rightIcon={\n <button type=\"button\" className=\"dauth-eye\"\n onClick={() => setShowPass(v => !v)}>\n {showPass ? <EyeOffIcon /> : <EyeIcon />}\n </button>\n }\n />\n <Input\n label=\"Confirm new password\"\n type={showPass ? 'text' : 'password'}\n placeholder=\"Re-enter password\"\n value={confirm}\n onChange={e => setConfirm(e.target.value)}\n />\n </div>\n\n <Button type=\"submit\" variant=\"primary\" fullWidth loading={loading} style={{ marginTop: 8 }}>\n Reset password\n </Button>\n </form>\n </div>\n </div>\n );\n};\n"],"mappings":"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","names":["next"]}
@@ -0,0 +1,152 @@
1
+ // src/screens/OTPScreen.tsx
2
+ import React from "react";
3
+ import { Button } from "@dravyn/ui";
4
+ import { Alert } from "@dravyn/ui";
5
+ var OTPScreen = ({
6
+ email: emailProp,
7
+ redirectTo = "/dashboard",
8
+ onSuccess,
9
+ logo,
10
+ brandName = "Dravyn",
11
+ apiBase = process.env.NEXT_PUBLIC_AUTH_API_URL ?? "http://localhost:3001",
12
+ length = 6
13
+ }) => {
14
+ const email = emailProp ?? (typeof sessionStorage !== "undefined" ? sessionStorage.getItem("dravyn_pending_email") ?? "" : "");
15
+ const [otp, setOtp] = React.useState(Array(length).fill(""));
16
+ const [loading, setLoading] = React.useState(false);
17
+ const [resending, setResending] = React.useState(false);
18
+ const [error, setError] = React.useState("");
19
+ const [success, setSuccess] = React.useState("");
20
+ const [countdown, setCountdown] = React.useState(60);
21
+ const inputRefs = React.useRef([]);
22
+ React.useEffect(() => {
23
+ if (countdown <= 0) return;
24
+ const t = setTimeout(() => setCountdown((c) => c - 1), 1e3);
25
+ return () => clearTimeout(t);
26
+ }, [countdown]);
27
+ React.useEffect(() => {
28
+ inputRefs.current[0]?.focus();
29
+ }, []);
30
+ const handleInput = (i, val) => {
31
+ if (val.length > 1) {
32
+ const digits = val.replace(/\D/g, "").slice(0, length).split("");
33
+ const next2 = [...otp];
34
+ digits.forEach((d, idx) => {
35
+ if (idx < length) next2[idx] = d;
36
+ });
37
+ setOtp(next2);
38
+ const last = Math.min(digits.length, length - 1);
39
+ inputRefs.current[last]?.focus();
40
+ return;
41
+ }
42
+ const digit = val.replace(/\D/g, "");
43
+ const next = [...otp];
44
+ next[i] = digit;
45
+ setOtp(next);
46
+ if (digit && i < length - 1) inputRefs.current[i + 1]?.focus();
47
+ };
48
+ const handleKeyDown = (i, e) => {
49
+ if (e.key === "Backspace" && !otp[i] && i > 0) {
50
+ inputRefs.current[i - 1]?.focus();
51
+ }
52
+ };
53
+ const handleVerify = async () => {
54
+ const code = otp.join("");
55
+ if (code.length < length) {
56
+ setError("Enter the complete verification code.");
57
+ return;
58
+ }
59
+ setLoading(true);
60
+ setError("");
61
+ try {
62
+ const res = await fetch(`${apiBase}/auth/verify-otp`, {
63
+ method: "POST",
64
+ headers: { "Content-Type": "application/json" },
65
+ body: JSON.stringify({ email, otp: code })
66
+ });
67
+ const data = await res.json();
68
+ if (!res.ok) {
69
+ setError(data.message ?? "Invalid or expired code.");
70
+ return;
71
+ }
72
+ sessionStorage.removeItem("dravyn_pending_email");
73
+ if (data.accessToken) {
74
+ localStorage.setItem("dravyn_token", data.accessToken);
75
+ localStorage.setItem("dravyn_user", JSON.stringify(data.user));
76
+ }
77
+ if (onSuccess) onSuccess();
78
+ else window.location.href = redirectTo;
79
+ } catch {
80
+ setError("Unable to connect.");
81
+ } finally {
82
+ setLoading(false);
83
+ }
84
+ };
85
+ const handleResend = async () => {
86
+ setResending(true);
87
+ setError("");
88
+ setSuccess("");
89
+ try {
90
+ const res = await fetch(`${apiBase}/auth/resend-otp`, {
91
+ method: "POST",
92
+ headers: { "Content-Type": "application/json" },
93
+ body: JSON.stringify({ email })
94
+ });
95
+ if (res.ok) {
96
+ setSuccess("A new code has been sent to your email.");
97
+ setCountdown(60);
98
+ setOtp(Array(length).fill(""));
99
+ inputRefs.current[0]?.focus();
100
+ } else {
101
+ setError("Could not resend. Try again shortly.");
102
+ }
103
+ } catch {
104
+ setError("Unable to connect.");
105
+ } finally {
106
+ setResending(false);
107
+ }
108
+ };
109
+ return /* @__PURE__ */ React.createElement("div", { className: "dauth-root" }, /* @__PURE__ */ React.createElement("div", { className: "dauth-card" }, /* @__PURE__ */ React.createElement("div", { className: "dauth-brand" }, logo ?? /* @__PURE__ */ React.createElement("div", { className: "dauth-logo-default" }, /* @__PURE__ */ React.createElement("span", { className: "dauth-logo-dot" }), brandName)), /* @__PURE__ */ React.createElement("div", { className: "dauth-otp-icon", "aria-hidden": "true" }, "\u2709"), /* @__PURE__ */ React.createElement("h1", { className: "dauth-title" }, "Check your email"), /* @__PURE__ */ React.createElement("p", { className: "dauth-sub" }, "We sent a ", length, "-digit code to", /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("strong", { style: { color: "var(--dauth-text)" } }, email || "your email address")), error && /* @__PURE__ */ React.createElement("div", { style: { margin: "12px 0" } }, /* @__PURE__ */ React.createElement(Alert, { variant: "danger" }, error)), success && /* @__PURE__ */ React.createElement("div", { style: { margin: "12px 0" } }, /* @__PURE__ */ React.createElement(Alert, { variant: "success" }, success)), /* @__PURE__ */ React.createElement("div", { className: "dauth-otp-boxes", role: "group", "aria-label": "Verification code" }, otp.map((digit, i) => /* @__PURE__ */ React.createElement(
110
+ "input",
111
+ {
112
+ key: i,
113
+ ref: (el) => {
114
+ inputRefs.current[i] = el;
115
+ },
116
+ className: `dauth-otp-box ${digit ? "dauth-otp-box--filled" : ""}`,
117
+ type: "text",
118
+ inputMode: "numeric",
119
+ pattern: "[0-9]*",
120
+ maxLength: length,
121
+ value: digit,
122
+ onChange: (e) => handleInput(i, e.target.value),
123
+ onKeyDown: (e) => handleKeyDown(i, e),
124
+ onFocus: (e) => e.target.select(),
125
+ "aria-label": `Digit ${i + 1}`
126
+ }
127
+ ))), /* @__PURE__ */ React.createElement(
128
+ Button,
129
+ {
130
+ variant: "primary",
131
+ fullWidth: true,
132
+ loading,
133
+ onClick: handleVerify,
134
+ style: { marginTop: 8 }
135
+ },
136
+ "Verify email"
137
+ ), /* @__PURE__ */ React.createElement("div", { className: "dauth-resend" }, countdown > 0 ? /* @__PURE__ */ React.createElement("span", null, "Resend code in ", /* @__PURE__ */ React.createElement("strong", { style: { color: "var(--dauth-teal)" } }, countdown, "s")) : /* @__PURE__ */ React.createElement(
138
+ "button",
139
+ {
140
+ type: "button",
141
+ className: "dauth-link",
142
+ onClick: handleResend,
143
+ disabled: resending
144
+ },
145
+ resending ? "Sending\u2026" : "Resend code"
146
+ )), /* @__PURE__ */ React.createElement("p", { className: "dauth-footer-text" }, "Wrong email?", " ", /* @__PURE__ */ React.createElement("a", { href: "/auth/register", className: "dauth-link" }, "Go back"))));
147
+ };
148
+
149
+ export {
150
+ OTPScreen
151
+ };
152
+ //# sourceMappingURL=chunk-FVXZZTKL.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/screens/OTPScreen.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\nimport { Button } from '@dravyn/ui';\nimport { Alert } from '@dravyn/ui';\nimport './auth.css';\n\nexport interface OTPScreenProps {\n /** Pre-fill the email (e.g. passed via query param or sessionStorage) */\n email?: string;\n redirectTo?: string;\n onSuccess?: () => void;\n logo?: React.ReactNode;\n brandName?: string;\n apiBase?: string;\n /** OTP length — default 6 */\n length?: number;\n}\n\nexport const OTPScreen: React.FC<OTPScreenProps> = ({\n email: emailProp,\n redirectTo = '/dashboard',\n onSuccess,\n logo,\n brandName = 'Dravyn',\n apiBase = process.env.NEXT_PUBLIC_AUTH_API_URL ?? 'http://localhost:3001',\n length = 6,\n}) => {\n const email = emailProp\n ?? (typeof sessionStorage !== 'undefined'\n ? sessionStorage.getItem('dravyn_pending_email') ?? ''\n : '');\n\n const [otp, setOtp] = React.useState<string[]>(Array(length).fill(''));\n const [loading, setLoading] = React.useState(false);\n const [resending, setResending] = React.useState(false);\n const [error, setError] = React.useState('');\n const [success, setSuccess] = React.useState('');\n const [countdown, setCountdown] = React.useState(60);\n const inputRefs = React.useRef<(HTMLInputElement | null)[]>([]);\n\n // Countdown timer for resend\n React.useEffect(() => {\n if (countdown <= 0) return;\n const t = setTimeout(() => setCountdown(c => c - 1), 1000);\n return () => clearTimeout(t);\n }, [countdown]);\n\n // Auto-focus first box\n React.useEffect(() => { inputRefs.current[0]?.focus(); }, []);\n\n const handleInput = (i: number, val: string) => {\n // Allow paste of full code\n if (val.length > 1) {\n const digits = val.replace(/\\D/g, '').slice(0, length).split('');\n const next = [...otp];\n digits.forEach((d, idx) => { if (idx < length) next[idx] = d; });\n setOtp(next);\n const last = Math.min(digits.length, length - 1);\n inputRefs.current[last]?.focus();\n return;\n }\n const digit = val.replace(/\\D/g, '');\n const next = [...otp];\n next[i] = digit;\n setOtp(next);\n if (digit && i < length - 1) inputRefs.current[i + 1]?.focus();\n };\n\n const handleKeyDown = (i: number, e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Backspace' && !otp[i] && i > 0) {\n inputRefs.current[i - 1]?.focus();\n }\n };\n\n const handleVerify = async () => {\n const code = otp.join('');\n if (code.length < length) { setError('Enter the complete verification code.'); return; }\n setLoading(true);\n setError('');\n try {\n const res = await fetch(`${apiBase}/auth/verify-otp`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email, otp: code }),\n });\n const data = await res.json();\n if (!res.ok) { setError(data.message ?? 'Invalid or expired code.'); return; }\n sessionStorage.removeItem('dravyn_pending_email');\n if (data.accessToken) {\n localStorage.setItem('dravyn_token', data.accessToken);\n localStorage.setItem('dravyn_user', JSON.stringify(data.user));\n }\n if (onSuccess) onSuccess();\n else window.location.href = redirectTo;\n } catch {\n setError('Unable to connect.');\n } finally {\n setLoading(false);\n }\n };\n\n const handleResend = async () => {\n setResending(true);\n setError('');\n setSuccess('');\n try {\n const res = await fetch(`${apiBase}/auth/resend-otp`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email }),\n });\n if (res.ok) {\n setSuccess('A new code has been sent to your email.');\n setCountdown(60);\n setOtp(Array(length).fill(''));\n inputRefs.current[0]?.focus();\n } else {\n setError('Could not resend. Try again shortly.');\n }\n } catch {\n setError('Unable to connect.');\n } finally {\n setResending(false);\n }\n };\n\n return (\n <div className=\"dauth-root\">\n <div className=\"dauth-card\">\n <div className=\"dauth-brand\">\n {logo ?? <div className=\"dauth-logo-default\"><span className=\"dauth-logo-dot\" />{brandName}</div>}\n </div>\n\n <div className=\"dauth-otp-icon\" aria-hidden=\"true\">✉</div>\n <h1 className=\"dauth-title\">Check your email</h1>\n <p className=\"dauth-sub\">\n We sent a {length}-digit code to<br />\n <strong style={{ color: 'var(--dauth-text)' }}>{email || 'your email address'}</strong>\n </p>\n\n {error && <div style={{ margin: '12px 0' }}><Alert variant=\"danger\">{error}</Alert></div>}\n {success && <div style={{ margin: '12px 0' }}><Alert variant=\"success\">{success}</Alert></div>}\n\n {/* OTP boxes */}\n <div className=\"dauth-otp-boxes\" role=\"group\" aria-label=\"Verification code\">\n {otp.map((digit, i) => (\n <input\n key={i}\n ref={el => { inputRefs.current[i] = el; }}\n className={`dauth-otp-box ${digit ? 'dauth-otp-box--filled' : ''}`}\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n maxLength={length} // allow paste\n value={digit}\n onChange={e => handleInput(i, e.target.value)}\n onKeyDown={e => handleKeyDown(i, e)}\n onFocus={e => e.target.select()}\n aria-label={`Digit ${i + 1}`}\n />\n ))}\n </div>\n\n <Button\n variant=\"primary\"\n fullWidth\n loading={loading}\n onClick={handleVerify}\n style={{ marginTop: 8 }}\n >\n Verify email\n </Button>\n\n {/* Resend */}\n <div className=\"dauth-resend\">\n {countdown > 0 ? (\n <span>Resend code in <strong style={{ color: 'var(--dauth-teal)' }}>{countdown}s</strong></span>\n ) : (\n <button\n type=\"button\"\n className=\"dauth-link\"\n onClick={handleResend}\n disabled={resending}\n >\n {resending ? 'Sending…' : 'Resend code'}\n </button>\n )}\n </div>\n\n <p className=\"dauth-footer-text\">\n Wrong email?{' '}\n <a href=\"/auth/register\" className=\"dauth-link\">Go back</a>\n </p>\n </div>\n </div>\n );\n};\n"],"mappings":"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","names":["next"]}