Skip to content

Commit 8c2af54

Browse files
committed
Added user menu on header, my profile page and update register page
1 parent 45befa4 commit 8c2af54

File tree

14 files changed

+561
-47
lines changed

14 files changed

+561
-47
lines changed

public/images/shield-key.svg

Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export const validateName = (name: any) => {
2+
let msg = '';
3+
if (name.trim().length < 2) {
4+
msg = 'Name must be at least 2 characters long.';
5+
}
6+
return msg;
7+
};
8+
9+
export const validateEmail = (email: string ) => {
10+
if (!isValidEmail(email)) return 'Invalid email address';
11+
return '';
12+
};
13+
14+
export const isValidEmail = (email: string) => {
15+
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
16+
return emailRegex.test(email);
17+
};
18+
19+
export const validatePassword = (pwd :string) => {
20+
if (!isvalidpassword(pwd)) return 'At least 6 characters including a number, uppercase, and lowercase letter';
21+
return '';
22+
};
23+
24+
const isvalidpassword = (newPassword: string) => {
25+
const passwordregex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{6,}$/;
26+
return passwordregex.test(newPassword);
27+
};

src/components/global/navbar/User.tsx

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,113 @@
1-
import IconButton from '@/components/common/icon_button/IconButton';
1+
import IconImgButton from '@/components/common/icon_button/IconButton';
22
import React from 'react';
3+
import Box from '@mui/material/Box';
4+
import Menu from '@mui/material/Menu';
5+
import MenuItem from '@mui/material/MenuItem';
6+
import ListItemIcon from '@mui/material/ListItemIcon';
7+
import Divider from '@mui/material/Divider';
8+
import IconButton from '@mui/material/IconButton';
9+
import Button from '@mui/material/Button';
10+
import Logout from '@mui/icons-material/Logout';
11+
import Login from '@mui/icons-material/Login';
12+
import { useRouter } from "next/router";
13+
import { useSession, signOut } from "next-auth/react"
314

415
function User() {
16+
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
17+
const open = Boolean(anchorEl);
18+
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
19+
setAnchorEl(event.currentTarget);
20+
};
21+
const { data: sessions } = useSession()
22+
const router = useRouter();
23+
24+
const { id_token } = sessions?.user as { id_token: string} || {};
25+
const handleClose = () => {
26+
setAnchorEl(null);
27+
};
28+
const dropdown = [
29+
["My Profile", "/my-profile", "/images/icon-container (2).svg"],
30+
["API Plans", "/api-plans", "/images/API.svg"],
31+
["API Keys", "/api-keys", "images/shield-key.svg"],
32+
]
33+
const propsConfig = {
34+
elevation: 0,
35+
sx: {
36+
overflow: 'visible',
37+
filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
38+
mt: 1,
39+
width: 190,
40+
'& .MuiAvatar-root': {
41+
width: 32,
42+
height: 32,
43+
ml: -0.7,
44+
mr: 1,
45+
},
46+
'&:before': {
47+
content: '""',
48+
display: 'block',
49+
position: 'absolute',
50+
top: 0,
51+
right: 14,
52+
width: 10,
53+
height: 10,
54+
bgcolor: 'background.paper',
55+
transform: 'translateY(-50%) rotate(45deg)',
56+
zIndex: 0,
57+
},
58+
},
59+
}
560
return (
661
<div className="flex items-center gap-1">
7-
{/* <IconButton icon="/images/icon-container (1).svg" /> */}
8-
{/* <IconButton icon="/images/icon-container (2).svg" /> */}
62+
{ id_token ? <>
63+
<IconImgButton icon="/images/icon-container (1).svg" />
64+
<Box sx={{ display: 'flex', alignItems: 'center', textAlign: 'center' }}>
65+
<IconButton
66+
onClick={handleClick}
67+
size="small"
68+
aria-controls={open ? 'user-menu' : undefined}
69+
aria-haspopup="true"
70+
aria-expanded={open ? 'true' : undefined}
71+
>
72+
<img src={"/images/icon-container (2).svg"} alt="user" />
73+
</IconButton>
74+
</Box>
75+
<Menu
76+
anchorEl={anchorEl}
77+
id="user-menu"
78+
open={open}
79+
onClose={handleClose}
80+
onClick={handleClose}
81+
PaperProps={ propsConfig}
82+
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
83+
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
84+
>
85+
{dropdown?.map((menuItem, index) => (
86+
<MenuItem key={index} className="ml-0" onClick={handleClose}>
87+
<ListItemIcon>
88+
<img src={menuItem[2]} alt="" />
89+
</ListItemIcon>
90+
<span className="mr-3.5"> {menuItem[0]}</span>
91+
</MenuItem>
92+
))
93+
}
94+
<Divider />
95+
<MenuItem onClick={handleClose}>
96+
<Button fullWidth color="inherit"
97+
variant="outlined"
98+
onClick={() => signOut()}
99+
startIcon={<Logout fontSize="inherit" />}>
100+
Sing Out
101+
</Button>
102+
</MenuItem>
103+
</Menu></>: <>
104+
<Button fullWidth color="inherit"
105+
variant="outlined"
106+
onClick={()=> router.push('/login')}
107+
startIcon={<Login fontSize="inherit" />}>
108+
Sing In
109+
</Button>
110+
</> }
9111
</div>
10112
);
11113
}

src/components/global/navbar/pages/Menu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Link from 'next/link';
22
import { useRouter } from 'next/router';
33
import React, { useEffect, useRef, useState } from 'react';
44

5-
function Menu(props: { name: string; dropdown: string[][] | undefined; id: string; url?: string }) {
5+
function Menu(props: { name: string | any ; dropdown: string[][] | undefined; id: string; url?: string }) {
66
const { pathname, push } = useRouter();
77
const [isOpen, setIsOpen] = useState(false);
88
const current = props.url === pathname;

src/pages/my-profile.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React, {useEffect} from 'react'
2+
import Profile from '@/views/Profile/profile'
3+
import ReactGA from "react-ga4";
4+
import SEO from "@/components/common/SEO";
5+
6+
const MyProfile = () => {
7+
useEffect(() => {
8+
ReactGA.send({ hitType: 'My Profile', page: window.location.pathname });
9+
}, []);
10+
return (
11+
<div>
12+
<SEO/>
13+
<Profile />
14+
</div>
15+
)
16+
}
17+
18+
export default MyProfile

src/styles/main.sass

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
@import "tailwindcss/base"
22
@import "tailwindcss/components"
33
@import "tailwindcss/utilities"
4-
4+
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap')
55
*
66
@apply box-border
77

@@ -61,4 +61,3 @@ body
6161

6262
.User_Operations_table td
6363
padding: 10px !important
64-

src/views/ApiKeys/index.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@ import { createAPIKey, fetchAPIKeyList } from "@/components/common/apiCalls/jiff
1313

1414
function APIKeys() {
1515
const [apiKeysTable, setApiKeysTable] = useState<tableDataT>(table_data as tableDataT);
16-
1716
const [tableLoading] = useState(false);
1817
const [captionText, setCaptionText] = useState('');
1918
const { data: sessions } = useSession()
20-
21-
const { id_token, sub } = sessions?.user as { id_token: string; sub: string } || {};;
19+
const { id_token, sub } = sessions?.user as { id_token: string; sub: string } || {};
2220

2321
useEffect(() => {
2422
const fetchData = async () => {

src/views/Profile/profile.tsx

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import React, { useState } from 'react';
2+
import Navbar from '@/components/global/navbar/Navbar';
3+
import TextField from '@mui/material/TextField';
4+
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
5+
import ProfileSection from './profileSection';
6+
import Visibilitypassword from './visibilityPassword';
7+
import UserInfo from './userInfo'
8+
import { isValidEmail, validateEmail, validatePassword } from '@/components/common/validation/validation';
9+
import { Divider } from '@mui/material';
10+
import Footer from '@/components/global/footer/Footer';
11+
12+
const Profile = () => {
13+
const validationOdj = {
14+
'current password': { error:false, fb: false, msg:'' },
15+
'email': { error:false, fb: false, msg:''},
16+
'new password' : { error:false, fb: false, msg:'' },
17+
'password' : { error:false, fb: false, msg:'' }
18+
}
19+
const [newEmail, setNewEmail] = useState('');
20+
const [password, setPassword] = useState('');
21+
const [nameError, setNameError] = useState<any>('');
22+
const [emailError, setEmailError] = useState('');
23+
const [currentPassword, setCurrentPassword] = useState('');
24+
const [newPassword, setNewPassword] = useState('');
25+
const [passwordError, setPasswordError] = useState('');
26+
const [nameSuccess, setNameSuccess] = useState('');
27+
const [emailSuccess, setEmailSuccess] = useState('');
28+
const [passwordSuccess, setPasswordSuccess] = useState('');
29+
const [isValid, setValidates] = useState(validationOdj);
30+
31+
const handleValidation = ({target}:any ) => {
32+
const { value, name, type } = target;
33+
let error = value === ''
34+
let msg =''
35+
if(!value) {
36+
msg =`${name} is required`
37+
} else if (name == 'email') {
38+
msg = validateEmail(value);
39+
error = !isValidEmail(value);
40+
} else if (type === 'password') {
41+
msg = validatePassword(value);
42+
}
43+
setValidates({...isValid, [name]:{error, msg}})
44+
}
45+
46+
const nameValue = ({validates}: any) => {
47+
if (validates['name'].error) {
48+
setNameError(validates['name'].msg);
49+
} else {
50+
setNameError('');
51+
//todo: need create submit method
52+
setNameSuccess(' Display name purely for a decorative purpose');
53+
}
54+
};
55+
56+
const emailValue = () => {
57+
setEmailError('');
58+
setEmailSuccess('You will receive a confirmation message to your current email');
59+
};
60+
61+
const passwordValue = () => {
62+
setPasswordError('');
63+
setPasswordSuccess('New Password save successfully');
64+
};
65+
66+
function ValidationMsg({ error, success }: any) {
67+
return (
68+
<div className="flex ml-4">
69+
<span className={` ${error ? 'text-red-500' : 'text-green-700'} flex items-end`}>
70+
{ error || success}
71+
</span>
72+
</div>
73+
);
74+
}
75+
76+
return (
77+
<div className="">
78+
<Navbar searchbar />
79+
<section className="px-3">
80+
<div className="container">
81+
<div className="flex flex-col pt-2 pb-48">
82+
<div>
83+
<h1 className="text-3xl font-bold">
84+
My Profile
85+
</h1>
86+
</div>
87+
<ProfileSection title="Account" buttonText="SAVE CHANGES" onClick={nameValue}>
88+
<div className="flex-col w-2/4">
89+
<div className="xl:w-11/12 md:w-5/6 lg:w-4/5 w-11/12">
90+
<UserInfo handleInfo={nameValue} />
91+
</div>
92+
</div>
93+
<Divider orientation="vertical" flexItem />
94+
<ValidationMsg error={nameError} success={nameSuccess} />
95+
</ProfileSection>
96+
<ProfileSection title="Change Email" buttonText="SAVE CHANGES" onClick={emailValue}>
97+
<div className="flex-col w-2/4 pr-6">
98+
<TextField
99+
label="New Email Address"
100+
id="emailAddress"
101+
size="small"
102+
name='email'
103+
variant="standard"
104+
className="w-full mb-6"
105+
value={newEmail}
106+
error={isValid['email']?.error}
107+
helperText={isValid['email']?.msg}
108+
onChange={(e) => setNewEmail(e.target.value)}
109+
onBlur={handleValidation}
110+
/>
111+
<Visibilitypassword
112+
value = {password}
113+
className="mr-6"
114+
name = "password"
115+
label='Current Password'
116+
error={isValid['password']?.error}
117+
helperText={isValid['password']?.msg}
118+
onChange={(e: any) => setPassword(e.target.value)}
119+
onBlur={handleValidation}
120+
/>
121+
</div>
122+
<Divider orientation="vertical" flexItem />
123+
<ValidationMsg success={emailSuccess} />
124+
</ProfileSection>
125+
<ProfileSection title="Change Password" buttonText="SAVE CHANGES" onClick={passwordValue}>
126+
<div className="flex-col w-2/4 pr-6">
127+
<TextField
128+
label="Current Password"
129+
id="standard2"
130+
name="current password"
131+
size="small"
132+
variant="standard"
133+
className="w-full mr-6 mb-6"
134+
type="password"
135+
error={isValid['current password']?.error}
136+
helperText={isValid['current password']?.msg}
137+
value={currentPassword}
138+
onChange={(e) => setCurrentPassword(e.target.value)}
139+
onBlur={handleValidation}
140+
/>
141+
<TextField
142+
label="New Password"
143+
id="standard3"
144+
name="new password"
145+
size="small"
146+
variant="standard"
147+
type="password"
148+
className="w-full mb-6"
149+
error={isValid['new password']?.error}
150+
helperText={isValid['new password']?.msg}
151+
value={newPassword}
152+
onChange={(e) => setNewPassword(e.target.value)}
153+
onBlur={handleValidation}
154+
/>
155+
</div>
156+
<Divider orientation="vertical" flexItem />
157+
<ValidationMsg error={isValid['current password']?.msg || isValid['new password']?.msg} success={passwordSuccess} />
158+
</ProfileSection>
159+
<div className='mt-6'>
160+
<button className="rounded-md border py-3 px-4 tracking-wider text-md">
161+
MORE <MoreHorizIcon className="ml-2 mb-1 text-sm" />
162+
</button>
163+
</div>
164+
</div>
165+
</div>
166+
</section>
167+
<Footer />
168+
</div>
169+
);
170+
};
171+
172+
export default Profile;
173+
174+

0 commit comments

Comments
 (0)