Skip to content

Commit

Permalink
Merge pull request #63 from FRIED-NOTE/58-improvement-add-userprofile…
Browse files Browse the repository at this point in the history
…-component

58 improvement add userprofile component
  • Loading branch information
strawji02 authored Nov 23, 2023
2 parents f0895c0 + 80026c2 commit ab0413a
Show file tree
Hide file tree
Showing 16 changed files with 246 additions and 30 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
"dependencies": {
"@emotion/react": "^11.11.1",
"@types/textarea-caret": "^3.0.3",
"axios": "^1.6.2",
"framer-motion": "^10.16.4",
"hangul-js": "^0.2.6",
"immer": "^10.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.15.0",
"textarea-caret": "^3.1.0"
"textarea-caret": "^3.1.0",
"zustand": "^4.4.6"
},
"devDependencies": {
"@commitlint/cli": "^17.6.7",
Expand Down
6 changes: 3 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import Layout from './layout';

function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<Layout>{useRoutes(routes)}</Layout>
</Suspense>
<Layout>
<Suspense fallback={<p>Loading...</p>}>{useRoutes(routes)}</Suspense>
</Layout>
);
}

Expand Down
3 changes: 3 additions & 0 deletions src/apis/instance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import axios from 'axios';

export default axios.create({});
46 changes: 46 additions & 0 deletions src/auth/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useContext, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { AuthContext } from '.';
import { UserSetPropType, useUserState } from './stores';

export const useAuth = () => {
const { removeUser, setUser } = useUserState();
const { token, setToken } = useContext(AuthContext);

const setLogin = (user: UserSetPropType & { token: string }) => {
setUser(user);
setToken(user.token);
};

const setLogout = () => {
removeUser();
setToken(undefined);
};

const updateToken = (token: string) => setToken(token);

return { token, setLogin, setLogout, updateToken };
};

export const useCurrentUser = ({
redirectTo = '',
redirectIfFound = false,
} = {}) => {
const { user } = useUserState();
const navigate = useNavigate();

useEffect(() => {
if (!redirectTo || !user) return;

if (
// If redirectTo is set, redirect if the user was not found.
(redirectTo && !redirectIfFound && !user?.isLoggedIn) ||
// If redirectIfFound is also set, redirect if the user was found
(redirectIfFound && user?.isLoggedIn)
) {
navigate(redirectTo);
}
}, [user, redirectIfFound, redirectTo]);

return { user };
};
62 changes: 62 additions & 0 deletions src/auth/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ReactNode, createContext, useEffect, useMemo, useState } from 'react';
import instance from '../apis/instance';
import { useUserState } from './stores';

export const AuthContext = createContext<{
token: string | undefined;
setToken: (token: string | undefined) => void;
}>({
token: undefined,
setToken: () => {
throw new Error('setToken function must be overridden');
},
});

export interface AuthProviderProps {
children: ReactNode;
}

const mockAuthUser = (token: string) => ({
name: 'hi',
email: '',
profileImage: '',
});

function AuthProvider({ children, ...props }: AuthProviderProps) {
const [token, setToken_] = useState(
localStorage.getItem('token') || undefined,
);

const { removeUser, setUser } = useUserState();

const setToken = (token: string | undefined) => {
setToken_(token);
};

useEffect(() => {
if (token) {
instance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
localStorage.setItem('token', token);
} else {
delete instance.defaults.headers.common['Authorization'];
localStorage.removeItem('token');
}
}, [token]);

useEffect(() => {
if (!token) {
removeUser();
return;
}
const userData = mockAuthUser(token);
if (userData) setUser(userData);
}, []);

const contextValue = useMemo(() => ({ token, setToken }), [token]);

return (
<AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
);
}

export default AuthProvider;
38 changes: 38 additions & 0 deletions src/auth/stores.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

export interface UserType {
name: string | null;
email: string | null;
profileImage: string | null;
isLoggedIn: boolean;
}

export type UserSetPropType = Omit<UserType, 'isLoggedIn'>;

interface UserState {
user: UserType;
setUser: (user: UserSetPropType) => void;
removeUser: () => void;
}

export const useUserState = create<UserState>()(
devtools((set) => ({
user: {
isLoggedIn: false,
name: null,
email: null,
profileImage: null,
},
setUser: (user) => set(() => ({ user: { ...user, isLoggedIn: true } })),
removeUser: () =>
set(() => ({
user: {
isLoggedIn: false,
name: null,
email: null,
profileImage: null,
},
})),
})),
);
11 changes: 4 additions & 7 deletions src/components/TopNav/TopNav.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { UserType } from '@/auth/stores';
import DesignSystem from '@/utils/designSystem';
import globalStyles from '@/utils/styles';
import { Group, Stack, Stroke, Typography } from '@base';
Expand Down Expand Up @@ -35,13 +36,9 @@ export type NavBarMenuItemType = {
label: string;
path: string;
};
export interface UserType {
img?: string;
name: string;
email: string;
}

interface TopNavProps {
user: UserType | null;
user: UserType;
navBarMenu: NavBarMenuItemType[];
onLoginClick?: () => void;
onLogoutClick: () => void;
Expand All @@ -66,7 +63,7 @@ function TopNav({
</div>
<TopNavTabs>{navBarMenu}</TopNavTabs>
</Group>
{user ? (
{user.isLoggedIn ? (
<TopNavUser user={user}>
<MenuButtonItem label="마이 레시피 보기" />
<MenuButtonItem label="피드백 남기기" />
Expand Down
8 changes: 4 additions & 4 deletions src/components/TopNav/TopNavUser.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import DefaultProfile from '@/assets/default-profile.svg';
import IconDropdown from '@/assets/icon-dropdown.svg';
import IconSettings from '@/assets/icon-settings.svg';
import { UserType } from '@/auth/stores';
import DesignSystem from '@/utils/designSystem';
import globalStyles from '@/utils/styles';
import { Group, Popover, Stack, Typography } from '@base';
import UserProfileImg from '@copmonents/UserProfileImg';
import { css } from '@emotion/react';
import { ReactNode } from 'react';
import { UserType } from './TopNav';

const styles = {
trigger: globalStyles.button,
Expand All @@ -32,7 +32,7 @@ function TopNavUser({ user, children }: TopNavUserProps) {
<Popover.Trigger>
<div>
<Group nowrap gap={14} css={styles.trigger}>
<img src={user.img || DefaultProfile} />
<UserProfileImg />
<img src={IconDropdown} />
</Group>
</div>
Expand All @@ -42,7 +42,7 @@ function TopNavUser({ user, children }: TopNavUserProps) {
<Stack>
<Group position="apart" css={styles.content.user}>
<Group gap={12}>
<img src={user.img || DefaultProfile} />
<UserProfileImg />
<Stack justify="left">
<Typography variant="subtitle">{user.name}</Typography>
<Typography variant="info" color="#848484">
Expand Down
2 changes: 2 additions & 0 deletions src/components/TopNav/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const meta: Meta<typeof TopNav> = {
user: {
email: '[email protected]',
name: '테스트 유저',
isLoggedIn: true,
profileImage: '',
},
navBarMenu: [
{ label: 'MY RECIPE', path: '/mypage' },
Expand Down
17 changes: 17 additions & 0 deletions src/components/UserProfileImg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import DefaultProfile from '@/assets/default-profile.svg';
import { useCurrentUser } from '@/auth/hooks';
import { ImgHTMLAttributes } from 'react';

export interface UserProfileProps extends ImgHTMLAttributes<HTMLImageElement> {
width?: number;
}

function UserProfileImg({ width, ...props }: UserProfileProps) {
const { user } = useCurrentUser();

return (
<img src={user.profileImage || DefaultProfile} css={{ width }} {...props} />
);
}

export default UserProfileImg;
19 changes: 14 additions & 5 deletions src/layout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useUserData } from '@/utils/hooks';
import { useAuth } from '@/auth/hooks';
import { useUserState } from '@/auth/stores';
import globalStyles from '@/utils/styles';
import TopNav, { UserType } from '@copmonents/TopNav/TopNav';
import TopNav from '@copmonents/TopNav/TopNav';
import { css } from '@emotion/react';
import { ReactNode } from 'react';
import { useLocation } from 'react-router-dom';
Expand All @@ -24,7 +25,8 @@ export interface LayoutProps {
const EXCEPT_PATH = ['/mypage/initial', '/post'];

function Layout({ children, ...props }: LayoutProps) {
const [user, setUser] = useUserData<UserType>();
const { user } = useUserState();
const { setLogin, setLogout } = useAuth();
const locaton = useLocation();

const isExceptPath = EXCEPT_PATH.includes(locaton.pathname);
Expand All @@ -39,8 +41,15 @@ function Layout({ children, ...props }: LayoutProps) {
{ label: 'INVENTORY', path: '/inventory' },
{ label: 'SEARCH', path: '/search' },
]}
onLoginClick={() => setUser({ email: '[email protected]', name: 'testUser' })}
onLogoutClick={() => setUser(null)}
onLoginClick={() =>
setLogin({
name: 'hi',
email: '',
profileImage: '',
token: 'temp token',
})
}
onLogoutClick={setLogout}
/>
<div css={[styles.body.default, !isExceptPath && styles.body.mediaQuery]}>
{children}
Expand Down
9 changes: 6 additions & 3 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom';
import App from './App';
import AuthProvider from './auth';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Router>
<App />
</Router>
<AuthProvider>
<Router>
<App />
</Router>
</AuthProvider>
</React.StrictMode>,
);
4 changes: 2 additions & 2 deletions src/pages/mypage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import EmptyCheckbox from '@/assets/checkbox-empty.svg';
import FillCheckbox from '@/assets/checkbox-fill.svg';
import DefaultProfile from '@/assets/default-profile.svg';
import EditIcon from '@/assets/icon-edit-box.svg';
import BackgroundImg from '@/assets/newmypage-background.png';
import DesignSystem from '@/utils/designSystem';
Expand All @@ -9,6 +8,7 @@ import { Group, Stack, Typography } from '@base';
import Button from '@copmonents/Button';
import Modal from '@copmonents/Modal';
import ToggleButton from '@copmonents/Toggle/ToggleButton';
import UserProfileImg from '@copmonents/UserProfileImg';
import { css } from '@emotion/react';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
Expand Down Expand Up @@ -180,7 +180,7 @@ function MyPage({ ...props }) {
<Stack spacing={73}>
<Group position="apart">
<Group gap={16}>
<img src={DefaultProfile} css={{ width: 36 }} />
<UserProfileImg width={36} />
<Typography variant="headline">해피밀</Typography>
</Group>
<ToggleButton tabs={['모든 레시피', '내 레시피만']} />
Expand Down
6 changes: 4 additions & 2 deletions src/pages/post/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import DefaultProfile from '@/assets/default-profile.svg';
import { ReactComponent as IconDropdown } from '@/assets/icon-dropdown.svg';
import { useCurrentUser } from '@/auth/hooks';
import DesignSystem from '@/utils/designSystem';
import globalStyles from '@/utils/styles';
import { Group, Stack, Typography } from '@base';
import ToggleButton from '@copmonents/Toggle/ToggleButton';
import UserProfileImg from '@copmonents/UserProfileImg';
import { css } from '@emotion/react';
import Editor from './components/Editor';

Expand Down Expand Up @@ -38,6 +39,7 @@ const styles = {
export interface PostPageProps {}

function PostPage({ ...props }: PostPageProps) {
useCurrentUser({ redirectTo: '/' });
return (
<>
<Group position="apart" css={styles.top.root}>
Expand All @@ -55,7 +57,7 @@ function PostPage({ ...props }: PostPageProps) {
<div css={styles.root}>
<Stack spacing={28}>
<Group gap={16}>
<img src={DefaultProfile} css={{ width: 36 }} />
<UserProfileImg width={36} />
<Typography variant="headline">해피밀</Typography>
</Group>
<Editor onChange={(data) => data} />
Expand Down
Loading

0 comments on commit ab0413a

Please sign in to comment.