diff --git a/.prettierrc b/.prettierrc index 8605fe0..66bd8b8 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,11 +1,12 @@ { - "arrowParens": "always", - "semi": false, - "trailingComma": "none", - "tabWidth": 2, - "endOfLine": "auto", - "useTabs": false, - "singleQuote": true, - "printWidth": 120, - "jsxSingleQuote": true - } \ No newline at end of file + "arrowParens": "always", + "semi": false, + "trailingComma": "none", + "tabWidth": 2, + "endOfLine": "auto", + "useTabs": false, + "singleQuote": true, + "printWidth": 120, + "jsxSingleQuote": true, + "plugins": ["prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"] +} diff --git a/package.json b/package.json index 8779276..50dab81 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,8 @@ "eslint-plugin-react-refresh": "^0.4.9", "globals": "^15.9.0", "postcss": "^8.4.47", - "prettier": "^3.3.3", - "prettier-plugin-tailwindcss": "^0.6.8", + "prettier": "^3.4.1", + "prettier-plugin-tailwindcss": "^0.6.9", "tailwindcss": "^3.4.13", "typescript": "5.5.4", "typescript-eslint": "^8.0.1", diff --git a/src/App.tsx b/src/App.tsx index 3ca7cbc..3555f2a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,18 @@ +import { useContext, useEffect } from 'react' import useRouteElement from './useRouteElements' import { ToastContainer } from 'react-toastify' import 'react-toastify/dist/ReactToastify.css' +import { LocalStorageEventTarget } from './utils/auth' +import { AppContext } from './contexts/app.context' function App() { const routeElements = useRouteElement() + const { reset } = useContext(AppContext) + useEffect(() => { + LocalStorageEventTarget.addEventListener('clearFormLS', reset) + return () => { + LocalStorageEventTarget.removeEventListener('clearFormLS', reset) + } + }, [reset]) return (
{routeElements} diff --git a/src/components/NavHeader/NavHeader.tsx b/src/components/NavHeader/NavHeader.tsx index a5d90e9..09f7fac 100644 --- a/src/components/NavHeader/NavHeader.tsx +++ b/src/components/NavHeader/NavHeader.tsx @@ -3,12 +3,12 @@ import Popover from '../Popover' import { Link } from 'react-router-dom' import path from 'src/constants/path' import { AppContext } from 'src/contexts/app.context' -import { useMutation } from '@tanstack/react-query' +import { useMutation, useQueryClient } from '@tanstack/react-query' import authApi from 'src/apis/auth.api' -import { queryClient } from 'src/main' export default function NavHeader() { const { isAuthenticated, setIsAuthenticated, profile, setProfile } = useContext(AppContext) + const queryClient = useQueryClient() const loginAccountMutation = useMutation({ mutationFn: authApi.logout, onSuccess: () => { diff --git a/src/components/input/Input.tsx b/src/components/input/Input.tsx index 03f2e68..9b32068 100644 --- a/src/components/input/Input.tsx +++ b/src/components/input/Input.tsx @@ -21,7 +21,7 @@ export default function Input({ const registerResult = register && name ? register(name, rules) : {} return (
- +
{errorMessage}
) diff --git a/src/constants/path.ts b/src/constants/path.ts index cc4c472..8e5d518 100644 --- a/src/constants/path.ts +++ b/src/constants/path.ts @@ -1,9 +1,10 @@ -import ProductDetail from 'src/pages/ProductDetail' - const path = { home: '/', - profile: '/profile', + profile: '/user/profile', + changPassword: '/user/password', + historyPurchase: '/user/purchase', login: '/login', + user: '/user', register: '/register', logout: '/logout', productDetail: ':nameId', diff --git a/src/contexts/app.context.tsx b/src/contexts/app.context.tsx index 9c6bc55..dade903 100644 --- a/src/contexts/app.context.tsx +++ b/src/contexts/app.context.tsx @@ -7,24 +7,30 @@ interface AppContextInterface { setIsAuthenticated: React.Dispatch> profile: User | null setProfile: React.Dispatch> + reset: () => void } const initialAppContext: AppContextInterface = { isAuthenticated: Boolean(getRefreshTokenFormLS()), setIsAuthenticated: () => null, profile: getProfilefromLS(), - setProfile: () => null + setProfile: () => null, + reset: () => null } export const AppContext = createContext(initialAppContext) export const AppProvider = ({ children }: { children: React.ReactNode }) => { const [isAuthenticated, setIsAuthenticated] = useState(initialAppContext.isAuthenticated) const [profile, setProfile] = useState(initialAppContext.profile) + const reset = () => { + setIsAuthenticated(false), setProfile(null) + } return ( {children} diff --git a/src/main.tsx b/src/main.tsx index e54deca..bc91816 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -6,10 +6,11 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { AppProvider } from './contexts/app.context' -export const queryClient = new QueryClient({ +const queryClient = new QueryClient({ defaultOptions: { queries: { - refetchOnWindowFocus: false + refetchOnWindowFocus: false, + retry: 0 } } }) diff --git a/src/pages/ProductDetail/ProductDetail.tsx b/src/pages/ProductDetail/ProductDetail.tsx index 98e582d..cdc2f40 100644 --- a/src/pages/ProductDetail/ProductDetail.tsx +++ b/src/pages/ProductDetail/ProductDetail.tsx @@ -1,4 +1,4 @@ -import { useMutation, useQuery } from '@tanstack/react-query' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useParams } from 'react-router-dom' import productApi from 'src/apis/product.api' import ProductRating from 'src/components/ProductRating' @@ -11,10 +11,10 @@ import 'react-medium-image-zoom/dist/styles.css' import Product from '../ProductList/components/Product' import QuantityController from 'src/components/QuantityController' import cartApi from 'src/apis/cart.api' -import { queryClient } from 'src/main' import Toast from 'src/components/Toast' export default function ProductDetail() { + const queryClient = useQueryClient() const [buyCount, setBuyCount] = useState(1) const { nameId } = useParams() const id = getIdFromNameId(nameId as string) diff --git a/src/pages/Profile/Profile.tsx b/src/pages/Profile/Profile.tsx deleted file mode 100644 index ca9aafe..0000000 --- a/src/pages/Profile/Profile.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Profile() { - return
Profile
-} diff --git a/src/pages/Register/Register.tsx b/src/pages/Register/Register.tsx index f4a97c3..3dbcd60 100644 --- a/src/pages/Register/Register.tsx +++ b/src/pages/Register/Register.tsx @@ -30,7 +30,7 @@ export default function Register() { //const rules = getRules(getValues) const onSubmit = handleSubmit((data) => { registerAccountMutation.mutate(data, { - onSuccess: (data) => { + onSuccess: () => { setIsAuthenticated(true) navigate('/') }, diff --git a/src/pages/User/UserLayout/UserLayout.tsx b/src/pages/User/UserLayout/UserLayout.tsx new file mode 100644 index 0000000..e74884f --- /dev/null +++ b/src/pages/User/UserLayout/UserLayout.tsx @@ -0,0 +1,21 @@ +import UserSideNav from '../components/UserSideNav' + +interface LayoutProps { + children?: React.ReactNode +} + +export default function UserLayout({ children }: LayoutProps) { + return ( +
+
+
+
+ +
+ +
{children}
+
+
+
+ ) +} diff --git a/src/pages/User/UserLayout/index.ts b/src/pages/User/UserLayout/index.ts new file mode 100644 index 0000000..e7b0d9f --- /dev/null +++ b/src/pages/User/UserLayout/index.ts @@ -0,0 +1,3 @@ +import UserLayout from './UserLayout' + +export default UserLayout diff --git a/src/pages/User/components/UserSideNav/UserSideNav.tsx b/src/pages/User/components/UserSideNav/UserSideNav.tsx new file mode 100644 index 0000000..f7c33f9 --- /dev/null +++ b/src/pages/User/components/UserSideNav/UserSideNav.tsx @@ -0,0 +1,66 @@ +import { Link } from 'react-router-dom' +import path from 'src/constants/path' + +export default function UserSideNav() { + return ( +
+
+ + avatar + +
+
Nguyễn Nhâm Ngọ
+ + + + + Sửa hồ sơ + +
+
+
+ +
+ +
+ Tải khoản của tôi + + +
+ +
+ Đổi mật khẩu + + +
+ +
+ Đơn mua + +
+
+ ) +} diff --git a/src/pages/User/components/UserSideNav/index.ts b/src/pages/User/components/UserSideNav/index.ts new file mode 100644 index 0000000..dc9e120 --- /dev/null +++ b/src/pages/User/components/UserSideNav/index.ts @@ -0,0 +1,3 @@ +import UserSideNav from './UserSideNav' + +export default UserSideNav diff --git a/src/pages/User/pages/ChangePassword/ChangePassword.tsx b/src/pages/User/pages/ChangePassword/ChangePassword.tsx new file mode 100644 index 0000000..95a1cae --- /dev/null +++ b/src/pages/User/pages/ChangePassword/ChangePassword.tsx @@ -0,0 +1,3 @@ +export default function ChangePassword() { + return
ChangePassword
+} diff --git a/src/pages/User/pages/ChangePassword/index.ts b/src/pages/User/pages/ChangePassword/index.ts new file mode 100644 index 0000000..13deed7 --- /dev/null +++ b/src/pages/User/pages/ChangePassword/index.ts @@ -0,0 +1,3 @@ +import ChangePassword from './ChangePassword' + +export default ChangePassword diff --git a/src/pages/User/pages/HistoryPurchase/HistoryPurchase.tsx b/src/pages/User/pages/HistoryPurchase/HistoryPurchase.tsx new file mode 100644 index 0000000..a0bdc01 --- /dev/null +++ b/src/pages/User/pages/HistoryPurchase/HistoryPurchase.tsx @@ -0,0 +1,3 @@ +export default function HistoryPurchase() { + return
HistoryPurchase
+} diff --git a/src/pages/User/pages/HistoryPurchase/index.ts b/src/pages/User/pages/HistoryPurchase/index.ts new file mode 100644 index 0000000..a22f138 --- /dev/null +++ b/src/pages/User/pages/HistoryPurchase/index.ts @@ -0,0 +1,3 @@ +import HistoryPurchase from './HistoryPurchase' + +export default HistoryPurchase diff --git a/src/pages/User/pages/Profile/Profile.tsx b/src/pages/User/pages/Profile/Profile.tsx new file mode 100644 index 0000000..7f1ead1 --- /dev/null +++ b/src/pages/User/pages/Profile/Profile.tsx @@ -0,0 +1,88 @@ +import Input from 'src/components/Input' + +export default function Profile() { + return ( +
+
+

Hồ sở của tôi

+
Quản lý thông tin hồ sở để bảo mật tài khoản
+
+
+
+
+
Email
+
+
nhamngoo*****@gmail.com
+
+
+
+
Họ và tên
+
+ +
+
+
+
Số điện thoại
+
+ +
+
+
+
Địa chỉ
+
+ +
+
+
+
Ngày sinh
+
+ + + +
+
+
+
+
+
+ avatar +
+ + +
+
Dung lương file tối đa 1 MB
+
Định dạng: ,JPG, .JPEG, .PNG
+
+
+
+
+
+ ) +} diff --git a/src/pages/Profile/index.ts b/src/pages/User/pages/Profile/index.ts similarity index 100% rename from src/pages/Profile/index.ts rename to src/pages/User/pages/Profile/index.ts diff --git a/src/useRouteElements.tsx b/src/useRouteElements.tsx index aee5fd4..9a2e3a5 100644 --- a/src/useRouteElements.tsx +++ b/src/useRouteElements.tsx @@ -2,7 +2,6 @@ import { Navigate, Outlet, useRoutes } from 'react-router-dom' import ProductList from './pages/ProductList' import Login from './pages/Login' import Register from './pages/Register' -import Profile from './pages/Profile' import { useContext } from 'react' import { AppContext } from './contexts/app.context' import path from './constants/path' @@ -11,6 +10,10 @@ import Cart from './pages/Cart' import RegisterHeader from './components/RegisterHeader' import Layout from './layouts' import CartHeader from './components/CartHeader' +import UserLayout from './pages/User/UserLayout' +import ChangePassword from './pages/User/pages/ChangePassword' +import HistoryPurchase from './pages/User/pages/HistoryPurchase' +import Profile from './pages/User/pages/Profile' function ProtectedRouted() { const { isAuthenticated } = useContext(AppContext) @@ -41,14 +44,52 @@ export default function useRouteElements() { ) }, { - path: '', + path: path.profile, + element: , + children: [ + { + path: path.profile, + element: ( + + + + + + ) + } + ] + }, + { + path: path.user, element: , children: [ { path: path.profile, element: ( - + + + + + ) + }, + { + path: path.changPassword, + element: ( + + + + + + ) + }, + { + path: path.historyPurchase, + element: ( + + + + ) } diff --git a/src/utils/auth.ts b/src/utils/auth.ts index da09416..b0a67f7 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,11 +1,13 @@ import { User } from 'src/types/user.type' -import { json } from 'stream/consumers' +export const LocalStorageEventTarget = new EventTarget() export const setRefreshTokenToLS = (refresh_token: string) => { localStorage.setItem('refresh_token', refresh_token) } export const clearFormLS = () => { localStorage.removeItem('refresh_token'), localStorage.removeItem('profile') + const clearLSEvent = new Event('clearFormLS') + LocalStorageEventTarget.dispatchEvent(clearLSEvent) } export const getRefreshTokenFormLS = () => { return localStorage.getItem('refresh_token') || '' diff --git a/src/utils/http.ts b/src/utils/http.ts index 8f0b6fb..eb5afa5 100644 --- a/src/utils/http.ts +++ b/src/utils/http.ts @@ -56,6 +56,9 @@ class Http { const message = data.Message || error.message toast.error(message) } + if (error.response?.status === HttpStatusCode.Unauthorized) { + clearFormLS() + } return Promise.reject(error) } ) diff --git a/tailwind.config.js b/tailwind.config.js index 10736ec..f7e8832 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,7 +1,9 @@ const plugin = require('tailwindcss/plugin') /** @type {import('tailwindcss').Config}*/ module.exports = { - content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'], + content: [ + './src/**/*.{html,js,jsx,ts,tsx}' // Đảm bảo rằng Tailwind có thể quét tất cả các tệp nơi bạn sử dụng các lớp CSS + ], corePlugins: { container: false }, diff --git a/tsconfig.json b/tsconfig.json index 1ffef60..d32ff68 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,4 @@ { "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] } diff --git a/yarn.lock b/yarn.lock index 47fa25d..2c62d37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2826,15 +2826,15 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier-plugin-tailwindcss@^0.6.8: - version "0.6.8" - resolved "https://registry.yarnpkg.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.8.tgz#8a178e1679e3f941cc9de396f109c6cffea676d8" - integrity sha512-dGu3kdm7SXPkiW4nzeWKCl3uoImdd5CTZEJGxyypEPL37Wj0HT2pLqjrvSei1nTeuQfO4PUfjeW5cTUNRLZ4sA== - -prettier@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" - integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== +prettier-plugin-tailwindcss@^0.6.9: + version "0.6.9" + resolved "https://registry.yarnpkg.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.9.tgz#db84c32918eae9b44e5a5f0aa4d1249cc39fa739" + integrity sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg== + +prettier@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.1.tgz#e211d451d6452db0a291672ca9154bc8c2579f7b" + integrity sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg== promise@^7.1.1: version "7.3.1"