@canmingir/link 1.2.44 → 1.2.49

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/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@canmingir/link",
3
- "version": "1.2.44",
3
+ "version": "1.2.49",
4
4
  "type": "module",
5
+ "bin": {
6
+ "link-server": "./server/server.ts"
7
+ },
5
8
  "exports": {
6
9
  ".": "./index.js",
7
10
  "./layouts": "./src/layouts/index.js",
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import path from "path";
4
+
5
+ const CORS_HEADERS: Record<string, string> = {
6
+ "Access-Control-Allow-Origin": "*",
7
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
8
+ "Access-Control-Allow-Headers": "*",
9
+ };
10
+
11
+ const TIMEOUT_MS = 120_000;
12
+
13
+ function parseArgs(): Record<string, string> {
14
+ const args = process.argv.slice(2);
15
+ const result: Record<string, string> = {};
16
+
17
+ for (let i = 0; i < args.length; i++) {
18
+ if (args[i].startsWith("--") && args[i + 1] && !args[i + 1].startsWith("--")) {
19
+ result[args[i].slice(2)] = args[i + 1];
20
+ i++;
21
+ }
22
+ }
23
+
24
+ return result;
25
+ }
26
+
27
+ async function getDefaultDashboardDist(): Promise<string> {
28
+ try {
29
+ const configPath = path.join(process.cwd(), "config.js");
30
+ const { default: config } = await import(configPath);
31
+ const base = (config.base as string)?.replace(/^\//, "") ?? "dashboard";
32
+ return `./${base}/dist`;
33
+ } catch {
34
+ return "./dashboard/dist";
35
+ }
36
+ }
37
+
38
+ async function proxyRequest(
39
+ req: Request,
40
+ targetUrl: string,
41
+ ): Promise<Response> {
42
+ const headers = new Headers(req.headers);
43
+ headers.delete("host");
44
+
45
+ const hasBody = req.method !== "GET" && req.method !== "HEAD";
46
+
47
+ const response = await fetch(targetUrl, {
48
+ method: req.method,
49
+ headers,
50
+ body: hasBody ? req.body : undefined,
51
+ signal: AbortSignal.timeout(TIMEOUT_MS),
52
+ // @ts-ignore — Bun requires this for streaming request bodies
53
+ duplex: "half",
54
+ });
55
+
56
+ const responseHeaders = new Headers(response.headers);
57
+ for (const [key, value] of Object.entries(CORS_HEADERS)) {
58
+ responseHeaders.set(key, value);
59
+ }
60
+
61
+ return new Response(response.body, {
62
+ status: response.status,
63
+ statusText: response.statusText,
64
+ headers: responseHeaders,
65
+ });
66
+ }
67
+
68
+ async function serveStatic(
69
+ pathname: string,
70
+ dashboardDist: string,
71
+ ): Promise<Response> {
72
+ const subPath = pathname.replace(/^\/dashboard\/?/, "") || "index.html";
73
+ const file = Bun.file(path.join(dashboardDist, subPath));
74
+
75
+ if (await file.exists()) {
76
+ return new Response(file);
77
+ }
78
+
79
+ return new Response(Bun.file(path.join(dashboardDist, "index.html")));
80
+ }
81
+
82
+ const parsed = parseArgs();
83
+
84
+ const PORT = Number(parsed["port"] ?? 3000);
85
+ const API_TARGET = parsed["api"] ?? "http://localhost:4000";
86
+ const DASHBOARD_DIST = path.resolve(
87
+ parsed["dist-location"] ?? await getDefaultDashboardDist()
88
+ );
89
+
90
+ const server = Bun.serve({
91
+ port: PORT,
92
+
93
+ async fetch(req) {
94
+ const url = new URL(req.url);
95
+
96
+ if (req.method === "OPTIONS") {
97
+ return new Response(null, { status: 204, headers: CORS_HEADERS });
98
+ }
99
+
100
+ if (url.pathname === "/") {
101
+ return Response.redirect("/dashboard", 301);
102
+ }
103
+
104
+ try {
105
+ if (url.pathname.startsWith("/api")) {
106
+ const newPath = url.pathname.replace(/^\/api/, "") || "/";
107
+ console.info(
108
+ `[Proxy] ${req.method} ${url.pathname} → ${API_TARGET}${newPath}`,
109
+ );
110
+ return await proxyRequest(req, `${API_TARGET}${newPath}${url.search}`);
111
+ }
112
+
113
+ return await serveStatic(url.pathname, DASHBOARD_DIST);
114
+ } catch (err: unknown) {
115
+ const message = err instanceof Error ? err.message : String(err);
116
+ console.error("[Proxy Error]", {
117
+ path: url.pathname,
118
+ method: req.method,
119
+ message,
120
+ });
121
+ return new Response(`Proxy Error: ${message}`, {
122
+ status: 500,
123
+ headers: CORS_HEADERS,
124
+ });
125
+ }
126
+ },
127
+ });
128
+
129
+ console.log(
130
+ `Server running on port ${server.port} [static: ${DASHBOARD_DIST}]`,
131
+ );
@@ -1,18 +1,18 @@
1
+ import React, { useEffect, useRef, useState } from "react";
2
+
1
3
  import Box from "@mui/material/Box";
2
4
  import Link from "@mui/material/Link";
3
5
  import { RouterLink } from "../../routes/components";
4
6
  import config from "../../config/config";
5
7
 
6
- import React, { useEffect, useRef, useState } from "react";
7
-
8
8
  const resolvedDimensions = {};
9
9
 
10
10
  const Logo = ({ disabledLink = false, sx, maxSize = 65, isLogin = false }) => {
11
11
  const { icon } = config().template.login;
12
- const key = `${icon}`;
12
+ const key = `${icon}_${maxSize}_${isLogin}`;
13
13
 
14
14
  const [dimensions, setDimensions] = useState(
15
- resolvedDimensions[key] || { width: maxSize, height: maxSize }
15
+ resolvedDimensions[key] || { width: maxSize, height: maxSize },
16
16
  );
17
17
 
18
18
  useEffect(() => {
@@ -65,13 +65,13 @@ export const MenuConfigSchema = Joi.object({
65
65
  path: Joi.string().required(),
66
66
  icon: Joi.string().required(),
67
67
  external: Joi.boolean().optional().default(false),
68
- })
68
+ }),
69
69
  )
70
70
  .optional(),
71
- })
71
+ }),
72
72
  )
73
73
  .required(),
74
- })
74
+ }),
75
75
  )
76
76
  .optional()
77
77
  .default([]),
@@ -81,7 +81,7 @@ export const MenuConfigSchema = Joi.object({
81
81
  title: Joi.string().required(),
82
82
  icon: Joi.string().required(),
83
83
  path: Joi.string().required(),
84
- })
84
+ }),
85
85
  )
86
86
  .optional()
87
87
  .default([]),
@@ -90,11 +90,12 @@ export const MenuConfigSchema = Joi.object({
90
90
  Joi.object({
91
91
  label: Joi.string().required(),
92
92
  linkTo: Joi.string().required(),
93
- })
93
+ }),
94
94
  )
95
95
  .optional()
96
96
  .default([]),
97
97
  actionButtons: Joi.array().items(Joi.any()).optional().default([]),
98
+ topBar: Joi.any().optional(),
98
99
  fullScreenLayout: Joi.string()
99
100
  .valid("left", "right")
100
101
  .optional()
@@ -139,7 +140,7 @@ export const TemplateConfigSchema = Joi.object({
139
140
  Joi.object({
140
141
  label: Joi.string().required(),
141
142
  panel: Joi.any().required(),
142
- })
143
+ }),
143
144
  )
144
145
  .optional()
145
146
  .default([]),
@@ -2,6 +2,7 @@ import { HEADER, NAV } from "../config-layout";
2
2
 
3
3
  import AccountPopover from "../common/account-popover";
4
4
  import AppBar from "@mui/material/AppBar";
5
+ import Box from "@mui/material/Box";
5
6
  import IconButton from "@mui/material/IconButton";
6
7
  import Iconify from "../../components/Iconify";
7
8
  import Logo from "../../components/logo";
@@ -23,6 +24,7 @@ export default function Header({ onOpenNav }) {
23
24
  const settings = useSettingsContext();
24
25
 
25
26
  const projectBar = config().template?.projectBar;
27
+ const TopBar = config().menu?.topBar;
26
28
 
27
29
  const isNavHorizontal = settings.themeLayout === "horizontal";
28
30
 
@@ -44,15 +46,21 @@ export default function Header({ onOpenNav }) {
44
46
  </IconButton>
45
47
  )}
46
48
  {projectBar && <ProjectBar />}
49
+ {TopBar && (
50
+ <Box sx={{ flexGrow: 1, mx: 2 }}>
51
+ <TopBar />
52
+ </Box>
53
+ )}
47
54
 
48
55
  <Stack
49
56
  direction="row"
50
57
  spacing={{ xs: 0.5, sm: 1 }}
51
58
  sx={{
52
- flexGrow: 1,
59
+ ...(!TopBar && { flexGrow: 1 }),
53
60
  alignItems: "center",
54
- justifyContent: "flex-end"
55
- }}>
61
+ justifyContent: "flex-end",
62
+ }}
63
+ >
56
64
  <NotificationsPopover />
57
65
 
58
66
  <SettingsButton />
@@ -67,7 +75,7 @@ export default function Header({ onOpenNav }) {
67
75
  data-cy="dashboard-layout-header"
68
76
  sx={{
69
77
  height: HEADER.H_MOBILE,
70
- zIndex: 0,
78
+ zIndex: theme.zIndex.appBar,
71
79
  ...bgBlur({
72
80
  color: theme.palette.background.default,
73
81
  }),
@@ -0,0 +1,117 @@
1
+ import { Box, Divider } from "@mui/material";
2
+
3
+ import React from "react";
4
+
5
+ const DevTool = ({
6
+ width = 62,
7
+ height = "auto",
8
+ top = "50%",
9
+ open = true,
10
+ content,
11
+ header,
12
+ footer,
13
+ sx,
14
+ }) => {
15
+ if (!open) return null;
16
+
17
+ return (
18
+ <Box
19
+ component="aside"
20
+ sx={{
21
+ position: "fixed",
22
+ top,
23
+ right: 0,
24
+ transform: "translateY(-50%)",
25
+ width,
26
+ height,
27
+ bgcolor: (theme) =>
28
+ theme.palette.mode === "dark"
29
+ ? "background.paper"
30
+ : "rgba(255, 255, 255, 0.85)",
31
+ backdropFilter: "blur(16px) saturate(180%)",
32
+ WebkitBackdropFilter: "blur(16px) saturate(180%)",
33
+ border: (theme) =>
34
+ `1px solid ${
35
+ theme.palette.mode === "dark"
36
+ ? "rgba(255,255,255,0.07)"
37
+ : "rgba(0,0,0,0.07)"
38
+ }`,
39
+ borderRadius: "4px",
40
+ boxShadow: (theme) =>
41
+ theme.palette.mode === "dark"
42
+ ? "0 8px 32px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04)"
43
+ : "0 8px 32px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04)",
44
+ zIndex: (theme) => theme.zIndex.modal + 3,
45
+ display: "flex",
46
+ flexDirection: "column",
47
+ alignItems: "center",
48
+ py: 1.5,
49
+ overflowY: "hidden",
50
+ overflowX: "hidden",
51
+ "&::-webkit-scrollbar": { display: "none" },
52
+ scrollbarWidth: "none",
53
+ ...(sx || {}),
54
+ }}
55
+ >
56
+ {header && (
57
+ <Box
58
+ sx={{
59
+ width: "100%",
60
+ display: "flex",
61
+ flexDirection: "column",
62
+ alignItems: "center",
63
+ flexShrink: 0,
64
+ }}
65
+ >
66
+ {header}
67
+ </Box>
68
+ )}
69
+
70
+ <Box
71
+ sx={{
72
+ flex: 1,
73
+ width: "100%",
74
+ display: "flex",
75
+ flexDirection: "column",
76
+ alignItems: "center",
77
+ overflowY: "auto",
78
+ overflowX: "hidden",
79
+ "&::-webkit-scrollbar": { display: "none" },
80
+ scrollbarWidth: "none",
81
+ }}
82
+ >
83
+ {content}
84
+ </Box>
85
+
86
+ {footer && (
87
+ <>
88
+ <Divider
89
+ flexItem
90
+ sx={{
91
+ borderColor: (theme) =>
92
+ theme.palette.mode === "dark"
93
+ ? "rgba(255,255,255,0.07)"
94
+ : "rgba(0,0,0,0.07)",
95
+ width: "100%",
96
+ flexShrink: 0,
97
+ }}
98
+ />
99
+ <Box
100
+ sx={{
101
+ width: "100%",
102
+ display: "flex",
103
+ flexDirection: "column",
104
+ alignItems: "center",
105
+ flexShrink: 0,
106
+ pt: 0.5,
107
+ }}
108
+ >
109
+ {footer}
110
+ </Box>
111
+ </>
112
+ )}
113
+ </Box>
114
+ );
115
+ };
116
+
117
+ export default DevTool;
@@ -0,0 +1 @@
1
+ export { default } from "./DevTool";
@@ -8,7 +8,7 @@ const DraggableNode = ({
8
8
  registerRef,
9
9
  onDrag,
10
10
  nodeId,
11
- selectionColor = "#64748b",
11
+ selectionColor = "#373739",
12
12
  initialPosition,
13
13
  onConnect,
14
14
  }) => {
@@ -164,20 +164,55 @@ const DraggableNode = ({
164
164
  transform: `translate(${offset.x}px, ${offset.y}px)`,
165
165
  cursor: "grab",
166
166
  "&:active": { cursor: "grabbing" },
167
- ...(selected && {
168
- "&::after": {
169
- content: '""',
170
- position: "absolute",
171
- inset: -6,
172
- border: `2px solid ${selectionColor}`,
173
- borderRadius: "12px",
174
- pointerEvents: "none",
175
- boxShadow: `0 0 8px ${selectionColor}66`,
176
- },
177
- }),
178
167
  }}
179
168
  >
180
169
  {children}
170
+ {selected &&
171
+ selectedIds.size > 1 &&
172
+ [
173
+ {
174
+ top: -10,
175
+ left: -6,
176
+ borderTop: 2,
177
+ borderLeft: 2,
178
+ color: selectionColor,
179
+ },
180
+ {
181
+ top: -8,
182
+ right: -12,
183
+ borderTop: 2,
184
+ borderRight: 2,
185
+ color: selectionColor,
186
+ },
187
+ {
188
+ bottom: -14,
189
+ left: -6,
190
+ borderBottom: 2,
191
+ borderLeft: 2,
192
+ color: selectionColor,
193
+ },
194
+ {
195
+ bottom: -16,
196
+ right: -12,
197
+ borderBottom: 2,
198
+ borderRight: 2,
199
+ color: selectionColor,
200
+ },
201
+ ].map((pos, i) => (
202
+ <Box
203
+ key={i}
204
+ sx={{
205
+ position: "absolute",
206
+ width: 10,
207
+ height: 10,
208
+ borderStyle: "solid",
209
+ borderColor: selectionColor,
210
+ borderWidth: 0,
211
+ pointerEvents: "none",
212
+ ...pos,
213
+ }}
214
+ />
215
+ ))}
181
216
  </Box>
182
217
  );
183
218
  };
@@ -1,6 +1,49 @@
1
1
  import { Box } from "@mui/material";
2
2
  import { hexToRgba } from "../utils/flowUtils";
3
3
 
4
+ const HANDLE_SIZE = 10;
5
+ const HANDLE_THICKNESS = 2;
6
+
7
+ const cornerStyles = (placement) => {
8
+ const base = {
9
+ position: "absolute",
10
+ width: HANDLE_SIZE,
11
+ height: HANDLE_SIZE,
12
+ borderColor: "inherit",
13
+ borderStyle: "solid",
14
+ borderWidth: 0,
15
+ };
16
+
17
+ const map = {
18
+ topLeft: {
19
+ top: 0,
20
+ left: 0,
21
+ borderTopWidth: HANDLE_THICKNESS,
22
+ borderLeftWidth: HANDLE_THICKNESS,
23
+ },
24
+ topRight: {
25
+ top: 0,
26
+ right: 0,
27
+ borderTopWidth: HANDLE_THICKNESS,
28
+ borderRightWidth: HANDLE_THICKNESS,
29
+ },
30
+ bottomLeft: {
31
+ bottom: 0,
32
+ left: 0,
33
+ borderBottomWidth: HANDLE_THICKNESS,
34
+ borderLeftWidth: HANDLE_THICKNESS,
35
+ },
36
+ bottomRight: {
37
+ bottom: 0,
38
+ right: 0,
39
+ borderBottomWidth: HANDLE_THICKNESS,
40
+ borderRightWidth: HANDLE_THICKNESS,
41
+ },
42
+ };
43
+
44
+ return { ...base, ...map[placement] };
45
+ };
46
+
4
47
  const SelectionOverlay = ({ box, selectionColor = "#64748b" }) => {
5
48
  if (!box) return null;
6
49
 
@@ -18,13 +61,17 @@ const SelectionOverlay = ({ box, selectionColor = "#64748b" }) => {
18
61
  top,
19
62
  width,
20
63
  height,
21
- border: `2px solid ${selectionColor}`,
22
- backgroundColor: hexToRgba(selectionColor, 0.1),
64
+ backgroundColor: hexToRgba(selectionColor, 0.08),
23
65
  pointerEvents: "none",
24
66
  zIndex: 9999,
25
67
  borderRadius: "4px",
68
+ color: selectionColor,
26
69
  }}
27
- />
70
+ >
71
+ {["topLeft", "topRight", "bottomLeft", "bottomRight"].map((placement) => (
72
+ <Box key={placement} sx={cornerStyles(placement)} />
73
+ ))}
74
+ </Box>
28
75
  );
29
76
  };
30
77
 
package/src/lib/index.js CHANGED
@@ -55,3 +55,4 @@ export {
55
55
  GlassCardContent,
56
56
  GlassCardActions,
57
57
  } from "./GlassCard/GlassCard";
58
+ export { default as DevTool } from "./DevTool";
@@ -1,9 +1,3 @@
1
- import config from "../../config/config";
2
- import { publish } from "@nucleoidai/react-event";
3
- import { storage } from "@nucleoidjs/webstorage";
4
- import { useNavigate } from "react-router-dom";
5
- import { useState } from "react";
6
-
7
1
  import {
8
2
  Box,
9
3
  Button,
@@ -20,9 +14,22 @@ import {
20
14
  Visibility,
21
15
  VisibilityOff,
22
16
  } from "@mui/icons-material";
23
- import { confirmSignup, getTokens, login, signup } from "./amplifyAuth";
17
+ import {
18
+ confirmForgotPassword,
19
+ confirmSignup,
20
+ forgotPassword,
21
+ getTokens,
22
+ login,
23
+ signup,
24
+ } from "./amplifyAuth";
24
25
  import { inputSx, primaryButtonSx } from "./styles";
25
26
 
27
+ import config from "../../config/config";
28
+ import { publish } from "@nucleoidai/react-event";
29
+ import { storage } from "@nucleoidjs/webstorage";
30
+ import { useNavigate } from "react-router-dom";
31
+ import { useState } from "react";
32
+
26
33
  export default function CognitoLogin() {
27
34
  const [mode, setMode] = useState("login");
28
35
 
@@ -96,6 +103,42 @@ export default function CognitoLogin() {
96
103
  }
97
104
  };
98
105
 
106
+ const handleForgotPassword = async () => {
107
+ try {
108
+ await forgotPassword(email);
109
+ publish("GLOBAL_MESSAGE_POSTED", {
110
+ status: true,
111
+ message: "Reset code sent! Check your email.",
112
+ severity: "success",
113
+ });
114
+ setMode("resetPassword");
115
+ } catch (e) {
116
+ publish("GLOBAL_MESSAGE_POSTED", {
117
+ status: true,
118
+ message: e.message || "Failed to send reset code",
119
+ severity: "error",
120
+ });
121
+ }
122
+ };
123
+
124
+ const handleResetPassword = async () => {
125
+ try {
126
+ await confirmForgotPassword(email, code, password);
127
+ publish("GLOBAL_MESSAGE_POSTED", {
128
+ status: true,
129
+ message: "Password reset successful! You can now sign in.",
130
+ severity: "success",
131
+ });
132
+ setMode("login");
133
+ } catch (e) {
134
+ publish("GLOBAL_MESSAGE_POSTED", {
135
+ status: true,
136
+ message: e.message || "Password reset failed",
137
+ severity: "error",
138
+ });
139
+ }
140
+ };
141
+
99
142
  const handleConfirm = async () => {
100
143
  try {
101
144
  await confirmSignup(email, code);
@@ -124,6 +167,14 @@ export default function CognitoLogin() {
124
167
  heading: "Verify your email",
125
168
  sub: "Enter the confirmation code sent to your inbox.",
126
169
  },
170
+ forgotPassword: {
171
+ heading: "Reset password",
172
+ sub: "Enter your email to receive a reset code.",
173
+ },
174
+ resetPassword: {
175
+ heading: "Set new password",
176
+ sub: "Enter the code from your email and your new password.",
177
+ },
127
178
  };
128
179
 
129
180
  const passwordAdornment = (
@@ -155,7 +206,7 @@ export default function CognitoLogin() {
155
206
  color: "primary.main",
156
207
  }}
157
208
  >
158
- {mode === "confirm" ? (
209
+ {mode === "confirm" || mode === "resetPassword" ? (
159
210
  <MarkEmailReadOutlined sx={{ fontSize: 22 }} />
160
211
  ) : (
161
212
  <LockOutlined sx={{ fontSize: 22 }} />
@@ -181,11 +232,11 @@ export default function CognitoLogin() {
181
232
  slotProps={{ input: { disableUnderline: true } }}
182
233
  />
183
234
 
184
- {mode !== "confirm" && (
235
+ {mode !== "confirm" && mode !== "forgotPassword" && (
185
236
  <TextField
186
237
  variant="filled"
187
238
  type={showPassword ? "text" : "password"}
188
- label="Password"
239
+ label={mode === "resetPassword" ? "New Password" : "Password"}
189
240
  value={password}
190
241
  onChange={(e) => setPassword(e.target.value)}
191
242
  fullWidth
@@ -230,7 +281,7 @@ export default function CognitoLogin() {
230
281
  />
231
282
  )}
232
283
 
233
- {mode === "confirm" && (
284
+ {(mode === "confirm" || mode === "resetPassword") && (
234
285
  <TextField
235
286
  variant="filled"
236
287
  label="Confirmation Code"
@@ -254,6 +305,17 @@ export default function CognitoLogin() {
254
305
  >
255
306
  Sign in &rarr;
256
307
  </Button>
308
+ <Button
309
+ onClick={() => setMode("forgotPassword")}
310
+ fullWidth
311
+ sx={{
312
+ textTransform: "none",
313
+ fontWeight: 600,
314
+ color: "text.secondary",
315
+ }}
316
+ >
317
+ Forgot password?
318
+ </Button>
257
319
  <Button
258
320
  onClick={() => setMode("signup")}
259
321
  fullWidth
@@ -304,6 +366,56 @@ export default function CognitoLogin() {
304
366
  Verify &rarr;
305
367
  </Button>
306
368
  )}
369
+ {mode === "forgotPassword" && (
370
+ <Stack spacing={1.5}>
371
+ <Button
372
+ variant="contained"
373
+ onClick={handleForgotPassword}
374
+ size="large"
375
+ fullWidth
376
+ disableElevation
377
+ sx={primaryButtonSx}
378
+ >
379
+ Send reset code &rarr;
380
+ </Button>
381
+ <Button
382
+ onClick={() => setMode("login")}
383
+ fullWidth
384
+ sx={{
385
+ textTransform: "none",
386
+ fontWeight: 600,
387
+ color: "text.secondary",
388
+ }}
389
+ >
390
+ &larr; Back to sign in
391
+ </Button>
392
+ </Stack>
393
+ )}
394
+ {mode === "resetPassword" && (
395
+ <Stack spacing={1.5}>
396
+ <Button
397
+ variant="contained"
398
+ onClick={handleResetPassword}
399
+ size="large"
400
+ fullWidth
401
+ disableElevation
402
+ sx={primaryButtonSx}
403
+ >
404
+ Reset password &rarr;
405
+ </Button>
406
+ <Button
407
+ onClick={() => setMode("login")}
408
+ fullWidth
409
+ sx={{
410
+ textTransform: "none",
411
+ fontWeight: 600,
412
+ color: "text.secondary",
413
+ }}
414
+ >
415
+ &larr; Back to sign in
416
+ </Button>
417
+ </Stack>
418
+ )}
307
419
  </Stack>
308
420
  );
309
421
  }
@@ -1,6 +1,8 @@
1
1
  import {
2
+ confirmResetPassword,
2
3
  confirmSignUp,
3
4
  fetchAuthSession,
5
+ resetPassword,
4
6
  signIn,
5
7
  signOut,
6
8
  signUp,
@@ -29,6 +31,18 @@ export async function confirmSignup(email, code) {
29
31
  });
30
32
  }
31
33
 
34
+ export async function forgotPassword(email) {
35
+ return resetPassword({ username: email });
36
+ }
37
+
38
+ export async function confirmForgotPassword(email, code, newPassword) {
39
+ return confirmResetPassword({
40
+ username: email,
41
+ confirmationCode: code,
42
+ newPassword,
43
+ });
44
+ }
45
+
32
46
  export async function logout() {
33
47
  await signOut();
34
48
  }
package/vite/vite.js CHANGED
@@ -15,6 +15,7 @@ if (error) {
15
15
 
16
16
  async function vite() {
17
17
  const base = value.base;
18
+ const api = value.api;
18
19
 
19
20
  return {
20
21
  plugins: [
@@ -31,6 +32,17 @@ async function vite() {
31
32
  },
32
33
  }),
33
34
  ],
35
+ server: {
36
+ port: 3000,
37
+ proxy: {
38
+ "/api": {
39
+ target: api?.split("/api")?.[0],
40
+ rewrite: (path) => path.replace(/^\/api/, ""),
41
+ changeOrigin: true,
42
+ timeout: 120_000,
43
+ },
44
+ },
45
+ },
34
46
  base,
35
47
  optimizeDeps: {
36
48
  esbuildOptions: {
@@ -55,7 +67,7 @@ async function vite() {
55
67
  find: /^src(.+)/,
56
68
  replacement: path.join(
57
69
  process.cwd(),
58
- "/node_modules/@nucleoidai/platform/minimal/src/$1"
70
+ "/node_modules/@nucleoidai/platform/minimal/src/$1",
59
71
  ),
60
72
  },
61
73
  ],