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 (
)
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 (
+
+ )
+}
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 (
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
+
+
+
+
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"