Spaces:
Sleeping
Sleeping
Update: reformating frontend
Browse files- frontend/.gitignore +2 -1
- frontend/public/about-us.jpg +0 -0
- frontend/public/header.jpg +0 -0
- frontend/src/index.js +82 -38
- frontend/src/molecules/AboutUsSection.js +39 -14
- frontend/src/molecules/AdminNavBar.js +65 -0
- frontend/src/molecules/ContactSection.js +4 -2
- frontend/src/molecules/Navbar.js +2 -2
- frontend/src/organisms/MenuSection.js +1 -1
- frontend/src/organisms/NewsSection.js +1 -1
- frontend/src/organisms/StoreSection.js +2 -2
- frontend/src/pages/AdminFeedPage.js +21 -0
- frontend/src/pages/{AdminHomePage.js → AdminLoginPage.js} +0 -0
- frontend/src/pages/AdminMenuPage.js +21 -0
- frontend/src/pages/AdminOrderPage.js +21 -0
- frontend/src/pages/AdminSchedulePage.js +21 -0
- frontend/src/pages/AdminStaffPage.js +21 -0
- frontend/src/pages/AdminSummaryPage.js +21 -0
- frontend/src/pages/LoginPage.js +8 -7
- frontend/src/pages/RegisterPage.js +45 -7
- frontend/src/styles/styles.css +170 -0
- frontend/src/templates/AdminTemplate.js +16 -0
frontend/.gitignore
CHANGED
@@ -24,4 +24,5 @@ yarn-error.log*
|
|
24 |
|
25 |
.env
|
26 |
note_dev.txt
|
27 |
-
object.json
|
|
|
|
24 |
|
25 |
.env
|
26 |
note_dev.txt
|
27 |
+
object.json
|
28 |
+
.json
|
frontend/public/about-us.jpg
ADDED
frontend/public/header.jpg
ADDED
frontend/src/index.js
CHANGED
@@ -1,8 +1,9 @@
|
|
1 |
import React from 'react';
|
2 |
import ReactDOM from 'react-dom/client';
|
3 |
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
4 |
-
import './styles/index.css';
|
5 |
import 'bootstrap/dist/css/bootstrap.min.css';
|
|
|
|
|
6 |
import ErrorPage from './pages/ErrorPage';
|
7 |
import HomePage from './pages/HomePage';
|
8 |
import reportWebVitals from './reportWebVitals';
|
@@ -13,48 +14,91 @@ import MenuPage from './pages/MenuPage';
|
|
13 |
import CartPage from './pages/CartPage';
|
14 |
import UserInfoPage from './pages/UserInfoPage';
|
15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
const router = createBrowserRouter([
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
]);
|
52 |
|
53 |
const root = ReactDOM.createRoot(document.getElementById('root'));
|
54 |
root.render(
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
);
|
59 |
|
60 |
// If you want to start measuring performance in your app, pass a function
|
|
|
1 |
import React from 'react';
|
2 |
import ReactDOM from 'react-dom/client';
|
3 |
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
|
|
4 |
import 'bootstrap/dist/css/bootstrap.min.css';
|
5 |
+
import './styles/index.css';
|
6 |
+
import './styles/styles.css';
|
7 |
import ErrorPage from './pages/ErrorPage';
|
8 |
import HomePage from './pages/HomePage';
|
9 |
import reportWebVitals from './reportWebVitals';
|
|
|
14 |
import CartPage from './pages/CartPage';
|
15 |
import UserInfoPage from './pages/UserInfoPage';
|
16 |
|
17 |
+
import AdminSummaryPage from './pages/AdminSummaryPage';
|
18 |
+
import AdminFeedPage from './pages/AdminFeedPage';
|
19 |
+
import AdminMenuPage from './pages/AdminMenuPage';
|
20 |
+
import AdminStaffPage from './pages/AdminStaffPage';
|
21 |
+
import AdminOrderPage from './pages/AdminOrderPage';
|
22 |
+
import AdminSchedulePage from './pages/AdminSchedulePage';
|
23 |
+
|
24 |
const router = createBrowserRouter([
|
25 |
+
{
|
26 |
+
path: "/",
|
27 |
+
element: <HomePage />,
|
28 |
+
errorElement: <ErrorPage />,
|
29 |
+
},
|
30 |
+
{
|
31 |
+
path: "/login",
|
32 |
+
element: <LoginPage />,
|
33 |
+
errorElement: <ErrorPage />
|
34 |
+
},
|
35 |
+
{
|
36 |
+
path: "/register",
|
37 |
+
element: <RegisterPage />,
|
38 |
+
errorElement: <ErrorPage />
|
39 |
+
},
|
40 |
+
{
|
41 |
+
path: "/news",
|
42 |
+
element: <NewsPage />,
|
43 |
+
errorElement: <ErrorPage />
|
44 |
+
},
|
45 |
+
{
|
46 |
+
path: "/menu",
|
47 |
+
element: <MenuPage />
|
48 |
+
},
|
49 |
+
{
|
50 |
+
path: "/cart",
|
51 |
+
element: <CartPage />,
|
52 |
+
errorElement: <ErrorPage />
|
53 |
+
},
|
54 |
+
{
|
55 |
+
path: "/userinfo",
|
56 |
+
element: <UserInfoPage />,
|
57 |
+
errorElement: <ErrorPage />
|
58 |
+
},
|
59 |
+
|
60 |
+
{
|
61 |
+
path: "/admin",
|
62 |
+
element: <AdminSummaryPage />,
|
63 |
+
errorElement: <ErrorPage />
|
64 |
+
},
|
65 |
+
{
|
66 |
+
path: "/admin-summary",
|
67 |
+
element: <AdminSummaryPage />,
|
68 |
+
errorElement: <ErrorPage />
|
69 |
+
},
|
70 |
+
{
|
71 |
+
path: "/admin-feed",
|
72 |
+
element: <AdminFeedPage />,
|
73 |
+
errorElement: <ErrorPage />
|
74 |
+
},
|
75 |
+
{
|
76 |
+
path: "/admin-schedule",
|
77 |
+
element: <AdminSchedulePage />,
|
78 |
+
errorElement: <ErrorPage />
|
79 |
+
},
|
80 |
+
{
|
81 |
+
path: "/admin-menu",
|
82 |
+
element: <AdminMenuPage />,
|
83 |
+
errorElement: <ErrorPage />
|
84 |
+
},
|
85 |
+
{
|
86 |
+
path: "/admin-staff",
|
87 |
+
element: <AdminStaffPage />,
|
88 |
+
errorElement: <ErrorPage />
|
89 |
+
},
|
90 |
+
{
|
91 |
+
path: "/admin-orders",
|
92 |
+
element: <AdminOrderPage />,
|
93 |
+
errorElement: <ErrorPage />
|
94 |
+
}
|
95 |
]);
|
96 |
|
97 |
const root = ReactDOM.createRoot(document.getElementById('root'));
|
98 |
root.render(
|
99 |
+
<React.StrictMode>
|
100 |
+
<RouterProvider router={router} />
|
101 |
+
</React.StrictMode>
|
102 |
);
|
103 |
|
104 |
// If you want to start measuring performance in your app, pass a function
|
frontend/src/molecules/AboutUsSection.js
CHANGED
@@ -1,21 +1,46 @@
|
|
1 |
import { Container, Row, Col } from "react-bootstrap";
|
2 |
|
3 |
-
export default function AboutUsSection
|
4 |
return (<>
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
-
|
16 |
-
Chào mừng mọi người đến với Cats Shop, phương châm của chúng tôi là code at the sink and sleep at the sea.
|
17 |
-
</Col>
|
18 |
-
</Row>
|
19 |
-
</Container>
|
20 |
</>)
|
21 |
}
|
|
|
1 |
import { Container, Row, Col } from "react-bootstrap";
|
2 |
|
3 |
+
export default function AboutUsSection() {
|
4 |
return (<>
|
5 |
+
<Container id="about-us" className="mb-5" style={{
|
6 |
+
maxWidth: "100%",
|
7 |
+
backgroundImage: "linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url('/about-us.jpg')", // Thay đường dẫn ảnh nền
|
8 |
+
backgroundSize: 'cover',
|
9 |
+
backgroundPosition: 'center',
|
10 |
+
backgroundAttachment: 'fixed',
|
11 |
+
width: '100%',
|
12 |
+
padding: '50px 0',
|
13 |
+
}}>
|
14 |
+
<div className="d-flex justify-content-center align-items-center" style={{
|
15 |
+
maxWidth: "90%",
|
16 |
+
minHeight: "50vh"
|
17 |
+
}} >
|
18 |
|
19 |
+
<Row className="align-items-center">
|
20 |
+
{/* <Col md={6}>
|
21 |
+
<img className="img-fluid" src="/cats-logo.png" alt="" style={{ width: "100%", height: "auto" }}></img>
|
22 |
+
</Col> */}
|
23 |
+
<Col md={3}></Col>
|
24 |
+
<Col md={6} className="align-items-center">
|
25 |
+
<h1 style={{
|
26 |
+
fontWeight: 'bold',
|
27 |
+
// fontFamily: 'Arial, sans-serif', /* Chọn font chữ */
|
28 |
+
'fontSize': '72px', /* Kích thước chữ cho h1 */
|
29 |
+
}}
|
30 |
+
className='mb-3'>CATS Shop
|
31 |
+
</h1>
|
32 |
+
<br />
|
33 |
+
<p style={{
|
34 |
+
// fontFamily: 'Arial, sans-serif', /* Chọn font chữ */
|
35 |
+
'fontSize': '36px', /* Kích thước chữ cho h1 */
|
36 |
+
}}>
|
37 |
+
Chào mừng mọi người đến với Cats Shop, phương châm của chúng tôi là code at the sink and sleep at the sea.
|
38 |
+
</p>
|
39 |
+
</Col>
|
40 |
+
<Col md={3}></Col>
|
41 |
+
</Row>
|
42 |
+
</div>
|
43 |
|
44 |
+
</Container>
|
|
|
|
|
|
|
|
|
45 |
</>)
|
46 |
}
|
frontend/src/molecules/AdminNavBar.js
CHANGED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {Container, Nav, Navbar, Button, Stack} from 'react-bootstrap'
|
2 |
+
import { useNavigate } from 'react-router-dom';
|
3 |
+
import DataStorage from '../organisms/DataStorage';
|
4 |
+
|
5 |
+
export default function AdminNavbar() {
|
6 |
+
|
7 |
+
const navigate = useNavigate();
|
8 |
+
|
9 |
+
function handleLogout() {
|
10 |
+
DataStorage.set('isLoggedInAdmin','false');
|
11 |
+
DataStorage.remove('accessTokenAdmin');
|
12 |
+
DataStorage.remove('roleAdmin');
|
13 |
+
DataStorage.remove('usernameAdmin');
|
14 |
+
DataStorage.remove('expiryDateAdmin');
|
15 |
+
navigate('/');
|
16 |
+
}
|
17 |
+
|
18 |
+
let username = DataStorage.get('usernameAdmin');
|
19 |
+
let isLoggedIn = DataStorage.get('isLoggedInAdmin');
|
20 |
+
|
21 |
+
let userContent;
|
22 |
+
if (isLoggedIn === 'true') {
|
23 |
+
userContent = <>
|
24 |
+
<Stack direction='horizontal' gap={2}>
|
25 |
+
<Button href="/userinfo" variant='primary'>
|
26 |
+
Xin chào, ADMIN {username}
|
27 |
+
</Button>
|
28 |
+
<Button onClick={handleLogout} variant='outline-primary'>
|
29 |
+
Đăng xuất
|
30 |
+
</Button>
|
31 |
+
</Stack>
|
32 |
+
</>
|
33 |
+
} else {
|
34 |
+
userContent = <></>
|
35 |
+
}
|
36 |
+
return (
|
37 |
+
<Navbar id="home" expand="lg" className="bg-body-tertiary" sticky='top' >
|
38 |
+
<Container>
|
39 |
+
<Navbar.Brand href="/admin">
|
40 |
+
<img
|
41 |
+
alt=""
|
42 |
+
src="/cats-logo.png"
|
43 |
+
width="30"
|
44 |
+
height="30"
|
45 |
+
className="d-inline-block align-top"
|
46 |
+
/>{' '}
|
47 |
+
CATS-Shop
|
48 |
+
</Navbar.Brand>
|
49 |
+
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
50 |
+
<Navbar.Collapse id="basic-navbar-nav">
|
51 |
+
<Nav className="me-auto text-center">
|
52 |
+
{/* These are the navigators */}
|
53 |
+
<Nav.Link href="/admin-summary">Dashboard</Nav.Link>
|
54 |
+
<Nav.Link href="/admin-feed">Quản lý bài đăng</Nav.Link>
|
55 |
+
<Nav.Link href="/admin-menu">Quản lý thực đơn</Nav.Link>
|
56 |
+
<Nav.Link href="/admin-staff">Quản lý nhân viên</Nav.Link>
|
57 |
+
<Nav.Link href="/admin-schedule">Lịch làm việc</Nav.Link>
|
58 |
+
<Nav.Link href="/admin-orders">Danh sách đơn hàng</Nav.Link>
|
59 |
+
</Nav>
|
60 |
+
{userContent}
|
61 |
+
</Navbar.Collapse>
|
62 |
+
</Container>
|
63 |
+
</Navbar>
|
64 |
+
)
|
65 |
+
}
|
frontend/src/molecules/ContactSection.js
CHANGED
@@ -2,10 +2,11 @@ import { Container, Row, Col } from "react-bootstrap";
|
|
2 |
|
3 |
export default function ContactSection() {
|
4 |
return (
|
5 |
-
<Container id="contact" className="my-5 align-items-center">
|
6 |
<h1 className="text-center mb-5">Thông tin liên hệ</h1>
|
7 |
-
<Row
|
8 |
{/* Cột 1: Số điện thoại liên hệ */}
|
|
|
9 |
<Col>
|
10 |
<h5>Liên hệ</h5>
|
11 |
<p>Số điện thoại: 0123-456-789</p>
|
@@ -26,6 +27,7 @@ export default function ContactSection() {
|
|
26 |
</a>
|
27 |
</p>
|
28 |
</Col>
|
|
|
29 |
</Row>
|
30 |
</Container>
|
31 |
);
|
|
|
2 |
|
3 |
export default function ContactSection() {
|
4 |
return (
|
5 |
+
<Container fluid id="contact" className="my-5 align-items-center">
|
6 |
<h1 className="text-center mb-5">Thông tin liên hệ</h1>
|
7 |
+
<Row>
|
8 |
{/* Cột 1: Số điện thoại liên hệ */}
|
9 |
+
<Col md={2}></Col>
|
10 |
<Col>
|
11 |
<h5>Liên hệ</h5>
|
12 |
<p>Số điện thoại: 0123-456-789</p>
|
|
|
27 |
</a>
|
28 |
</p>
|
29 |
</Col>
|
30 |
+
<Col md={1}></Col>
|
31 |
</Row>
|
32 |
</Container>
|
33 |
);
|
frontend/src/molecules/Navbar.js
CHANGED
@@ -49,8 +49,8 @@ export default function ANavbar() {
|
|
49 |
}
|
50 |
|
51 |
return (
|
52 |
-
<Navbar id="home" expand="lg" className="bg-body-tertiary" sticky='top' >
|
53 |
-
<Container>
|
54 |
<Navbar.Brand href="/">
|
55 |
<img
|
56 |
alt=""
|
|
|
49 |
}
|
50 |
|
51 |
return (
|
52 |
+
<Navbar id="home" expand="lg" className="bg-body-tertiary" sticky='top' style={{'min-height':'6vh'}}>
|
53 |
+
<Container fluid style={{maxWidth:"90%"}}>
|
54 |
<Navbar.Brand href="/">
|
55 |
<img
|
56 |
alt=""
|
frontend/src/organisms/MenuSection.js
CHANGED
@@ -18,7 +18,7 @@ function MenuSection() {
|
|
18 |
const countCarouselSlides = 4;
|
19 |
|
20 |
return (
|
21 |
-
<Container id="menu" className="text-center justify-content-center align-items-center my-5">
|
22 |
<h1 className='mb-5'>Menu</h1>
|
23 |
<Carousel activeIndex={index} onSelect={handleSelect} data-bs-theme="dark">
|
24 |
{Array.from({ length: countCarouselSlides }).map((_, slideIndex) => (
|
|
|
18 |
const countCarouselSlides = 4;
|
19 |
|
20 |
return (
|
21 |
+
<Container fluid id="menu" className="text-center justify-content-center align-items-center my-5" style={{maxWidth:"90%"}}>
|
22 |
<h1 className='mb-5'>Menu</h1>
|
23 |
<Carousel activeIndex={index} onSelect={handleSelect} data-bs-theme="dark">
|
24 |
{Array.from({ length: countCarouselSlides }).map((_, slideIndex) => (
|
frontend/src/organisms/NewsSection.js
CHANGED
@@ -10,7 +10,7 @@ function NewsSection() {
|
|
10 |
];
|
11 |
|
12 |
return (
|
13 |
-
<Container id="news" className="text-center justify-content-center align-items-center my-5">
|
14 |
<h1 className='mb-5'>Tin tức</h1>
|
15 |
<Row xs={1} md={2} xl={3} className="g-4">
|
16 |
{Array.from(newsFeeds).map((feed, idx) => (
|
|
|
10 |
];
|
11 |
|
12 |
return (
|
13 |
+
<Container fluid id="news" className="text-center justify-content-center align-items-center my-5" style={{maxWidth:"90%"}}>
|
14 |
<h1 className='mb-5'>Tin tức</h1>
|
15 |
<Row xs={1} md={2} xl={3} className="g-4">
|
16 |
{Array.from(newsFeeds).map((feed, idx) => (
|
frontend/src/organisms/StoreSection.js
CHANGED
@@ -10,7 +10,7 @@ function StoreSection() {
|
|
10 |
];
|
11 |
|
12 |
return (
|
13 |
-
<Container id="store" className="text-center justify-content-center align-items-center my-5">
|
14 |
<h1 className='mb-5'>Các chi nhánh</h1>
|
15 |
<Row className="align-items-center">
|
16 |
<Col xs={12} md={4} className="d-flex justify-content-center align-items-center">
|
@@ -18,7 +18,7 @@ function StoreSection() {
|
|
18 |
</Col>
|
19 |
|
20 |
<Col xs={12} md={8}>
|
21 |
-
<Container>
|
22 |
<Row xs={1} md={2} xl={3} className="g-4">
|
23 |
{Array.from(stores).map((store, idx) => (
|
24 |
<Col key={idx}>
|
|
|
10 |
];
|
11 |
|
12 |
return (
|
13 |
+
<Container fluid id="store" className="text-center justify-content-center align-items-center my-5" style={{maxWidth:"90%"}}>
|
14 |
<h1 className='mb-5'>Các chi nhánh</h1>
|
15 |
<Row className="align-items-center">
|
16 |
<Col xs={12} md={4} className="d-flex justify-content-center align-items-center">
|
|
|
18 |
</Col>
|
19 |
|
20 |
<Col xs={12} md={8}>
|
21 |
+
<Container fluid>
|
22 |
<Row xs={1} md={2} xl={3} className="g-4">
|
23 |
{Array.from(stores).map((store, idx) => (
|
24 |
<Col key={idx}>
|
frontend/src/pages/AdminFeedPage.js
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Container, Row, Col } from "react-bootstrap";
|
2 |
+
import AdminTemplate from "../templates/AdminTemplate";
|
3 |
+
|
4 |
+
export default function AdminFeedPage() {
|
5 |
+
return (
|
6 |
+
<AdminTemplate content={
|
7 |
+
(
|
8 |
+
<Container className='d-flex text-center align-items-center justify-content-center' style={{ 'min-height': '80vh' }}>
|
9 |
+
<Row>
|
10 |
+
<Col xs={12}>
|
11 |
+
<h1>This is a demo feed page</h1>
|
12 |
+
</Col>
|
13 |
+
<Col xs={12}>
|
14 |
+
<h3>In the future, we hope to view list of feed, add, edit or remove feeds.</h3>
|
15 |
+
</Col>
|
16 |
+
</Row>
|
17 |
+
</Container>
|
18 |
+
)
|
19 |
+
} />
|
20 |
+
);
|
21 |
+
}
|
frontend/src/pages/{AdminHomePage.js → AdminLoginPage.js}
RENAMED
File without changes
|
frontend/src/pages/AdminMenuPage.js
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Container, Row, Col } from "react-bootstrap";
|
2 |
+
import AdminTemplate from "../templates/AdminTemplate";
|
3 |
+
|
4 |
+
export default function AdminMenuPage() {
|
5 |
+
return (
|
6 |
+
<AdminTemplate content={
|
7 |
+
(
|
8 |
+
<Container className='d-flex text-center align-items-center justify-content-center' style={{ 'min-height': '80vh' }}>
|
9 |
+
<Row>
|
10 |
+
<Col xs={12}>
|
11 |
+
<h1>This is a demo menu page</h1>
|
12 |
+
</Col>
|
13 |
+
<Col xs={12}>
|
14 |
+
<h3>In the future, the menu should be retrieve from master-list, and branch admins can decide which item is available at this branch</h3>
|
15 |
+
</Col>
|
16 |
+
</Row>
|
17 |
+
</Container>
|
18 |
+
)
|
19 |
+
} />
|
20 |
+
);
|
21 |
+
}
|
frontend/src/pages/AdminOrderPage.js
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Container, Row, Col } from "react-bootstrap";
|
2 |
+
import AdminTemplate from "../templates/AdminTemplate";
|
3 |
+
|
4 |
+
export default function AdminOrderPage() {
|
5 |
+
return (
|
6 |
+
<AdminTemplate content={
|
7 |
+
(
|
8 |
+
<Container className='d-flex text-center align-items-center justify-content-center' style={{ 'min-height': '80vh' }}>
|
9 |
+
<Row>
|
10 |
+
<Col xs={12}>
|
11 |
+
<h1>This is a demo orders page</h1>
|
12 |
+
</Col>
|
13 |
+
<Col xs={12}>
|
14 |
+
<h3>In the future, we hope to view orders daily/monthly on this page, with search and filters</h3>
|
15 |
+
</Col>
|
16 |
+
</Row>
|
17 |
+
</Container>
|
18 |
+
)
|
19 |
+
} />
|
20 |
+
);
|
21 |
+
}
|
frontend/src/pages/AdminSchedulePage.js
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Container, Row, Col } from "react-bootstrap";
|
2 |
+
import AdminTemplate from "../templates/AdminTemplate";
|
3 |
+
|
4 |
+
export default function AdminSchedulePage() {
|
5 |
+
return (
|
6 |
+
<AdminTemplate content={
|
7 |
+
(
|
8 |
+
<Container className='d-flex text-center align-items-center justify-content-center' style={{ 'min-height': '80vh' }}>
|
9 |
+
<Row>
|
10 |
+
<Col xs={12}>
|
11 |
+
<h1>This is a demo schedule page</h1>
|
12 |
+
</Col>
|
13 |
+
<Col xs={12}>
|
14 |
+
<h3>In the future, we will view monthly work schedules from this page</h3>
|
15 |
+
</Col>
|
16 |
+
</Row>
|
17 |
+
</Container>
|
18 |
+
)
|
19 |
+
} />
|
20 |
+
);
|
21 |
+
}
|
frontend/src/pages/AdminStaffPage.js
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Container, Row, Col } from "react-bootstrap";
|
2 |
+
import AdminTemplate from "../templates/AdminTemplate";
|
3 |
+
|
4 |
+
export default function AdminStaffPage() {
|
5 |
+
return (
|
6 |
+
<AdminTemplate content={
|
7 |
+
(
|
8 |
+
<Container className='d-flex text-center align-items-center justify-content-center' style={{ 'min-height': '80vh' }}>
|
9 |
+
<Row>
|
10 |
+
<Col xs={12}>
|
11 |
+
<h1>This is a demo staff page</h1>
|
12 |
+
</Col>
|
13 |
+
<Col xs={12}>
|
14 |
+
<h3>In the future, we are about to view staffs info, add, edit, and remove staffs in this page</h3>
|
15 |
+
</Col>
|
16 |
+
</Row>
|
17 |
+
</Container>
|
18 |
+
)
|
19 |
+
} />
|
20 |
+
);
|
21 |
+
}
|
frontend/src/pages/AdminSummaryPage.js
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Container, Row, Col } from "react-bootstrap";
|
2 |
+
import AdminTemplate from "../templates/AdminTemplate";
|
3 |
+
|
4 |
+
export default function AdminSummaryPage() {
|
5 |
+
return (
|
6 |
+
<AdminTemplate content={
|
7 |
+
(
|
8 |
+
<Container className='d-flex text-center align-items-center justify-content-center' style={{ 'min-height': '80vh' }}>
|
9 |
+
<Row>
|
10 |
+
<Col xs={12}>
|
11 |
+
<h1>This is a demo summary page</h1>
|
12 |
+
</Col>
|
13 |
+
<Col xs={12}>
|
14 |
+
<h3>In the future, we hope to connect to PowerBI API and show the dashboard</h3>
|
15 |
+
</Col>
|
16 |
+
</Row>
|
17 |
+
</Container>
|
18 |
+
)
|
19 |
+
} />
|
20 |
+
);
|
21 |
+
}
|
frontend/src/pages/LoginPage.js
CHANGED
@@ -51,11 +51,12 @@ export default function LoginPage() {
|
|
51 |
}
|
52 |
})
|
53 |
.catch((error) => {
|
54 |
-
if (error.status === 400) {
|
55 |
-
|
56 |
-
} else if (error.status === 500) {
|
57 |
-
|
58 |
-
}
|
|
|
59 |
})
|
60 |
|
61 |
}
|
@@ -76,10 +77,10 @@ export default function LoginPage() {
|
|
76 |
{error && <Alert variant="danger">{error}</Alert>}
|
77 |
|
78 |
<Form.Group controlId="username" className='mb-3'>
|
79 |
-
<Form.Label>
|
80 |
<Form.Control
|
81 |
type="text"
|
82 |
-
placeholder="
|
83 |
onChange={(e) => setUsername(e.target.value)}
|
84 |
/>
|
85 |
</Form.Group>
|
|
|
51 |
}
|
52 |
})
|
53 |
.catch((error) => {
|
54 |
+
// if (error.status === 400) {
|
55 |
+
// setError('Lỗi đăng nhập, vui lòng kiểm tra lại tài khoản và mật khẩu');
|
56 |
+
// } else if (error.status === 500) {
|
57 |
+
// setError('Server đang tạm gặp vấn đề');
|
58 |
+
// }
|
59 |
+
setError(JSON.stringify(error));
|
60 |
})
|
61 |
|
62 |
}
|
|
|
77 |
{error && <Alert variant="danger">{error}</Alert>}
|
78 |
|
79 |
<Form.Group controlId="username" className='mb-3'>
|
80 |
+
<Form.Label>Email/Số điện thoại</Form.Label>
|
81 |
<Form.Control
|
82 |
type="text"
|
83 |
+
placeholder="Email/số điện thoại"
|
84 |
onChange={(e) => setUsername(e.target.value)}
|
85 |
/>
|
86 |
</Form.Group>
|
frontend/src/pages/RegisterPage.js
CHANGED
@@ -3,6 +3,9 @@ import validator from 'validator';
|
|
3 |
import { useNavigate } from 'react-router-dom';
|
4 |
import { Container, Form, Button, Alert, Row, Col, Card } from 'react-bootstrap';
|
5 |
import BasicTemplate from '../templates/BasicTemplate';
|
|
|
|
|
|
|
6 |
|
7 |
const RegisterPage = () => {
|
8 |
const [full_name, setFullname] = useState('');
|
@@ -14,13 +17,15 @@ const RegisterPage = () => {
|
|
14 |
|
15 |
const navigator = useNavigate();
|
16 |
|
|
|
|
|
17 |
const handleSubmit = (e) => {
|
18 |
e.preventDefault();
|
19 |
// Validate password and confirm password match
|
20 |
if (full_name.length === 0) {
|
21 |
setError('Họ và tên không thể để trống');
|
22 |
} else if (!validator.isMobilePhone(phone_number, 'vi-VN')) {
|
23 |
-
setError('Số điện thoại không hợp lệ');
|
24 |
} else if (!validator.isEmail(email)) {
|
25 |
setError('Email không hợp lệ');
|
26 |
} else if (password.length < 8) {
|
@@ -30,7 +35,40 @@ const RegisterPage = () => {
|
|
30 |
} else {
|
31 |
setError('');
|
32 |
// gọi API đăng ký ở đây
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
navigator('/');
|
35 |
}
|
36 |
};
|
@@ -57,7 +95,7 @@ const RegisterPage = () => {
|
|
57 |
type="text"
|
58 |
placeholder="Họ và tên"
|
59 |
onChange={(e) => setFullname(e.target.value)}
|
60 |
-
|
61 |
/>
|
62 |
</Form.Group>
|
63 |
|
@@ -68,7 +106,7 @@ const RegisterPage = () => {
|
|
68 |
type="text"
|
69 |
placeholder="Số điện thoại"
|
70 |
onChange={(e) => setPhoneNumber(e.target.value)}
|
71 |
-
|
72 |
/>
|
73 |
</Form.Group>
|
74 |
|
@@ -79,7 +117,7 @@ const RegisterPage = () => {
|
|
79 |
type="text"
|
80 |
placeholder="Email"
|
81 |
onChange={(e) => setEmail(e.target.value)}
|
82 |
-
|
83 |
/>
|
84 |
</Form.Group>
|
85 |
|
@@ -90,7 +128,7 @@ const RegisterPage = () => {
|
|
90 |
type="password"
|
91 |
placeholder="Nhập mật khẩu"
|
92 |
onChange={(e) => setPassword(e.target.value)}
|
93 |
-
|
94 |
/>
|
95 |
</Form.Group>
|
96 |
|
@@ -100,7 +138,7 @@ const RegisterPage = () => {
|
|
100 |
type="password"
|
101 |
placeholder="Xác nhận mật khẩu"
|
102 |
onChange={(e) => setConfirmPassword(e.target.value)}
|
103 |
-
|
104 |
/>
|
105 |
</Form.Group>
|
106 |
<div className='d-flex justify-content-between align-items-center'>
|
|
|
3 |
import { useNavigate } from 'react-router-dom';
|
4 |
import { Container, Form, Button, Alert, Row, Col, Card } from 'react-bootstrap';
|
5 |
import BasicTemplate from '../templates/BasicTemplate';
|
6 |
+
import axios from 'axios';
|
7 |
+
import DataStorage from '../organisms/DataStorage';
|
8 |
+
import jwtDecoder from '../organisms/jwtDecoder';
|
9 |
|
10 |
const RegisterPage = () => {
|
11 |
const [full_name, setFullname] = useState('');
|
|
|
17 |
|
18 |
const navigator = useNavigate();
|
19 |
|
20 |
+
const domain = process.env.REACT_APP_API_URL;
|
21 |
+
|
22 |
const handleSubmit = (e) => {
|
23 |
e.preventDefault();
|
24 |
// Validate password and confirm password match
|
25 |
if (full_name.length === 0) {
|
26 |
setError('Họ và tên không thể để trống');
|
27 |
} else if (!validator.isMobilePhone(phone_number, 'vi-VN')) {
|
28 |
+
setError('Số điện thoại không hợp lệ');
|
29 |
} else if (!validator.isEmail(email)) {
|
30 |
setError('Email không hợp lệ');
|
31 |
} else if (password.length < 8) {
|
|
|
35 |
} else {
|
36 |
setError('');
|
37 |
// gọi API đăng ký ở đây
|
38 |
+
let data = {
|
39 |
+
"email": email,
|
40 |
+
"phone_number": phone_number,
|
41 |
+
"full_name": full_name,
|
42 |
+
"password": password
|
43 |
+
}
|
44 |
+
|
45 |
+
axios.post(domain + '/authentication/signup', data)
|
46 |
+
.then((response) => {
|
47 |
+
if (response.status === 200 || response.status === 201) {
|
48 |
+
const decodedToken = jwtDecoder(response.data.access_token);
|
49 |
+
const full_name = decodedToken.payload.username;
|
50 |
+
const role = decodedToken.payload.roles;
|
51 |
+
const expiryTime = decodedToken.payload.exp;
|
52 |
+
DataStorage.set('expiryDate', expiryTime, { expiryDate: expiryTime });
|
53 |
+
DataStorage.set('username', full_name);
|
54 |
+
DataStorage.set('role', role);
|
55 |
+
DataStorage.set('isLoggedIn', 'true');
|
56 |
+
DataStorage.set('accessToken', response.data.access_token);
|
57 |
+
DataStorage.set('cart', '{}');
|
58 |
+
navigator('/');
|
59 |
+
} else {
|
60 |
+
setError(JSON.stringify(response));
|
61 |
+
}
|
62 |
+
})
|
63 |
+
.catch((error) => {
|
64 |
+
if (error.status === 400) {
|
65 |
+
setError('Lỗi đăng ký');
|
66 |
+
} else if (error.status === 500) {
|
67 |
+
setError('Server đang tạm gặp vấn đề');
|
68 |
+
}
|
69 |
+
})
|
70 |
+
|
71 |
+
console.log('Đăng ký thành công:', { full_name, password });
|
72 |
navigator('/');
|
73 |
}
|
74 |
};
|
|
|
95 |
type="text"
|
96 |
placeholder="Họ và tên"
|
97 |
onChange={(e) => setFullname(e.target.value)}
|
98 |
+
|
99 |
/>
|
100 |
</Form.Group>
|
101 |
|
|
|
106 |
type="text"
|
107 |
placeholder="Số điện thoại"
|
108 |
onChange={(e) => setPhoneNumber(e.target.value)}
|
109 |
+
|
110 |
/>
|
111 |
</Form.Group>
|
112 |
|
|
|
117 |
type="text"
|
118 |
placeholder="Email"
|
119 |
onChange={(e) => setEmail(e.target.value)}
|
120 |
+
|
121 |
/>
|
122 |
</Form.Group>
|
123 |
|
|
|
128 |
type="password"
|
129 |
placeholder="Nhập mật khẩu"
|
130 |
onChange={(e) => setPassword(e.target.value)}
|
131 |
+
|
132 |
/>
|
133 |
</Form.Group>
|
134 |
|
|
|
138 |
type="password"
|
139 |
placeholder="Xác nhận mật khẩu"
|
140 |
onChange={(e) => setConfirmPassword(e.target.value)}
|
141 |
+
|
142 |
/>
|
143 |
</Form.Group>
|
144 |
<div className='d-flex justify-content-between align-items-center'>
|
frontend/src/styles/styles.css
CHANGED
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* Thay đổi màu sắc chính của trang */
|
2 |
+
:root {
|
3 |
+
--primary-color: #300604;
|
4 |
+
/* Màu đỏ ấm, có thể chỉnh sửa */
|
5 |
+
--secondary-color: #b17219;
|
6 |
+
/* Màu cam nhạt */
|
7 |
+
--background-color: #222020;
|
8 |
+
/* Màu nền nhẹ nhàng */
|
9 |
+
--text-color: #fbf5f5;
|
10 |
+
/* Màu chữ chính */
|
11 |
+
--container-background-color: rgba(44, 41, 41, 0.5);
|
12 |
+
background-color: var(--background-color);
|
13 |
+
}
|
14 |
+
|
15 |
+
/* Đặt nền cho toàn bộ trang */
|
16 |
+
body {
|
17 |
+
color: var(--text-color);
|
18 |
+
/* background-image: url('../../public/header.jpg'); */
|
19 |
+
background-color: var(--background-color); /* Màu nền */
|
20 |
+
/* Hoặc sử dụng hình ảnh nền */
|
21 |
+
background-size: cover; /* Để hình ảnh phủ đầy toàn bộ trang */
|
22 |
+
background-repeat: no-repeat; /* Để hình ảnh không lặp lại */
|
23 |
+
background-attachment: fixed; /* Để nền cố định khi cuộn trang */
|
24 |
+
background-position: center; /* Căn giữa hình ảnh */
|
25 |
+
}
|
26 |
+
|
27 |
+
|
28 |
+
/* Container */
|
29 |
+
.container {
|
30 |
+
padding-top: 20px;
|
31 |
+
padding-bottom: 20px;
|
32 |
+
background-color: var(--container-background-color);
|
33 |
+
}
|
34 |
+
|
35 |
+
/* Navbar */
|
36 |
+
.navbar {
|
37 |
+
background-color: var(--primary-color) !important;
|
38 |
+
font-size: 1.1rem;
|
39 |
+
}
|
40 |
+
|
41 |
+
.navbar-brand,
|
42 |
+
.nav-link {
|
43 |
+
color: #fff !important;
|
44 |
+
font-weight: bold;
|
45 |
+
transition: color 0.3s;
|
46 |
+
}
|
47 |
+
|
48 |
+
.nav-link:hover {
|
49 |
+
color: var(--secondary-color) !important;
|
50 |
+
}
|
51 |
+
|
52 |
+
.navbar-toggler {
|
53 |
+
border-color: rgba(255, 255, 255, 0.5);
|
54 |
+
}
|
55 |
+
|
56 |
+
.navbar-light .navbar-toggler-icon {
|
57 |
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
|
58 |
+
}
|
59 |
+
|
60 |
+
/* Button */
|
61 |
+
.btn-primary {
|
62 |
+
background-color: var(--secondary-color);
|
63 |
+
border-color: var(--secondary-color);
|
64 |
+
color: #fff;
|
65 |
+
font-weight: bold;
|
66 |
+
transition: background-color 0.3s, color 0.3s;
|
67 |
+
}
|
68 |
+
|
69 |
+
.btn-primary:hover {
|
70 |
+
background-color: #e99e40;
|
71 |
+
border-color: #e99e40;
|
72 |
+
}
|
73 |
+
|
74 |
+
/* .btn-outline-primary {
|
75 |
+
color: var(--primary-color);
|
76 |
+
border-color: var(--primary-color);
|
77 |
+
font-weight: bold;
|
78 |
+
}
|
79 |
+
|
80 |
+
.btn-outline-primary:hover {
|
81 |
+
background-color: var(--primary-color);
|
82 |
+
color: #fff;
|
83 |
+
} */
|
84 |
+
|
85 |
+
/* Style cho nút secondary */
|
86 |
+
.btn-outline-primary {
|
87 |
+
background-color: var(--text-color);
|
88 |
+
/* Màu nền trắng */
|
89 |
+
color: var(--primary-color);
|
90 |
+
/* Màu chữ đỏ */
|
91 |
+
font-weight: bold;
|
92 |
+
border-color: var(--secondary-color);
|
93 |
+
/* Màu viền đỏ */
|
94 |
+
}
|
95 |
+
|
96 |
+
.btn-outline-primary:hover {
|
97 |
+
background-color: #c2bdaf;
|
98 |
+
/* Màu nền nhạt hơn khi hover */
|
99 |
+
color: var(--primary-color);
|
100 |
+
/* Màu chữ đỏ khi hover */
|
101 |
+
border-color: var(--secondary-color);
|
102 |
+
/* Giữ màu viền đỏ khi hover */
|
103 |
+
}
|
104 |
+
|
105 |
+
|
106 |
+
|
107 |
+
/* Card */
|
108 |
+
.card {
|
109 |
+
border: none;
|
110 |
+
border-radius: 10px;
|
111 |
+
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.3);
|
112 |
+
transition: transform 0.3s;
|
113 |
+
}
|
114 |
+
|
115 |
+
.card:hover {
|
116 |
+
transform: scale(1.02);
|
117 |
+
}
|
118 |
+
|
119 |
+
.card-img-top {
|
120 |
+
border-top-left-radius: 10px;
|
121 |
+
border-top-right-radius: 10px;
|
122 |
+
height: 200px;
|
123 |
+
/* Tùy chỉnh chiều cao ảnh */
|
124 |
+
object-fit: cover;
|
125 |
+
}
|
126 |
+
|
127 |
+
.card-title {
|
128 |
+
font-size: 1.25rem;
|
129 |
+
font-weight: bold;
|
130 |
+
color: var(--primary-color);
|
131 |
+
}
|
132 |
+
|
133 |
+
.card-text {
|
134 |
+
color: var(--text-color);
|
135 |
+
}
|
136 |
+
|
137 |
+
.card-footer {
|
138 |
+
background-color: #fff;
|
139 |
+
border-top: none;
|
140 |
+
text-align: center;
|
141 |
+
}
|
142 |
+
|
143 |
+
/* Container cho các phần */
|
144 |
+
.menu-section {
|
145 |
+
padding: 40px 0;
|
146 |
+
background-color: #fff;
|
147 |
+
}
|
148 |
+
|
149 |
+
.header-section {
|
150 |
+
padding: 60px 0;
|
151 |
+
background-image: url('../../public/header.jpg');
|
152 |
+
/* Thêm hình nền nếu có */
|
153 |
+
background-size: cover;
|
154 |
+
background-position: center;
|
155 |
+
/* color: #fff; */
|
156 |
+
text-align: center;
|
157 |
+
}
|
158 |
+
|
159 |
+
.header-section h1 {
|
160 |
+
font-size: 2.5rem;
|
161 |
+
font-weight: bold;
|
162 |
+
}
|
163 |
+
|
164 |
+
.header-section p {
|
165 |
+
font-size: 1.2rem;
|
166 |
+
margin-top: 10px;
|
167 |
+
max-width: 600px;
|
168 |
+
margin: auto;
|
169 |
+
color: #f1f1f1;
|
170 |
+
}
|
frontend/src/templates/AdminTemplate.js
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// pages/HomePage.js
|
2 |
+
// import React, { useState } from 'react';
|
3 |
+
import AdminNavbar from '../molecules/AdminNavBar';
|
4 |
+
function AdminTemplate ({content}) {
|
5 |
+
// const [isLoggedIn, setIsLoggedIn] = useState(false);
|
6 |
+
// const [user, setUser] = useState({ name: 'User' });
|
7 |
+
|
8 |
+
return (
|
9 |
+
<>
|
10 |
+
<AdminNavbar></AdminNavbar>
|
11 |
+
{content}
|
12 |
+
</>
|
13 |
+
);
|
14 |
+
};
|
15 |
+
|
16 |
+
export default AdminTemplate;
|