@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.
- package/dist/chunk-35G4WYX3.mjs +154 -0
- package/dist/chunk-35G4WYX3.mjs.map +1 -0
- package/dist/chunk-6BBGRN4E.mjs +1 -0
- package/dist/chunk-6BBGRN4E.mjs.map +1 -0
- package/dist/chunk-FAI3ERB3.mjs +209 -0
- package/dist/chunk-FAI3ERB3.mjs.map +1 -0
- package/dist/chunk-FVXZZTKL.mjs +152 -0
- package/dist/chunk-FVXZZTKL.mjs.map +1 -0
- package/dist/chunk-Z2ICX4UY.mjs +132 -0
- package/dist/chunk-Z2ICX4UY.mjs.map +1 -0
- package/dist/index.css +235 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +678 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +22 -0
- package/dist/index.mjs.map +1 -0
- package/dist/screens/ForgotResetScreen.css +235 -0
- package/dist/screens/ForgotResetScreen.css.map +1 -0
- package/dist/screens/ForgotResetScreen.d.mts +17 -0
- package/dist/screens/ForgotResetScreen.d.ts +17 -0
- package/dist/screens/ForgotResetScreen.js +245 -0
- package/dist/screens/ForgotResetScreen.js.map +1 -0
- package/dist/screens/ForgotResetScreen.mjs +11 -0
- package/dist/screens/ForgotResetScreen.mjs.map +1 -0
- package/dist/screens/LoginScreen.css +235 -0
- package/dist/screens/LoginScreen.css.map +1 -0
- package/dist/screens/LoginScreen.d.mts +36 -0
- package/dist/screens/LoginScreen.d.ts +36 -0
- package/dist/screens/LoginScreen.js +167 -0
- package/dist/screens/LoginScreen.js.map +1 -0
- package/dist/screens/LoginScreen.mjs +9 -0
- package/dist/screens/LoginScreen.mjs.map +1 -0
- package/dist/screens/OTPScreen.css +235 -0
- package/dist/screens/OTPScreen.css.map +1 -0
- package/dist/screens/OTPScreen.d.mts +16 -0
- package/dist/screens/OTPScreen.d.ts +16 -0
- package/dist/screens/OTPScreen.js +187 -0
- package/dist/screens/OTPScreen.js.map +1 -0
- package/dist/screens/OTPScreen.mjs +9 -0
- package/dist/screens/OTPScreen.mjs.map +1 -0
- package/dist/screens/RegisterScreen.css +235 -0
- package/dist/screens/RegisterScreen.css.map +1 -0
- package/dist/screens/RegisterScreen.d.mts +21 -0
- package/dist/screens/RegisterScreen.d.ts +21 -0
- package/dist/screens/RegisterScreen.js +189 -0
- package/dist/screens/RegisterScreen.js.map +1 -0
- package/dist/screens/RegisterScreen.mjs +9 -0
- package/dist/screens/RegisterScreen.mjs.map +1 -0
- package/package.json +39 -0
- 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"]}
|