refine-mantine 1.0.0-dev.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/LICENSE +21 -0
- package/README.md +21 -0
- package/dist/index.d.ts +276 -0
- package/dist/index.js +1576 -0
- package/package.json +89 -0
- package/src/App.tsx +33 -0
- package/src/Router.tsx +97 -0
- package/src/components/ColorSchemeToggle.tsx +13 -0
- package/src/components/breadcrumb/Breadcrumb.tsx +80 -0
- package/src/components/buttons/CloneButton.story.tsx +22 -0
- package/src/components/buttons/CloneButton.tsx +86 -0
- package/src/components/buttons/CreateButton.story.tsx +22 -0
- package/src/components/buttons/CreateButton.tsx +81 -0
- package/src/components/buttons/DeleteButton.story.tsx +22 -0
- package/src/components/buttons/DeleteButton.tsx +133 -0
- package/src/components/buttons/EditButton.story.tsx +22 -0
- package/src/components/buttons/EditButton.tsx +87 -0
- package/src/components/buttons/ExportButton.story.tsx +28 -0
- package/src/components/buttons/ExportButton.tsx +48 -0
- package/src/components/buttons/ImportButton.story.tsx +44 -0
- package/src/components/buttons/ImportButton.tsx +61 -0
- package/src/components/buttons/ListButton.story.tsx +25 -0
- package/src/components/buttons/ListButton.tsx +91 -0
- package/src/components/buttons/RefreshButton.story.tsx +22 -0
- package/src/components/buttons/RefreshButton.tsx +59 -0
- package/src/components/buttons/SaveButton.story.tsx +22 -0
- package/src/components/buttons/SaveButton.tsx +51 -0
- package/src/components/buttons/ShowButton.story.tsx +22 -0
- package/src/components/buttons/ShowButton.tsx +83 -0
- package/src/components/crud/Create.story.tsx +83 -0
- package/src/components/crud/Create.tsx +146 -0
- package/src/components/crud/Edit.story.tsx +173 -0
- package/src/components/crud/Edit.tsx +236 -0
- package/src/components/crud/List.story.tsx +98 -0
- package/src/components/crud/List.tsx +109 -0
- package/src/components/crud/Show.tsx +220 -0
- package/src/components/layout/Layout.story.tsx +28 -0
- package/src/components/layout/Layout.tsx +257 -0
- package/src/components/notification/AutoSaveIndicator.story.tsx +41 -0
- package/src/components/notification/AutoSaveIndicator.tsx +58 -0
- package/src/components/notification/Message.story.tsx +30 -0
- package/src/components/notification/Message.tsx +79 -0
- package/src/components/table/ColumnFilter.tsx +107 -0
- package/src/components/table/ColumnSorter.tsx +26 -0
- package/src/components/table/Table.story.tsx +146 -0
- package/src/components/table/Table.tsx +64 -0
- package/src/favicon.svg +1 -0
- package/src/hooks/useForm.ts +271 -0
- package/src/hooks/useOtp.ts +45 -0
- package/src/index.ts +36 -0
- package/src/main.tsx +4 -0
- package/src/pages/auth/DefaultTitle.tsx +13 -0
- package/src/pages/auth/ForgotPasswordPage.story.tsx +14 -0
- package/src/pages/auth/ForgotPasswordPage.tsx +128 -0
- package/src/pages/auth/LoginPage.story.tsx +98 -0
- package/src/pages/auth/LoginPage.tsx +256 -0
- package/src/pages/auth/RegisterPage.story.tsx +18 -0
- package/src/pages/auth/RegisterPage.tsx +151 -0
- package/src/pages/auth/UpdatePasswordPage.story.tsx +27 -0
- package/src/pages/auth/UpdatePasswordPage.tsx +124 -0
- package/src/pages/category/CategoryCreate.tsx +1 -0
- package/src/pages/category/CategoryEdit.tsx +1 -0
- package/src/pages/category/CategoryList.tsx +1 -0
- package/src/pages/category/CategoryShow.tsx +1 -0
- package/src/pages/product/ProductCreate.tsx +1 -0
- package/src/pages/product/ProductEdit.tsx +1 -0
- package/src/pages/product/ProductList.tsx +1 -0
- package/src/pages/product/ProductShow.tsx +1 -0
- package/src/providers/authProvider.ts +105 -0
- package/src/providers/i18nProvider.ts +7 -0
- package/src/providers/notificationProvider.tsx +122 -0
- package/src/resources.tsx +63 -0
- package/src/theme.ts +5 -0
- package/src/utils/paths.ts +52 -0
- package/src/utils/wait.ts +4 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Anchor,
|
|
3
|
+
Button,
|
|
4
|
+
type ButtonProps,
|
|
5
|
+
Card,
|
|
6
|
+
type CardProps,
|
|
7
|
+
Center,
|
|
8
|
+
Collapse,
|
|
9
|
+
Divider,
|
|
10
|
+
Input,
|
|
11
|
+
LoadingOverlay,
|
|
12
|
+
type LoadingOverlayProps,
|
|
13
|
+
PinInput,
|
|
14
|
+
type PinInputProps,
|
|
15
|
+
ScrollArea,
|
|
16
|
+
type ScrollAreaProps,
|
|
17
|
+
Stack,
|
|
18
|
+
type StackProps,
|
|
19
|
+
Text,
|
|
20
|
+
TextInput,
|
|
21
|
+
type TextInputProps,
|
|
22
|
+
Title
|
|
23
|
+
} from "@mantine/core";
|
|
24
|
+
import { type FormValidateInput, isEmail, isNotEmpty, useForm } from "@mantine/form";
|
|
25
|
+
import { Link, type OAuthProvider, type LoginFormTypes as RefineLoginFormTypes, useLogin, useTranslate } from "@refinedev/core";
|
|
26
|
+
import { IconAt, IconLockPassword } from "@tabler/icons-react";
|
|
27
|
+
import { type ReactNode, useCallback } from "react";
|
|
28
|
+
import type { OtpHandler } from "@/hooks/useOtp";
|
|
29
|
+
import { DefaultTitle } from "./DefaultTitle";
|
|
30
|
+
|
|
31
|
+
export type TranslateFn = ReturnType<typeof useTranslate>;
|
|
32
|
+
|
|
33
|
+
export interface LoginArgs {
|
|
34
|
+
providerName?: string; // providerName prop is used by several AuthPage implementations
|
|
35
|
+
email?: string;
|
|
36
|
+
password?: string;
|
|
37
|
+
otpHandler?: OtpHandler;
|
|
38
|
+
translate?: TranslateFn;
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface OAuthProviderMantine extends OAuthProvider {
|
|
43
|
+
buttonProps?: ButtonProps;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type LoginPageProps = {
|
|
47
|
+
providers?: OAuthProviderMantine[];
|
|
48
|
+
mutationVariables?: RefineLoginFormTypes;
|
|
49
|
+
registerLink?: string;
|
|
50
|
+
forgotPasswordLink?: string;
|
|
51
|
+
validate?: FormValidateInput<LoginForm>;
|
|
52
|
+
// customization
|
|
53
|
+
wrapperProps?: StackProps;
|
|
54
|
+
scrollAreaProps?: ScrollAreaProps;
|
|
55
|
+
emailFieldProps?: TextInputProps;
|
|
56
|
+
passwordFieldProps?: TextInputProps;
|
|
57
|
+
cardProps?: CardProps;
|
|
58
|
+
loadingOverlayProps?: LoadingOverlayProps;
|
|
59
|
+
otpInputProps?: PinInputProps;
|
|
60
|
+
submitButtonProps?: ButtonProps;
|
|
61
|
+
cancelButtonProps?: ButtonProps;
|
|
62
|
+
// components
|
|
63
|
+
icon?: ReactNode;
|
|
64
|
+
title?: ReactNode;
|
|
65
|
+
// otp promise handler
|
|
66
|
+
otpHandler?: OtpHandler;
|
|
67
|
+
} & (
|
|
68
|
+
{
|
|
69
|
+
method: "oauth";
|
|
70
|
+
providers: OAuthProvider[];
|
|
71
|
+
} | {
|
|
72
|
+
method: "otp" | "mfa" | "password";
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
interface LoginForm {
|
|
77
|
+
email: string;
|
|
78
|
+
password: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const LoginPage: React.FC<LoginPageProps> = (p) => {
|
|
82
|
+
const translate = useTranslate();
|
|
83
|
+
const login = useLogin<LoginArgs>();
|
|
84
|
+
|
|
85
|
+
const { getInputProps, onSubmit, key } = useForm<LoginForm>({
|
|
86
|
+
initialValues: {
|
|
87
|
+
email: "",
|
|
88
|
+
password: "",
|
|
89
|
+
},
|
|
90
|
+
validate: {
|
|
91
|
+
email: isEmail(
|
|
92
|
+
translate("pages.login.validate.invalidEmail", "Email is not valid")
|
|
93
|
+
),
|
|
94
|
+
password: (p.method === "password" || p.method === "mfa")
|
|
95
|
+
? isNotEmpty(translate("pages.login.validate.passwordRequired", "Password is required"))
|
|
96
|
+
: undefined,
|
|
97
|
+
...p.validate,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const handleProviderLogin = useCallback((provider: OAuthProvider) => {
|
|
102
|
+
login.mutate({
|
|
103
|
+
providerName: provider.name,
|
|
104
|
+
translate,
|
|
105
|
+
...p.mutationVariables
|
|
106
|
+
});
|
|
107
|
+
}, [login, translate, p.mutationVariables]);
|
|
108
|
+
|
|
109
|
+
const handleLogin = onSubmit(({ email, password }) => {
|
|
110
|
+
login.mutate({
|
|
111
|
+
email,
|
|
112
|
+
password,
|
|
113
|
+
otpHandler: p.otpHandler,
|
|
114
|
+
translate,
|
|
115
|
+
...p.mutationVariables,
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<Stack h="100vh" align="center" justify="center" {...p.wrapperProps}>
|
|
121
|
+
<ScrollArea type="never" {...p.scrollAreaProps}>
|
|
122
|
+
{p.icon ?? <DefaultTitle />}
|
|
123
|
+
<Card shadow="sm" padding="lg" radius="md" withBorder {...p.cardProps}>
|
|
124
|
+
<LoadingOverlay
|
|
125
|
+
visible={login.isPending && !p.otpHandler?.isPending}
|
|
126
|
+
{...p.loadingOverlayProps}
|
|
127
|
+
/>
|
|
128
|
+
{p.title ?? (
|
|
129
|
+
<Title order={5} mb="lg" ta="center">
|
|
130
|
+
{translate("pages.login.title", "Sign in to your account")}
|
|
131
|
+
</Title>
|
|
132
|
+
)}
|
|
133
|
+
<Collapse in={!p.otpHandler?.isPending}>
|
|
134
|
+
{p.providers?.length && (
|
|
135
|
+
<Providers
|
|
136
|
+
providers={p.providers}
|
|
137
|
+
withDivider={p.method !== "oauth"}
|
|
138
|
+
onClickProvider={handleProviderLogin}
|
|
139
|
+
/>
|
|
140
|
+
)}
|
|
141
|
+
</Collapse>
|
|
142
|
+
{p.method !== "oauth" && (
|
|
143
|
+
<form onSubmit={handleLogin}>
|
|
144
|
+
<Collapse in={!p.otpHandler?.isPending}>
|
|
145
|
+
<TextInput
|
|
146
|
+
mb="xs"
|
|
147
|
+
type="email"
|
|
148
|
+
label={translate("pages.login.fields.email", "Email")}
|
|
149
|
+
leftSection={<IconAt size={18} />}
|
|
150
|
+
placeholder={translate("pages.login.fields.emailPlaceholder", "name@example.com")}
|
|
151
|
+
key={key("email")}
|
|
152
|
+
{...getInputProps("email")}
|
|
153
|
+
{...p.emailFieldProps}
|
|
154
|
+
/>
|
|
155
|
+
{(p.method === "mfa" || p.method === "password") && (
|
|
156
|
+
<TextInput
|
|
157
|
+
label={translate("pages.login.fields.password", "Password")}
|
|
158
|
+
leftSection={<IconLockPassword size={18} />}
|
|
159
|
+
placeholder={translate("pages.login.fields.passwordPlaceholder", "●●●●●●●●")}
|
|
160
|
+
type="password"
|
|
161
|
+
key={key("password")}
|
|
162
|
+
{...getInputProps("password")}
|
|
163
|
+
{...p.passwordFieldProps}
|
|
164
|
+
/>
|
|
165
|
+
)}
|
|
166
|
+
{p.forgotPasswordLink &&
|
|
167
|
+
<Text size="xs" mt="xs" ta="end">
|
|
168
|
+
{/** biome-ignore lint/suspicious/noExplicitAny: refines type is messed up */}
|
|
169
|
+
<Anchor component={Link as any} to={p.forgotPasswordLink}>
|
|
170
|
+
{translate( "pages.login.buttons.forgotPassword", "Forgot password?")}
|
|
171
|
+
</Anchor>
|
|
172
|
+
</Text>
|
|
173
|
+
}
|
|
174
|
+
<Button
|
|
175
|
+
fullWidth
|
|
176
|
+
mt="lg"
|
|
177
|
+
type="submit"
|
|
178
|
+
{...p.submitButtonProps}
|
|
179
|
+
>
|
|
180
|
+
{translate("pages.login.signin", "Login")}
|
|
181
|
+
</Button>
|
|
182
|
+
{p.registerLink &&
|
|
183
|
+
<Text mt="lg" size="xs" ta="center">
|
|
184
|
+
{translate("pages.login.buttons.noAccount", "Don’t have an account?")}
|
|
185
|
+
{" "}
|
|
186
|
+
{/** biome-ignore lint/suspicious/noExplicitAny: that's fine */}
|
|
187
|
+
<Anchor component={Link as any} to={p.registerLink}>
|
|
188
|
+
{translate("pages.login.register", "Sign up")}
|
|
189
|
+
</Anchor>
|
|
190
|
+
</Text>
|
|
191
|
+
}
|
|
192
|
+
</Collapse>
|
|
193
|
+
<Collapse in={!!p.otpHandler?.isPending}>
|
|
194
|
+
<Input.Wrapper
|
|
195
|
+
label={translate("pages.login.fields.otp", "Enter or paste your auth token")}
|
|
196
|
+
>
|
|
197
|
+
<Center>
|
|
198
|
+
<PinInput
|
|
199
|
+
type="number"
|
|
200
|
+
oneTimeCode
|
|
201
|
+
disabled={login.isPending && !p.otpHandler?.isPending}
|
|
202
|
+
onComplete={p.otpHandler?.resolve}
|
|
203
|
+
{...p.otpInputProps}
|
|
204
|
+
/>
|
|
205
|
+
</Center>
|
|
206
|
+
</Input.Wrapper>
|
|
207
|
+
<Button
|
|
208
|
+
fullWidth
|
|
209
|
+
mt="lg"
|
|
210
|
+
variant="outline"
|
|
211
|
+
onClick={p.otpHandler?.reject}
|
|
212
|
+
{...p.cancelButtonProps}
|
|
213
|
+
>
|
|
214
|
+
{translate("pages.login.buttons.cancel", "Cancel")}
|
|
215
|
+
</Button>
|
|
216
|
+
</Collapse>
|
|
217
|
+
</form>
|
|
218
|
+
)}
|
|
219
|
+
</Card>
|
|
220
|
+
</ScrollArea>
|
|
221
|
+
</Stack>
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const Providers = (p: {
|
|
226
|
+
providers: OAuthProviderMantine[];
|
|
227
|
+
withDivider: boolean;
|
|
228
|
+
onClickProvider: (provider: OAuthProvider) => void;
|
|
229
|
+
}) => {
|
|
230
|
+
const translate = useTranslate();
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<>
|
|
234
|
+
<Stack gap={8}>
|
|
235
|
+
{p.providers.map((provider) =>
|
|
236
|
+
<Button
|
|
237
|
+
key={provider.name}
|
|
238
|
+
fullWidth
|
|
239
|
+
leftSection={provider.icon}
|
|
240
|
+
onClick={() => p.onClickProvider(provider)}
|
|
241
|
+
{...provider.buttonProps}
|
|
242
|
+
>
|
|
243
|
+
{provider.label ?? provider.name}
|
|
244
|
+
</Button>
|
|
245
|
+
)}
|
|
246
|
+
</Stack>
|
|
247
|
+
{p.withDivider && (
|
|
248
|
+
<Divider
|
|
249
|
+
my="md"
|
|
250
|
+
labelPosition="center"
|
|
251
|
+
label={translate("pages.login.divider", "or")}
|
|
252
|
+
/>
|
|
253
|
+
)}
|
|
254
|
+
</>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RegisterPage } from './RegisterPage';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Auth/RegisterPage',
|
|
5
|
+
component: RegisterPage,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const WithPassword = () =>
|
|
9
|
+
<RegisterPage />;
|
|
10
|
+
|
|
11
|
+
export const WithPasswordConfirmation = () =>
|
|
12
|
+
<RegisterPage withConfirmation />;
|
|
13
|
+
|
|
14
|
+
export const WithLink = () =>
|
|
15
|
+
<RegisterPage
|
|
16
|
+
withConfirmation
|
|
17
|
+
loginLink="/login"
|
|
18
|
+
/>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Anchor,
|
|
3
|
+
Button,
|
|
4
|
+
type ButtonProps,
|
|
5
|
+
Card,
|
|
6
|
+
type CardProps,
|
|
7
|
+
LoadingOverlay,
|
|
8
|
+
type LoadingOverlayProps,
|
|
9
|
+
type PinInputProps,
|
|
10
|
+
ScrollArea,
|
|
11
|
+
type ScrollAreaProps,
|
|
12
|
+
Stack,
|
|
13
|
+
type StackProps,
|
|
14
|
+
Text,
|
|
15
|
+
TextInput,
|
|
16
|
+
type TextInputProps,
|
|
17
|
+
Title
|
|
18
|
+
} from "@mantine/core";
|
|
19
|
+
import { type FormValidateInput, isEmail, isNotEmpty, useForm } from "@mantine/form";
|
|
20
|
+
import { Link, type RegisterFormTypes, useRegister, useTranslate } from "@refinedev/core";
|
|
21
|
+
import { IconAt, IconLockPassword } from "@tabler/icons-react";
|
|
22
|
+
import type { ReactNode } from "react";
|
|
23
|
+
import { DefaultTitle } from "./DefaultTitle";
|
|
24
|
+
|
|
25
|
+
export interface RegisterPageProps {
|
|
26
|
+
withConfirmation?: boolean;
|
|
27
|
+
mutationVariables?: RegisterFormTypes;
|
|
28
|
+
loginLink?: string;
|
|
29
|
+
validate?: FormValidateInput<RegisterForm>;
|
|
30
|
+
// props
|
|
31
|
+
wrapperProps?: StackProps;
|
|
32
|
+
scrollAreaProps?: ScrollAreaProps;
|
|
33
|
+
emailFieldProps?: TextInputProps;
|
|
34
|
+
passwordFieldProps?: TextInputProps;
|
|
35
|
+
passwordConfirmationFieldProps?: TextInputProps;
|
|
36
|
+
cardProps?: CardProps;
|
|
37
|
+
loadingOverlayProps?: LoadingOverlayProps;
|
|
38
|
+
otpInputProps?: PinInputProps;
|
|
39
|
+
submitButtonProps?: ButtonProps;
|
|
40
|
+
cancelButtonProps?: ButtonProps;
|
|
41
|
+
// components
|
|
42
|
+
icon?: ReactNode;
|
|
43
|
+
title?: ReactNode;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface RegisterForm {
|
|
47
|
+
email: string;
|
|
48
|
+
password: string;
|
|
49
|
+
passwordConfirmation: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const RegisterPage: React.FC<RegisterPageProps> = (p) => {
|
|
53
|
+
const translate = useTranslate();
|
|
54
|
+
|
|
55
|
+
const { getInputProps, onSubmit, key } = useForm<RegisterForm>({
|
|
56
|
+
initialValues: {
|
|
57
|
+
email: "",
|
|
58
|
+
password: "",
|
|
59
|
+
passwordConfirmation: "",
|
|
60
|
+
},
|
|
61
|
+
validate: {
|
|
62
|
+
email: isEmail(
|
|
63
|
+
translate("pages.register.validate.invalidEmail", "Email is not valid")
|
|
64
|
+
),
|
|
65
|
+
password: isNotEmpty(translate("pages.register.validate.passwordRequired", "Password is required")),
|
|
66
|
+
passwordConfirmation: (value, form) =>
|
|
67
|
+
p.withConfirmation && form.password !== value
|
|
68
|
+
? translate("pages.register.validate.passwordMismatch", "Passwords do not match")
|
|
69
|
+
: undefined,
|
|
70
|
+
...p.validate,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const register = useRegister<RegisterFormTypes>();
|
|
75
|
+
|
|
76
|
+
const handleSubmit = onSubmit(({ email, password }) => {
|
|
77
|
+
register.mutate({ email, password, ...p.mutationVariables });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Stack h="100vh" align="center" justify="center" {...p.wrapperProps}>
|
|
82
|
+
<ScrollArea type="never" {...p.scrollAreaProps}>
|
|
83
|
+
{p.icon ?? <DefaultTitle />}
|
|
84
|
+
<Card shadow="sm" padding="lg" radius="md" withBorder {...p.cardProps}>
|
|
85
|
+
<LoadingOverlay
|
|
86
|
+
visible={register.isPending}
|
|
87
|
+
{...p.loadingOverlayProps}
|
|
88
|
+
/>
|
|
89
|
+
{p.title ?? (
|
|
90
|
+
<Title order={5} mb="lg" ta="center">
|
|
91
|
+
{translate("pages.register.title", "Sign up for your account")}
|
|
92
|
+
</Title>
|
|
93
|
+
)}
|
|
94
|
+
<form onSubmit={handleSubmit}>
|
|
95
|
+
<TextInput
|
|
96
|
+
mb="xs"
|
|
97
|
+
type="email"
|
|
98
|
+
label={translate("pages.register.email", "Email")}
|
|
99
|
+
leftSection={<IconAt size={18} />}
|
|
100
|
+
placeholder={translate("pages.register.emailPlaceholder", "name@example.com")}
|
|
101
|
+
key={key("email")}
|
|
102
|
+
{...getInputProps("email")}
|
|
103
|
+
{...p.emailFieldProps}
|
|
104
|
+
/>
|
|
105
|
+
<TextInput
|
|
106
|
+
mb="sm"
|
|
107
|
+
label={translate("pages.register.password", "Password")}
|
|
108
|
+
leftSection={<IconLockPassword size={18} />}
|
|
109
|
+
placeholder={translate("pages.register.passwordPlaceholder", "●●●●●●●●")}
|
|
110
|
+
type="password"
|
|
111
|
+
key={key("password")}
|
|
112
|
+
{...getInputProps("password")}
|
|
113
|
+
{...p.passwordFieldProps}
|
|
114
|
+
/>
|
|
115
|
+
{p.withConfirmation && (
|
|
116
|
+
<TextInput
|
|
117
|
+
mb="sm"
|
|
118
|
+
label={translate("pages.register.passwordConfirmation", "Confirm Password")}
|
|
119
|
+
leftSection={<IconLockPassword size={18} />}
|
|
120
|
+
placeholder={translate("pages.register.passwordConfirmationPlaceholder", "●●●●●●●●")}
|
|
121
|
+
type="password"
|
|
122
|
+
key={key("passwordConfirmation")}
|
|
123
|
+
{...getInputProps("passwordConfirmation")}
|
|
124
|
+
{...p.passwordConfirmationFieldProps}
|
|
125
|
+
/>
|
|
126
|
+
)}
|
|
127
|
+
<Button
|
|
128
|
+
mt="lg"
|
|
129
|
+
fullWidth
|
|
130
|
+
type="submit"
|
|
131
|
+
disabled={register.isPending}
|
|
132
|
+
{...p.submitButtonProps}
|
|
133
|
+
>
|
|
134
|
+
{translate("pages.register.submit", "Sign up")}
|
|
135
|
+
</Button>
|
|
136
|
+
</form>
|
|
137
|
+
{p.loginLink &&
|
|
138
|
+
<Text mt="lg" size="xs" ta="center">
|
|
139
|
+
{translate("pages.register.haveAccount", "Have an account?")}
|
|
140
|
+
{" "}
|
|
141
|
+
{/** biome-ignore lint/suspicious/noExplicitAny: refines types are messed up */}
|
|
142
|
+
<Anchor component={Link as any} to={p.loginLink}>
|
|
143
|
+
{translate("pages.register.login", "Sign in")}
|
|
144
|
+
</Anchor>
|
|
145
|
+
</Text>
|
|
146
|
+
}
|
|
147
|
+
</Card>
|
|
148
|
+
</ScrollArea>
|
|
149
|
+
</Stack>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { hasLength } from '@mantine/form';
|
|
2
|
+
import { UpdatePasswordPage } from './UpdatePasswordPage';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Auth/UpdatePasswordPage',
|
|
6
|
+
component: UpdatePasswordPage,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const WithPasswordConfirmation = () =>
|
|
10
|
+
<UpdatePasswordPage />;
|
|
11
|
+
|
|
12
|
+
export const WithAsterisk = () =>
|
|
13
|
+
<UpdatePasswordPage
|
|
14
|
+
passwordFieldProps={{
|
|
15
|
+
withAsterisk: true,
|
|
16
|
+
}}
|
|
17
|
+
confirmPasswordFieldProps={{
|
|
18
|
+
withAsterisk: true,
|
|
19
|
+
}}
|
|
20
|
+
/>;
|
|
21
|
+
|
|
22
|
+
export const WithCustomValidation = () =>
|
|
23
|
+
<UpdatePasswordPage
|
|
24
|
+
validate={{
|
|
25
|
+
password: hasLength({ min: 2, max: 10 }, 'Password must be 2-10 characters long'),
|
|
26
|
+
}}
|
|
27
|
+
/>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
type ButtonProps,
|
|
4
|
+
Card,
|
|
5
|
+
type CardProps,
|
|
6
|
+
LoadingOverlay,
|
|
7
|
+
type LoadingOverlayProps,
|
|
8
|
+
Stack,
|
|
9
|
+
type StackProps,
|
|
10
|
+
TextInput,
|
|
11
|
+
type TextInputProps,
|
|
12
|
+
Title,
|
|
13
|
+
} from "@mantine/core";
|
|
14
|
+
import { type FormValidateInput, isNotEmpty } from "@mantine/form";
|
|
15
|
+
import {
|
|
16
|
+
type UpdatePasswordFormTypes,
|
|
17
|
+
useParsed,
|
|
18
|
+
useTranslate,
|
|
19
|
+
useUpdatePassword,
|
|
20
|
+
} from "@refinedev/core";
|
|
21
|
+
import { IconLockPassword } from "@tabler/icons-react";
|
|
22
|
+
import type { ReactNode } from "react";
|
|
23
|
+
import { useForm } from "@/hooks/useForm";
|
|
24
|
+
import { DefaultTitle } from "./DefaultTitle";
|
|
25
|
+
|
|
26
|
+
export interface UpdatePasswordPageProps {
|
|
27
|
+
mutationVariables?: UpdatePasswordFormTypes;
|
|
28
|
+
validate?: FormValidateInput<UpdatePasswordFormTypes>;
|
|
29
|
+
withToken?: boolean;
|
|
30
|
+
// props
|
|
31
|
+
wrapperProps?: StackProps;
|
|
32
|
+
passwordFieldProps?: TextInputProps;
|
|
33
|
+
confirmPasswordFieldProps?: TextInputProps;
|
|
34
|
+
cardProps?: CardProps;
|
|
35
|
+
loadingOverlayProps?: LoadingOverlayProps;
|
|
36
|
+
submitButtonProps?: ButtonProps;
|
|
37
|
+
// components
|
|
38
|
+
icon?: ReactNode;
|
|
39
|
+
title?: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const UpdatePasswordPage: React.FC<UpdatePasswordPageProps> = (p) => {
|
|
43
|
+
const translate = useTranslate();
|
|
44
|
+
const { mutate: updatePassword, isPending } = useUpdatePassword();
|
|
45
|
+
|
|
46
|
+
const { params } = useParsed<{ token: string }>();
|
|
47
|
+
const token = params?.token;
|
|
48
|
+
|
|
49
|
+
const { getInputProps, onSubmit, key } = useForm({
|
|
50
|
+
initialValues: {
|
|
51
|
+
password: "",
|
|
52
|
+
confirmPassword: "",
|
|
53
|
+
},
|
|
54
|
+
validate: {
|
|
55
|
+
password: isNotEmpty(translate("pages.updatePassword.validate.passwordRequired", "Password is required")),
|
|
56
|
+
confirmPassword: (value, form) =>
|
|
57
|
+
form.password !== value
|
|
58
|
+
? translate("pages.updatePassword.validate.passwordMismatch", "Passwords do not match")
|
|
59
|
+
: undefined,
|
|
60
|
+
...p.validate,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const handleSubmit = onSubmit(({password, confirmPassword}) => {
|
|
65
|
+
if (!p.withToken || token) {
|
|
66
|
+
updatePassword({
|
|
67
|
+
password,
|
|
68
|
+
confirmPassword,
|
|
69
|
+
token,
|
|
70
|
+
translate,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Stack h="100vh" align="center" justify="center" {...p.wrapperProps}>
|
|
77
|
+
{p.icon ?? <DefaultTitle />}
|
|
78
|
+
<Card shadow="sm" padding="lg" radius="md" withBorder {...p.cardProps}>
|
|
79
|
+
<LoadingOverlay
|
|
80
|
+
visible={isPending}
|
|
81
|
+
{...p.loadingOverlayProps}
|
|
82
|
+
/>
|
|
83
|
+
{p.title ?? (
|
|
84
|
+
<Title order={5} mb="lg" ta="center">
|
|
85
|
+
{translate("pages.updatePassword.title", "Update Password")}
|
|
86
|
+
</Title>
|
|
87
|
+
)}
|
|
88
|
+
<form onSubmit={handleSubmit}>
|
|
89
|
+
<TextInput
|
|
90
|
+
mb="sm"
|
|
91
|
+
label={translate("pages.updatePassword.fields.password", "New Password")}
|
|
92
|
+
leftSection={<IconLockPassword size={18} />}
|
|
93
|
+
placeholder="●●●●●●●●"
|
|
94
|
+
type="password"
|
|
95
|
+
key={key("password")}
|
|
96
|
+
{...getInputProps("password")}
|
|
97
|
+
{...p.passwordFieldProps}
|
|
98
|
+
/>
|
|
99
|
+
<TextInput
|
|
100
|
+
mb="lg"
|
|
101
|
+
label={translate(
|
|
102
|
+
"pages.updatePassword.fields.confirmPassword",
|
|
103
|
+
"Confirm New Password",
|
|
104
|
+
)}
|
|
105
|
+
leftSection={<IconLockPassword size={18} />}
|
|
106
|
+
placeholder="●●●●●●●●"
|
|
107
|
+
type="password"
|
|
108
|
+
key={key("confirmPassword")}
|
|
109
|
+
{...getInputProps("confirmPassword")}
|
|
110
|
+
{...p.confirmPasswordFieldProps}
|
|
111
|
+
/>
|
|
112
|
+
<Button
|
|
113
|
+
type="submit"
|
|
114
|
+
fullWidth
|
|
115
|
+
disabled={isPending}
|
|
116
|
+
{...p.submitButtonProps}
|
|
117
|
+
>
|
|
118
|
+
{translate("pages.updatePassword.buttons.submit", "Update")}
|
|
119
|
+
</Button>
|
|
120
|
+
</form>
|
|
121
|
+
</Card>
|
|
122
|
+
</Stack>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CategoryCreate = () => <></>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CategoryEdit = () => <></>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CategoryList = () => <></>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CategoryShow = () => <></>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ProductCreate = () => <></>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ProductEdit = () => <></>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ProductList = () => <></>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ProductShow = () => <></>;
|