feature: Profile page update user info

This commit is contained in:
Moon Patel
2023-07-14 19:50:42 +05:30
parent e8775ff8e1
commit 78a3187e9a
7 changed files with 215 additions and 82 deletions
+19
View File
@@ -41,6 +41,25 @@ const userSchema = new Schema(
return this.fname + " " + this.lname;
},
},
_friends_: {
async get() {
await this.populate("friends", "username email");
// console.log(this.friends);
return this.friends.map((friend) => {
return {
username: friend.username,
email: friend.email,
id: friend.id,
};
});
},
},
_games_: {
async get() {
await this.populate("games");
return this.games;
},
},
},
methods: {
async getFriends() {
+14 -3
View File
@@ -11,7 +11,6 @@ router.use(
catchAsync(async (req, res, next) => {
let userID = req.url.split("/")[1];
let userData = await User.findById(userID);
console.log(userData);
if (userData) {
req.userData = userData;
next();
@@ -28,8 +27,19 @@ router.get(
checkAuth,
catchAsync(async (req, res, next) => {
let userData = req.userData;
let { id, username, email, friends, fullName, country, location } = userData;
let resData = { id, username, email, friends, fullName, country, location };
let {
id,
username,
email,
fname,
lname,
country,
location,
} = userData;
let friends = await userData.getFriends();
let games = await userData.getGames();
let resData = { id, username, email, friends, fname, lname, country, location, games };
console.log(resData)
return res.json({ success: true, data: resData });
})
);
@@ -42,6 +52,7 @@ router.patch(
catchAsync(async (req, res, next) => {
let { userid } = req.params;
let updatedData = req.body;
console.log('Updated data: ',updatedData)
// console.log(updatedData)
await User.findByIdAndUpdate(userid, { ...updatedData });
let { id, username, email, fname, lname, location, country, fullName } = await User.findById(userid);
+3 -2
View File
@@ -16,6 +16,7 @@ import { logoutAction } from './components/Logout'
import ChallengeFriend, { playFriendAction } from './pages/Play/ChallengeFriend'
import ChessGame from './pages/Chess/ChessGame'
import ChessGameContextProvider from './context/chess-game-context'
import { action as profileAction } from './pages/Settings/Profile'
const router = createBrowserRouter([{
path: '/',
@@ -43,8 +44,8 @@ const router = createBrowserRouter([{
},
{
path: 'settings', element: <Settings />, children: [
{ index: true, element: <Profile /> },
{ path: 'profile', element: <Profile /> },
{ index: true, element: <Profile />, action: profileAction },
{ path: 'profile', element: <Profile />, action: profileAction },
{ path: 'themes', element: <Themes /> },
{ path: 'password', element: <Password /> },
{ path: 'friends', element: <Friends /> },
@@ -0,0 +1,45 @@
import React, { createContext, useEffect, useState } from 'react'
import { getAuthToken, getUserData } from '../../utils/auth';
export const UserDataContext = createContext();
const UserDataContextProvider = ({ children }) => {
let { id: userid } = getUserData();
const [user, setUser] = useState(null);
const [friends, setFriends] = useState(null)
const [games, setGames] = useState(null);
async function fetchUserDetails() {
let url = `${import.meta.env.VITE_BACKEND_HOST}/api/user/${userid}`;
let response = await fetch(url, {
headers: {
Authorization: `Bearer ${getAuthToken()}`
}
});
let resData = await response.json();
if (resData.success) {
let { id, username, email, friends: _friends_, fname, lname, fullName, country, location, games: _games_ } = resData.data;
setUser({ id, username, email, fname, lname, fullName, country, location })
setFriends(_friends_)
setGames(_games_)
localStorage.setItem('user', JSON.stringify({ id, username, email, fname, lname, fullName, country, location }));
localStorage.setItem('friends', JSON.stringify(_friends_));
localStorage.setItem('games', JSON.stringify(_games_));
} else {
throw resData.error
}
}
useEffect(() => {
fetchUserDetails()
}, []);
return (
<UserDataContext.Provider value={{ user, friends, games }}>
{children}
</UserDataContext.Provider>
)
}
export default UserDataContextProvider
+56 -53
View File
@@ -1,23 +1,24 @@
import { AppShell, Burger, Button, Container, Header, MediaQuery, Navbar, Paper, Text, createStyles, useMantineTheme } from '@mantine/core'
import { AppShell, Burger, Container, Header, MediaQuery, Navbar, Paper, Text, createStyles, useMantineTheme } from '@mantine/core'
import React, { useState } from 'react'
import NavbarLinks from '../components/NavbarLinks';
import { Outlet } from 'react-router-dom'
import Logout from '../components/Logout';
import UserDataContextProvider from '../context/user-data-context';
const useStyles = createStyles((theme) => ({
body: {
width: '100%',
height:'100%'
height: '100%'
},
root: {
width: '100%',
height:'100vh'
height: '100vh'
},
main: {
width: '100%',
height:'100vh'
height: '100vh'
}
}))
@@ -26,57 +27,59 @@ const MainLayout = () => {
const theme = useMantineTheme();
const [opened, setOpened] = useState(false);
return (
<Paper>
<AppShell classNames={{
body: classes.body,
root: classes.root,
main: classes.main
}}
styles={{
main: {
background: theme.colorScheme === 'dark' ? '#302e2a' : theme.colors.gray[0]
},
body: {
textAlign: 'center'
}
<UserDataContextProvider>
<Paper>
<AppShell classNames={{
body: classes.body,
root: classes.root,
main: classes.main
}}
layout='alt'
navbar={
<Navbar py="md" px="0" hiddenBreakpoint="md" sx={{backgroundColor:'#262523'}} hidden={!opened} width={{ md: 150 }}>
<Navbar.Section>
<Text size={30} weight={700}>Chess</Text>
</Navbar.Section>
<Navbar.Section grow mt="md">
<NavbarLinks />
</Navbar.Section>
<Navbar.Section >
<Logout />
</Navbar.Section>
</Navbar>
}
header={
<MediaQuery largerThan="sm" styles={{ display: 'none' }}>
<Header zIndex={1000} p="md">
<div style={{ display: 'flex', alignItems: 'center', height: '100%' }}>
<Burger
opened={opened}
onClick={() => setOpened((o) => !o)}
size="sm"
color={theme.colors.gray[6]}
mr="xl"
/>
styles={{
main: {
background: theme.colorScheme === 'dark' ? '#302e2a' : theme.colors.gray[0]
},
body: {
textAlign: 'center'
}
}}
layout='alt'
navbar={
<Navbar py="md" px="0" hiddenBreakpoint="md" sx={{ backgroundColor: '#262523' }} hidden={!opened} width={{ md: 150 }}>
<Navbar.Section>
<Text size={30} weight={700}>Chess</Text>
</Navbar.Section>
<Navbar.Section grow mt="md">
<NavbarLinks />
</Navbar.Section>
<Navbar.Section >
<Logout />
</Navbar.Section>
</Navbar>
}
header={
<MediaQuery largerThan="sm" styles={{ display: 'none' }}>
<Header zIndex={1000} p="md">
<div style={{ display: 'flex', alignItems: 'center', height: '100%' }}>
<Burger
opened={opened}
onClick={() => setOpened((o) => !o)}
size="sm"
color={theme.colors.gray[6]}
mr="xl"
/>
<Text>Application header</Text>
</div>
</Header>
</MediaQuery>
}
>
<Container size="100%" px={{ 'md': '10px', 'lg': '30px' }} >
<Outlet />
</Container>
</AppShell>
</Paper>
<Text>Application header</Text>
</div>
</Header>
</MediaQuery>
}
>
<Container size="100%" px={{ 'md': '10px', 'lg': '30px' }} >
<Outlet />
</Container>
</AppShell>
</Paper>
</UserDataContextProvider>
);
}
+76 -22
View File
@@ -1,9 +1,26 @@
import React from 'react'
import { Avatar, Card, Container, Flex, Grid, Group, Image, Stack, Text, TextInput, Title } from '@mantine/core'
import { getUserData } from '../../../utils/auth'
import React, { useContext } from 'react'
import { Avatar, Button, Flex, Grid, Group, Loader, Stack, Text, TextInput, Title } from '@mantine/core'
import { getAuthToken, getUserData } from '../../../utils/auth'
import { UserDataContext } from '../../context/user-data-context'
import { useForm } from '@mantine/form'
import { Form } from 'react-router-dom'
const Profile = () => {
let { username } = getUserData()
let { user } = useContext(UserDataContext);
if(!user) {
return <Loader />
}
let { username, email, fname, lname, country, location } = user;
const form = useForm({
initialValues: {
username, fname, email, lname, country, location
},
})
let fullName = (fname && lname) ? fname + ' ' + lname : null;
return (
<Stack>
<Flex gap="md" sx={{ display: 'flex', backgroundColor: '#272623', borderRadius: '5px' }} bg='gray' w="100%" p="30px">
@@ -15,29 +32,66 @@ const Profile = () => {
<div>
<Title>{username}</Title>
<Group position='left'>
<Text>{'-------'}</Text>
<Text>city</Text>
<Text>{fullName || '-------'},</Text>
<Text>{location || '-----'}</Text>
</Group>
</div>
</Flex>
<Flex gap="md" sx={{ display: 'flex', backgroundColor: '#272623', borderRadius: '5px', textAlign: 'left' }} bg='gray' w="100%" p="30px">
<Grid w='100%' gutter={30} columns={12}>
<Grid.Col span={6}>
<TextInput label='Username' />
</Grid.Col>
<Grid.Col span={6}>
<TextInput label='Email address' />
</Grid.Col>
<Grid.Col span={6}>
<TextInput label='First Name' />
</Grid.Col>
<Grid.Col span={6}>
<TextInput label='Last Name' />
</Grid.Col>
</Grid>
</Flex>
<Form action='/settings/profile' method='patch' >
<Flex gap="md" sx={{ display: 'flex', backgroundColor: '#272623', borderRadius: '5px', textAlign: 'left' }} bg='gray' w="100%" p="30px">
<Grid w='100%' gutter={30} columns={12}>
<Grid.Col span={6}>
<TextInput name='username' label='Username' readOnly value={form.values.username} />
</Grid.Col>
<Grid.Col span={6}>
<TextInput name='email' label='Email address' readOnly value={form.values.email} />
</Grid.Col>
<Grid.Col span={6}>
<TextInput name='fname' label='First Name' {...form.getInputProps('fname')} />
</Grid.Col>
<Grid.Col span={6}>
<TextInput name='lname' label='Last Name' {...form.getInputProps('lname')} />
</Grid.Col>
<Grid.Col span={6}>
<TextInput name='location' label='City' {...form.getInputProps('location')} />
</Grid.Col>
<Grid.Col span={6}>
<TextInput name='country' label='Country' {...form.getInputProps('country')} />
</Grid.Col>
</Grid>
<Group>
<Button onClick={form.reset} color='dark'>Cancel</Button>
<Button type='submit' color='lime'>Save</Button>
</Group>
</Flex>
</Form>
</Stack>
)
}
export const action = async ({ request }) => {
const data = await request.formData();
let url = `${import.meta.env.VITE_BACKEND_HOST}/api/user/${getUserData().id}`
const reqBody = {
fname: data.get('fname'), lname: data.get('lname'), country: data.get('country'), location: data.get('location')
}
console.log(reqBody)
const response = await fetch(url, {
body: JSON.stringify(reqBody),
method: 'PATCH',
headers: {
Authorization: `Bearer ${getAuthToken()}`,
'Content-Type':'application/json'
}
})
const resData = await response.json();
console.log(resData)
if (!resData.success) {
return resData.error;
} else return null;
}
export default Profile
+2 -2
View File
@@ -1,5 +1,5 @@
import { Box, Flex, NavLink, Stack, Text, ThemeIcon, Title } from '@mantine/core'
import { ColorWheelIcon, GearIcon, GlobeIcon, LockOpen1Icon, LockOpen2Icon, PersonIcon } from '@radix-ui/react-icons'
import { Box, Flex, NavLink, Title } from '@mantine/core'
import { ColorWheelIcon, GearIcon, GlobeIcon, LockOpen1Icon, PersonIcon } from '@radix-ui/react-icons'
import React, { useState } from 'react'
import { Link, Outlet } from 'react-router-dom';