@campxdev/campx-web-utils 1.1.9 → 1.2.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 (48) hide show
  1. package/dist/cjs/index.js +1 -1
  2. package/dist/esm/index.js +1 -1
  3. package/dist/index.d.ts +2 -152
  4. package/export.ts +9 -0
  5. package/package.json +7 -4
  6. package/src/App.css +38 -0
  7. package/src/App.tsx +16 -0
  8. package/src/AppContent.tsx +19 -0
  9. package/src/components/ActivityLog.tsx +96 -0
  10. package/src/components/ChangePassword.tsx +183 -0
  11. package/src/config/axios.ts +146 -0
  12. package/src/context/ConfirmDialogProvider.tsx +78 -0
  13. package/src/context/ErrorBoundary/ErrorBoundary.tsx +131 -0
  14. package/src/context/ErrorBoundary/Login.tsx +206 -0
  15. package/src/context/Providers.tsx +76 -0
  16. package/src/context/SnackbarProvider.tsx +42 -0
  17. package/src/context/application-store.ts +86 -0
  18. package/src/context/export.ts +5 -0
  19. package/src/hooks/export.ts +1 -0
  20. package/src/hooks/useConfirm.ts +57 -0
  21. package/src/index.css +13 -0
  22. package/src/index.tsx +19 -0
  23. package/src/layout/AppLayout/AppLayout.tsx +155 -0
  24. package/src/layout/AppLayout/components/HelpDocs.tsx +121 -0
  25. package/src/logo.svg +1 -0
  26. package/src/react-app-env.d.ts +1 -0
  27. package/src/reportWebVitals.ts +15 -0
  28. package/src/routes/main.tsx +32 -0
  29. package/src/selectors/BatchSelector.tsx +15 -0
  30. package/src/selectors/CourseSelector.tsx +16 -0
  31. package/src/selectors/DepartmentSelector.tsx +19 -0
  32. package/src/selectors/EmployeesSelector.tsx +20 -0
  33. package/src/selectors/FeeTypeSelector.tsx +15 -0
  34. package/src/selectors/HostelBlocksSelector.tsx +19 -0
  35. package/src/selectors/HostelFloorSelector.tsx +19 -0
  36. package/src/selectors/HostelRoomSelector.tsx +20 -0
  37. package/src/selectors/PrintFormatSelector.tsx +17 -0
  38. package/src/selectors/ProgramSelector.tsx +16 -0
  39. package/src/selectors/RegulationSelector.tsx +19 -0
  40. package/src/selectors/SemesterSelector.tsx +16 -0
  41. package/src/selectors/YearRangeSelector.tsx +26 -0
  42. package/src/selectors/export.ts +13 -0
  43. package/src/selectors/utils.tsx +39 -0
  44. package/src/setupTests.ts +5 -0
  45. package/src/utils/constants.ts +7 -0
  46. package/src/utils/functions.tsx +11 -0
  47. package/dist/cjs/types/src/layout/AppLayout/AppLayoutV2.d.ts +0 -150
  48. package/dist/esm/types/src/layout/AppLayout/AppLayoutV2.d.ts +0 -150
package/dist/index.d.ts CHANGED
@@ -5,7 +5,6 @@ import * as react from 'react';
5
5
  import { ReactNode } from 'react';
6
6
  import * as _mui_material_OverridableComponent from '@mui/material/OverridableComponent';
7
7
  import * as _mui_material from '@mui/material';
8
- import { SxProps } from '@mui/material';
9
8
  import * as _axios from 'axios';
10
9
  import { Severity, ConfirmDialogType, SideMenuItemProps, SingleSelectProps } from '@campxdev/react-blueprint';
11
10
  import { Store } from 'pullstate';
@@ -147,155 +146,6 @@ type Props = {
147
146
  };
148
147
  declare const AppLayout: React.FC<Props>;
149
148
 
150
- /**
151
- * AppLayoutV2 - An improved layout component for application pages
152
- *
153
- * @example Basic usage with menu-based sidebar:
154
- * ```tsx
155
- * function MyApp() {
156
- * const [collapsed, setCollapsed] = useState(false);
157
- * const menuItems = [
158
- * { label: 'Dashboard', path: '/dashboard', icon: <DashboardIcon /> },
159
- * { label: 'Users', path: '/users', icon: <UsersIcon /> },
160
- * ];
161
- *
162
- * return (
163
- * <AppLayoutV2
164
- * userFullName="John Doe"
165
- * designation="Software Engineer"
166
- * collapsed={collapsed}
167
- * onToggleSidebar={() => setCollapsed(!collapsed)}
168
- * menu={menuItems}
169
- * headerActions={[<Button key="add">Add New</Button>]}
170
- * profileActions={[<MenuItem key="settings">Settings</MenuItem>]}
171
- * institutionsData={institutions}
172
- * onLogoutClick={handleLogout}
173
- * >
174
- * <YourPageContent />
175
- * </AppLayoutV2>
176
- * );
177
- * }
178
- * ```
179
- *
180
- * @example With custom sidebar and breadcrumbs:
181
- * ```tsx
182
- * function MyAppWithCustomSidebar() {
183
- * const [collapsed, setCollapsed] = useState(false);
184
- *
185
- * const customSidebar = (
186
- * <div style={{ padding: '16px' }}>
187
- * <nav>
188
- * <ul>
189
- * <li><Link to="/dashboard">Dashboard</Link></li>
190
- * <li><Link to="/reports">Reports</Link></li>
191
- * </ul>
192
- * </nav>
193
- * </div>
194
- * );
195
- *
196
- * const breadcrumbs = (
197
- * <Breadcrumbs aria-label="breadcrumb">
198
- * <Link to="/">Home</Link>
199
- * <Link to="/dashboard">Dashboard</Link>
200
- * <Typography color="textPrimary">Current Page</Typography>
201
- * </Breadcrumbs>
202
- * );
203
- *
204
- * return (
205
- * <AppLayoutV2
206
- * userFullName="Jane Smith"
207
- * designation="Project Manager"
208
- * collapsed={collapsed}
209
- * onToggleSidebar={() => setCollapsed(!collapsed)}
210
- * sidebar={customSidebar}
211
- * breadcrumbs={breadcrumbs}
212
- * headerActions={[
213
- * <IconButton key="notifications"><NotificationsIcon /></IconButton>,
214
- * <Button key="export">Export</Button>
215
- * ]}
216
- * showActiveDevices={true}
217
- * clientName="My Company"
218
- * headerSx={{ backgroundColor: 'primary.light' }}
219
- * onLogoutClick={handleLogout}
220
- * >
221
- * <Dashboard />
222
- * </AppLayoutV2>
223
- * );
224
- * }
225
- * ```
226
- *
227
- * @example Minimal usage (no sidebar):
228
- * ```tsx
229
- * function SimpleLayout() {
230
- * return (
231
- * <AppLayoutV2
232
- * userFullName="Admin User"
233
- * collapsed={false} // This won't matter since no sidebar
234
- * onLogoutClick={() => console.log('Logout')}
235
- * >
236
- * <SimplePageContent />
237
- * </AppLayoutV2>
238
- * );
239
- * }
240
- * ```
241
- */
242
-
243
- interface AppLayoutV2Props {
244
- /** Main content to be rendered in the layout */
245
- children: ReactNode;
246
- /** Optional sidebar content */
247
- sidebar?: ReactNode;
248
- /** Actions to be displayed in the header */
249
- headerActions?: ReactNode[];
250
- /** Actions for the user profile dropdown */
251
- profileActions?: ReactNode[];
252
- /** User's full name */
253
- userFullName: string;
254
- /** User's designation/title */
255
- designation?: string;
256
- /** Whether the sidebar is collapsed */
257
- collapsed: boolean;
258
- /** Callback to toggle sidebar state */
259
- onToggleSidebar?: () => void;
260
- /** Breadcrumbs component */
261
- breadcrumbs?: ReactNode;
262
- /** Institution data for multi-tenant scenarios */
263
- institutionsData?: any[];
264
- /** Custom styling for the profile component */
265
- profileSx?: SxProps;
266
- /** Logout click handler */
267
- onLogoutClick?: () => void;
268
- /** Whether to show active devices option */
269
- showActiveDevices?: boolean;
270
- /** Custom styling for the header */
271
- headerSx?: SxProps;
272
- /** Custom styling for the main content container */
273
- mainContainerSx?: SxProps;
274
- /** Client/organization name */
275
- clientName?: string;
276
- /** Menu items for sidebar (only used when sidebar prop is not provided) */
277
- menu?: SideMenuItemProps[];
278
- /** Help documentation configuration */
279
- helpDocsConfig?: Record<string, {
280
- actions: {
281
- name: string;
282
- onClick: () => void;
283
- }[];
284
- }>;
285
- }
286
- /**
287
- * AppLayoutV2 - A flexible and responsive layout component for application pages.
288
- *
289
- * This component provides a modern layout structure with:
290
- * - Responsive header using AppHeader
291
- * - Optional collapsible sidebar
292
- * - Flexible main content area
293
- * - Mobile-responsive design
294
- * - Error boundaries and loading states
295
- * - Help documentation support
296
- */
297
- declare const AppLayoutV2: React.FC<AppLayoutV2Props>;
298
-
299
149
  interface HelpDocsActionType {
300
150
  name: string;
301
151
  onClick?: () => void;
@@ -343,5 +193,5 @@ declare const workspace: string;
343
193
 
344
194
  declare const openInNewTab: (pathAfterBase: string) => void;
345
195
 
346
- export { AppLayout, AppLayoutV2, ApplicationStore, BatchSelector, ChangePassword, ChangePasswordDialog, ConfirmDialogProvider, CourseSelector, DepartmentSelector, EmployeesSelector, ErrorBoundary, FeeTypeSelector, HelpDocs, HostelBlocksSelector, HostelFloorSelector, HostelRoomSelector, PrintFormatSelector, ProgramSelector, Providers, RegulationSelector, SemesterSelector, SnackbarProvider, StyledMenuItem, YearRangeSelector, axios, initialApplicationState, institutionCode, isDevelopment, openInNewTab, tenantCode, useConfirm, workspace };
347
- export type { AppLayoutV2Props, ApplicationStoreType, ErrorBoundaryProps };
196
+ export { AppLayout, ApplicationStore, BatchSelector, ChangePassword, ChangePasswordDialog, ConfirmDialogProvider, CourseSelector, DepartmentSelector, EmployeesSelector, ErrorBoundary, FeeTypeSelector, HelpDocs, HostelBlocksSelector, HostelFloorSelector, HostelRoomSelector, PrintFormatSelector, ProgramSelector, Providers, RegulationSelector, SemesterSelector, SnackbarProvider, StyledMenuItem, YearRangeSelector, axios, initialApplicationState, institutionCode, isDevelopment, openInNewTab, tenantCode, useConfirm, workspace };
197
+ export type { ApplicationStoreType, ErrorBoundaryProps };
package/export.ts ADDED
@@ -0,0 +1,9 @@
1
+ export * from './src/components/ChangePassword';
2
+ export * from './src/config/axios';
3
+ export * from './src/context/export';
4
+ export * from './src/hooks/export';
5
+ export * from './src/layout/AppLayout/AppLayout';
6
+ export * from './src/layout/AppLayout/components/HelpDocs';
7
+ export * from './src/selectors/export';
8
+ export * from './src/utils/constants';
9
+ export * from './src/utils/functions';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@campxdev/campx-web-utils",
3
- "version": "1.1.9",
3
+ "version": "1.2.1",
4
4
  "author": "CampX",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -14,11 +14,13 @@
14
14
  "module": "dist/esm/index.js",
15
15
  "types": "dist/index.d.ts",
16
16
  "files": [
17
- "dist"
17
+ "dist",
18
+ "src",
19
+ "export.ts"
18
20
  ],
19
21
  "private": false,
20
22
  "peerDependencies": {
21
- "@campxdev/react-blueprint": "2.1.5",
23
+ "@campxdev/react-blueprint": "2.2.1",
22
24
  "@emotion/react": "^11.14.0",
23
25
  "@emotion/styled": "^11.14.0",
24
26
  "@mui/icons-material": "^7.0.2",
@@ -29,7 +31,7 @@
29
31
  "react-router-dom": "^6.24.0"
30
32
  },
31
33
  "dependencies": {
32
- "@campxdev/react-blueprint": "2.1.4",
34
+ "@campxdev/react-blueprint": "2.2.1",
33
35
  "@hookform/resolvers": "^2.9.10",
34
36
  "@mui/x-date-pickers": "^8.1.0",
35
37
  "axios": "^1.7.2",
@@ -93,6 +95,7 @@
93
95
  "test": "craco test",
94
96
  "eject": "react-scripts eject",
95
97
  "build": "rollup -c --bundleConfigAsCjs",
98
+ "yalc:dev": "cp package.json package.json.backup && node -e \"const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('./package.json')); pkg.main='export.ts'; pkg.module='export.ts'; pkg.types='export.ts'; pkg.exports={'.':'./export.ts'}; delete pkg.scripts.prepare; fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2));\" && yalc publish --push --sig --no-scripts && mv package.json.backup package.json",
96
99
  "prepare": "npm run build"
97
100
  },
98
101
  "eslintConfig": {
package/src/App.css ADDED
@@ -0,0 +1,38 @@
1
+ .App {
2
+ text-align: center;
3
+ }
4
+
5
+ .App-logo {
6
+ height: 40vmin;
7
+ pointer-events: none;
8
+ }
9
+
10
+ @media (prefers-reduced-motion: no-preference) {
11
+ .App-logo {
12
+ animation: App-logo-spin infinite 20s linear;
13
+ }
14
+ }
15
+
16
+ .App-header {
17
+ background-color: #282c34;
18
+ min-height: 100vh;
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ justify-content: center;
23
+ font-size: calc(10px + 2vmin);
24
+ color: white;
25
+ }
26
+
27
+ .App-link {
28
+ color: #61dafb;
29
+ }
30
+
31
+ @keyframes App-logo-spin {
32
+ from {
33
+ transform: rotate(0deg);
34
+ }
35
+ to {
36
+ transform: rotate(360deg);
37
+ }
38
+ }
package/src/App.tsx ADDED
@@ -0,0 +1,16 @@
1
+ import { useRoutes } from 'react-router-dom';
2
+
3
+ import { Providers } from './context/export';
4
+ import { mainRoutes } from './routes/main';
5
+
6
+ export default function App() {
7
+ return (
8
+ <Providers>
9
+ <AppRouter />
10
+ </Providers>
11
+ );
12
+ }
13
+ const AppRouter = () => {
14
+ const app = useRoutes(mainRoutes);
15
+ return app;
16
+ };
@@ -0,0 +1,19 @@
1
+ import { Breadcrumbs, Icons, TextField } from '@campxdev/react-blueprint';
2
+ import { useState } from 'react';
3
+ import { CourseSelector } from './selectors/CourseSelector';
4
+
5
+ function AppContent() {
6
+ const [value, setValue] = useState('63b810ee995d6d64f4a248de');
7
+
8
+ return (
9
+ <>
10
+ <Breadcrumbs pathTrimCount={0} />
11
+ <TextField label="habdkhabs" />
12
+ <Icons.AppsIcon />
13
+
14
+ <CourseSelector onChange={() => {}} />
15
+ </>
16
+ );
17
+ }
18
+
19
+ export default AppContent;
@@ -0,0 +1,96 @@
1
+ import { ActivityLogView } from '@campxdev/react-blueprint';
2
+ import { Store } from 'pullstate';
3
+ import { useEffect, useState } from 'react';
4
+ import { useInfiniteQuery } from 'react-query';
5
+ import { axios } from '../config/axios';
6
+
7
+ function formatDate(date: Date): string {
8
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
9
+ }
10
+
11
+ export type ActivityLogType = {
12
+ activitiesData: any[];
13
+ lastTimestamp: number | null;
14
+ };
15
+
16
+ export type ActivityLogStoreType = {
17
+ [logType: string]: ActivityLogType;
18
+ };
19
+
20
+ export const ActivityLogStore = new Store<ActivityLogStoreType>({});
21
+
22
+ export default function ActivityLog({
23
+ apiEndpoint,
24
+ params,
25
+ }: {
26
+ apiEndpoint: string;
27
+ params: Record<string, any>;
28
+ }) {
29
+ const [fromDate, setFromDate] = useState<Date | null>(null);
30
+ const [toDate, setToDate] = useState<Date | null>(null);
31
+
32
+ const { activitiesData } = ActivityLogStore.useState(
33
+ (s) => s[params.logType] || { activitiesData: [], lastTimestamp: null },
34
+ );
35
+
36
+ const fetchActivities = async ({ pageParam = null }) => {
37
+ const response = await axios.get(apiEndpoint, {
38
+ params: {
39
+ ...params,
40
+ lastTimestamp: pageParam,
41
+ ...(fromDate && { fromDate: formatDate(fromDate) }),
42
+ ...(toDate && { toDate: formatDate(toDate) }),
43
+ },
44
+ });
45
+ return response.data;
46
+ };
47
+
48
+ const { fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
49
+ useInfiniteQuery(
50
+ ['activities', params.logType, fromDate, toDate],
51
+ fetchActivities,
52
+ {
53
+ getNextPageParam: (lastPage) =>
54
+ lastPage?.length
55
+ ? new Date(lastPage[lastPage.length - 1]?.timestamp).getTime()
56
+ : null,
57
+ onSuccess: (data) => {
58
+ ActivityLogStore.update((s) => {
59
+ const currentData = s[params.logType] || {
60
+ activitiesData: [],
61
+ lastTimestamp: null,
62
+ };
63
+ currentData.activitiesData = data.pages.flatMap(
64
+ (page) => page ?? [],
65
+ );
66
+ currentData.lastTimestamp = data.pages[data.pages.length - 1]
67
+ ?.timestamp
68
+ ? new Date(data.pages[data.pages.length - 1].timestamp).getTime()
69
+ : null;
70
+ s[params.logType] = currentData;
71
+ });
72
+ },
73
+ enabled: activitiesData.length === 0 || !!fromDate || !!toDate,
74
+ },
75
+ );
76
+
77
+ useEffect(() => {
78
+ if (fromDate || toDate) {
79
+ fetchNextPage();
80
+ }
81
+ }, [fromDate, toDate, fetchNextPage]);
82
+
83
+ return (
84
+ <ActivityLogView
85
+ activitiesData={activitiesData}
86
+ isFetchingNextPage={isFetchingNextPage}
87
+ fetchNextPage={fetchNextPage}
88
+ hasNextPage={hasNextPage}
89
+ fromDate={fromDate}
90
+ toDate={toDate}
91
+ isLoading={isLoading}
92
+ setFromDate={setFromDate}
93
+ setToDate={setToDate}
94
+ />
95
+ );
96
+ }
@@ -0,0 +1,183 @@
1
+ import {
2
+ CircularProgress,
3
+ IconButton,
4
+ InputAdornment,
5
+ MenuItem,
6
+ Stack,
7
+ styled,
8
+ } from '@mui/material';
9
+ import { useState } from 'react';
10
+ import { useForm } from 'react-hook-form';
11
+
12
+ import {
13
+ Button,
14
+ DialogButton,
15
+ FormControlWrapper,
16
+ TextField,
17
+ } from '@campxdev/react-blueprint';
18
+ import { Visibility, VisibilityOff } from '@mui/icons-material';
19
+ import { toast } from 'react-toastify';
20
+ import { axios } from '../config/axios';
21
+ import { isDevelopment } from '../utils/constants';
22
+
23
+ interface PasswordVisibilityState {
24
+ oldPassword: boolean;
25
+ newPassword: boolean;
26
+ confirmPassword: boolean;
27
+ }
28
+
29
+ interface FormData {
30
+ oldPassword: string;
31
+ newPassword: string;
32
+ confirmPassword: string;
33
+ }
34
+
35
+ interface Field {
36
+ label: string;
37
+ name: keyof PasswordVisibilityState;
38
+ }
39
+
40
+ interface ChangePasswordProps {
41
+ close: () => void;
42
+ }
43
+
44
+ export const StyledMenuItem = styled(MenuItem)(() => ({
45
+ display: 'flex',
46
+ alignItems: 'center',
47
+ gap: '5px',
48
+ '& .MuiListItemIcon-root': {
49
+ minWidth: '24px',
50
+ },
51
+ '& .MuiSvgIcon-root': {
52
+ height: '14px',
53
+ width: '14px',
54
+ },
55
+ }));
56
+
57
+ export function ChangePassword({ close }: ChangePasswordProps) {
58
+ const [showPassword, setShowPassword] = useState<PasswordVisibilityState>({
59
+ oldPassword: false,
60
+ newPassword: false,
61
+ confirmPassword: false,
62
+ });
63
+ const [isLoading, setIsLoading] = useState(false);
64
+
65
+ const { handleSubmit, control, reset } = useForm<FormData>();
66
+
67
+ const onSubmit = async (formData: any) => {
68
+ setIsLoading(true);
69
+ const { oldPassword, newPassword, confirmPassword } = formData;
70
+ if (newPassword === confirmPassword) {
71
+ try {
72
+ await axios.post(
73
+ isDevelopment
74
+ ? 'https://api.campx.dev/auth-server/auth/change-password'
75
+ : 'https://api.campx.in/auth-server/auth/change-password',
76
+ {
77
+ oldPassword,
78
+ newPassword,
79
+ },
80
+ );
81
+ toast.success('Password Changed Successfully');
82
+ setIsLoading(false);
83
+ reset();
84
+ close();
85
+ } catch (error) {
86
+ console.log(error, 'this is error');
87
+ // axiosErrorToast(error);
88
+ setIsLoading(false);
89
+ }
90
+ } else {
91
+ toast.error('New Password, Confirm Password must be same');
92
+ setIsLoading(false);
93
+ }
94
+ };
95
+
96
+ const fields: Field[] = [
97
+ { label: 'Old Password', name: 'oldPassword' },
98
+ { label: 'New Password', name: 'newPassword' },
99
+ { label: 'Confirm Password', name: 'confirmPassword' },
100
+ ];
101
+
102
+ return (
103
+ <form onSubmit={handleSubmit(onSubmit)}>
104
+ <FormControlWrapper control={control}>
105
+ <Stack gap={1} direction="column">
106
+ {fields.map((item) => {
107
+ return (
108
+ <>
109
+ <TextField
110
+ label={item.label}
111
+ name={item.name}
112
+ type={showPassword[item.name] ? 'text' : 'password'}
113
+ required
114
+ InputProps={{
115
+ endAdornment: (
116
+ <InputAdornment position="end">
117
+ <IconButton
118
+ size="small"
119
+ onClick={() =>
120
+ setShowPassword((prev: any) => ({
121
+ ...prev,
122
+ [item.name]: !prev[item.name],
123
+ }))
124
+ }
125
+ edge="end"
126
+ >
127
+ {showPassword[item.name] ? (
128
+ <Visibility />
129
+ ) : (
130
+ <VisibilityOff />
131
+ )}
132
+ </IconButton>
133
+ </InputAdornment>
134
+ ),
135
+ }}
136
+ />
137
+ </>
138
+ );
139
+ })}
140
+
141
+ <Stack direction={'row'} gap={2} sx={{ marginTop: '15px' }}>
142
+ <Button variant="outlined" onClick={close}>
143
+ Cancel
144
+ </Button>
145
+ <Button
146
+ type="submit"
147
+ variant="contained"
148
+ endIcon={
149
+ isLoading && (
150
+ <CircularProgress
151
+ style={{ color: 'white' }}
152
+ size="30px"
153
+ thickness={1.7}
154
+ />
155
+ )
156
+ }
157
+ >
158
+ Submit
159
+ </Button>
160
+ </Stack>
161
+ </Stack>
162
+ </FormControlWrapper>
163
+ </form>
164
+ );
165
+ }
166
+
167
+ export const ChangePasswordDialog: React.FC = () => (
168
+ <>
169
+ <DialogButton
170
+ anchor={({ open }) => (
171
+ <StyledMenuItem
172
+ onClick={() => {
173
+ open();
174
+ }}
175
+ >
176
+ Change Password
177
+ </StyledMenuItem>
178
+ )}
179
+ content={({ close }) => <ChangePassword close={close} />}
180
+ title="Change Password"
181
+ />
182
+ </>
183
+ );