@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
package/dist/index.js ADDED
@@ -0,0 +1,678 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ForgotPasswordScreen: () => ForgotPasswordScreen,
34
+ LoginScreen: () => LoginScreen,
35
+ OTPScreen: () => OTPScreen,
36
+ RegisterScreen: () => RegisterScreen,
37
+ ResetPasswordScreen: () => ResetPasswordScreen
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/screens/LoginScreen.tsx
42
+ var import_react = __toESM(require("react"));
43
+ var import_ui = require("@dravyn/ui");
44
+ var import_ui2 = require("@dravyn/ui");
45
+ var import_ui3 = require("@dravyn/ui");
46
+ var LoginScreen = ({
47
+ redirectTo = "/dashboard",
48
+ showRegisterLink = true,
49
+ onSuccess,
50
+ logo,
51
+ brandName = "Dravyn",
52
+ apiBase = process.env.NEXT_PUBLIC_AUTH_API_URL ?? "http://localhost:3001"
53
+ }) => {
54
+ const [email, setEmail] = import_react.default.useState("");
55
+ const [password, setPassword] = import_react.default.useState("");
56
+ const [showPass, setShowPass] = import_react.default.useState(false);
57
+ const [loading, setLoading] = import_react.default.useState(false);
58
+ const [error, setError] = import_react.default.useState("");
59
+ const emailRef = import_react.default.useRef(null);
60
+ import_react.default.useEffect(() => {
61
+ emailRef.current?.focus();
62
+ }, []);
63
+ const validate = () => {
64
+ if (!email.trim()) return "Email address is required.";
65
+ if (!/\S+@\S+\.\S+/.test(email)) return "Enter a valid email address.";
66
+ if (!password) return "Password is required.";
67
+ if (password.length < 6) return "Password must be at least 6 characters.";
68
+ return null;
69
+ };
70
+ const handleSubmit = async (e) => {
71
+ e.preventDefault();
72
+ const err = validate();
73
+ if (err) {
74
+ setError(err);
75
+ return;
76
+ }
77
+ setLoading(true);
78
+ setError("");
79
+ try {
80
+ const res = await fetch(`${apiBase}/auth/login`, {
81
+ method: "POST",
82
+ headers: { "Content-Type": "application/json" },
83
+ body: JSON.stringify({ email: email.trim().toLowerCase(), password })
84
+ });
85
+ const data = await res.json();
86
+ if (!res.ok) {
87
+ setError(data.message ?? "Invalid email or password.");
88
+ return;
89
+ }
90
+ localStorage.setItem("dravyn_token", data.accessToken);
91
+ localStorage.setItem("dravyn_user", JSON.stringify(data.user));
92
+ if (onSuccess) {
93
+ onSuccess(data.accessToken, data.user);
94
+ } else {
95
+ window.location.href = redirectTo;
96
+ }
97
+ } catch {
98
+ setError("Unable to connect. Please check your internet connection.");
99
+ } finally {
100
+ setLoading(false);
101
+ }
102
+ };
103
+ const handleGoogle = async () => {
104
+ window.location.href = `${apiBase}/auth/google`;
105
+ };
106
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "dauth-root" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dauth-card" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dauth-brand" }, logo ?? /* @__PURE__ */ import_react.default.createElement("div", { className: "dauth-logo-default" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "dauth-logo-dot" }), brandName)), /* @__PURE__ */ import_react.default.createElement("h1", { className: "dauth-title" }, "Welcome back"), /* @__PURE__ */ import_react.default.createElement("p", { className: "dauth-sub" }, "Sign in to your ", brandName, " account"), error && /* @__PURE__ */ import_react.default.createElement("div", { style: { marginBottom: 16 } }, /* @__PURE__ */ import_react.default.createElement(import_ui3.Alert, { variant: "danger" }, error)), /* @__PURE__ */ import_react.default.createElement("form", { onSubmit: handleSubmit, noValidate: true }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dauth-fields" }, /* @__PURE__ */ import_react.default.createElement(
107
+ import_ui2.Input,
108
+ {
109
+ ref: emailRef,
110
+ label: "Email address",
111
+ type: "email",
112
+ placeholder: "you@example.com",
113
+ value: email,
114
+ onChange: (e) => setEmail(e.target.value),
115
+ leftIcon: /* @__PURE__ */ import_react.default.createElement(EmailIcon, null),
116
+ autoComplete: "email",
117
+ required: true
118
+ }
119
+ ), /* @__PURE__ */ import_react.default.createElement("div", { className: "dauth-password-wrap" }, /* @__PURE__ */ import_react.default.createElement(
120
+ import_ui2.Input,
121
+ {
122
+ label: "Password",
123
+ type: showPass ? "text" : "password",
124
+ placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
125
+ value: password,
126
+ onChange: (e) => setPassword(e.target.value),
127
+ leftIcon: /* @__PURE__ */ import_react.default.createElement(LockIcon, null),
128
+ rightIcon: /* @__PURE__ */ import_react.default.createElement(
129
+ "button",
130
+ {
131
+ type: "button",
132
+ className: "dauth-eye",
133
+ onClick: () => setShowPass((v) => !v),
134
+ "aria-label": showPass ? "Hide password" : "Show password"
135
+ },
136
+ showPass ? /* @__PURE__ */ import_react.default.createElement(EyeOffIcon, null) : /* @__PURE__ */ import_react.default.createElement(EyeIcon, null)
137
+ ),
138
+ autoComplete: "current-password",
139
+ required: true
140
+ }
141
+ ), /* @__PURE__ */ import_react.default.createElement("a", { href: "/auth/forgot-password", className: "dauth-forgot" }, "Forgot password?"))), /* @__PURE__ */ import_react.default.createElement(
142
+ import_ui.Button,
143
+ {
144
+ type: "submit",
145
+ variant: "primary",
146
+ fullWidth: true,
147
+ loading,
148
+ style: { marginTop: 8 }
149
+ },
150
+ "Sign in"
151
+ )), /* @__PURE__ */ import_react.default.createElement("div", { className: "dauth-divider" }, /* @__PURE__ */ import_react.default.createElement("span", null, "or continue with")), /* @__PURE__ */ import_react.default.createElement(
152
+ "button",
153
+ {
154
+ type: "button",
155
+ className: "dauth-google-btn",
156
+ onClick: handleGoogle,
157
+ disabled: loading
158
+ },
159
+ /* @__PURE__ */ import_react.default.createElement(GoogleIcon, null),
160
+ "Continue with Google"
161
+ ), showRegisterLink && /* @__PURE__ */ import_react.default.createElement("p", { className: "dauth-footer-text" }, "Don't have an account?", " ", /* @__PURE__ */ import_react.default.createElement("a", { href: "/auth/register", className: "dauth-link" }, "Create one"))));
162
+ };
163
+ var EmailIcon = () => /* @__PURE__ */ import_react.default.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" }));
164
+ var LockIcon = () => /* @__PURE__ */ import_react.default.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("rect", { width: "18", height: "11", x: "3", y: "11", rx: "2", ry: "2" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" }));
165
+ var EyeIcon = () => /* @__PURE__ */ import_react.default.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }), /* @__PURE__ */ import_react.default.createElement("circle", { cx: "12", cy: "12", r: "3" }));
166
+ var EyeOffIcon = () => /* @__PURE__ */ import_react.default.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }), /* @__PURE__ */ import_react.default.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__ */ import_react.default.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__ */ import_react.default.createElement("line", { x1: "2", x2: "22", y1: "2", y2: "22" }));
167
+ var GoogleIcon = () => /* @__PURE__ */ import_react.default.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24" }, /* @__PURE__ */ import_react.default.createElement("path", { fill: "#4285F4", d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" }), /* @__PURE__ */ import_react.default.createElement("path", { fill: "#34A853", d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" }), /* @__PURE__ */ import_react.default.createElement("path", { fill: "#FBBC05", d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" }), /* @__PURE__ */ import_react.default.createElement("path", { fill: "#EA4335", d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" }));
168
+
169
+ // src/screens/RegisterScreen.tsx
170
+ var import_react2 = __toESM(require("react"));
171
+ var import_ui4 = require("@dravyn/ui");
172
+ var import_ui5 = require("@dravyn/ui");
173
+ var import_ui6 = require("@dravyn/ui");
174
+ var RegisterScreen = ({
175
+ redirectTo = "/auth/verify",
176
+ onSuccess,
177
+ logo,
178
+ brandName = "Dravyn",
179
+ apiBase = process.env.NEXT_PUBLIC_AUTH_API_URL ?? "http://localhost:3001",
180
+ extraFields,
181
+ requireOTP = true
182
+ }) => {
183
+ const [form, setForm] = import_react2.default.useState({
184
+ firstName: "",
185
+ lastName: "",
186
+ email: "",
187
+ password: "",
188
+ confirm: ""
189
+ });
190
+ const [showPass, setShowPass] = import_react2.default.useState(false);
191
+ const [loading, setLoading] = import_react2.default.useState(false);
192
+ const [error, setError] = import_react2.default.useState("");
193
+ const [fieldErrs, setFieldErrs] = import_react2.default.useState({});
194
+ const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value }));
195
+ const validate = () => {
196
+ const errs = {};
197
+ if (!form.firstName.trim()) errs.firstName = "First name is required.";
198
+ if (!form.lastName.trim()) errs.lastName = "Last name is required.";
199
+ if (!form.email.trim()) errs.email = "Email is required.";
200
+ else if (!/\S+@\S+\.\S+/.test(form.email)) errs.email = "Enter a valid email.";
201
+ if (!form.password) errs.password = "Password is required.";
202
+ else if (form.password.length < 8) errs.password = "Password must be at least 8 characters.";
203
+ if (form.confirm !== form.password) errs.confirm = "Passwords do not match.";
204
+ return errs;
205
+ };
206
+ const handleSubmit = async (e) => {
207
+ e.preventDefault();
208
+ const errs = validate();
209
+ if (Object.keys(errs).length) {
210
+ setFieldErrs(errs);
211
+ return;
212
+ }
213
+ setFieldErrs({});
214
+ setLoading(true);
215
+ setError("");
216
+ try {
217
+ const res = await fetch(`${apiBase}/auth/register`, {
218
+ method: "POST",
219
+ headers: { "Content-Type": "application/json" },
220
+ body: JSON.stringify({
221
+ firstName: form.firstName.trim(),
222
+ lastName: form.lastName.trim(),
223
+ email: form.email.trim().toLowerCase(),
224
+ password: form.password
225
+ })
226
+ });
227
+ const data = await res.json();
228
+ if (!res.ok) {
229
+ setError(data.message ?? "Registration failed.");
230
+ return;
231
+ }
232
+ if (requireOTP) {
233
+ sessionStorage.setItem("dravyn_pending_email", form.email.trim().toLowerCase());
234
+ window.location.href = redirectTo;
235
+ } else {
236
+ localStorage.setItem("dravyn_token", data.accessToken);
237
+ localStorage.setItem("dravyn_user", JSON.stringify(data.user));
238
+ if (onSuccess) onSuccess(data.accessToken, data.user);
239
+ else window.location.href = redirectTo;
240
+ }
241
+ } catch {
242
+ setError("Unable to connect. Check your internet connection.");
243
+ } finally {
244
+ setLoading(false);
245
+ }
246
+ };
247
+ const EyeIcon2 = () => /* @__PURE__ */ import_react2.default.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }), /* @__PURE__ */ import_react2.default.createElement("circle", { cx: "12", cy: "12", r: "3" }));
248
+ const EyeOffIcon2 = () => /* @__PURE__ */ import_react2.default.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }), /* @__PURE__ */ import_react2.default.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__ */ import_react2.default.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__ */ import_react2.default.createElement("line", { x1: "2", x2: "22", y1: "2", y2: "22" }));
249
+ return /* @__PURE__ */ import_react2.default.createElement("div", { className: "dauth-root" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "dauth-card" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "dauth-brand" }, logo ?? /* @__PURE__ */ import_react2.default.createElement("div", { className: "dauth-logo-default" }, /* @__PURE__ */ import_react2.default.createElement("span", { className: "dauth-logo-dot" }), brandName)), /* @__PURE__ */ import_react2.default.createElement("h1", { className: "dauth-title" }, "Create your account"), /* @__PURE__ */ import_react2.default.createElement("p", { className: "dauth-sub" }, "Join ", brandName, " \u2014 it's free"), error && /* @__PURE__ */ import_react2.default.createElement("div", { style: { marginBottom: 16 } }, /* @__PURE__ */ import_react2.default.createElement(import_ui6.Alert, { variant: "danger" }, error)), /* @__PURE__ */ import_react2.default.createElement("form", { onSubmit: handleSubmit, noValidate: true }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "dauth-fields" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "dauth-row" }, /* @__PURE__ */ import_react2.default.createElement(
250
+ import_ui5.Input,
251
+ {
252
+ label: "First name",
253
+ placeholder: "Jeremiah",
254
+ value: form.firstName,
255
+ onChange: set("firstName"),
256
+ error: fieldErrs.firstName,
257
+ required: true
258
+ }
259
+ ), /* @__PURE__ */ import_react2.default.createElement(
260
+ import_ui5.Input,
261
+ {
262
+ label: "Last name",
263
+ placeholder: "Adeniyi",
264
+ value: form.lastName,
265
+ onChange: set("lastName"),
266
+ error: fieldErrs.lastName,
267
+ required: true
268
+ }
269
+ )), /* @__PURE__ */ import_react2.default.createElement(
270
+ import_ui5.Input,
271
+ {
272
+ label: "Email address",
273
+ type: "email",
274
+ placeholder: "you@example.com",
275
+ value: form.email,
276
+ onChange: set("email"),
277
+ error: fieldErrs.email,
278
+ autoComplete: "email",
279
+ required: true
280
+ }
281
+ ), /* @__PURE__ */ import_react2.default.createElement(
282
+ import_ui5.Input,
283
+ {
284
+ label: "Password",
285
+ type: showPass ? "text" : "password",
286
+ placeholder: "Min. 8 characters",
287
+ value: form.password,
288
+ onChange: set("password"),
289
+ error: fieldErrs.password,
290
+ hint: !fieldErrs.password ? "At least 8 characters." : void 0,
291
+ rightIcon: /* @__PURE__ */ import_react2.default.createElement(
292
+ "button",
293
+ {
294
+ type: "button",
295
+ className: "dauth-eye",
296
+ onClick: () => setShowPass((v) => !v),
297
+ "aria-label": showPass ? "Hide" : "Show"
298
+ },
299
+ showPass ? /* @__PURE__ */ import_react2.default.createElement(EyeOffIcon2, null) : /* @__PURE__ */ import_react2.default.createElement(EyeIcon2, null)
300
+ ),
301
+ autoComplete: "new-password",
302
+ required: true
303
+ }
304
+ ), /* @__PURE__ */ import_react2.default.createElement(
305
+ import_ui5.Input,
306
+ {
307
+ label: "Confirm password",
308
+ type: showPass ? "text" : "password",
309
+ placeholder: "Re-enter your password",
310
+ value: form.confirm,
311
+ onChange: set("confirm"),
312
+ error: fieldErrs.confirm,
313
+ autoComplete: "new-password",
314
+ required: true
315
+ }
316
+ ), extraFields), /* @__PURE__ */ import_react2.default.createElement(import_ui4.Button, { type: "submit", variant: "primary", fullWidth: true, loading, style: { marginTop: 8 } }, "Create account")), /* @__PURE__ */ import_react2.default.createElement("p", { className: "dauth-footer-text", style: { marginTop: 20 } }, "Already have an account?", " ", /* @__PURE__ */ import_react2.default.createElement("a", { href: "/auth/login", className: "dauth-link" }, "Sign in")), /* @__PURE__ */ import_react2.default.createElement("p", { className: "dauth-terms" }, "By creating an account you agree to our", " ", /* @__PURE__ */ import_react2.default.createElement("a", { href: "/terms", className: "dauth-link" }, "Terms of Service"), " ", "and", " ", /* @__PURE__ */ import_react2.default.createElement("a", { href: "/privacy", className: "dauth-link" }, "Privacy Policy"), ".")));
317
+ };
318
+
319
+ // src/screens/OTPScreen.tsx
320
+ var import_react3 = __toESM(require("react"));
321
+ var import_ui7 = require("@dravyn/ui");
322
+ var import_ui8 = require("@dravyn/ui");
323
+ var OTPScreen = ({
324
+ email: emailProp,
325
+ redirectTo = "/dashboard",
326
+ onSuccess,
327
+ logo,
328
+ brandName = "Dravyn",
329
+ apiBase = process.env.NEXT_PUBLIC_AUTH_API_URL ?? "http://localhost:3001",
330
+ length = 6
331
+ }) => {
332
+ const email = emailProp ?? (typeof sessionStorage !== "undefined" ? sessionStorage.getItem("dravyn_pending_email") ?? "" : "");
333
+ const [otp, setOtp] = import_react3.default.useState(Array(length).fill(""));
334
+ const [loading, setLoading] = import_react3.default.useState(false);
335
+ const [resending, setResending] = import_react3.default.useState(false);
336
+ const [error, setError] = import_react3.default.useState("");
337
+ const [success, setSuccess] = import_react3.default.useState("");
338
+ const [countdown, setCountdown] = import_react3.default.useState(60);
339
+ const inputRefs = import_react3.default.useRef([]);
340
+ import_react3.default.useEffect(() => {
341
+ if (countdown <= 0) return;
342
+ const t = setTimeout(() => setCountdown((c) => c - 1), 1e3);
343
+ return () => clearTimeout(t);
344
+ }, [countdown]);
345
+ import_react3.default.useEffect(() => {
346
+ inputRefs.current[0]?.focus();
347
+ }, []);
348
+ const handleInput = (i, val) => {
349
+ if (val.length > 1) {
350
+ const digits = val.replace(/\D/g, "").slice(0, length).split("");
351
+ const next2 = [...otp];
352
+ digits.forEach((d, idx) => {
353
+ if (idx < length) next2[idx] = d;
354
+ });
355
+ setOtp(next2);
356
+ const last = Math.min(digits.length, length - 1);
357
+ inputRefs.current[last]?.focus();
358
+ return;
359
+ }
360
+ const digit = val.replace(/\D/g, "");
361
+ const next = [...otp];
362
+ next[i] = digit;
363
+ setOtp(next);
364
+ if (digit && i < length - 1) inputRefs.current[i + 1]?.focus();
365
+ };
366
+ const handleKeyDown = (i, e) => {
367
+ if (e.key === "Backspace" && !otp[i] && i > 0) {
368
+ inputRefs.current[i - 1]?.focus();
369
+ }
370
+ };
371
+ const handleVerify = async () => {
372
+ const code = otp.join("");
373
+ if (code.length < length) {
374
+ setError("Enter the complete verification code.");
375
+ return;
376
+ }
377
+ setLoading(true);
378
+ setError("");
379
+ try {
380
+ const res = await fetch(`${apiBase}/auth/verify-otp`, {
381
+ method: "POST",
382
+ headers: { "Content-Type": "application/json" },
383
+ body: JSON.stringify({ email, otp: code })
384
+ });
385
+ const data = await res.json();
386
+ if (!res.ok) {
387
+ setError(data.message ?? "Invalid or expired code.");
388
+ return;
389
+ }
390
+ sessionStorage.removeItem("dravyn_pending_email");
391
+ if (data.accessToken) {
392
+ localStorage.setItem("dravyn_token", data.accessToken);
393
+ localStorage.setItem("dravyn_user", JSON.stringify(data.user));
394
+ }
395
+ if (onSuccess) onSuccess();
396
+ else window.location.href = redirectTo;
397
+ } catch {
398
+ setError("Unable to connect.");
399
+ } finally {
400
+ setLoading(false);
401
+ }
402
+ };
403
+ const handleResend = async () => {
404
+ setResending(true);
405
+ setError("");
406
+ setSuccess("");
407
+ try {
408
+ const res = await fetch(`${apiBase}/auth/resend-otp`, {
409
+ method: "POST",
410
+ headers: { "Content-Type": "application/json" },
411
+ body: JSON.stringify({ email })
412
+ });
413
+ if (res.ok) {
414
+ setSuccess("A new code has been sent to your email.");
415
+ setCountdown(60);
416
+ setOtp(Array(length).fill(""));
417
+ inputRefs.current[0]?.focus();
418
+ } else {
419
+ setError("Could not resend. Try again shortly.");
420
+ }
421
+ } catch {
422
+ setError("Unable to connect.");
423
+ } finally {
424
+ setResending(false);
425
+ }
426
+ };
427
+ return /* @__PURE__ */ import_react3.default.createElement("div", { className: "dauth-root" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "dauth-card" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "dauth-brand" }, logo ?? /* @__PURE__ */ import_react3.default.createElement("div", { className: "dauth-logo-default" }, /* @__PURE__ */ import_react3.default.createElement("span", { className: "dauth-logo-dot" }), brandName)), /* @__PURE__ */ import_react3.default.createElement("div", { className: "dauth-otp-icon", "aria-hidden": "true" }, "\u2709"), /* @__PURE__ */ import_react3.default.createElement("h1", { className: "dauth-title" }, "Check your email"), /* @__PURE__ */ import_react3.default.createElement("p", { className: "dauth-sub" }, "We sent a ", length, "-digit code to", /* @__PURE__ */ import_react3.default.createElement("br", null), /* @__PURE__ */ import_react3.default.createElement("strong", { style: { color: "var(--dauth-text)" } }, email || "your email address")), error && /* @__PURE__ */ import_react3.default.createElement("div", { style: { margin: "12px 0" } }, /* @__PURE__ */ import_react3.default.createElement(import_ui8.Alert, { variant: "danger" }, error)), success && /* @__PURE__ */ import_react3.default.createElement("div", { style: { margin: "12px 0" } }, /* @__PURE__ */ import_react3.default.createElement(import_ui8.Alert, { variant: "success" }, success)), /* @__PURE__ */ import_react3.default.createElement("div", { className: "dauth-otp-boxes", role: "group", "aria-label": "Verification code" }, otp.map((digit, i) => /* @__PURE__ */ import_react3.default.createElement(
428
+ "input",
429
+ {
430
+ key: i,
431
+ ref: (el) => {
432
+ inputRefs.current[i] = el;
433
+ },
434
+ className: `dauth-otp-box ${digit ? "dauth-otp-box--filled" : ""}`,
435
+ type: "text",
436
+ inputMode: "numeric",
437
+ pattern: "[0-9]*",
438
+ maxLength: length,
439
+ value: digit,
440
+ onChange: (e) => handleInput(i, e.target.value),
441
+ onKeyDown: (e) => handleKeyDown(i, e),
442
+ onFocus: (e) => e.target.select(),
443
+ "aria-label": `Digit ${i + 1}`
444
+ }
445
+ ))), /* @__PURE__ */ import_react3.default.createElement(
446
+ import_ui7.Button,
447
+ {
448
+ variant: "primary",
449
+ fullWidth: true,
450
+ loading,
451
+ onClick: handleVerify,
452
+ style: { marginTop: 8 }
453
+ },
454
+ "Verify email"
455
+ ), /* @__PURE__ */ import_react3.default.createElement("div", { className: "dauth-resend" }, countdown > 0 ? /* @__PURE__ */ import_react3.default.createElement("span", null, "Resend code in ", /* @__PURE__ */ import_react3.default.createElement("strong", { style: { color: "var(--dauth-teal)" } }, countdown, "s")) : /* @__PURE__ */ import_react3.default.createElement(
456
+ "button",
457
+ {
458
+ type: "button",
459
+ className: "dauth-link",
460
+ onClick: handleResend,
461
+ disabled: resending
462
+ },
463
+ resending ? "Sending\u2026" : "Resend code"
464
+ )), /* @__PURE__ */ import_react3.default.createElement("p", { className: "dauth-footer-text" }, "Wrong email?", " ", /* @__PURE__ */ import_react3.default.createElement("a", { href: "/auth/register", className: "dauth-link" }, "Go back"))));
465
+ };
466
+
467
+ // src/screens/ForgotResetScreen.tsx
468
+ var import_react4 = __toESM(require("react"));
469
+ var import_ui9 = require("@dravyn/ui");
470
+ var import_ui10 = require("@dravyn/ui");
471
+ var import_ui11 = require("@dravyn/ui");
472
+ var ForgotPasswordScreen = ({
473
+ logo,
474
+ brandName = "Dravyn",
475
+ apiBase = process.env.NEXT_PUBLIC_AUTH_API_URL ?? "http://localhost:3001"
476
+ }) => {
477
+ const [email, setEmail] = import_react4.default.useState("");
478
+ const [loading, setLoading] = import_react4.default.useState(false);
479
+ const [error, setError] = import_react4.default.useState("");
480
+ const [sent, setSent] = import_react4.default.useState(false);
481
+ const handleSubmit = async (e) => {
482
+ e.preventDefault();
483
+ if (!email.trim()) {
484
+ setError("Email address is required.");
485
+ return;
486
+ }
487
+ if (!/\S+@\S+\.\S+/.test(email)) {
488
+ setError("Enter a valid email address.");
489
+ return;
490
+ }
491
+ setLoading(true);
492
+ setError("");
493
+ try {
494
+ const res = await fetch(`${apiBase}/auth/forgot-password`, {
495
+ method: "POST",
496
+ headers: { "Content-Type": "application/json" },
497
+ body: JSON.stringify({ email: email.trim().toLowerCase() })
498
+ });
499
+ if (res.ok || res.status === 404) {
500
+ sessionStorage.setItem("dravyn_reset_email", email.trim().toLowerCase());
501
+ setSent(true);
502
+ } else {
503
+ const d = await res.json();
504
+ setError(d.message ?? "Something went wrong. Try again.");
505
+ }
506
+ } catch {
507
+ setError("Unable to connect.");
508
+ } finally {
509
+ setLoading(false);
510
+ }
511
+ };
512
+ if (sent) {
513
+ return /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-root" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-card" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-brand" }, logo ?? /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-logo-default" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "dauth-logo-dot" }), brandName)), /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-otp-icon" }, "\u2713"), /* @__PURE__ */ import_react4.default.createElement("h1", { className: "dauth-title" }, "Check your inbox"), /* @__PURE__ */ import_react4.default.createElement("p", { className: "dauth-sub" }, "If ", /* @__PURE__ */ import_react4.default.createElement("strong", { style: { color: "var(--dauth-text)" } }, email), " is registered, you'll receive a reset code shortly."), /* @__PURE__ */ import_react4.default.createElement(
514
+ import_ui9.Button,
515
+ {
516
+ variant: "primary",
517
+ fullWidth: true,
518
+ style: { marginTop: 20 },
519
+ onClick: () => window.location.href = "/auth/reset-password"
520
+ },
521
+ "Enter reset code"
522
+ ), /* @__PURE__ */ import_react4.default.createElement("p", { className: "dauth-footer-text" }, /* @__PURE__ */ import_react4.default.createElement("a", { href: "/auth/login", className: "dauth-link" }, "Back to sign in"))));
523
+ }
524
+ return /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-root" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-card" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-brand" }, logo ?? /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-logo-default" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "dauth-logo-dot" }), brandName)), /* @__PURE__ */ import_react4.default.createElement("h1", { className: "dauth-title" }, "Reset your password"), /* @__PURE__ */ import_react4.default.createElement("p", { className: "dauth-sub" }, "Enter your email and we'll send you a reset code."), error && /* @__PURE__ */ import_react4.default.createElement("div", { style: { marginBottom: 14 } }, /* @__PURE__ */ import_react4.default.createElement(import_ui11.Alert, { variant: "danger" }, error)), /* @__PURE__ */ import_react4.default.createElement("form", { onSubmit: handleSubmit, noValidate: true }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-fields" }, /* @__PURE__ */ import_react4.default.createElement(
525
+ import_ui10.Input,
526
+ {
527
+ label: "Email address",
528
+ type: "email",
529
+ placeholder: "you@example.com",
530
+ value: email,
531
+ onChange: (e) => setEmail(e.target.value),
532
+ autoComplete: "email",
533
+ required: true
534
+ }
535
+ )), /* @__PURE__ */ import_react4.default.createElement(import_ui9.Button, { type: "submit", variant: "primary", fullWidth: true, loading, style: { marginTop: 8 } }, "Send reset code")), /* @__PURE__ */ import_react4.default.createElement("p", { className: "dauth-footer-text" }, "Remember it?", " ", /* @__PURE__ */ import_react4.default.createElement("a", { href: "/auth/login", className: "dauth-link" }, "Back to sign in"))));
536
+ };
537
+ var ResetPasswordScreen = ({
538
+ logo,
539
+ brandName = "Dravyn",
540
+ apiBase = process.env.NEXT_PUBLIC_AUTH_API_URL ?? "http://localhost:3001",
541
+ redirectTo = "/auth/login"
542
+ }) => {
543
+ const email = typeof sessionStorage !== "undefined" ? sessionStorage.getItem("dravyn_reset_email") ?? "" : "";
544
+ const [otp, setOtp] = import_react4.default.useState(["", "", "", "", "", ""]);
545
+ const [password, setPassword] = import_react4.default.useState("");
546
+ const [confirm, setConfirm] = import_react4.default.useState("");
547
+ const [showPass, setShowPass] = import_react4.default.useState(false);
548
+ const [loading, setLoading] = import_react4.default.useState(false);
549
+ const [error, setError] = import_react4.default.useState("");
550
+ const [done, setDone] = import_react4.default.useState(false);
551
+ const inputRefs = import_react4.default.useRef([]);
552
+ const handleOtpInput = (i, val) => {
553
+ if (val.length > 1) {
554
+ const digits = val.replace(/\D/g, "").slice(0, 6).split("");
555
+ const next2 = [...otp];
556
+ digits.forEach((d, idx) => {
557
+ if (idx < 6) next2[idx] = d;
558
+ });
559
+ setOtp(next2);
560
+ inputRefs.current[Math.min(digits.length, 5)]?.focus();
561
+ return;
562
+ }
563
+ const digit = val.replace(/\D/g, "");
564
+ const next = [...otp];
565
+ next[i] = digit;
566
+ setOtp(next);
567
+ if (digit && i < 5) inputRefs.current[i + 1]?.focus();
568
+ };
569
+ const handleSubmit = async (e) => {
570
+ e.preventDefault();
571
+ const code = otp.join("");
572
+ if (code.length < 6) {
573
+ setError("Enter the complete 6-digit code.");
574
+ return;
575
+ }
576
+ if (!password) {
577
+ setError("Enter a new password.");
578
+ return;
579
+ }
580
+ if (password.length < 8) {
581
+ setError("Password must be at least 8 characters.");
582
+ return;
583
+ }
584
+ if (password !== confirm) {
585
+ setError("Passwords do not match.");
586
+ return;
587
+ }
588
+ setLoading(true);
589
+ setError("");
590
+ try {
591
+ const res = await fetch(`${apiBase}/auth/reset-password`, {
592
+ method: "POST",
593
+ headers: { "Content-Type": "application/json" },
594
+ body: JSON.stringify({ email, otp: code, newPassword: password })
595
+ });
596
+ const data = await res.json();
597
+ if (!res.ok) {
598
+ setError(data.message ?? "Reset failed. Check the code and try again.");
599
+ return;
600
+ }
601
+ sessionStorage.removeItem("dravyn_reset_email");
602
+ setDone(true);
603
+ } catch {
604
+ setError("Unable to connect.");
605
+ } finally {
606
+ setLoading(false);
607
+ }
608
+ };
609
+ const EyeIcon2 = () => /* @__PURE__ */ import_react4.default.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }), /* @__PURE__ */ import_react4.default.createElement("circle", { cx: "12", cy: "12", r: "3" }));
610
+ const EyeOffIcon2 = () => /* @__PURE__ */ import_react4.default.createElement("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }), /* @__PURE__ */ import_react4.default.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__ */ import_react4.default.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__ */ import_react4.default.createElement("line", { x1: "2", x2: "22", y1: "2", y2: "22" }));
611
+ if (done) {
612
+ return /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-root" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-card" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-otp-icon", style: { color: "var(--dauth-green)" } }, "\u2713"), /* @__PURE__ */ import_react4.default.createElement("h1", { className: "dauth-title" }, "Password reset!"), /* @__PURE__ */ import_react4.default.createElement("p", { className: "dauth-sub" }, "Your password has been updated. You can now sign in."), /* @__PURE__ */ import_react4.default.createElement(
613
+ import_ui9.Button,
614
+ {
615
+ variant: "primary",
616
+ fullWidth: true,
617
+ style: { marginTop: 20 },
618
+ onClick: () => window.location.href = redirectTo
619
+ },
620
+ "Sign in"
621
+ )));
622
+ }
623
+ return /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-root" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-card" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-brand" }, logo ?? /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-logo-default" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "dauth-logo-dot" }), brandName)), /* @__PURE__ */ import_react4.default.createElement("h1", { className: "dauth-title" }, "Set new password"), /* @__PURE__ */ import_react4.default.createElement("p", { className: "dauth-sub" }, "Enter the code we sent you and choose a new password."), error && /* @__PURE__ */ import_react4.default.createElement("div", { style: { marginBottom: 14 } }, /* @__PURE__ */ import_react4.default.createElement(import_ui11.Alert, { variant: "danger" }, error)), /* @__PURE__ */ import_react4.default.createElement("form", { onSubmit: handleSubmit, noValidate: true }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-fields" }, /* @__PURE__ */ import_react4.default.createElement("div", null, /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-label" }, "Reset code"), /* @__PURE__ */ import_react4.default.createElement("div", { className: "dauth-otp-boxes", style: { marginTop: 6 } }, otp.map((d, i) => /* @__PURE__ */ import_react4.default.createElement(
624
+ "input",
625
+ {
626
+ key: i,
627
+ ref: (el) => {
628
+ inputRefs.current[i] = el;
629
+ },
630
+ className: `dauth-otp-box ${d ? "dauth-otp-box--filled" : ""}`,
631
+ type: "text",
632
+ inputMode: "numeric",
633
+ maxLength: 6,
634
+ value: d,
635
+ onChange: (e) => handleOtpInput(i, e.target.value),
636
+ onKeyDown: (e) => {
637
+ if (e.key === "Backspace" && !d && i > 0) inputRefs.current[i - 1]?.focus();
638
+ },
639
+ "aria-label": `Digit ${i + 1}`
640
+ }
641
+ )))), /* @__PURE__ */ import_react4.default.createElement(
642
+ import_ui10.Input,
643
+ {
644
+ label: "New password",
645
+ type: showPass ? "text" : "password",
646
+ placeholder: "Min. 8 characters",
647
+ value: password,
648
+ onChange: (e) => setPassword(e.target.value),
649
+ rightIcon: /* @__PURE__ */ import_react4.default.createElement(
650
+ "button",
651
+ {
652
+ type: "button",
653
+ className: "dauth-eye",
654
+ onClick: () => setShowPass((v) => !v)
655
+ },
656
+ showPass ? /* @__PURE__ */ import_react4.default.createElement(EyeOffIcon2, null) : /* @__PURE__ */ import_react4.default.createElement(EyeIcon2, null)
657
+ )
658
+ }
659
+ ), /* @__PURE__ */ import_react4.default.createElement(
660
+ import_ui10.Input,
661
+ {
662
+ label: "Confirm new password",
663
+ type: showPass ? "text" : "password",
664
+ placeholder: "Re-enter password",
665
+ value: confirm,
666
+ onChange: (e) => setConfirm(e.target.value)
667
+ }
668
+ )), /* @__PURE__ */ import_react4.default.createElement(import_ui9.Button, { type: "submit", variant: "primary", fullWidth: true, loading, style: { marginTop: 8 } }, "Reset password"))));
669
+ };
670
+ // Annotate the CommonJS export names for ESM import in node:
671
+ 0 && (module.exports = {
672
+ ForgotPasswordScreen,
673
+ LoginScreen,
674
+ OTPScreen,
675
+ RegisterScreen,
676
+ ResetPasswordScreen
677
+ });
678
+ //# sourceMappingURL=index.js.map