feature: Profile page update user info
This commit is contained in:
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user