Skip to content

Commit 66a223a

Browse files
add change-password form to nextjs app template
1 parent 87a98c8 commit 66a223a

File tree

8 files changed

+115
-4
lines changed

8 files changed

+115
-4
lines changed

packages/devextreme-cli/src/templates/nextjs/application/src/api/auth.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ export async function createAccount(email<%=#isTypeScript%>: string<%=/isTypeScr
5151
}
5252
}
5353

54-
export async function changePassword(email<%=#isTypeScript%>: string<%=/isTypeScript%>, recoveryCode<%=#isTypeScript%>?: string<%=/isTypeScript%>) {
54+
export async function changePassword(email<%=#isTypeScript%>: string<%=/isTypeScript%>) {
5555
try {
5656
// Send request
57-
console.log(email, recoveryCode);
57+
console.log(email);
5858

5959
return {
6060
isOk: true

packages/devextreme-cli/src/templates/nextjs/application/src/app/auth/[type]/page.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
import { use } from 'react';
33
import { notFound } from 'next/navigation';
44
import { SingleCard } from '@/layouts';
5-
import { LoginForm, CreateAccountForm, ResetPasswordForm } from '@/components'
5+
import {
6+
LoginForm,
7+
CreateAccountForm,
8+
ResetPasswordForm,
9+
ChangePasswordForm,
10+
} from '@/components';
611

712
const formText<%=#isTypeScript%>: Record<string, Record<string, string>><%=/isTypeScript%> = {
813
'login': {
@@ -14,6 +19,9 @@ const formText<%=#isTypeScript%>: Record<string, Record<string, string>><%=/isTy
1419
'reset-password': {
1520
title: 'Reset Password',
1621
description: 'Please enter the email address that you used to register, and we will send you a link to reset your password via Email.'
22+
},
23+
'change-password': {
24+
title: 'Change Password',
1725
}
1826
}
1927

@@ -22,6 +30,7 @@ function AuthForm({name}<%=#isTypeScript%>: {name: string}<%=/isTypeScript%>) {
2230
case 'login': return <LoginForm />;
2331
case 'create-account': return <CreateAccountForm />;
2432
case 'reset-password': return <ResetPasswordForm />;
33+
case 'change-password': return <ChangePasswordForm />;
2534
}
2635
}
2736

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.reset-password-form {
2+
.submit-button {
3+
margin-top: 18px;
4+
}
5+
6+
.login-link {
7+
color: var(--base-accent);
8+
font-size: 12px;
9+
text-align: center;
10+
margin-top: 6px;
11+
}
12+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
'use client'
2+
import <%=#isTypeScript%>React, <%=/isTypeScript%>{ useState, useRef, useCallback } from 'react';
3+
import { useRouter, useParams } from 'next/navigation';
4+
import Form, {
5+
Item,
6+
Label,
7+
ButtonItem,
8+
ButtonOptions,
9+
RequiredRule,
10+
CustomRule,
11+
} from 'devextreme-react/form';
12+
import LoadIndicator from 'devextreme-react/load-indicator';
13+
import notify from 'devextreme/ui/notify';
14+
<%=#isTypeScript%>import { ValidationCallbackData } from 'devextreme-react/common';<%=/isTypeScript%>
15+
import { changePassword } from '../../api/auth';
16+
17+
export default function ChangePasswordForm() {
18+
const router = useRouter();
19+
const [loading, setLoading] = useState(false);
20+
const formData = useRef({ password: '' });
21+
22+
const onSubmit = useCallback(async (e<%=#isTypeScript%>: React.FormEvent<HTMLFormElement><%=/isTypeScript%>) => {
23+
e.preventDefault();
24+
const { password } = formData.current;
25+
setLoading(true);
26+
27+
const result = await changePassword(password);
28+
setLoading(false);
29+
30+
if (result.isOk) {
31+
router('/login');
32+
} else {
33+
notify(result.message, 'error', 2000);
34+
}
35+
}, [router]);
36+
37+
const confirmPassword = useCallback(
38+
({ value }<%=#isTypeScript%>: ValidationCallbackData<%=/isTypeScript%>) => value === formData.current.password,
39+
[]
40+
);
41+
42+
return (
43+
<form onSubmit={onSubmit}>
44+
<Form formData={formData.current} disabled={loading}>
45+
<Item
46+
dataField={'password'}
47+
editorType={'dxTextBox'}
48+
editorOptions={passwordEditorOptions}
49+
>
50+
<RequiredRule message="Password is required" />
51+
<Label visible={false} />
52+
</Item>
53+
<Item
54+
dataField={'confirmedPassword'}
55+
editorType={'dxTextBox'}
56+
editorOptions={confirmedPasswordEditorOptions}
57+
>
58+
<RequiredRule message="Password is required" />
59+
<CustomRule
60+
message={'Passwords do not match'}
61+
validationCallback={confirmPassword}
62+
/>
63+
<Label visible={false} />
64+
</Item>
65+
<ButtonItem>
66+
<ButtonOptions
67+
width={'100%'}
68+
type={'default'}
69+
useSubmitBehavior={true}
70+
>
71+
<span className="dx-button-text">
72+
{
73+
loading
74+
? <LoadIndicator width={'24px'} height={'24px'} visible={true} />
75+
: 'Continue'
76+
}
77+
</span>
78+
</ButtonOptions>
79+
</ButtonItem>
80+
</Form>
81+
</form>
82+
);
83+
}
84+
85+
const passwordEditorOptions = { stylingMode: 'filled', placeholder: 'Password', mode: 'password' };
86+
const confirmedPasswordEditorOptions = { stylingMode: 'filled', placeholder: 'Confirm Password', mode: 'password' };

packages/devextreme-cli/src/templates/nextjs/application/src/components/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ export { default as Header } from './header/Header';
22
export { default as Footer } from './footer/Footer';
33
export { default as LoginForm } from './login-form/LoginForm';
44
export { default as ResetPasswordForm } from './reset-password-form/ResetPasswordForm';
5+
export { default as ChangePasswordForm } from './change-password-form/ChangePasswordForm';
56
export { default as CreateAccountForm } from './create-account-form/CreateAccountForm';
67
export { default as SideNavigationMenu } from './side-navigation-menu/SideNavigationMenu';

packages/devextreme-cli/testing/__tests__/nextjs-ts.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ testAppTemplate(nextjsTs, {
66
profile: 'pages/profile',
77
tasks: 'pages/tasks',
88
page: 'pages/new-page',
9+
'change-password': 'auth/change-password',
910
},
1011
});

packages/devextreme-cli/testing/__tests__/nextjs.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ testAppTemplate(nextjsJs, {
66
profile: 'pages/profile',
77
tasks: 'pages/tasks',
88
page: 'pages/new-page',
9+
'change-password': 'auth/change-password',
910
},
1011
});

packages/devextreme-cli/testing/app-template.test.shared.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module.exports = (env, { port, urls } = { port: '8080', urls: {} }) => {
1616
profile: 'profile',
1717
tasks: 'tasks',
1818
page: `${(env.engine === 'angular' ? 'pages/' : '')}new-page`,
19+
'change-password': 'change-password/123',
1920
...urls,
2021
};
2122

@@ -322,7 +323,7 @@ module.exports = (env, { port, urls } = { port: '8080', urls: {} }) => {
322323
await openPage(appUrl);
323324
await logOut();
324325
await page.evaluate(
325-
'const a = document.createElement("a");a.href="#/change-password/123";a.click()'
326+
`const a = document.createElement("a");a.href="${getPageURL('change-password')}";a.click()`
326327
);
327328
await page.waitForSelector('form');
328329

0 commit comments

Comments
 (0)