first commit

This commit is contained in:
hardik 2025-07-10 11:33:00 +05:30
commit 300ba97431
63 changed files with 36544 additions and 0 deletions

7
.dockerignore Normal file
View File

@ -0,0 +1,7 @@
.env*
.drone.yml
.dockerignore
Dockerfile
build/
node_modules/

52
.drone.yml Normal file
View File

@ -0,0 +1,52 @@
kind: pipeline
name: Build Image
steps:
- name: Lint Check
image: node:21-alpine
commands:
- yarn install
- yarn lint
- name: Build Docker Image
image: plugins/docker
settings:
build_args: "API_BASE_URL=https://kineticgreen-backend.navigolabs.com"
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: git.navigolabs.com/mayank/kinetic_green-frontend
registry: git.navigolabs.com
when:
branch:
- main
event:
- push
- custom
- name: Deploy image
image: appleboy/drone-ssh
settings:
host: kineticgreen.navigolabs.com
username: kinetic_green-frontend
key:
from_secret: server_ssh_pkey
port: 22
command_timeout: 3m
script:
- echo "Deploying image"
- sudo /opt/deployable/kinetic_green-frontend/docker/deploy.sh
- echo "Completed Deployment"
when:
branch:
- main
event:
- push
- custom
# trigger:
# branch:
# - main
# - feature/env_api_url
# event:
# - push

4
.env Normal file
View File

@ -0,0 +1,4 @@
PORT=3001
# REACT_APP_API_BASE_URL=http://localhost:3010
REACT_APP_API_BASE_URL=https://kineticgreen-backend.navigolabs.com

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

26
Dockerfile Normal file
View File

@ -0,0 +1,26 @@
FROM node:21-alpine as builder
ARG API_BASE_URL="https://diagnostic-api.navigolabs.com"
ENV REACT_APP_API_BASE_URL $API_BASE_URL
WORKDIR /app
COPY package.json package.json
COPY yarn.lock yarn.lock
RUN yarn install --frozen-lockfile
COPY . .
RUN yarn build
FROM scratch
WORKDIR /app
COPY --from=builder /app/build ./build
CMD ["sh"]

46
README.md Normal file
View File

@ -0,0 +1,46 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

20324
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

60
package.json Normal file
View File

@ -0,0 +1,60 @@
{
"name": "Kinetic_tbt",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^6.4.12",
"@mui/material": "^6.1.2",
"@mui/styled-engine-sc": "^6.1.2",
"@mui/types": "^7.2.17",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.0.0",
"framer-motion": "^12.23.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropzone": "^14.3.8",
"react-router-dom": "^6.27.0",
"react-scripts": "5.0.1",
"react-slick": "^0.30.3",
"styled-components": "^6.1.13",
"typed.js": "^2.1.0",
"typescript": "^4.4.2",
"web-vitals": "^2.1.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"lint": "eslint src/**/*.{js,jsx,ts,tsx,json}",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/react-dropzone": "^5.1.0"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

43
public/index.html Normal file
View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Kinetic</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

25
public/manifest.json Normal file
View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
public/robots.txt Normal file
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

38
src/App.css Normal file
View File

@ -0,0 +1,38 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

9
src/App.test.tsx Normal file
View File

@ -0,0 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

34
src/App.tsx Normal file
View File

@ -0,0 +1,34 @@
import React from "react";
import { BrowserRouter } from "react-router-dom";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import AppRoutes from "./routes";
// import AppRoutes from "./routes/AppRoutes";
// import Layout from "./components/Layout";
const theme = createTheme({
palette: {
background: {
default: "#f8f8f8",
},
primary: {
main: "#000000",
},
},
typography: {
fontFamily: '"Inter", sans-serif',
},
});
const App: React.FC = () => {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<BrowserRouter>
<AppRoutes />
</BrowserRouter>
</ThemeProvider>
);
};
export default App;

BIN
src/caraone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@ -0,0 +1,158 @@
import React, { useEffect, useRef, useState } from "react";
import { Box, Typography, Avatar, Button, Alert, TextField, Card } from "@mui/material";
import Typed from "typed.js";
import imageone from "../../images/kineticAdminPanelBg.png";
import logo from "../../images/kineticlogo1.png";
const API_URL = "https://kineticgreen-backend.navigolabs.com/user/forgotpassword";
const AnimatedText: React.FC = () => {
const typedRef = useRef<HTMLSpanElement | null>(null);
const typedInstance = useRef<Typed | null>(null);
useEffect(() => {
if (typedRef.current) {
typedInstance.current = new Typed(typedRef.current, {
strings: ["Trucks", "Automobiles", "Diagnostics"],
typeSpeed: 50,
backSpeed: 30,
loop: true,
});
}
return () => typedInstance.current?.destroy();
}, []);
return (
<div style={{ textAlign: "center", marginTop: "10px", fontWeight: "bold", fontSize: "2rem", color: "#61A143" }}>
<span ref={typedRef} />
</div>
);
};
const ForgotPassword: React.FC = () => {
const [username, setUsername] = useState("");
const [new_pwd, setNewPwd] = useState("");
const [conf_new_pwd, setConfNewPwd] = useState("");
const [error, setError] = useState<string | null>(null);
const [message, setMessage] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const handleForgotPassword = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setMessage(null); // Reset message before request
setLoading(true);
try {
const response = await fetch(API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, new_pwd, conf_new_pwd }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || "Invalid username or password.");
}
setMessage(data.message || "Password reset request sent to your superior");
} catch (error: any) {
setError(error.message);
} finally {
setLoading(false);
}
};
return (
<Box
sx={{
justifyContent: "center",
alignItems: "center",
height: "100vh",
display: "flex",
backgroundImage: `url(${imageone})`,
backgroundPosition: "left center",
backgroundSize: "100%",
backgroundRepeat: "no-repeat",
backgroundColor: "rgba(0, 0, 0, 0.72)",
}}
>
<Card
sx={{
width: "400px",
padding: 4,
boxShadow: 6,
borderRadius: 8,
backgroundColor: "#ffffff",
backdropFilter: "blur(5px)",
border: "1px solid rgba(255, 255, 255, 0.3)",
}}
>
<Box sx={{ mb: 3, textAlign: "center" }}>
<Avatar sx={{ bgcolor: "#61A143", width: 56, height: 56, margin: "0 auto" }}>
<img src={logo} alt="Logo" style={{ width: "40px", height: "40px", borderRadius: "50%" }} />
</Avatar>
<Typography variant="h5" sx={{ mt: 2, fontWeight: "bold" }}>
KINETIC
</Typography>
<AnimatedText />
</Box>
{/* Display success or error messages */}
{message && <Alert severity="success">{message}</Alert>}
{error && <Alert severity="error">{error}</Alert>}
<Box component="form" sx={{ display: "flex", flexDirection: "column", gap: 2 }} onSubmit={handleForgotPassword}>
<TextField
label="Username"
variant="outlined"
fullWidth
required
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<TextField
label="Enter New Password"
type="password"
variant="outlined"
fullWidth
required
value={new_pwd}
onChange={(e) => setNewPwd(e.target.value)}
/>
<TextField
label="Confirm New Password"
type="password"
variant="outlined"
fullWidth
required
value={conf_new_pwd}
onChange={(e) => setConfNewPwd(e.target.value)}
/>
<Typography variant="body2" sx={{ textAlign: "right", mb: 1 }}>
<a href="/login" style={{ color: "#61A143", textDecoration: "none" }}>
Login
</a>
</Typography>
<Button
type="submit"
variant="contained"
fullWidth
disabled={loading}
sx={{
mt: 2,
color: "white",
background: "#61A143",
"&:hover": { background: "#4a8a3a" },
}}
>
{loading ? "Processing..." : "Reset Password"}
</Button>
</Box>
</Card>
</Box>
);
};
export default ForgotPassword;

View File

@ -0,0 +1,256 @@
import React, { useEffect, useRef, useState } from "react";
import {
Box,
Typography,
Avatar,
Button,
Divider,
InputAdornment,
TextField,
Card,
Alert,
} from "@mui/material";
import { AccountCircle } from "@mui/icons-material";
import GoogleIcon from "@mui/icons-material/Google";
import Typed from "typed.js";
import imageone from "../../images/kineticAdminPanelBg.png";
import logo from "../../images/kineticlogo1.png";
import { useNavigate } from "react-router-dom";
const API_URL = "https://kineticgreen-backend.navigolabs.com/user/login";
const AnimatedText: React.FC = () => {
const typedRef = useRef<HTMLSpanElement | null>(null);
const typedInstance = useRef<Typed | null>(null);
useEffect(() => {
if (typedRef.current) {
typedInstance.current = new Typed(typedRef.current, {
strings: ["Trucks", "Automobiles", "Diagnostics"],
typeSpeed: 50,
backSpeed: 30,
loop: true,
});
}
return () => typedInstance.current?.destroy();
}, []);
return (
<div
style={{
textAlign: "center",
marginTop: "10px",
fontWeight: "bold",
fontSize: "2rem",
color: "#61A143",
}}
>
<span ref={typedRef} />
</div>
);
};
const LoginPage: React.FC = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
useEffect(() => {
const token = localStorage.getItem("token");
if (token) {
const decodedToken = JSON.parse(atob(token.split(".")[1]));
if (
decodedToken.role?.role_name === "KineticAdmin" ||
decodedToken.role?.role_name === "SuperAdmin"
) {
window.location.href = "/home";
}
}
}, []);
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setLoading(true);
try {
const response = await fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, password }),
});
if (response.ok) {
const jsonData = await response.json();
const decodedToken = JSON.parse(atob(jsonData.token.split(".")[1]));
console.log("Decoded Token:", decodedToken);
localStorage.setItem("decodedToken", JSON.stringify(decodedToken));
localStorage.setItem("token", jsonData.token);
// localStorage.setItem("user", JSON.stringify(jsonData));
navigate("../home");
} else {
setError("Invalid username or password");
}
if (!response.ok) {
throw new Error("Invalid username or password.");
}
const data = await response.json();
const { token } = data;
if (!token) {
throw new Error("Invalid server response.");
}
const decodedToken = JSON.parse(atob(token.split(".")[1]));
console.log("Decoded Token:", decodedToken);
localStorage.setItem("decodedToken", JSON.stringify(decodedToken));
const hasKineticAdminPermissions =
decodedToken.role?.permissions.includes("viewKineticAdmin") ||
decodedToken.role?.permissions.includes("manageKineticAdmin");
const hasSuperAdminPermissions =
decodedToken.role?.permissions.includes("viewsuperAdmin") ||
decodedToken.role?.permissions.includes("managesuperAdmin");
if (hasKineticAdminPermissions || hasSuperAdminPermissions) {
localStorage.setItem("token", token);
localStorage.setItem("user", JSON.stringify(decodedToken));
alert("Login successful!");
window.location.href = "/home";
} else {
throw new Error("You do not have permission to access this system.");
}
} catch (error: any) {
setError(error.message);
} finally {
setLoading(false);
}
};
return (
<Box
sx={{
justifyContent: "center",
alignItems: "center",
height: "100vh",
display: "flex",
backgroundImage: `url(${imageone})`,
backgroundPosition: "left center",
backgroundSize: "100%",
backgroundRepeat: "no-repeat",
backgroundColor: "rgba(0, 0, 0, 0.72)",
}}
>
<Card
sx={{
width: "400px",
padding: 4,
boxShadow: 6,
borderRadius: 8,
backgroundColor: "#ffffff",
backdropFilter: "blur(5px)",
border: "1px solid rgba(255, 255, 255, 0.3)",
}}
>
<Box sx={{ mb: 3, textAlign: "center" }}>
<Avatar
sx={{ bgcolor: "#61A143", width: 56, height: 56, margin: "0 auto" }}
>
<img
src={logo}
alt="Logo"
style={{ width: "40px", height: "40px", borderRadius: "50%" }}
/>
</Avatar>
<Typography variant="h5" sx={{ mt: 2, fontWeight: "bold" }}>
KINETIC
</Typography>
<AnimatedText />
</Box>
{error && <Alert severity="error">{error}</Alert>}
<Box
component="form"
sx={{ display: "flex", flexDirection: "column", gap: 2 }}
onSubmit={handleLogin}
>
<TextField
label="Username"
variant="outlined"
fullWidth
required
value={username}
onChange={(e) => setUsername(e.target.value)}
// InputProps={{
// endAdornment: (
// <InputAdornment position="start">
// <AccountCircle />
// </InputAdornment>
// ),
// }}
/>
<TextField
label="Password"
type="password"
variant="outlined"
fullWidth
required
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<Typography variant="body2" sx={{ textAlign: "right", mb: 1 }}>
<span
onClick={() => navigate("/forgotpassword")}
style={{
color: "#61A143",
textDecoration: "none",
cursor: "pointer",
}}
>
Forgot Password?
</span>
</Typography>
<Button
type="submit"
variant="contained"
fullWidth
disabled={loading}
sx={{
mt: 2,
color: "white",
background: "#61A143",
"&:hover": { background: "#4a8a3a" },
}}
>
{loading ? "Logging in..." : "Login"}
</Button>
</Box>
{/* <Divider sx={{ my: 2 }}>OR</Divider>
<Button
variant="outlined"
fullWidth
startIcon={<GoogleIcon />}
sx={{
textTransform: "none",
color: "#61A143",
borderColor: "#61A143",
"&:hover": { backgroundColor: "#61A143", borderColor: "#4a8a3a", color: "white" },
}}
onClick={() => alert("Google Login not implemented yet")}
>
Login with Google
</Button> */}
</Card>
</Box>
);
};
export default LoginPage;

View File

@ -0,0 +1,14 @@
import React from "react";
import { Navigate, Outlet } from "react-router-dom";
const ProtectedLayout: React.FC = () => {
const token = localStorage.getItem("token");
if (!token) {
return <Navigate to="/login" replace />;
}
return <Outlet />;
};
export default ProtectedLayout;

View File

@ -0,0 +1,159 @@
import React, { useState } from "react";
import {
TextField,
Button,
Typography,
Snackbar,
Alert,
Box,
Grid,
} from "@mui/material";
import loginpageimg from "../../images/loginPageimg.jpg";
import { useNavigate } from "react-router-dom";
const UserLogin: React.FC = () => {
const [useremail, setUserEmail] = useState("");
const [password, setPassword] = useState("");
const [responseMessage, setResponseMessage] = useState<string | null>(null);
const [severity, setSeverity] = useState<"success" | "error">("success");
const [error, setError] = useState("");
const navigate = useNavigate();
const handleLogin = async (event: React.FormEvent) => {
event.preventDefault();
const credentials = {
useremail,
password,
};
try {
const response = await fetch("http://localhost:3000/users/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(credentials),
});
if (response.ok) {
const jsonData = await response.json();
const decodedToken = JSON.parse(atob(jsonData.token.split(".")[1]));
console.log("Decoded Token:", decodedToken);
localStorage.setItem("decodedToken", JSON.stringify(decodedToken));
localStorage.setItem("token", jsonData.token);
// localStorage.setItem("user", JSON.stringify(jsonData));
navigate("../home");
} else {
setError("Invalid username or password");
}
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.token) {
localStorage.setItem("token", data.token);
}
setResponseMessage(data.message || "Login successful");
setSeverity("success");
navigate("/home");
} catch (error) {
setResponseMessage("Error logging in");
setSeverity("error");
}
};
const handleCloseSnackbar = () => {
setResponseMessage(null);
};
return (
<Box
sx={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundImage: `url(${loginpageimg})`,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
overflow: "hidden",
}}
>
<Grid
container
alignItems="center"
justifyContent="center"
sx={{
height: "100%",
}}
>
<Grid
item
xs={12}
sm={8}
md={4}
sx={{
backgroundColor: "rgba(255, 255, 255, 0.8)",
padding: 4,
borderRadius: 2,
}}
>
<Typography variant="h5" gutterBottom>
Loginnssss
</Typography>
<form onSubmit={handleLogin}>
<TextField
fullWidth
label="Email"
value={useremail}
onChange={(e) => setUserEmail(e.target.value)}
margin="normal"
required
/>
<TextField
fullWidth
label="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
margin="normal"
required
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
sx={{ mt: 2 }}
>
Login
</Button>
</form>
</Grid>
</Grid>
<Snackbar
open={!!responseMessage}
autoHideDuration={6000}
onClose={handleCloseSnackbar}
>
<Alert
onClose={handleCloseSnackbar}
severity={severity}
sx={{ width: "100%" }}
>
{responseMessage}
</Alert>
</Snackbar>
</Box>
);
};
export default UserLogin;

View File

@ -0,0 +1,385 @@
import React from "react";
import {
Box,
Container,
Typography,
Button,
Divider,
useTheme,
Grid,
Stack,
Avatar,
Accordion,
AccordionSummary,
AccordionDetails,
} from "@mui/material";
import { Link } from "react-router-dom";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import teamImage from "../images/caraone.png";
import factoryImage from "../images/caraone.png";
import designImage from "../images/caraone.png";
const AboutPage: React.FC = () => {
const theme = useTheme();
return (
<Box sx={{ backgroundColor: "#f9f9f9", minHeight: "100vh", py: 8 }}>
{/* Hero Section with Full-width Image */}
<Box
sx={{
backgroundImage: `linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url(${teamImage})`,
backgroundSize: "cover",
backgroundPosition: "center",
height: "60vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
mb: 8,
color: "#fff",
textAlign: "center",
}}
>
<Container maxWidth="md">
<Typography
variant="h1"
sx={{
mb: 3,
fontSize: { xs: "2.5rem", md: "4rem" },
fontWeight: 800,
lineHeight: 1.2,
}}
>
Crafting Comfort Since 2010
</Typography>
<Typography variant="h5" sx={{ mb: 4, fontWeight: 400 }}>
Premium furniture designed for modern living
</Typography>
<Button
component={Link}
to="/products"
variant="contained"
size="large"
sx={{
backgroundColor: "#FF6D00",
color: "#fff",
py: 1.5,
px: 6,
fontSize: "1.1rem",
fontWeight: 600,
"&:hover": {
backgroundColor: "#FF5722",
},
}}
>
Explore Our Collection
</Button>
</Container>
</Box>
<Container maxWidth="lg">
{/* Our Story Section */}
<Box sx={{ mb: 10, textAlign: "center" }}>
<Typography
variant="h2"
sx={{
mb: 3,
fontSize: { xs: "2rem", md: "2.5rem" },
fontWeight: 700,
position: "relative",
display: "inline-block",
"&:after": {
content: '""',
position: "absolute",
bottom: -12,
left: "50%",
transform: "translateX(-50%)",
width: "80px",
height: "4px",
backgroundColor: "#FF6D00",
},
}}
>
Our Story
</Typography>
<Typography
variant="body1"
sx={{
mb: 4,
fontSize: "1.1rem",
lineHeight: 1.8,
maxWidth: "800px",
mx: "auto",
}}
>
Founded in a small workshop in Portland, Comfort Haven has grown
into a nationally recognized furniture brand while maintaining our
commitment to craftsmanship and sustainable practices. What started
as a passion project between two design school graduates has
blossomed into a movement redefining modern comfort.
</Typography>
{/* Image with Caption */}
<Box sx={{ maxWidth: "1000px", mx: "auto", mb: 6 }}>
<img
src={factoryImage}
alt="Our workshop"
style={{
width: "100%",
borderRadius: "12px",
boxShadow: theme.shadows[4],
marginBottom: "1rem",
}}
/>
<Typography variant="caption" sx={{ fontStyle: "italic" }}>
Our craftsmen hand-assembling furniture in Portland, Oregon
</Typography>
</Box>
</Box>
{/* Values Section */}
<Box sx={{ mb: 10 }}>
<Typography
variant="h2"
sx={{
mb: 6,
fontSize: { xs: "2rem", md: "2.5rem" },
fontWeight: 700,
textAlign: "center",
position: "relative",
"&:after": {
content: '""',
position: "absolute",
bottom: -12,
left: "50%",
transform: "translateX(-50%)",
width: "80px",
height: "4px",
backgroundColor: "#FF6D00",
},
}}
>
Our Values
</Typography>
<Grid container spacing={6}>
<Grid item xs={12} md={4}>
<Stack alignItems="center" sx={{ textAlign: "center" }}>
<Avatar
sx={{
bgcolor: "rgba(255, 109, 0, 0.1)",
color: "#FF6D00",
width: 80,
height: 80,
mb: 3,
fontSize: "2rem",
}}
>
</Avatar>
<Typography variant="h4" sx={{ mb: 2, fontWeight: 600 }}>
Design Excellence
</Typography>
<Typography variant="body1" sx={{ lineHeight: 1.7 }}>
We combine aesthetic beauty with ergonomic principles to
create pieces that look as good as they feel.
</Typography>
</Stack>
</Grid>
<Grid item xs={12} md={4}>
<Stack alignItems="center" sx={{ textAlign: "center" }}>
<Avatar
sx={{
bgcolor: "rgba(255, 109, 0, 0.1)",
color: "#FF6D00",
width: 80,
height: 80,
mb: 3,
fontSize: "2rem",
}}
>
🌎
</Avatar>
<Typography variant="h4" sx={{ mb: 2, fontWeight: 600 }}>
Sustainable Practices
</Typography>
<Typography variant="body1" sx={{ lineHeight: 1.7 }}>
From responsibly sourced materials to eco-friendly packaging,
we minimize our environmental impact at every step.
</Typography>
</Stack>
</Grid>
<Grid item xs={12} md={4}>
<Stack alignItems="center" sx={{ textAlign: "center" }}>
<Avatar
sx={{
bgcolor: "rgba(255, 109, 0, 0.1)",
color: "#FF6D00",
width: 80,
height: 80,
mb: 3,
fontSize: "2rem",
}}
>
🤝
</Avatar>
<Typography variant="h4" sx={{ mb: 2, fontWeight: 600 }}>
Customer Commitment
</Typography>
<Typography variant="body1" sx={{ lineHeight: 1.7 }}>
Your satisfaction is our priority, backed by a 5-year warranty
and exceptional customer service.
</Typography>
</Stack>
</Grid>
</Grid>
</Box>
{/* Design Process Section */}
<Box sx={{ mb: 10 }}>
<Grid container spacing={6} alignItems="center">
<Grid item xs={12} md={6}>
<img
src={designImage}
alt="Design process"
style={{
width: "100%",
borderRadius: "12px",
boxShadow: theme.shadows[4],
}}
/>
</Grid>
<Grid item xs={12} md={6}>
<Typography
variant="h2"
sx={{
mb: 3,
fontSize: { xs: "2rem", md: "2.5rem" },
fontWeight: 700,
}}
>
The Comfort Haven Difference
</Typography>
<Typography
variant="body1"
sx={{ mb: 3, lineHeight: 1.8, fontSize: "1.1rem" }}
>
Each piece begins with hundreds of hours of research into
ergonomic principles and material science. Our designers then
create prototypes that undergo rigorous testing before reaching
production.
</Typography>
<Typography
variant="body1"
sx={{ lineHeight: 1.8, fontSize: "1.1rem" }}
>
We partner with skilled artisans who share our commitment to
quality, ensuring every item meets our exacting standards before
it reaches your home.
</Typography>
</Grid>
</Grid>
</Box>
{/* Policies Section */}
<Box sx={{ mb: 6 }}>
<Typography
variant="h2"
sx={{
mb: 4,
fontSize: { xs: "2rem", md: "2.5rem" },
fontWeight: 700,
textAlign: "center",
position: "relative",
"&:after": {
content: '""',
position: "absolute",
bottom: -12,
left: "50%",
transform: "translateX(-50%)",
width: "80px",
height: "4px",
backgroundColor: "#FF6D00",
},
}}
>
Company Policies
</Typography>
<Accordion
defaultExpanded
sx={{
mb: 2,
borderRadius: "8px !important",
boxShadow: "none",
border: "1px solid #e0e0e0",
}}
>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
sx={{
backgroundColor: "#f5f5f5",
borderRadius: "8px 8px 0 0",
minHeight: "64px !important",
}}
>
<Typography variant="h5" fontWeight={600}>
Terms & Conditions
</Typography>
</AccordionSummary>
<AccordionDetails>
<Box sx={{ mb: 3 }}>
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
Order Acceptance
</Typography>
<Typography variant="body1" sx={{ mb: 2, lineHeight: 1.8 }}>
All orders are subject to acceptance and availability. We
reserve the right to refuse service to anyone for any reason
at any time.
</Typography>
</Box>
{/* Additional policy content... */}
</AccordionDetails>
</Accordion>
<Accordion
sx={{
borderRadius: "8px !important",
boxShadow: "none",
border: "1px solid #e0e0e0",
}}
>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
sx={{
backgroundColor: "#f5f5f5",
borderRadius: "8px 8px 0 0",
minHeight: "64px !important",
}}
>
<Typography variant="h5" fontWeight={600}>
Privacy Policy
</Typography>
</AccordionSummary>
<AccordionDetails>
<Box sx={{ mb: 3 }}>
<Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
Information Collection
</Typography>
<Typography variant="body1" sx={{ mb: 2, lineHeight: 1.8 }}>
We collect personal information you provide such as name,
address, email, and payment details to process your orders and
improve your shopping experience.
</Typography>
</Box>
{/* Additional policy content... */}
</AccordionDetails>
</Accordion>
</Box>
</Container>
</Box>
);
};
export default AboutPage;

View File

@ -0,0 +1,570 @@
import React, { useState } from "react";
import {
Box,
Container,
Typography,
Button,
Grid,
Card,
CardMedia,
Divider,
IconButton,
Paper,
Chip,
useTheme,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
} from "@mui/material";
import { Link } from "react-router-dom";
import AddIcon from "@mui/icons-material/Add";
import RemoveIcon from "@mui/icons-material/Remove";
import DeleteIcon from "@mui/icons-material/Delete";
import ShoppingCartCheckoutIcon from "@mui/icons-material/ShoppingCartCheckout";
import chairone from "../images/chairone.png";
import chairtwo from "../images/chairtwo.png";
import chairthree from "../images/chairthree.png";
// Sample cart data
const cartItems = [
{
id: "ergo-luxe",
name: "Ergo-Luxe Executive Chair",
price: 499,
image: chairone,
quantity: 1,
},
{
id: "cloud-comfort",
name: "Cloud Comfort Chair",
price: 349,
image: chairtwo,
quantity: 2,
},
{
id: "velvet-throne",
name: "Velvet Throne Armchair",
price: 899,
image: chairthree,
quantity: 1,
},
];
// Similar products data
const similarProducts = [
{
id: "modern-executive",
name: "Modern Executive Chair",
price: "$429",
image: "/images/chair-similar1.jpg",
},
{
id: "leather-lounge",
name: "Leather Lounge Chair",
price: "$599",
image: "/images/chair-similar2.jpg",
},
{
id: "contemporary-comfort",
name: "Contemporary Comfort Chair",
price: "$379",
image: "/images/chair-similar3.jpg",
},
];
const CartPage = () => {
const theme = useTheme();
const [items, setItems] = useState(cartItems);
const updateQuantity = (id: string, newQuantity: number) => {
if (newQuantity < 1) return;
setItems(
items.map((item) =>
item.id === id ? { ...item, quantity: newQuantity } : item
)
);
};
const removeItem = (id: string) => {
setItems(items.filter((item) => item.id !== id));
};
const subtotal = items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
const shipping = subtotal > 1000 ? 0 : 49;
const tax = subtotal * 0.08;
const total = subtotal + shipping + tax;
return (
<Box sx={{ backgroundColor: "#f9f9f9", minHeight: "100vh", py: 8 }}>
<Container maxWidth="lg">
<Typography
variant="h2"
fontWeight="bold"
sx={{ mb: 6, color: theme.palette.text.primary }}
>
Your Shopping Cart
</Typography>
{items.length === 0 ? (
<Box sx={{ textAlign: "center", py: 10 }}>
<Typography variant="h4" sx={{ mb: 3 }}>
Your cart is empty
</Typography>
<Button
component={Link}
to="/products"
variant="contained"
size="large"
sx={{
backgroundColor: "#FF6D00",
color: "#fff",
py: 1.5,
px: 4,
fontSize: "1.1rem",
fontWeight: 600,
"&:hover": {
backgroundColor: "#FF5722",
},
}}
>
Continue Shopping
</Button>
</Box>
) : (
<Grid container spacing={4}>
{/* Cart Items */}
<Grid item xs={12} md={8}>
<TableContainer
component={Paper}
sx={{
borderRadius: "12px",
boxShadow: theme.shadows[2],
overflow: "hidden",
}}
>
<Table>
<TableHead>
<TableRow sx={{ backgroundColor: "#f5f5f5" }}>
<TableCell sx={{ fontWeight: 700 }}>Product</TableCell>
<TableCell align="center" sx={{ fontWeight: 700 }}>
Price
</TableCell>
<TableCell align="center" sx={{ fontWeight: 700 }}>
Quantity
</TableCell>
<TableCell align="right" sx={{ fontWeight: 700 }}>
Subtotal
</TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((item) => (
<TableRow key={item.id}>
<TableCell>
<Box sx={{ display: "flex", alignItems: "center" }}>
<CardMedia
component="img"
image={item.image}
alt={item.name}
sx={{
width: 80,
height: 80,
objectFit: "cover",
borderRadius: "8px",
mr: 3,
}}
/>
<Typography variant="body1" fontWeight={500}>
{item.name}
</Typography>
</Box>
</TableCell>
<TableCell align="center">
${item.price.toFixed(2)}
</TableCell>
<TableCell align="center">
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<IconButton
onClick={() =>
updateQuantity(item.id, item.quantity - 1)
}
sx={{
border: "1px solid #ddd",
"&:hover": {
backgroundColor: "#FF6D00",
color: "#fff",
},
}}
>
<RemoveIcon fontSize="small" />
</IconButton>
<Typography
sx={{
mx: 2,
minWidth: "30px",
textAlign: "center",
fontWeight: 500,
}}
>
{item.quantity}
</Typography>
<IconButton
onClick={() =>
updateQuantity(item.id, item.quantity + 1)
}
sx={{
border: "1px solid #ddd",
"&:hover": {
backgroundColor: "#FF6D00",
color: "#fff",
},
}}
>
<AddIcon fontSize="small" />
</IconButton>
</Box>
</TableCell>
<TableCell align="right">
${(item.price * item.quantity).toFixed(2)}
</TableCell>
<TableCell align="right">
<IconButton
onClick={() => removeItem(item.id)}
sx={{
color: theme.palette.error.main,
"&:hover": {
backgroundColor: "rgba(244, 67, 54, 0.08)",
},
}}
>
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<Box
sx={{ mt: 3, display: "flex", justifyContent: "space-between" }}
>
<Button
component={Link}
to="/products"
variant="outlined"
sx={{
borderColor: "#FF6D00",
color: "#FF6D00",
py: 1,
px: 3,
fontWeight: 600,
"&:hover": {
backgroundColor: "rgba(255, 109, 0, 0.08)",
},
}}
>
Continue Shopping
</Button>
<Button
variant="outlined"
sx={{
borderColor: theme.palette.grey[400],
color: theme.palette.text.secondary,
py: 1,
px: 3,
fontWeight: 600,
"&:hover": {
backgroundColor: "rgba(0, 0, 0, 0.04)",
},
}}
onClick={() => setItems([])}
>
Clear Cart
</Button>
</Box>
</Grid>
{/* Order Summary */}
<Grid item xs={12} md={4}>
<Paper
sx={{
p: 3,
borderRadius: "12px",
boxShadow: theme.shadows[2],
backgroundColor: "#fff",
}}
>
<Typography
variant="h5"
fontWeight="bold"
sx={{ mb: 3, color: theme.palette.text.primary }}
>
Order Summary
</Typography>
<Box sx={{ mb: 2 }}>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
mb: 1,
}}
>
<Typography variant="body1">Subtotal:</Typography>
<Typography variant="body1" fontWeight={500}>
${subtotal.toFixed(2)}
</Typography>
</Box>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
mb: 1,
}}
>
<Typography variant="body1">Shipping:</Typography>
<Typography variant="body1" fontWeight={500}>
{shipping === 0 ? (
<Box component="span" sx={{ color: "success.main" }}>
Free
</Box>
) : (
`$${shipping.toFixed(2)}`
)}
</Typography>
</Box>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
mb: 2,
}}
>
<Typography variant="body1">Tax (8%):</Typography>
<Typography variant="body1" fontWeight={500}>
${tax.toFixed(2)}
</Typography>
</Box>
<Divider sx={{ my: 2 }} />
<Box
sx={{
display: "flex",
justifyContent: "space-between",
mb: 3,
}}
>
<Typography variant="h6" fontWeight={700}>
Total:
</Typography>
<Typography variant="h6" fontWeight={700} color="#FF6D00">
${total.toFixed(2)}
</Typography>
</Box>
</Box>
<Button
fullWidth
variant="contained"
size="large"
startIcon={<ShoppingCartCheckoutIcon />}
sx={{
backgroundColor: "#FF6D00",
color: "#fff",
py: 1.5,
fontSize: "1.1rem",
fontWeight: 600,
borderRadius: "8px",
"&:hover": {
backgroundColor: "#FF5722",
boxShadow: "0 4px 12px rgba(255,109,0,0.3)",
},
}}
>
Proceed to Checkout
</Button>
<Typography
variant="body2"
sx={{ mt: 2, textAlign: "center", color: "#757575" }}
>
or{" "}
<Link
to="/products"
style={{
color: "#FF6D00",
textDecoration: "none",
fontWeight: 500,
}}
>
Continue Shopping
</Link>
</Typography>
</Paper>
{/* Promo Code */}
<Paper
sx={{
p: 3,
mt: 3,
borderRadius: "12px",
boxShadow: theme.shadows[2],
backgroundColor: "#fff",
}}
>
<Typography
variant="h6"
fontWeight="bold"
sx={{ mb: 2, color: theme.palette.text.primary }}
>
Have a Promo Code?
</Typography>
<Box sx={{ display: "flex" }}>
<input
type="text"
placeholder="Enter promo code"
style={{
flexGrow: 1,
padding: "12px 16px",
border: "1px solid #ddd",
borderRadius: "8px 0 0 8px",
fontSize: "1rem",
outline: "none",
}}
/>
<Button
variant="contained"
sx={{
backgroundColor: "#FF6D00",
color: "#fff",
borderRadius: "0 8px 8px 0",
px: 3,
"&:hover": {
backgroundColor: "#FF5722",
},
}}
>
Apply
</Button>
</Box>
</Paper>
</Grid>
</Grid>
)}
{/* Similar Products */}
{items.length > 0 && (
<Box sx={{ mt: 10 }}>
<Typography
variant="h4"
fontWeight="bold"
sx={{
mb: 6,
position: "relative",
"&:after": {
content: '""',
position: "absolute",
bottom: -12,
left: 0,
width: "80px",
height: "4px",
backgroundColor: "#FF6D00",
},
}}
>
You Might Also Like
</Typography>
<Grid container spacing={4}>
{similarProducts.map((product) => (
<Grid item xs={12} sm={6} md={4} key={product.id}>
<Card
sx={{
height: "100%",
display: "flex",
flexDirection: "column",
transition: "all 0.3s ease",
borderRadius: "12px",
overflow: "hidden",
boxShadow: theme.shadows[2],
"&:hover": {
transform: "translateY(-8px)",
boxShadow: theme.shadows[6],
},
}}
>
<Box
sx={{
height: "200px",
backgroundImage: `url(${product.image})`,
backgroundSize: "cover",
backgroundPosition: "center",
transition: "transform 0.5s ease",
"&:hover": {
transform: "scale(1.05)",
},
}}
/>
<Box sx={{ p: 3, flexGrow: 1 }}>
<Typography
variant="h5"
fontWeight="bold"
sx={{
mb: 1,
fontSize: "1.3rem",
color: theme.palette.text.primary,
}}
>
{product.name}
</Typography>
<Typography
variant="h6"
color="#FF6D00"
sx={{ mb: 2, fontWeight: 700 }}
>
{product.price}
</Typography>
<Button
component={Link}
to={`/products/chairs/${product.id}`}
fullWidth
variant="outlined"
sx={{
borderColor: "#FF6D00",
color: "#FF6D00",
fontWeight: 600,
"&:hover": {
backgroundColor: "rgba(255,109,0,0.1)",
borderColor: "#FF5722",
},
}}
>
View Product
</Button>
</Box>
</Card>
</Grid>
))}
</Grid>
</Box>
)}
</Container>
</Box>
);
};
export default CartPage;

View File

@ -0,0 +1,19 @@
import React from "react";
import { Outlet } from "react-router-dom";
import { Box } from "@mui/material";
// import CopyrightFooter from '../../assets/copyright';
export const Cartbase: React.FC = () => {
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
Height: "100vh",
backgroundColor: "red",
}}
>
<Outlet />
</Box>
);
};

View File

@ -0,0 +1,297 @@
import React, { useEffect } from "react";
import {
Box,
Container,
Typography,
Button,
Grid,
Card,
Paper,
Divider,
} from "@mui/material";
import { useNavigate } from "react-router-dom";
import { Category } from "./types";
import chairone from "../images/chairone.png";
import chairtwo from "../images/chairtwo.png";
import chairthree from "../images/chairthree.png";
import carimage from "../images/caraone.png";
export const categories: Category[] = [
{
id: "chairs",
name: "Premium Seating Collection",
image: `${chairone}`,
description: "Ergonomic masterpieces combining comfort and style",
count: 28,
},
{
id: "luxurychairs",
name: "Luxury Chairs",
image: `${chairtwo}`,
description: "Ergonomic designs for modern living",
count: 28,
},
{
id: "Premium Beds",
name: "Premium Beds",
image: `${chairthree}`,
description: "Sleep in ultimate comfort",
count: 15,
},
{
id: "DesignerSofas",
name: "Designer Sofas",
image: `${chairone}`,
description: "Centerpieces for your living room",
count: 22,
},
{
id: "ElegantLighting",
name: "Elegant Lighting",
image: `${chairthree}`,
description: "Illuminate with style",
count: 34,
},
{
id: "ModernTables",
name: "Modern Tables",
image: `${chairtwo}`,
description: "Functional art for your home",
count: 19,
},
{
id: "HomeDecor",
name: "Home Decor",
image: `${chairone}`,
description: "Complete your interior design",
count: 42,
},
];
const CategoryPage = () => {
const navigate = useNavigate();
return (
<Box sx={{ backgroundColor: "#f5f5f5", minHeight: "100vh" }}>
{/* Hero Section */}
<Box
sx={{
height: { xs: "60vh", md: "80vh" },
backgroundImage: `url(${carimage})`,
backgroundSize: "cover",
backgroundPosition: "center",
display: "flex",
alignItems: "center",
justifyContent: "center",
textAlign: "center",
color: "#fff",
position: "relative",
}}
>
<Box sx={{ position: "relative", zIndex: 2, maxWidth: "800px", px: 3 }}>
<Typography
variant="h1"
fontWeight="bold"
sx={{
fontSize: { xs: "2.5rem", md: "4rem" },
mb: 3,
textShadow: "2px 2px 4px rgba(0,0,0,0.5)",
}}
>
Crafted for Comfort, Designed for Life
</Typography>
<Typography
variant="h5"
sx={{
mb: 4,
color: "#FFA500",
fontWeight: 500,
}}
>
Discover furniture that transforms your space
</Typography>
</Box>
</Box>
{/* About Section */}
<Paper
elevation={0}
sx={{
backgroundColor: "#fff",
py: 8,
borderBottom: "1px solid rgba(0,0,0,0.1)",
}}
>
<Container maxWidth="lg">
<Typography
variant="h3"
fontWeight="bold"
align="center"
sx={{ mb: 4 }}
>
Our Craftsmanship Philosophy
</Typography>
<Grid container spacing={6}>
<Grid item xs={12} md={6}>
<Typography
variant="body1"
sx={{ mb: 3, fontSize: "1.1rem", lineHeight: 1.8 }}
>
At Panto, we believe furniture should be both beautiful and
functional. Each piece in our collection is meticulously crafted
by skilled artisans using sustainable materials and time-honored
techniques.
</Typography>
<Typography
variant="body1"
sx={{ fontSize: "1.1rem", lineHeight: 1.8 }}
>
Our designers work closely with ergonomic specialists to create
pieces that don't just look good, but feel good to use every
day. From the carefully selected fabrics to the precision
joinery, every detail matters.
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography
variant="body1"
sx={{ mb: 3, fontSize: "1.1rem", lineHeight: 1.8 }}
>
We source our materials from responsible suppliers, ensuring
that our environmental impact is minimized. The hardwoods in our
furniture come from FSC-certified forests, and our fabrics are
OEKO-TEX certified.
</Typography>
<Typography
variant="body1"
sx={{ fontSize: "1.1rem", lineHeight: 1.8 }}
>
With over 15 years in the industry, we've perfected the balance
between form and function, offering designs that stand the test
of time both in durability and style.
</Typography>
</Grid>
</Grid>
</Container>
</Paper>
{/* Category Grid */}
<Container maxWidth="xl" sx={{ py: 10 }}>
<Typography
variant="h2"
fontWeight="bold"
align="center"
sx={{
mb: 2,
color: "#333",
}}
>
Explore Our Collections
</Typography>
<Typography
variant="h5"
align="center"
sx={{
mb: 6,
color: "#666",
}}
>
Each collection tells a unique design story
</Typography>
<Grid container spacing={6}>
{categories.map((category) => (
<Grid item xs={12} sm={6} md={4} key={category.id}>
<Card
sx={{
height: "100%",
display: "flex",
flexDirection: "column",
transition: "all 0.3s ease",
"&:hover": {
transform: "translateY(-10px)",
boxShadow: "0 15px 30px rgba(0,0,0,0.15)",
},
}}
>
<Box
sx={{
height: "300px",
backgroundImage: `url(${category.image})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
/>
<Box sx={{ p: 4, flexGrow: 1 }}>
<Typography variant="h4" fontWeight="bold" sx={{ mb: 2 }}>
{category.name}
</Typography>
<Typography variant="body1" sx={{ mb: 3, color: "#666" }}>
{category.description}
</Typography>
<Button
fullWidth
variant="contained"
onClick={() => navigate(`/collections/${category.id}`)}
sx={{
backgroundColor: "#FFA500",
color: "#fff",
py: 1.5,
"&:hover": {
backgroundColor: "#E59400",
},
}}
>
Browse Collection ({category.count})
</Button>
</Box>
</Card>
</Grid>
))}
</Grid>
</Container>
{/* Testimonials */}
<Paper
elevation={0}
sx={{ backgroundColor: "#333", color: "#fff", py: 10 }}
>
<Container maxWidth="lg">
<Typography
variant="h3"
fontWeight="bold"
align="center"
sx={{ mb: 6 }}
>
What Our Customers Say
</Typography>
<Grid container spacing={4}>
{[
"The most comfortable chair I've ever owned - worth every penny!",
"Transformed my living room completely. The quality is exceptional.",
"Best furniture purchase I've made in years. Stunning and sturdy.",
].map((quote, i) => (
<Grid item xs={12} md={4} key={i}>
<Box
sx={{ p: 4, backgroundColor: "#444", borderRadius: "8px" }}
>
<Typography
variant="body1"
sx={{ fontStyle: "italic", mb: 2 }}
>
"{quote}"
</Typography>
<Typography variant="body2" color="#FFA500">
Happy Customer
</Typography>
</Box>
</Grid>
))}
</Grid>
</Container>
</Paper>
</Box>
);
};
export default CategoryPage;

View File

@ -0,0 +1,81 @@
import React from "react";
import { Box, Typography, Button } from "@mui/material";
import { motion } from "framer-motion";
import { Link } from "react-router-dom";
interface CategoryCardProps {
name: string;
image: string;
description: string;
count: number;
delay?: number;
}
const CategoryCard: React.FC<CategoryCardProps> = ({
name,
image,
description,
count,
delay = 0,
}) => {
return (
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay }}
whileHover={{ y: -10 }}
>
<Box
sx={{
height: "100%",
backgroundColor: "#fff",
borderRadius: "16px",
overflow: "hidden",
boxShadow: "0 10px 20px rgba(0,0,0,0.1)",
transition: "all 0.3s ease",
"&:hover": {
boxShadow: "0 15px 30px rgba(0,0,0,0.2)",
},
}}
>
<Box
sx={{
height: "300px",
backgroundImage: `linear-gradient(rgba(0,0,0,0.1), rgba(0,0,0,0.3)), url(${image})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
/>
<Box sx={{ p: 4 }}>
<Typography variant="h4" fontWeight="bold" sx={{ mb: 1 }}>
{name}
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}>
{description}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
{count} products available
</Typography>
<Button
component={Link}
to={`/categories/${name.toLowerCase().replace(" ", "-")}`}
variant="outlined"
size="medium"
sx={{
color: "#000",
borderColor: "#000",
"&:hover": {
backgroundColor: "#000",
color: "#fff",
},
}}
>
View Collection
</Button>
</Box>
</Box>
</motion.div>
);
};
export default CategoryCard;

View File

@ -0,0 +1,19 @@
import React from "react";
import { Outlet } from "react-router-dom";
import { Box } from "@mui/material";
// import CopyrightFooter from '../../assets/copyright';
export const Categorybase: React.FC = () => {
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
Height: "100vh",
backgroundColor: "red",
}}
>
<Outlet />
</Box>
);
};

View File

@ -0,0 +1,289 @@
import React from "react";
import {
Box,
Container,
Typography,
Grid,
Card,
Button,
Paper,
Divider,
} from "@mui/material";
import { useParams, Link } from "react-router-dom";
import { Product } from "./types";
import chairone from "../images/chairone.png";
import chairtwo from "../images/chairtwo.png";
import chairthree from "../images/chairthree.png";
const collectionItems: Record<string, Product[]> = {
chairs: [
{
id: "ergo-luxe",
name: "Ergo-Luxe Executive Chair",
price: "$499",
image: `${chairone}`,
description: "Premium ergonomic support with breathable mesh back",
details: [
"Adjustable lumbar support",
"Tilt tension control",
"Waterfall seat edge",
"Dimensions: 27.5″D x 27″W x 40-44″H",
"Weight capacity: 350 lbs",
],
features: [
"Lumbar support with 3 adjustment points",
"Tilt lock mechanism",
"Certified sustainable materials",
],
},
{
id: "cloud-comfort",
name: "Cloud Comfort Chair",
price: "$349",
image: `${chairtwo}`,
description: "Plush seating with exceptional support",
details: [
"High-density foam cushioning",
"360-degree swivel",
"Nylon base with dual-wheel casters",
],
features: [
"Breathable fabric upholstery",
"Easy assembly",
"Modern minimalist design",
],
},
],
"luxury-chairs": [
{
id: "velvet-throne",
name: "Velvet Throne Armchair",
price: "$899",
image: `${chairthree}`,
description: "Opulent seating with premium velvet upholstery",
details: [
"Solid walnut legs",
"Hand-tufted detailing",
"Dimensions: 32″D x 36″W x 34″H",
],
features: [
"Premium Italian velvet",
"Handcrafted construction",
"Limited edition",
],
},
],
"premium-beds": [
{
id: "serenity-sleep",
name: "Serenity Sleep King Bed",
price: "$2,499",
image: `${chairone}`,
description: "Ultimate sleep system with premium materials",
details: [
"Solid hardwood frame",
"Integrated slat system",
"Dimensions: 80″W x 76″L x 42″H",
],
features: [
"No-squeak construction",
"Eco-friendly finishes",
"10-year warranty",
],
},
],
// Add more collections as needed
};
const CollectionPage = () => {
const { collectionId } = useParams<{ collectionId: string }>();
const collection = collectionId ? collectionItems[collectionId] || [] : [];
return (
<Box sx={{ backgroundColor: "#f5f5f5", minHeight: "100vh" }}>
{/* Collection Hero */}
<Box
sx={{
height: "50vh",
backgroundImage:
"linear-gradient(rgba(0,0,0,0.2), rgba(0,0,0,0.2)), url(/images/collection-bg.jpg)",
backgroundSize: "cover",
backgroundPosition: "center",
display: "flex",
alignItems: "center",
justifyContent: "center",
textAlign: "center",
color: "#fff",
position: "relative",
}}
>
<Box sx={{ position: "relative", zIndex: 2 }}>
<Typography
variant="h1"
fontWeight="bold"
sx={{
fontSize: { xs: "2.5rem", md: "4rem" },
mb: 2,
textTransform: "capitalize",
}}
>
{collectionId} Collection
</Typography>
<Typography variant="h5" sx={{ color: "#FFA500" }}>
{collection.length} premium designs available
</Typography>
</Box>
</Box>
{/* Collection Description */}
<Paper elevation={0} sx={{ backgroundColor: "#fff", py: 8 }}>
<Container maxWidth="lg">
<Typography variant="h3" fontWeight="bold" sx={{ mb: 4 }}>
About This Collection
</Typography>
<Typography
variant="body1"
sx={{ mb: 3, fontSize: "1.1rem", lineHeight: 1.8 }}
>
Our {collectionId} collection represents the pinnacle of design and
craftsmanship. Each piece is created with meticulous attention to
detail, using only the finest materials sourced from sustainable
suppliers.
</Typography>
<Typography
variant="body1"
sx={{ fontSize: "1.1rem", lineHeight: 1.8 }}
>
From the initial sketches to the final quality inspection, every
step in our process is carefully monitored to ensure exceptional
quality and durability that will last for generations.
</Typography>
<Divider sx={{ my: 6 }} />
<Typography variant="h4" fontWeight="bold" sx={{ mb: 3 }}>
Collection Highlights
</Typography>
<Grid container spacing={4} sx={{ mb: 6 }}>
{[
"Handcrafted by skilled artisans",
"Sustainable materials",
"5-year craftsmanship warranty",
"Free design consultation available",
].map((item, i) => (
<Grid item xs={12} sm={6} key={i}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Box
sx={{
width: "8px",
height: "8px",
backgroundColor: "#FFA500",
mr: 2,
}}
/>
<Typography>{item}</Typography>
</Box>
</Grid>
))}
</Grid>
</Container>
</Paper>
{/* Products Grid */}
<Container maxWidth="xl" sx={{ py: 10 }}>
<Typography
variant="h2"
fontWeight="bold"
sx={{
mb: 2,
color: "#333",
}}
>
Featured {collectionId}
</Typography>
<Typography
variant="h5"
sx={{
mb: 6,
color: "#666",
}}
>
Browse our curated selection
</Typography>
<Grid container spacing={6}>
{collection.map((product) => (
<Grid item xs={12} sm={6} md={4} key={product.id}>
<Card
sx={{
height: "100%",
display: "flex",
flexDirection: "column",
transition: "all 0.3s ease",
"&:hover": {
transform: "translateY(-10px)",
boxShadow: "0 15px 30px rgba(0,0,0,0.15)",
},
}}
>
<Box
sx={{
height: "300px",
backgroundImage: `url(${product.image})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
/>
<Box sx={{ p: 4, flexGrow: 1 }}>
<Typography variant="h4" fontWeight="bold" sx={{ mb: 2 }}>
{product.name}
</Typography>
<Typography variant="body1" sx={{ mb: 2, color: "#666" }}>
{product.description}
</Typography>
<Box sx={{ mb: 3 }}>
{product.details?.map((detail, i) => (
<Typography
key={i}
variant="body2"
sx={{
display: "flex",
alignItems: "center",
mb: 1,
}}
>
<span style={{ color: "#FFA500", marginRight: "8px" }}>
</span>
{detail}
</Typography>
))}
</Box>
<Typography variant="h5" color="#FFA500" sx={{ mb: 3 }}>
{product.price}
</Typography>
<Button
component={Link}
to={`/products/${collectionId}/${product.id}`}
fullWidth
variant="contained"
sx={{
backgroundColor: "#FFA500",
color: "#fff",
py: 1.5,
"&:hover": {
backgroundColor: "#E59400",
},
}}
>
View Details
</Button>
</Box>
</Card>
</Grid>
))}
</Grid>
</Container>
</Box>
);
};
export default CollectionPage;

View File

@ -0,0 +1,18 @@
// src/types.ts
export interface Category {
id: string;
name: string;
image: string;
description: string;
count: number;
}
export interface Product {
id: string;
name: string;
price: string;
image: string;
description?: string;
details?: string[];
features: string[];
}

View File

@ -0,0 +1,410 @@
import React, { useState, useEffect } from "react";
import {
TextField,
Button,
Box,
Typography,
Grid,
InputAdornment,
IconButton,
Fade,
Slide,
} from "@mui/material";
import {
Person,
Phone,
Email,
Chat,
Facebook,
Twitter,
LinkedIn,
Instagram,
} from "@mui/icons-material";
import { keyframes } from "@emotion/react";
const fadeIn = keyframes`
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
`;
const pulse = keyframes`
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
`;
const changingTexts = [
"Design",
"Comfort",
"Quality",
"Inspiration",
"Your Space",
];
const ContactForm: React.FC = () => {
const [currentText, setCurrentText] = useState<string>(changingTexts[0]);
const [animate, setAnimate] = useState<boolean>(true);
useEffect(() => {
const interval = setInterval(() => {
setAnimate(false);
setTimeout(() => {
setCurrentText((prevText) => {
const currentIndex = changingTexts.indexOf(prevText);
return changingTexts[(currentIndex + 1) % changingTexts.length];
});
setAnimate(true);
}, 300);
}, 2000);
return () => clearInterval(interval);
}, []);
return (
<Box
sx={{
minHeight: "100vh",
width: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
bgcolor: "#f9f5f0", // Warm off-white background
color: "#333",
p: 4,
position: "relative",
overflow: "hidden",
}}
>
{/* Decorative elements */}
<Box
sx={{
position: "absolute",
top: -100,
right: -100,
width: 400,
height: 400,
borderRadius: "50%",
bgcolor: "rgba(255, 152, 0, 0.08)",
zIndex: 0,
}}
/>
<Box
sx={{
position: "absolute",
bottom: -150,
left: -150,
width: 500,
height: 500,
borderRadius: "50%",
bgcolor: "rgba(255, 152, 0, 0.05)",
zIndex: 0,
}}
/>
<Grid
container
spacing={6}
alignItems="center"
justifyContent="space-between"
sx={{ position: "relative", zIndex: 1 }}
>
{/* Left Side */}
<Grid item xs={12} md={6}>
<Slide in={true} direction="right" timeout={800}>
<Box>
<Typography
variant="h2"
fontWeight="bold"
sx={{
color: "#333",
mb: 2,
fontSize: { xs: "2.5rem", md: "3.5rem" },
}}
>
Let's Create Something Beautiful
</Typography>
<Box sx={{ height: 80, display: "flex", alignItems: "center" }}>
<Fade in={animate} timeout={500}>
<Typography
variant="h2"
fontWeight="bold"
sx={{
color: "#FF6D00",
fontSize: { xs: "2.5rem", md: "3.5rem" },
animation: `${fadeIn} 0.5s ease-out`,
}}
>
{currentText}
</Typography>
</Fade>
</Box>
<Typography
variant="h6"
mt={3}
sx={{
color: "#666",
lineHeight: 1.6,
maxWidth: 500,
}}
>
Whether you're looking for custom furniture solutions or have
questions about our products, our design team is ready to help
bring your vision to life.
</Typography>
{/* Contact Info */}
<Box sx={{ mt: 4 }}>
<Typography
variant="body1"
sx={{
display: "flex",
alignItems: "center",
mb: 1.5,
color: "#555",
}}
>
<Email sx={{ color: "#FF6D00", mr: 2 }} />{" "}
contact@furniturehaven.com
</Typography>
<Typography
variant="body1"
sx={{
display: "flex",
alignItems: "center",
color: "#555",
}}
>
<Phone sx={{ color: "#FF6D00", mr: 2 }} /> (123) 456-7890
</Typography>
</Box>
{/* Social Media Icons */}
<Box sx={{ display: "flex", gap: 1, mt: 4 }}>
{[Facebook, Twitter, LinkedIn, Instagram].map((Icon, index) => (
<Slide
key={index}
in={true}
direction="up"
timeout={800 + index * 100}
>
<IconButton
sx={{
color: "#888",
bgcolor: "rgba(255, 109, 0, 0.1)",
"&:hover": {
color: "#FF6D00",
bgcolor: "rgba(255, 109, 0, 0.2)",
},
transition: "all 0.3s",
}}
>
<Icon />
</IconButton>
</Slide>
))}
</Box>
</Box>
</Slide>
</Grid>
{/* Right Side - Contact Form */}
<Grid item xs={12} md={6}>
<Slide in={true} direction="left" timeout={800}>
<Box
component="form"
sx={{
bgcolor: "white",
p: { xs: 3, md: 4 },
borderRadius: 2,
boxShadow: "0 10px 30px rgba(0,0,0,0.05)",
position: "relative",
overflow: "hidden",
"&:hover": {
boxShadow: "0 15px 35px rgba(0,0,0,0.1)",
},
transition: "all 0.3s ease",
}}
>
{/* Decorative corner */}
<Box
sx={{
position: "absolute",
top: 0,
right: 0,
width: 0,
height: 0,
borderStyle: "solid",
borderWidth: "0 80px 80px 0",
borderColor: "transparent #FF6D00 transparent transparent",
zIndex: 1,
}}
/>
<Typography
variant="h5"
fontWeight="bold"
sx={{
mb: 4,
color: "#333",
position: "relative",
"&:after": {
content: '""',
position: "absolute",
bottom: -10,
left: 0,
width: 60,
height: 3,
bgcolor: "#FF6D00",
},
}}
>
Get In Touch
</Typography>
<TextField
fullWidth
variant="outlined"
placeholder="Your Name"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Person sx={{ color: "#FF6D00" }} />
</InputAdornment>
),
}}
sx={{
mb: 3,
"& .MuiOutlinedInput-root": {
"& fieldset": {
borderColor: "#ddd",
},
"&:hover fieldset": {
borderColor: "#FF6D00",
},
"&.Mui-focused fieldset": {
borderColor: "#FF6D00",
},
},
}}
/>
<Grid container spacing={2} sx={{ mb: 3 }}>
<Grid item xs={12} sm={6}>
<TextField
fullWidth
variant="outlined"
placeholder="Phone Number"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Phone sx={{ color: "#FF6D00" }} />
</InputAdornment>
),
}}
sx={{
"& .MuiOutlinedInput-root": {
"& fieldset": {
borderColor: "#ddd",
},
"&:hover fieldset": {
borderColor: "#FF6D00",
},
"&.Mui-focused fieldset": {
borderColor: "#FF6D00",
},
},
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
fullWidth
variant="outlined"
placeholder="Email Address"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Email sx={{ color: "#FF6D00" }} />
</InputAdornment>
),
}}
sx={{
"& .MuiOutlinedInput-root": {
"& fieldset": {
borderColor: "#ddd",
},
"&:hover fieldset": {
borderColor: "#FF6D00",
},
"&.Mui-focused fieldset": {
borderColor: "#FF6D00",
},
},
}}
/>
</Grid>
</Grid>
<TextField
fullWidth
variant="outlined"
placeholder="How can we help you?"
multiline
rows={4}
InputProps={{
startAdornment: (
<InputAdornment
position="start"
sx={{ alignSelf: "flex-start", mt: 1 }}
>
<Chat sx={{ color: "#FF6D00" }} />
</InputAdornment>
),
}}
sx={{
mb: 4,
"& .MuiOutlinedInput-root": {
"& fieldset": {
borderColor: "#ddd",
},
"&:hover fieldset": {
borderColor: "#FF6D00",
},
"&.Mui-focused fieldset": {
borderColor: "#FF6D00",
},
},
}}
/>
<Button
variant="contained"
fullWidth
sx={{
bgcolor: "#FF6D00",
color: "white",
fontWeight: "bold",
borderRadius: 1,
py: 1.5,
fontSize: "1rem",
textTransform: "none",
"&:hover": {
bgcolor: "#E65100",
animation: `${pulse} 1s infinite`,
},
}}
>
Send Message
</Button>
</Box>
</Slide>
</Grid>
</Grid>
</Box>
);
};
export default ContactForm;

283
src/components/foorter.tsx Normal file
View File

@ -0,0 +1,283 @@
import React from "react";
import {
Box,
Container,
Grid,
Typography,
Divider,
Link as MuiLink,
useTheme,
} from "@mui/material";
import { Link } from "react-router-dom";
import FacebookIcon from "@mui/icons-material/Facebook";
import TwitterIcon from "@mui/icons-material/Twitter";
import InstagramIcon from "@mui/icons-material/Instagram";
import PinterestIcon from "@mui/icons-material/Pinterest";
interface FooterLink {
text: string;
to: string;
}
const Footer: React.FC = () => {
const theme = useTheme();
const shopLinks: FooterLink[] = [
{ text: "Chairs", to: "/products/chairs" },
{ text: "Sofas", to: "/products/sofas" },
{ text: "Tables", to: "/products/tables" },
{ text: "Accessories", to: "/products/accessories" },
];
const helpLinks: FooterLink[] = [
{ text: "Contact Us", to: "/contact" },
{ text: "Shipping Info", to: "/shipping" },
{ text: "Returns", to: "/returns" },
{ text: "FAQ", to: "/faq" },
];
const companyLinks: FooterLink[] = [
{ text: "About Us", to: "/about" },
{ text: "Blog", to: "/blog" },
{ text: "Careers", to: "/careers" },
{ text: "Press", to: "/press" },
];
const policyLinks: FooterLink[] = [
{ text: "Privacy Policy", to: "/privacy" },
{ text: "Terms of Service", to: "/terms" },
{ text: "Cookie Policy", to: "/cookies" },
];
return (
<Box
component="footer"
sx={{
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.secondary,
pt: 8,
pb: 4,
borderTop: `1px solid ${theme.palette.divider}`,
}}
>
<Container maxWidth="lg">
<Grid container spacing={6}>
{/* About Column */}
<Grid item xs={12} md={4}>
<Typography
variant="h6"
sx={{ mb: 3, fontWeight: 700, color: theme.palette.text.primary }}
>
Comfort Haven
</Typography>
<Typography variant="body1" sx={{ mb: 3, lineHeight: 1.7 }}>
Bringing you premium furniture that combines style, comfort, and
functionality since 2010.
</Typography>
<Box sx={{ display: "flex", gap: 2 }}>
<MuiLink
href="#"
sx={{
color: "inherit",
"&:hover": { color: "#FF6D00" },
}}
>
<FacebookIcon />
</MuiLink>
<MuiLink
href="#"
sx={{
color: "inherit",
"&:hover": { color: "#FF6D00" },
}}
>
<TwitterIcon />
</MuiLink>
<MuiLink
href="#"
sx={{
color: "inherit",
"&:hover": { color: "#FF6D00" },
}}
>
<InstagramIcon />
</MuiLink>
<MuiLink
href="#"
sx={{
color: "inherit",
"&:hover": { color: "#FF6D00" },
}}
>
<PinterestIcon />
</MuiLink>
</Box>
</Grid>
{/* Quick Links Column */}
<Grid item xs={6} md={2}>
<Typography
variant="h6"
sx={{ mb: 3, fontWeight: 700, color: theme.palette.text.primary }}
>
Shop
</Typography>
<Box component="ul" sx={{ listStyle: "none", p: 0, m: 0 }}>
{shopLinks.map((link) => (
<li key={link.to}>
<MuiLink
component={Link}
to={link.to}
sx={{
display: "block",
mb: 1.5,
color: "inherit",
textDecoration: "none",
"&:hover": { color: "#FF6D00" },
}}
>
{link.text}
</MuiLink>
</li>
))}
</Box>
</Grid>
{/* Customer Service Column */}
<Grid item xs={6} md={2}>
<Typography
variant="h6"
sx={{ mb: 3, fontWeight: 700, color: theme.palette.text.primary }}
>
Help
</Typography>
<Box component="ul" sx={{ listStyle: "none", p: 0, m: 0 }}>
{helpLinks.map((link) => (
<li key={link.to}>
<MuiLink
component={Link}
to={link.to}
sx={{
display: "block",
mb: 1.5,
color: "inherit",
textDecoration: "none",
"&:hover": { color: "#FF6D00" },
}}
>
{link.text}
</MuiLink>
</li>
))}
</Box>
</Grid>
{/* Company Column */}
<Grid item xs={12} md={4}>
<Typography
variant="h6"
sx={{ mb: 3, fontWeight: 700, color: theme.palette.text.primary }}
>
Company
</Typography>
<Box component="ul" sx={{ listStyle: "none", p: 0, m: 0 }}>
{companyLinks.map((link) => (
<li key={link.to}>
<MuiLink
component={Link}
to={link.to}
sx={{
display: "block",
mb: 1.5,
color: "inherit",
textDecoration: "none",
"&:hover": { color: "#FF6D00" },
}}
>
{link.text}
</MuiLink>
</li>
))}
</Box>
{/* Newsletter */}
<Box sx={{ mt: 4 }}>
<Typography variant="body1" fontWeight={600} sx={{ mb: 2 }}>
Subscribe to our newsletter
</Typography>
<Box component="form" sx={{ display: "flex" }}>
<input
type="email"
placeholder="Your email"
style={{
flexGrow: 1,
padding: "10px 16px",
border: "1px solid #ddd",
borderRadius: "8px 0 0 8px",
fontSize: "1rem",
outline: "none",
}}
/>
<button
type="submit"
style={{
backgroundColor: "#FF6D00",
color: "#fff",
border: "none",
borderRadius: "0 8px 8px 0",
padding: "0 16px",
cursor: "pointer",
fontWeight: 600,
transition: "background-color 0.3s",
}}
onMouseOver={(e) =>
(e.currentTarget.style.backgroundColor = "#FF5722")
}
onMouseOut={(e) =>
(e.currentTarget.style.backgroundColor = "#FF6D00")
}
>
Join
</button>
</Box>
</Box>
</Grid>
</Grid>
<Divider sx={{ my: 4 }} />
{/* Bottom Footer */}
<Box
sx={{
display: "flex",
flexDirection: { xs: "column", sm: "row" },
justifyContent: "space-between",
alignItems: "center",
}}
>
<Typography variant="body2">
© {new Date().getFullYear()} Comfort Haven. All rights reserved.
</Typography>
<Box sx={{ display: "flex", gap: 3, mt: { xs: 2, sm: 0 } }}>
{policyLinks.map((link) => (
<MuiLink
key={link.to}
component={Link}
to={link.to}
sx={{
color: "inherit",
textDecoration: "none",
"&:hover": { color: "#FF6D00" },
}}
>
{link.text}
</MuiLink>
))}
</Box>
</Box>
</Container>
</Box>
);
};
export default Footer;

267
src/components/header.tsx Normal file
View File

@ -0,0 +1,267 @@
// components/HeaderSection.tsx
import React, { useState, useEffect } from "react";
import { useLocation } from "react-router-dom";
import {
AppBar,
Box,
Button,
Toolbar,
Typography,
IconButton,
Menu,
MenuItem,
Paper,
Stack,
Divider,
Badge,
} from "@mui/material";
import ShoppingCartIcon from "@mui/icons-material/ShoppingCart";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
interface Category {
id: string;
name: string;
image: string;
description: string;
count: number;
}
const categories: Category[] = [
{
id: "chairs",
name: "Premium Seating",
image: "/images/chair-category.jpg",
description: "Ergonomic masterpieces",
count: 28,
},
{
id: "luxury-chairs",
name: "Luxury Chairs",
image: "/images/luxury-chair.jpg",
description: "Opulent designs",
count: 15,
},
{
id: "premium-beds",
name: "Premium Beds",
image: "/images/bed-category.jpg",
description: "Sleep in comfort",
count: 12,
},
{
id: "designer-sofas",
name: "Designer Sofas",
image: "/images/sofa-category.jpg",
description: "Statement pieces",
count: 22,
},
];
const HeaderSection: React.FC = () => {
const location = useLocation();
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [headerBg, setHeaderBg] = useState("transparent");
const [cartItems] = useState(3);
// Dynamic background based on route
useEffect(() => {
const path = location.pathname;
if (
path === "http://localhost:3001" ||
path === "/" ||
path === "/home" ||
path === "/cat"
) {
setHeaderBg("transparent");
} else if (
path.startsWith("/categories/") ||
path.startsWith("/product/")
) {
setHeaderBg("black");
} else {
setHeaderBg("transparent");
}
}, [location.pathname]);
// Menu handlers - simplified
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleMenuClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
return (
<AppBar
position="static"
sx={{
background:
headerBg === "rgba(0, 0, 0, 0)"
? "rgba(0, 0, 0, 0.5)"
: "rgba(0, 0, 0, 0.9)",
backdropFilter: "blur(10px)",
transition: "background 0.3s ease",
}}
>
<Toolbar
sx={{ justifyContent: "space-between", px: { xs: 2, md: 4 }, py: 1 }}
>
{/* Logo */}
<Typography variant="h4" sx={logoStyles}>
PANTO
</Typography>
{/* Navigation */}
<Box sx={{ display: { xs: "none", md: "flex" }, gap: 3, mx: 4 }}>
<Button sx={navButtonStyles}>Home</Button>
{/* Furniture Dropdown - Fixed */}
<Box>
<Button
endIcon={<KeyboardArrowDownIcon />}
onClick={handleMenuOpen}
sx={{
...navButtonStyles,
color: open ? "primary.main" : "white",
}}
aria-controls="furniture-menu"
aria-haspopup="true"
aria-expanded={open ? "true" : undefined}
>
Furniture
</Button>
<Menu
id="furniture-menu"
anchorEl={anchorEl}
open={open}
onClose={handleMenuClose}
MenuListProps={{ sx: { p: 0 } }}
PaperProps={{
sx: menuPaperStyles,
}}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
>
<Stack
direction="row"
spacing={4}
divider={<Divider orientation="vertical" flexItem />}
>
<Box sx={{ width: "30%" }}>
<Typography variant="h6" sx={{ mb: 2, fontWeight: "bold" }}>
Shop By Category
</Typography>
<Stack spacing={1}>
{categories.map((category) => (
<MenuItem
key={category.id}
onClick={handleMenuClose}
sx={menuItemStyles}
>
<Box sx={categoryItemStyles}>
<Box
component="img"
src={category.image}
sx={categoryImageStyles}
alt={category.name}
/>
<Box>
<Typography>{category.name}</Typography>
<Typography
variant="caption"
color="text.secondary"
>
{category.count} items
</Typography>
</Box>
</Box>
</MenuItem>
))}
</Stack>
</Box>
{/* ... rest of the menu content ... */}
</Stack>
</Menu>
</Box>
<Button sx={navButtonStyles}>About Us</Button>
<Button sx={navButtonStyles}>Contact</Button>
</Box>
{/* Shopping Cart */}
<IconButton sx={{ color: "white" }}>
<Badge badgeContent={cartItems} color="primary">
<ShoppingCartIcon fontSize="medium" />
</Badge>
</IconButton>
</Toolbar>
</AppBar>
);
};
// Styles extracted for better readability
const logoStyles = {
fontWeight: "bold",
color: "white",
fontFamily: "'Playfair Display', serif",
letterSpacing: "1px",
};
const navButtonStyles = {
color: "white",
fontSize: "1rem",
textTransform: "none",
"&:hover": { color: "primary.main" },
};
const menuPaperStyles = {
width: "80vw",
maxWidth: "1200px",
overflow: "visible",
mt: 2,
p: 3,
bgcolor: "background.paper",
boxShadow: "0px 10px 30px rgba(0, 0, 0, 0.2)",
"&:before": {
content: '""',
display: "block",
position: "absolute",
top: 0,
left: "50%",
width: 12,
height: 12,
bgcolor: "background.paper",
transform: "translateY(-50%) rotate(45deg)",
zIndex: 0,
},
};
const menuItemStyles = {
px: 0,
py: 1,
"&:hover": { color: "primary.main" },
};
const categoryItemStyles = {
display: "flex",
alignItems: "center",
};
const categoryImageStyles = {
width: 40,
height: 40,
objectFit: "cover",
borderRadius: 1,
mr: 2,
};
export default HeaderSection;

View File

@ -0,0 +1,319 @@
import React, { useState } from "react";
import {
Box,
Container,
Typography,
Grid,
Card,
CardMedia,
CardContent,
IconButton,
Button,
CardActionArea,
Rating,
useTheme,
Grow,
Slide,
} from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
import chairone from "../images/chairone.png";
import chairtwo from "../images/chairtwo.png";
import chairthree from "../images/chairthree.png";
const products = [
{
category: "Chair",
title: "Sakarias Armchair",
price: "$392",
rating: 5,
image: `${chairone}`,
},
{
category: "Chair",
title: "Baltsar Chair",
price: "$299",
rating: 4,
image: `${chairtwo}`,
},
{
category: "Chair",
title: "Anjay Chair",
price: "$519",
rating: 4,
image: `${chairthree}`,
},
{
category: "Chair",
title: "Nyantuy Chair",
price: "$921",
rating: 5,
image: `${chairone}`,
},
{
category: "Beds",
title: "King Bed",
price: "$1899",
rating: 5,
image: `${chairtwo}`,
},
{
category: "Beds",
title: "Queen Bed",
price: "$1499",
rating: 4,
image: `${chairthree}`,
},
{
category: "Beds",
title: "Single Bed",
price: "$899",
rating: 4,
image: `${chairone}`,
},
{
category: "Beds",
title: "Bunk Bed",
price: "$1299",
rating: 5,
image: `${chairtwo}`,
},
{
category: "Sofa",
title: "Luxury Sofa",
price: "$1299",
rating: 5,
image: `${chairthree}`,
},
{
category: "Sofa",
title: "Sectional Sofa",
price: "$1999",
rating: 4,
image: `${chairone}`,
},
{
category: "Sofa",
title: "Leather Sofa",
price: "$1599",
rating: 5,
image: `${chairtwo}`,
},
{
category: "Sofa",
title: "Modern Sofa",
price: "$1099",
rating: 4,
image: `${chairthree}`,
},
{
category: "Lamp",
title: "Modern Lamp",
price: "$199",
rating: 5,
image: `${chairone}`,
},
{
category: "Lamp",
title: "Desk Lamp",
price: "$129",
rating: 4,
image: `${chairtwo}`,
},
{
category: "Lamp",
title: "Floor Lamp",
price: "$249",
rating: 5,
image: `${chairthree}`,
},
{
category: "Lamp",
title: "Vintage Lamp",
price: "$179",
rating: 4,
image: `${chairone}`,
},
];
const categories = ["Chair", "Beds", "Sofa", "Lamp"];
const BestSellingProductSection: React.FC = () => {
const [selectedCategory, setSelectedCategory] = useState<string>("Chair");
const theme = useTheme();
const filteredProducts = products.filter(
(p) => p.category === selectedCategory
);
return (
<Box
sx={{
py: 10,
backgroundColor: "rgba(82, 78, 78, 0.2)",
}}
>
<Container maxWidth="xl">
<Slide in={true} direction="down" timeout={500}>
<Typography
variant="h4"
fontWeight="bold"
align="center"
sx={{ mb: 6 }}
>
Best Selling Products
</Typography>
</Slide>
{/* Tab Container */}
<Box
sx={{
display: "inline-flex",
justifyContent: "center",
gap: 1,
mb: 8,
px: 3,
py: 1.5,
backgroundColor: "white",
borderRadius: "50px",
width: "fit-content",
margin: "0 auto",
boxShadow: theme.shadows[1],
height: "auto",
minHeight: "56px",
alignItems: "center",
}}
>
{categories.map((cat) => (
<Button
key={cat}
variant={selectedCategory === cat ? "contained" : "text"}
onClick={() => setSelectedCategory(cat)}
sx={{
borderRadius: "50px",
textTransform: "none",
fontWeight: 600,
letterSpacing: "0.5px",
px: 3,
py: 1,
minWidth: 0,
backgroundColor:
selectedCategory === cat ? "#000" : "transparent",
color: selectedCategory === cat ? "#fff" : "#333",
"&:hover": {
backgroundColor:
selectedCategory === cat ? "#333" : "rgba(0,0,0,0.05)",
},
transition: "all 0.3s ease",
}}
>
{cat}
</Button>
))}
</Box>
<Box sx={{ mt: 6 }}>
<Grid container spacing={4} justifyContent="center">
{filteredProducts.slice(0, 4).map((product, idx) => (
<Grid item key={idx} xs={12} sm={6} md={3} lg={3}>
<Grow
in={true}
timeout={500}
style={{ transitionDelay: `${idx * 100}ms` }}
>
<Card
sx={{
borderRadius: "16px",
transition: "all 0.3s ease",
"&:hover": {
transform: "translateY(-8px)",
boxShadow: theme.shadows[8],
},
}}
>
<CardActionArea>
<Box
sx={{
p: 3,
display: "flex",
justifyContent: "center",
backgroundColor: "rgba(0,0,0,0.02)",
}}
>
<CardMedia
component="img"
height="220"
image={product.image}
alt={product.title}
sx={{
objectFit: "contain",
transition: "transform 0.3s ease",
"&:hover": {
transform: "scale(1.05)",
},
}}
/>
</Box>
<CardContent sx={{ px: 3, pb: 3 }}>
<Typography
variant="overline"
color="text.secondary"
sx={{
display: "block",
mb: 1,
fontWeight: 600,
}}
>
{product.category}
</Typography>
<Typography
variant="h6"
fontWeight="bold"
sx={{ mb: 1 }}
>
{product.title}
</Typography>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
mt: 2,
}}
>
<Typography variant="h6" color="text.primary">
{product.price}
</Typography>
<Box>
<Rating
value={product.rating}
readOnly
size="small"
/>
<IconButton
sx={{
ml: 1,
color: "#000",
backgroundColor: "rgba(0, 0, 0, 0.1)",
"&:hover": {
backgroundColor: "rgba(0, 0, 0, 0.2)",
},
}}
>
<AddIcon />
</IconButton>
</Box>
</Box>
</CardContent>
</CardActionArea>
</Card>
</Grow>
</Grid>
))}
</Grid>
</Box>
</Container>
</Box>
);
};
export default BestSellingProductSection;

View File

@ -0,0 +1,92 @@
import React from "react";
import { Box } from "@mui/material";
import CarouselSection from "./homepage";
import WhyChooseUsSection from "./WhyChooseUsSection";
// import WhyChooseUsSection from "./WhyChooseUsSection";
import BestSellingProductSection from "./BestSellingProductSection";
import LandingPageSection from "./ex";
import HeaderSection from "../header";
import Testimonials from "./Testimonials";
import carimage from "../images/caraone.png";
import Footer from "../foorter";
import ContactForm from "../contactus/contactus";
const LandingPage: React.FC = () => {
return (
<>
<Box
sx={{
position: "relative",
width: "100%",
height: "130vh",
overflow: "hidden",
}}
>
{/* Background Image - Full width with height contained */}
<Box
sx={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: "100%",
display: "flex",
justifyContent: "center",
backgroundColor: "#f0f0f0", // Fallback color
}}
>
<img
src={carimage}
alt="background"
style={{
width: "100%", // Full width
height: "auto", // Height adjusts proportionally
objectFit: "cover", // Cover width while containing height
objectPosition: "center bottom", // Align at top
}}
/>
</Box>
{/* Dark Overlay (optional) */}
<Box
sx={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0,0,0,0.2)", // Adjust opacity
}}
/>
{/* Transparent Header */}
<HeaderSection />
{/* Centered Content */}
<Box
sx={{
position: "absolute",
top: "30%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "100%",
maxWidth: "1200px", // Optional max width for content
padding: 3,
textAlign: "center",
color: "white",
zIndex: 2,
}}
>
<CarouselSection />
</Box>
</Box>
<WhyChooseUsSection />
<BestSellingProductSection />
<LandingPageSection />
<Testimonials />
<ContactForm />
<Footer />
</>
);
};
export default LandingPage;

View File

@ -0,0 +1,171 @@
import React from "react";
import {
Box,
Typography,
Grid,
Avatar,
Rating,
keyframes,
useTheme,
} from "@mui/material";
const fadeIn = keyframes`
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
`;
const Testimonials: React.FC = () => {
const theme = useTheme();
const testimonials = [
{
name: "Bunga Utami",
initials: "BU",
quote:
"Terimakasih banyak, kini ruanganku menjadi lebih menarik dan terlihat lebih hidup.",
rating: 4.5,
delay: "0.2s",
},
{
name: "Ibnu Sukijan",
initials: "IS",
quote:
"Makasih Panto, aku sekarang berasa tinggal di apartemen yang kerenan bareng-bareng yang terlihat mewah!",
rating: 5,
delay: "0.4s",
},
{
name: "Mpok Ina",
initials: "MI",
quote: "Sopir terjangkau untung banyak",
rating: 4,
delay: "0.6s",
},
];
return (
<Box
sx={{
py: 10,
px: { xs: 4, md: 8 },
backgroundColor: "#fff",
textAlign: "center",
maxWidth: "1400px",
margin: "0 auto",
}}
>
{/* Section Header */}
<Box sx={{ mb: 6 }}>
<Typography
variant="subtitle1"
sx={{
color: "#f4a261",
fontWeight: 600,
letterSpacing: 2,
mb: 1,
animation: `${fadeIn} 0.6s ease-out`,
}}
>
TESTIMONIALS
</Typography>
<Typography
variant="h3"
sx={{
fontWeight: 700,
color: "#000",
fontSize: { xs: "2rem", md: "2.5rem" },
animation: `${fadeIn} 0.6s ease-out 0.1s forwards`,
opacity: 0,
}}
>
Our Client Reviews
</Typography>
</Box>
{/* Testimonial Cards */}
<Grid container spacing={4} justifyContent="center">
{testimonials.map((testimonial, index) => (
<Grid item xs={12} md={4} key={index}>
<Box
sx={{
backgroundColor: "#f9f9f9",
padding: "30px",
borderRadius: "8px",
height: "100%",
display: "flex",
flexDirection: "column",
borderLeft: "4px solid #f4a261",
boxShadow: "0 5px 15px rgba(0,0,0,0.05)",
transition: "transform 0.3s ease",
animation: `${fadeIn} 0.6s ease-out ${testimonial.delay} forwards`,
opacity: 0,
"&:hover": {
transform: "translateY(-5px)",
boxShadow: "0 8px 25px rgba(0,0,0,0.1)",
},
}}
>
<Rating
value={testimonial.rating}
precision={0.5}
readOnly
sx={{
mb: 2,
"& .MuiRating-icon": {
color: "#f4a261",
},
}}
/>
<Typography
variant="body1"
sx={{
color: "#333",
lineHeight: 1.7,
mb: 3,
flexGrow: 1,
fontStyle: "italic",
position: "relative",
pl: 2,
"&::before": {
content: '"\\201C"',
position: "absolute",
left: -5,
top: -15,
fontSize: "3rem",
color: "#f4a261",
opacity: 0.3,
lineHeight: 1,
},
}}
>
{testimonial.quote}
</Typography>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Avatar
sx={{
width: 50,
height: 50,
mr: 2,
bgcolor: "#333",
color: "#fff",
fontWeight: 600,
}}
>
{testimonial.initials}
</Avatar>
<Typography
variant="subtitle1"
sx={{ fontWeight: 600, color: "#000" }}
>
{testimonial.name}
</Typography>
</Box>
</Box>
</Grid>
))}
</Grid>
</Box>
);
};
export default Testimonials;

View File

@ -0,0 +1,109 @@
import React from "react";
import { Box, Container, Typography, Grid, Link } from "@mui/material";
const features = [
{
title: "Luxury Facilities",
desc: "Experience unparalleled comfort with our premium workspaces featuring state-of-the-art amenities, ergonomic furniture, and meticulously designed environments.",
},
{
title: "Affordable Pricing",
desc: "We believe quality shouldn't break the bank. Our competitively priced workspaces deliver exceptional value without compromising on quality.",
},
{
title: "Diverse Choices",
desc: "From cozy private offices to vibrant collaborative spaces, we offer an extensive range of workspace solutions for all business needs.",
},
];
const WhyChooseUsSection: React.FC = () => {
return (
<Box
sx={{
width: "100vw",
position: "relative",
py: 10,
backgroundColor: "#f8f8f8", // Light gray background
}}
>
<Container maxWidth="lg" sx={{ px: 4, width: "100vw" }}>
<Grid container alignItems="flex-start" spacing={6}>
{/* Left Column - Title */}
<Grid item xs={12} md={3} sx={{ pr: 4 }}>
<Typography
variant="h3"
fontWeight="bold"
sx={{
lineHeight: 1.3,
position: "relative",
mb: 4,
"&:after": {
content: '""',
position: "absolute",
bottom: -20,
left: 0,
width: 80,
height: 3,
backgroundColor: "#FF6D00",
},
}}
>
Why Choose Us
</Typography>
</Grid>
{/* Right Column - Features */}
<Grid item xs={12} md={9}>
<Grid container spacing={4}>
{features.map((item, index) => (
<Grid item xs={12} sm={4} key={index}>
<Typography
variant="h6"
fontWeight="bold"
gutterBottom
sx={{
color: "text.primary",
mb: 2,
}}
>
{item.title}
</Typography>
<Typography
variant="body1"
sx={{
color: "text.secondary",
mb: 2,
lineHeight: 1.6,
}}
>
{item.desc}
</Typography>
<Link
href="#"
underline="none"
sx={{
display: "inline-flex",
alignItems: "center",
color: "#FF6D00",
fontWeight: 600,
"&:hover": {
color: "#E65100",
},
}}
>
More Info{" "}
<Box component="span" sx={{ ml: 1 }}>
</Box>
</Link>
</Grid>
))}
</Grid>
</Grid>
</Grid>
</Container>
</Box>
);
};
export default WhyChooseUsSection;

View File

@ -0,0 +1,170 @@
import React from "react";
import { Box, Grid, Typography, keyframes } from "@mui/material";
import image1 from "../images/caraone.png";
import image2 from "../images/caraone.png";
// Define keyframe animations
const fadeIn = keyframes`
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
`;
const LandingPageSection: React.FC = () => {
return (
<Box
sx={{
px: { xs: 4, md: 8 },
py: { xs: 6, md: 10 },
backgroundColor: "#ffffff",
maxWidth: "1400px",
margin: "0 auto",
}}
>
{/* Top Section */}
<Grid
container
spacing={6}
alignItems="center"
sx={{ mb: { xs: 6, md: 10 } }}
>
<Grid item xs={12} md={6}>
<Box
sx={{
borderRadius: 3,
overflow: "hidden",
boxShadow: 3,
height: { xs: "300px", md: "400px" },
animation: `${fadeIn} 0.8s ease-out`,
"& img": {
width: "100%",
height: "100%",
objectFit: "cover",
borderRadius: "12px",
transition: "transform 0.3s ease",
"&:hover": {
transform: "scale(1.02)",
},
},
}}
>
<img src={image1} alt="Experience" />
</Box>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ animation: `${fadeIn} 0.8s ease-out 0.2s forwards` }}>
<Typography
variant="subtitle1"
sx={{ color: "#F4A261", mb: 1, fontWeight: 600 }}
>
EXPERIENCES
</Typography>
<Typography
variant="h4"
fontWeight={700}
sx={{ mb: 2, fontSize: { xs: "1.8rem", md: "2.2rem" } }}
>
We Provide You The Best Experience
</Typography>
<Typography
variant="body1"
sx={{ color: "#555", mt: 2, fontSize: "1.1rem", lineHeight: 1.6 }}
>
You don't have to worry about the result because all of these
interiors are made by people who are professionals in their fields
with an elegant and luxurious style and with premium quality
materials.
</Typography>
<Typography
variant="body1"
sx={{
mt: 3,
fontWeight: 600,
color: "#F4A261",
cursor: "pointer",
display: "inline-block",
"&:hover": { textDecoration: "underline" },
}}
>
More Info
</Typography>
</Box>
</Grid>
</Grid>
{/* Bottom Section */}
<Grid container spacing={6} sx={{ mt: { xs: 4, md: 6 } }}>
<Grid item xs={12} md={6}>
<Box sx={{ animation: `${fadeIn} 0.8s ease-out 0.4s forwards` }}>
<Typography
variant="subtitle1"
sx={{ color: "#F4A261", mb: 1, fontWeight: 600 }}
>
MATERIALS
</Typography>
<Typography
variant="h4"
fontWeight={700}
sx={{ mb: 2, fontSize: { xs: "1.8rem", md: "2.2rem" } }}
>
Very Serious Materials For Making Furniture
</Typography>
<Typography
variant="body1"
sx={{ color: "#555", mt: 2, fontSize: "1.1rem", lineHeight: 1.6 }}
>
Because Panto was very serious about designing furniture for our
environment, using a very expensive and famous capital but at a
relatively low price.
</Typography>
<Typography
variant="body1"
sx={{
mt: 3,
fontWeight: 600,
color: "#F4A261",
cursor: "pointer",
display: "inline-block",
"&:hover": { textDecoration: "underline" },
}}
>
More Info
</Typography>
</Box>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{ animation: `${fadeIn} 0.8s ease-out 0.6s forwards` }}>
<Grid container spacing={2}>
{[image1, image2, image1, image2].map((img, i) => (
<Grid item xs={6} key={i}>
<Box
sx={{
borderRadius: 2,
overflow: "hidden",
boxShadow: 2,
backgroundColor: "#f5f5f5",
height: { xs: "140px", md: "180px" },
"& img": {
width: "100%",
height: "100%",
objectFit: "cover",
borderRadius: "12px",
transition: "transform 0.3s ease",
"&:hover": {
transform: "scale(1.05)",
},
},
}}
>
<img src={img} alt={`Gallery ${i}`} />
</Box>
</Grid>
))}
</Grid>
</Box>
</Grid>
</Grid>
</Box>
);
};
export default LandingPageSection;

View File

@ -0,0 +1,113 @@
import React from "react";
import {
Box,
Container,
IconButton,
InputBase,
Typography,
} from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";
const CarouselSection: React.FC = () => {
return (
<Box
sx={{
position: "relative",
width: "100%",
py: { xs: 8, md: 12 }, // Balanced padding
background: "transparent",
zIndex: 2,
}}
>
<Container maxWidth="md">
<Box
sx={{
textAlign: "center",
px: 2, // Side padding for mobile
width: "1000px",
}}
>
{/* Clean, Bold Heading */}
<Typography
variant="h1"
fontWeight={800}
gutterBottom
sx={{
fontSize: { xs: "3.5rem", md: "6rem" }, // Large but not overwhelming
lineHeight: 1.2,
color: "white",
mb: 3,
letterSpacing: "-0.02em",
}}
>
Make your interior more minimalistic & modern
</Typography>
{/* Airy Subtitle */}
<Typography
variant="subtitle1"
sx={{
fontSize: { xs: "1.1rem", md: "1.25rem" },
fontWeight: 400,
color: "rgba(255,255,255,0.9)",
mb: 5,
maxWidth: 600,
mx: "auto",
lineHeight: 1.6,
}}
>
Transform your space with our curated collection of minimalist
furniture
</Typography>
{/* Refined Search Bar */}
<Box
sx={{
display: "flex",
alignItems: "center",
maxWidth: 500,
mx: "auto",
border: "1px solid rgba(255,255,255,0.4)",
borderRadius: "28px",
px: 2,
py: 0.8,
backgroundColor: "rgba(255,255,255,0.12)",
backdropFilter: "blur(4px)",
"&:hover": {
backgroundColor: "rgba(255,255,255,0.18)",
},
}}
>
<InputBase
placeholder="Find furniture..."
sx={{
flex: 1,
fontSize: "0.95rem",
color: "white",
"&::placeholder": {
color: "rgba(255,255,255,0.7)",
fontSize: "0.9rem",
},
}}
/>
<IconButton
type="submit"
size="small"
sx={{
color: "rgba(255,255,255,0.8)",
"&:hover": {
backgroundColor: "transparent",
color: "white",
},
}}
>
<SearchIcon sx={{ fontSize: "1.3rem" }} />
</IconButton>
</Box>
</Box>
</Container>
</Box>
);
};
export default CarouselSection;

View File

@ -0,0 +1,19 @@
import React from 'react';
import { Outlet } from 'react-router-dom';
import { Box } from '@mui/material';
// import CopyrightFooter from '../../assets/copyright';
export const HomeBase: React.FC = () => {
return (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
backgroundColor: '#f4f4f4',
}}
>
<Outlet />
</Box>
);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

View File

@ -0,0 +1,47 @@
import React from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { Box, Button } from "@mui/material";
const NavigationTab: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
// Define routes and their display names
const tabs = [
{ name: "home", path: "/home" },
{ name: "User Management", path: "/usermanagement" },
{ name: "Vehicle Management", path: "/vehiclemanagement" },
{ name: "Model Management", path: "/modelmanagement" },
// { name: "Password Request", path: "/passwordreqmanagement" },
];
return (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: "transparent",
padding: "20px",
}}
>
{tabs.map((tab) => (
<Button
key={tab.path}
sx={{
color: location.pathname === tab.path ? "green" : "white",
background: location.pathname === tab.path ? "#ffffff9e" : "transparent",
mx: 2,
fontWeight: location.pathname === tab.path ? "bold" : "normal",
}}
variant="text"
onClick={() => navigate(tab.path)}
>
{tab.name}
</Button>
))}
</Box>
);
};
export default NavigationTab;

View File

@ -0,0 +1,607 @@
import React, { useState } from "react";
import {
Box,
Container,
Typography,
Button,
Grid,
Card,
CardMedia,
Divider,
IconButton,
Paper,
Chip,
useTheme,
} from "@mui/material";
import { useParams, Link } from "react-router-dom";
import { Product } from "../category/types";
import AddIcon from "@mui/icons-material/Add";
import RemoveIcon from "@mui/icons-material/Remove";
import ShoppingCartIcon from "@mui/icons-material/ShoppingCart";
import StarIcon from "@mui/icons-material/Star";
import chairone from "../images/chairone.png";
import chairtwo from "../images/chairtwo.png";
import chairthree from "../images/chairthree.png";
const products: Record<string, Record<string, Product>> = {
chairs: {
"ergo-luxe": {
id: "ergo-luxe",
name: "Ergo-Luxe Executive Chair",
price: "$499",
image: `${chairone}`,
description:
"The Ergo-Luxe Executive Chair redefines workplace comfort with its advanced ergonomic design and premium materials. Perfect for long hours of focused work.",
details: [
"Dimensions: 27.5″D x 27″W x 40-44″H",
"Material: Breathable mesh back, premium leather seat",
"Weight capacity: 350 lbs",
"Adjustable arms, seat height, and tilt tension",
"5-year limited warranty",
],
features: [
"Lumbar support with 3 adjustment points",
"Tilt lock mechanism",
"Waterfall seat edge reduces leg pressure",
"Certified sustainable materials",
],
},
"cloud-comfort": {
id: "cloud-comfort",
name: "Cloud Comfort Chair",
price: "$349",
image: `${chairtwo}`,
description:
"Experience cloud-like comfort with our best-selling office chair designed for all-day support and relaxation.",
details: [
"Dimensions: 25″D x 25″W x 38-42″H",
"Material: High-resilience foam, polyester fabric",
"Weight capacity: 300 lbs",
"360-degree swivel with smooth-rolling casters",
"3-year warranty",
],
features: [
"Ergonomic contoured seat",
"Breathable fabric keeps you cool",
"Easy to assemble in under 15 minutes",
"Available in 6 colors",
],
},
},
"luxury-chairs": {
"velvet-throne": {
id: "velvet-throne",
name: "Velvet Throne Armchair",
price: "$899",
image: `${chairthree}`,
description:
"A statement piece that combines luxury and comfort with its sumptuous velvet upholstery and handcrafted details.",
details: [
"Dimensions: 32″D x 36″W x 34″H",
"Material: Italian velvet, solid walnut legs",
"Weight capacity: 400 lbs",
"Hand-tufted diamond pattern",
"Made to order (4-6 week delivery)",
],
features: [
"Premium down-filled cushions",
"Antique brass nailhead trim",
"Expertly handcrafted by artisans",
"Available in 8 velvet colors",
],
},
},
};
const ProductPage = () => {
const { collectionId, productId } = useParams<{
collectionId: string;
productId: string;
}>();
const [quantity, setQuantity] = useState(1);
const [activeImage, setActiveImage] = useState(0);
const theme = useTheme();
const product =
collectionId && productId ? products[collectionId]?.[productId] : null;
if (!product) return <Typography variant="h4">Product not found</Typography>;
const productImages = [
product.image,
"/images/chair-alt1.jpg",
"/images/chair-alt2.jpg",
"/images/chair-detail.jpg",
];
return (
<Box sx={{ backgroundColor: "#f9f9f9", minHeight: "100vh", py: 8 }}>
<Container maxWidth="lg">
<Grid container spacing={6}>
{/* Product Images */}
<Grid item xs={12} md={6}>
<Card
sx={{
mb: 3,
borderRadius: "12px",
overflow: "hidden",
boxShadow: theme.shadows[4],
transition: "transform 0.3s ease",
"&:hover": {
transform: "translateY(-2px)",
},
}}
>
<CardMedia
component="img"
height="500"
image={productImages[activeImage]}
alt={product.name}
sx={{ objectFit: "cover" }}
/>
</Card>
{/* Thumbnail Gallery */}
<Grid container spacing={2} sx={{ mb: 4 }}>
{productImages.map((img, index) => (
<Grid item xs={3} key={index}>
<Card
sx={{
cursor: "pointer",
border:
activeImage === index
? "2px solid #FF6D00"
: "1px solid #e0e0e0",
borderRadius: "8px",
overflow: "hidden",
transition: "all 0.3s ease",
"&:hover": {
borderColor: "#FF6D00",
transform: "scale(1.03)",
},
}}
onClick={() => setActiveImage(index)}
>
<CardMedia
component="img"
height="100"
image={img}
alt={`${product.name} view ${index + 1}`}
sx={{
objectFit: "cover",
opacity: activeImage === index ? 1 : 0.8,
}}
/>
</Card>
</Grid>
))}
</Grid>
{/* Enhanced Description Section */}
<Paper
elevation={0}
sx={{
p: 3,
backgroundColor: "transparent",
border: "1px solid #e0e0e0",
borderRadius: "12px",
mb: 4,
}}
>
<Typography
variant="h5"
sx={{
mb: 2,
fontWeight: 600,
color: theme.palette.text.primary,
display: "flex",
alignItems: "center",
}}
>
<Box
component="span"
sx={{
width: "8px",
height: "8px",
backgroundColor: "#FF6D00",
borderRadius: "50%",
display: "inline-block",
mr: 1.5,
}}
/>
About This Product
</Typography>
<Typography
variant="body1"
sx={{
lineHeight: 1.8,
color: theme.palette.text.secondary,
fontSize: "1.05rem",
}}
>
{product.description}
</Typography>
</Paper>
</Grid>
{/* Product Info */}
<Grid item xs={12} md={6}>
<Paper
elevation={0}
sx={{
backgroundColor: "#fff",
p: { xs: 3, md: 5 },
borderRadius: "12px",
boxShadow: theme.shadows[2],
border: "1px solid rgba(0,0,0,0.05)",
}}
>
<Typography
variant="h3"
fontWeight="bold"
sx={{
mb: 2,
fontSize: { xs: "2rem", md: "2.5rem" },
lineHeight: 1.2,
}}
>
{product.name}
</Typography>
<Box sx={{ display: "flex", alignItems: "center", mb: 3 }}>
<Box sx={{ display: "flex", mr: 2 }}>
{[1, 2, 3, 4, 5].map((i) => (
<StarIcon
key={i}
sx={{
color: i <= 4 ? "#FFA500" : "#e0e0e0",
fontSize: "1.3rem",
}}
/>
))}
</Box>
<Typography variant="body2" color="text.secondary">
(47 customer reviews)
</Typography>
</Box>
<Typography
variant="h3"
color="#FF6D00"
sx={{
mb: 4,
fontSize: "2rem",
fontWeight: 700,
}}
>
{product.price}
<Typography
component="span"
sx={{
fontSize: "1rem",
color: theme.palette.text.secondary,
ml: 1,
}}
>
+ FREE Shipping
</Typography>
</Typography>
{/* Key Features */}
<Box sx={{ mb: 4 }}>
<Typography
variant="h5"
fontWeight="bold"
sx={{
mb: 2,
position: "relative",
"&:after": {
content: '""',
position: "absolute",
bottom: -8,
left: 0,
width: "50px",
height: "3px",
backgroundColor: "#FF6D00",
},
}}
>
Key Features
</Typography>
<Grid container spacing={2}>
{product.features?.map((feature, i) => (
<Grid item xs={12} sm={6} key={i}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Box
sx={{
width: "8px",
height: "8px",
backgroundColor: "#FF6D00",
mr: 2,
borderRadius: "50%",
}}
/>
<Typography sx={{ fontSize: "1.05rem" }}>
{feature}
</Typography>
</Box>
</Grid>
))}
</Grid>
</Box>
{/* Quantity Selector */}
<Box
sx={{
display: "flex",
alignItems: "center",
mb: 4,
p: 2,
backgroundColor: "#f5f5f5",
borderRadius: "8px",
maxWidth: "200px",
}}
>
<Typography variant="h6" sx={{ mr: 3, fontWeight: 500 }}>
Quantity:
</Typography>
<IconButton
onClick={() => setQuantity((q) => Math.max(1, q - 1))}
sx={{
border: "1px solid #ddd",
"&:hover": {
backgroundColor: "#FF6D00",
color: "#fff",
},
}}
>
<RemoveIcon />
</IconButton>
<Typography
sx={{
mx: 2,
minWidth: "30px",
textAlign: "center",
fontWeight: 500,
}}
>
{quantity}
</Typography>
<IconButton
onClick={() => setQuantity((q) => q + 1)}
sx={{
border: "1px solid #ddd",
"&:hover": {
backgroundColor: "#FF6D00",
color: "#fff",
},
}}
>
<AddIcon />
</IconButton>
</Box>
{/* Action Buttons */}
<Button
fullWidth
variant="contained"
size="large"
startIcon={<ShoppingCartIcon />}
sx={{
backgroundColor: "#FF6D00",
color: "#fff",
py: 1.5,
mb: 2,
fontSize: "1.1rem",
fontWeight: 600,
borderRadius: "8px",
"&:hover": {
backgroundColor: "#FF5722",
boxShadow: "0 4px 12px rgba(255,109,0,0.3)",
},
}}
>
Add to Cart
</Button>
<Button
fullWidth
variant="outlined"
size="large"
sx={{
borderColor: "#FF6D00",
color: "#FF6D00",
py: 1.5,
mb: 4,
fontSize: "1.1rem",
fontWeight: 600,
borderRadius: "8px",
"&:hover": {
backgroundColor: "rgba(255,109,0,0.08)",
borderColor: "#FF5722",
},
}}
>
Buy Now
</Button>
<Divider sx={{ my: 4, borderColor: "#e0e0e0" }} />
{/* Product Details */}
<Box>
<Typography
variant="h5"
fontWeight="bold"
sx={{
mb: 3,
position: "relative",
"&:after": {
content: '""',
position: "absolute",
bottom: -8,
left: 0,
width: "50px",
height: "3px",
backgroundColor: "#FF6D00",
},
}}
>
Product Details
</Typography>
<Box component="ul" sx={{ pl: 0 }}>
{product.details?.map((detail, i) => (
<Box
component="li"
key={i}
sx={{
listStyle: "none",
mb: 2,
display: "flex",
alignItems: "flex-start",
backgroundColor:
i % 2 === 0 ? "#f9f9f9" : "transparent",
p: 1.5,
borderRadius: "4px",
}}
>
<Chip
label={i + 1}
size="small"
sx={{
backgroundColor: "#FF6D00",
color: "#fff",
mr: 2,
mt: "2px",
fontWeight: 600,
}}
/>
<Typography sx={{ fontSize: "1.05rem" }}>
{detail}
</Typography>
</Box>
))}
</Box>
</Box>
</Paper>
</Grid>
</Grid>
{/* Related Products */}
<Box sx={{ mt: 10 }}>
<Typography
variant="h3"
fontWeight="bold"
sx={{
mb: 6,
position: "relative",
"&:after": {
content: '""',
position: "absolute",
bottom: -12,
left: 0,
width: "80px",
height: "4px",
backgroundColor: "#FF6D00",
},
}}
>
You May Also Like
</Typography>
<Grid container spacing={4}>
{[
{
id: "chair2",
name: "Luxury Lounge Chair",
price: "$399",
image: "/images/chair2.jpg",
},
{
id: "chair3",
name: "Modern Minimal Chair",
price: "$279",
image: "/images/chair3.jpg",
},
{
id: "chair4",
name: "Executive Leather Chair",
price: "$599",
image: "/images/chair4.jpg",
},
].map((item) => (
<Grid item xs={12} sm={6} md={4} key={item.id}>
<Card
sx={{
height: "100%",
display: "flex",
flexDirection: "column",
transition: "all 0.3s ease",
borderRadius: "12px",
overflow: "hidden",
boxShadow: theme.shadows[2],
"&:hover": {
transform: "translateY(-8px)",
boxShadow: theme.shadows[6],
},
}}
>
<Box
sx={{
height: "250px",
backgroundImage: `url(${item.image})`,
backgroundSize: "cover",
backgroundPosition: "center",
transition: "transform 0.5s ease",
"&:hover": {
transform: "scale(1.05)",
},
}}
/>
<Box sx={{ p: 3, flexGrow: 1 }}>
<Typography
variant="h5"
fontWeight="bold"
sx={{
mb: 1,
fontSize: "1.4rem",
}}
>
{item.name}
</Typography>
<Typography
variant="h6"
color="#FF6D00"
sx={{
mb: 2,
fontWeight: 700,
}}
>
{item.price}
</Typography>
<Button
component={Link}
to={`/products/chairs/${item.id}`}
fullWidth
variant="outlined"
sx={{
borderColor: "#FF6D00",
color: "#FF6D00",
fontWeight: 600,
"&:hover": {
backgroundColor: "rgba(255,109,0,0.1)",
borderColor: "#FF5722",
},
}}
>
View Product
</Button>
</Box>
</Card>
</Grid>
))}
</Grid>
</Box>
</Container>
</Box>
);
};
export default ProductPage;

View File

@ -0,0 +1,24 @@
// src/hooks/useScrollAnimation.ts
import { useState, useEffect } from "react";
const useScrollAnimation = () => {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const handleScroll = () => {
const elements = document.querySelectorAll(".scroll-animate");
elements.forEach((el) => {
const rect = el.getBoundingClientRect();
if (rect.top <= window.innerHeight * 0.8) {
el.classList.add("animate");
}
});
};
window.addEventListener("scroll", handleScroll);
handleScroll(); // Initial check
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return isVisible;
};

View File

@ -0,0 +1,279 @@
import React, { useEffect, useState } from "react";
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
Button,
MenuItem,
InputAdornment,
IconButton,
} from "@mui/material";
import { Person, Email, LocationOn, Phone, AssignmentInd, Visibility, VisibilityOff } from "@mui/icons-material"; // Icons
interface Role {
id: number;
name: string;
}
interface AddUserDialogProps {
open: boolean;
onClose: () => void;
onUserAdded: () => void;
}
const AddUserDialog: React.FC<AddUserDialogProps> = ({ open, onClose, onUserAdded }) => {
const [name, setName] = useState("");
const [password, setPassword] = useState("newuser");
const [showPassword, setShowPassword] = useState(false); // Toggle password visibility
const [address, setAddress] = useState("");
const [email, setEmail] = useState("");
const [phone_number, setPhoneNumber] = useState("");
const [is_under, setIsUnder] = useState<number | "">("");
const [roles, setRoles] = useState<Role[]>([]);
// const [status, setStatus] = useState(1); // 1 = Active, 0 = Inactive
useEffect(() => {
fetch(`${process.env.REACT_APP_API_BASE_URL}/role`)
.then((res) => res.json())
.then((data) => setRoles(data))
.catch((error) => console.error("Error fetching roles:", error));
}, []);
const handleSubmit = () => {
const newUser = {
name,
password,
address,
email,
phone_number,
role: 1, // Always "TBT"
is_under,
// status: status === 1, // Convert 1 -> true, 0 -> false
};
const token: string | null = localStorage.getItem('token');
if (!token) {
return;
}
fetch(`${process.env.REACT_APP_API_BASE_URL}/user`, {
method: "POST",
headers: {
"Content-Type": "application/json",'x-api-key': token
},
body: JSON.stringify(newUser),
})
.then((response) => response.json())
.then(() => {
onUserAdded();
onClose();
})
.catch((error) => console.error("Error adding user:", error));
};
return (
<Dialog
open={open}
onClose={onClose}
PaperProps={{
sx: {
backgroundColor: "#121212",
border: "2px solid green",
borderRadius: "12px",
padding: "16px",
width: "400px",
},
}}
>
<DialogTitle sx={{ color: "green", textAlign: "center" }}>Add New User</DialogTitle>
<DialogContent>
{/* Name */}
<TextField
autoFocus
margin="dense"
label="Name"
type="text"
fullWidth
variant="outlined"
value={name}
onChange={(e) => setName(e.target.value)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Person sx={{ color: "white" }} />
</InputAdornment>
),
}}
sx={{
input: { color: "white" },
label: { color: "white" },
"& .MuiOutlinedInput-root": {
"& fieldset": { borderColor: "white" },
"&:hover fieldset": { borderColor: "limegreen" },
"&.Mui-focused fieldset": { borderColor: "limegreen" },
},
}}
/>
{/* Email */}
<TextField
margin="dense"
label="Email"
type="email"
fullWidth
variant="outlined"
value={email}
onChange={(e) => setEmail(e.target.value)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Email sx={{ color: "white" }} />
</InputAdornment>
),
}}
sx={{
input: { color: "white" },
label: { color: "white" },
"& .MuiOutlinedInput-root": {
"& fieldset": { borderColor: "white" },
"&:hover fieldset": { borderColor: "limegreen" },
"&.Mui-focused fieldset": { borderColor: "limegreen" },
},
}}
/>
{/* Password */}
<TextField
margin="dense"
label="Password"
type={showPassword ? "text" : "password"}
fullWidth
variant="outlined"
value={password}
onChange={(e) => setPassword(e.target.value)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={() => setShowPassword(!showPassword)}>
{showPassword ? <VisibilityOff sx={{ color: "white" }} /> : <Visibility sx={{ color: "white" }} />}
</IconButton>
</InputAdornment>
),
}}
sx={{
input: { color: "white" },
label: { color: "white" },
"& .MuiOutlinedInput-root": {
"& fieldset": { borderColor: "white" },
"&:hover fieldset": { borderColor: "limegreen" },
"&.Mui-focused fieldset": { borderColor: "limegreen" },
},
}}
/>
{/* Address */}
<TextField
margin="dense"
label="Address"
type="text"
fullWidth
variant="outlined"
value={address}
onChange={(e) => setAddress(e.target.value)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<LocationOn sx={{ color: "white" }} />
</InputAdornment>
),
}}
sx={{
input: { color: "white" },
label: { color: "white" },
"& .MuiOutlinedInput-root": {
"& fieldset": { borderColor: "white" },
"&:hover fieldset": { borderColor: "limegreen" },
"&.Mui-focused fieldset": { borderColor: "limegreen" },
},
}}
/>
{/* Phone Number */}
<TextField
margin="dense"
label="Phone Number"
type="tel"
fullWidth
variant="outlined"
value={phone_number}
onChange={(e) => setPhoneNumber(e.target.value)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Phone sx={{ color: "white" }} />
</InputAdornment>
),
}}
sx={{
input: { color: "white" },
label: { color: "white" },
"& .MuiOutlinedInput-root": {
"& fieldset": { borderColor: "white" },
"&:hover fieldset": { borderColor: "limegreen" },
"&.Mui-focused fieldset": { borderColor: "limegreen" },
},
}}
/>
{/* Is Under (Dropdown) */}
<TextField
select
fullWidth
variant="outlined"
label="Is Under"
value={is_under}
onChange={(e) => setIsUnder(Number(e.target.value))}
sx={{
input: { color: "white" },
label: { color: "white" },
"& .MuiOutlinedInput-root": {
"& fieldset": { borderColor: "white" },
"&:hover fieldset": { borderColor: "limegreen" },
"&.Mui-focused fieldset": { borderColor: "limegreen" },
},
}}
>
{roles.map((role) => (
<MenuItem key={role.id} value={role.id}>
{role.name}
</MenuItem>
))}
</TextField>
{/* <Typography sx={{ color: "white", marginTop: 2 }}>Status</Typography>
<FormControlLabel
control={
<Switch
checked={status === 1}
onChange={(e) => setStatus(e.target.checked ? 1 : 0)}
color="success"
/>
}
label={status === 1 ? "Active" : "Inactive"}
sx={{ color: "white", marginTop: 2 }}
/> */}
</DialogContent>
<DialogActions sx={{ justifyContent: "center" }}>
<Button onClick={onClose} sx={{ color: "white", border: "1px solid green" }}>
Cancel
</Button>
<Button onClick={handleSubmit} sx={{ backgroundColor: "green", color: "black" }}>
Add User
</Button>
</DialogActions>
</Dialog>
);
};
export default AddUserDialog;

View File

@ -0,0 +1,17 @@
export interface Role {
id: number;
name: string;
description: string;
}
export interface Users {
id: number;
name: string;
email: string;
phone_number: string;
address: string;
status: boolean;
password: string;
role: Role;
}

View File

@ -0,0 +1,277 @@
import React, { useEffect, useState } from "react";
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
Button,
InputAdornment,
MenuItem,
Select,
FormControl,
InputLabel,
FormControlLabel,
Switch,
} from "@mui/material";
import { DirectionsCar } from "@mui/icons-material";
export interface Model {
id: number;
model_name: string;
}
export interface Vehicle {
created_at: string;
id: number;
plateNo: string | null;
// status: boolean;
// updated_at: string;
vehicleModel: Model;
vin: string | null;
}
interface EditVehicleDialogProps {
open: boolean;
onClose: () => void;
vehicle: Vehicle | null;
onVehicleUpdated: () => void;
}
interface VehicleModel {
id: number;
model_name: string;
}
export const EditVehicleDialog: React.FC<EditVehicleDialogProps> = ({
open,
onClose,
vehicle,
onVehicleUpdated,
}) => {
const [plateNo, setPlateNo] = useState("");
const [vin, setVin] = useState("");
const [status, setStatus] = useState(false);
const [vehicleModelId, setVehicleModelId] = useState<number>(0);
const [vehicleModels, setVehicleModels] = useState<VehicleModel[]>([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (vehicle) {
setPlateNo(vehicle.plateNo || "");
setVin(vehicle.vin || "");
// setStatus(vehicle.status);
setVehicleModelId(vehicle.vehicleModel.id);
}
}, [vehicle]);
useEffect(() => {
const fetchVehicleModels = async () => {
const token = localStorage.getItem("token");
if (!token) return;
setIsLoading(true);
try {
const response = await fetch(
`${process.env.REACT_APP_API_BASE_URL}/vehicleModels`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
"x-api-key": token,
},
}
);
const data = await response.json();
if (Array.isArray(data)) {
setVehicleModels(data);
}
} catch (error) {
console.error("Error fetching vehicle models:", error);
} finally {
setIsLoading(false);
}
};
if (open) {
fetchVehicleModels();
}
}, [open]);
const handleSubmit = async () => {
if (!vehicle) return;
const token = localStorage.getItem("token");
if (!token) return;
const updatedVehicle = {
plate_no: plateNo,
vin: vin,
status: status,
vehicle_model_id: vehicleModelId,
};
setIsLoading(true);
try {
const response = await fetch(
`${process.env.REACT_APP_API_BASE_URL}/vehicle/${vehicle.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
"x-api-key": token,
},
body: JSON.stringify(updatedVehicle),
}
);
const data = await response.json();
if (data.message) {
console.error("Error updating vehicle:", data.message);
} else {
onVehicleUpdated();
onClose();
}
} catch (error) {
console.error("Error updating vehicle:", error);
} finally {
setIsLoading(false);
}
};
return (
<Dialog
open={open}
onClose={onClose}
PaperProps={{
sx: {
backgroundColor: "#121212",
border: "2px solid green",
borderRadius: "12px",
padding: "16px",
width: "400px",
},
}}
>
<DialogTitle sx={{ color: "green", textAlign: "center" }}>
Edit Vehicle
</DialogTitle>
<DialogContent>
{/* Plate Number Field */}
<TextField
margin="dense"
label="Plate Number"
type="text"
fullWidth
variant="outlined"
value={plateNo}
onChange={(e) => setPlateNo(e.target.value)}
sx={{
input: { color: "white" },
label: { color: "white" },
"& .MuiOutlinedInput-root": {
"& fieldset": { borderColor: "white" },
"&:hover fieldset": { borderColor: "limegreen" },
"&.Mui-focused fieldset": { borderColor: "limegreen" },
},
marginBottom: "16px",
}}
/>
{/* VIN Field */}
<TextField
margin="dense"
label="VIN"
type="text"
fullWidth
variant="outlined"
value={vin}
onChange={(e) => setVin(e.target.value)}
sx={{
input: { color: "white" },
label: { color: "white" },
"& .MuiOutlinedInput-root": {
"& fieldset": { borderColor: "white" },
"&:hover fieldset": { borderColor: "limegreen" },
"&.Mui-focused fieldset": { borderColor: "limegreen" },
},
marginBottom: "16px",
}}
/>
{/* Vehicle Model Dropdown */}
<FormControl fullWidth sx={{ marginBottom: "16px" }}>
<InputLabel sx={{ color: "white" }}>Vehicle Model</InputLabel>
<Select
value={vehicleModelId}
onChange={(e) => setVehicleModelId(Number(e.target.value))}
disabled={isLoading}
sx={{
color: "white",
"& .MuiOutlinedInput-notchedOutline": {
borderColor: "white",
},
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: "limegreen",
},
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
borderColor: "limegreen",
},
}}
inputProps={{
MenuProps: {
PaperProps: {
sx: {
backgroundColor: "#121212",
color: "white",
},
},
},
}}
startAdornment={
<InputAdornment position="start">
<DirectionsCar sx={{ color: "white", marginRight: 1 }} />
</InputAdornment>
}
>
{vehicleModels.map((model) => (
<MenuItem key={model.id} value={model.id}>
{model.model_name}
</MenuItem>
))}
</Select>
</FormControl>
</DialogContent>
<DialogActions sx={{ justifyContent: "center" }}>
<Button
onClick={onClose}
disabled={isLoading}
sx={{
color: "white",
border: "1px solid green",
"&:disabled": {
opacity: 0.7,
},
}}
>
Cancel
</Button>
<Button
onClick={handleSubmit}
disabled={isLoading}
sx={{
backgroundColor: "green",
color: "black",
"&:disabled": {
backgroundColor: "darkgreen",
color: "gray",
},
}}
>
{isLoading ? "Updating..." : "Update Vehicle"}
</Button>
</DialogActions>
</Dialog>
);
};
export default EditVehicleDialog;

25
src/index.css Normal file
View File

@ -0,0 +1,25 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
height: 100%;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
/* Add to your global CSS or in a style tag in index.html */
/* .scroll-animate {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
.scroll-animate.animate {
opacity: 1;
transform: translateY(0);
} */

20
src/index.tsx Normal file
View File

@ -0,0 +1,20 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

10
src/logo.svg Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="288" height="288">
<path d="M0 0 C0.82564453 0.23605957 1.65128906 0.47211914 2.50195312 0.71533203 C19.62616105 5.81119667 36.10522456 13.67679494 50 25 C50.79229004 25.64437012 50.79229004 25.64437012 51.60058594 26.30175781 C75.90230716 46.17485592 93.66166728 71.49547987 102 102 C102.19754883 102.7120459 102.39509766 103.4240918 102.59863281 104.15771484 C108.70511531 127.28939034 108.58180401 154.9991228 102 178 C101.76394043 178.82564453 101.52788086 179.65128906 101.28466797 180.50195312 C96.18880333 197.62616105 88.32320506 214.10522456 77 228 C76.35562988 228.79229004 76.35562988 228.79229004 75.69824219 229.60058594 C55.82514408 253.90230716 30.50452013 271.66166728 0 280 C-0.7120459 280.19754883 -1.4240918 280.39509766 -2.15771484 280.59863281 C-25.28939034 286.70511531 -52.9991228 286.58180401 -76 280 C-76.82564453 279.76394043 -77.65128906 279.52788086 -78.50195312 279.28466797 C-95.62616105 274.18880333 -112.10522456 266.32320506 -126 255 C-126.79229004 254.35562988 -126.79229004 254.35562988 -127.60058594 253.69824219 C-151.90230716 233.82514408 -169.66166728 208.50452013 -178 178 C-178.19754883 177.2879541 -178.39509766 176.5759082 -178.59863281 175.84228516 C-184.70511531 152.71060966 -184.58180401 125.0008772 -178 102 C-177.76394043 101.17435547 -177.52788086 100.34871094 -177.28466797 99.49804688 C-172.18880333 82.37383895 -164.32320506 65.89477544 -153 52 C-152.57041992 51.47180664 -152.14083984 50.94361328 -151.69824219 50.39941406 C-131.82514408 26.09769284 -106.50452013 8.33833272 -76 0 C-75.2879541 -0.19754883 -74.5759082 -0.39509766 -73.84228516 -0.59863281 C-50.71060966 -6.70511531 -23.0008772 -6.58180401 0 0 Z " fill="#FDFDFD" transform="translate(182,4)"/>
<path d="M0 0 C0.73224792 -0.00043808 1.46449585 -0.00087616 2.21893311 -0.00132751 C4.62694437 0.000754 7.03424981 0.02405407 9.44213867 0.04760742 C11.11650716 0.05320566 12.79088057 0.05747468 14.46525574 0.06047058 C18.86300322 0.07190143 23.26045145 0.10135332 27.65808105 0.13458252 C32.14921013 0.16532144 36.64038775 0.17898407 41.1315918 0.1940918 C49.93706736 0.22622485 58.74230012 0.27739972 67.54760742 0.34057617 C65.67935944 2.5422287 65.67935944 2.5422287 63.81111145 4.74388123 C57.33960077 12.9219544 53.94641534 24.13087013 50.38818359 33.79199219 C48.56620391 38.73790296 46.60048226 43.56339996 44.42480469 48.36425781 C43.36339692 50.75560094 42.46562149 53.18115304 41.61010742 55.65307617 C38.65525489 63.93825092 35.21078887 72.02072984 31.53979492 80.01245117 C30.70519711 81.9707988 29.94718767 83.96153445 29.20776367 85.95776367 C27.35223001 90.95378805 25.23164356 95.82789693 23.11010742 100.71557617 C19.43409794 109.19581389 15.96232897 117.73850408 12.58276367 126.34057617 C10.61071239 131.35698162 8.58942979 136.35221713 6.54760742 141.34057617 C6.10288086 142.44272461 5.6581543 143.54487305 5.19995117 144.68041992 C4.81967773 145.53764648 4.4394043 146.39487305 4.04760742 147.27807617 C3.74854492 147.96772461 3.44948242 148.65737305 3.14135742 149.36791992 C-0.34597158 153.6843443 -4.20964394 156.04294441 -9.76208496 156.77980042 C-13.81416476 156.91858722 -17.83743754 156.90377899 -21.88989258 156.82885742 C-23.3806031 156.8195174 -24.87132911 156.81240691 -26.36206055 156.80741882 C-30.26193551 156.78844193 -34.16086081 156.7394246 -38.06036377 156.68389893 C-42.04898232 156.63250314 -46.0377562 156.6098617 -50.02661133 156.5847168 C-57.8356353 156.53129413 -65.64388025 156.4460979 -73.45239258 156.34057617 C-73.51566999 151.75431005 -73.37620049 148.25309951 -71.70239258 143.96557617 C-71.15131836 142.54051758 -71.15131836 142.54051758 -70.58911133 141.08666992 C-69.30134857 137.97568291 -67.92224286 134.91625953 -66.51879883 131.85620117 C-64.7185072 127.60935937 -63.27673145 123.2346567 -61.79614258 118.86791992 C-60.81353118 116.28856501 -59.80950141 114.01468768 -58.54614258 111.58666992 C-56.04704884 106.71331992 -54.26890301 101.62418197 -52.45239258 96.46557617 C-50.21440158 90.16248435 -47.91860459 83.90347641 -45.40795898 77.70336914 C-43.9492251 74.09641294 -42.54593446 70.46832189 -41.13989258 66.84057617 C-40.84429443 66.07938477 -40.54869629 65.31819336 -40.24414062 64.53393555 C-39.31307886 62.13633473 -38.38265078 59.73848887 -37.45239258 57.34057617 C-27.06162543 30.55645064 -27.06162543 30.55645064 -23.51489258 22.77026367 C-22.72318351 20.95981131 -21.98085587 19.12761004 -21.26489258 17.28588867 C-17.13279313 6.72320946 -12.06907005 -0.19050107 0 0 Z " fill="#020202" transform="translate(104.452392578125,63.659423828125)"/>
<path d="M0 0 C1.55940523 0.00746919 3.11881991 0.01315892 4.67823792 0.01715088 C8.76372627 0.03235411 12.84863721 0.07158894 16.93389893 0.1159668 C21.11015006 0.15703501 25.28649535 0.17518643 29.46289062 0.1953125 C37.64349284 0.23808938 45.82362752 0.30627498 54.00390625 0.390625 C53.17601543 3.93730928 51.88541288 7.00494861 50.37890625 10.328125 C47.25835949 17.36517852 44.85018025 24.59088108 42.54052734 31.92944336 C41.01852781 36.68220417 39.27715468 41.04876537 36.96166992 45.46777344 C35.68576575 48.0293389 34.71816504 50.66209242 33.74560547 53.35058594 C32.44037056 56.94062639 31.00193941 60.4761286 29.56640625 64.015625 C29.27958984 64.74007812 28.99277344 65.46453125 28.69726562 66.2109375 C25.80533856 73.32160005 22.90391346 78.5171131 16.00390625 82.390625 C12.62490845 82.87164307 12.62490845 82.87164307 8.89697266 82.84472656 C8.21334656 82.84531067 7.52972046 82.84589478 6.82537842 82.84649658 C4.5848765 82.84372871 2.34573574 82.81270181 0.10546875 82.78125 C-1.45536574 82.77378291 -3.01620964 82.76809219 -4.57705688 82.76409912 C-8.67077853 82.74887941 -12.76392597 82.70963007 -16.85742188 82.6652832 C-21.04028558 82.62425084 -25.22324273 82.60607117 -29.40625 82.5859375 C-37.6031271 82.54313148 -45.79953885 82.47492554 -53.99609375 82.390625 C-53.26292767 78.52286401 -52.22118899 75.00841677 -50.8515625 71.3125 C-50.42166016 70.15105469 -49.99175781 68.98960937 -49.54882812 67.79296875 C-48.87303711 65.98505859 -48.87303711 65.98505859 -48.18359375 64.140625 C-47.74466797 62.95082031 -47.30574219 61.76101562 -46.85351562 60.53515625 C-44.69022854 54.71710331 -42.43703641 48.99121381 -39.8605957 43.34423828 C-38.57384661 40.43642517 -37.533744 37.45740293 -36.49609375 34.453125 C-33.12153052 24.86442705 -29.27342019 15.61141888 -24.99609375 6.390625 C-24.33609375 6.390625 -23.67609375 6.390625 -22.99609375 6.390625 C-22.99609375 5.730625 -22.99609375 5.070625 -22.99609375 4.390625 C-16.11977266 -1.04274969 -8.33280645 -0.11739892 0 0 Z " fill="#63A145" transform="translate(200.99609375,63.609375)"/>
<path d="M0 0 C0.69771011 0.00359528 1.39542023 0.00719055 2.11427307 0.01089478 C2.84593689 0.01074875 3.57760071 0.01060272 4.33143616 0.01045227 C6.76094989 0.0111515 9.19038789 0.01894432 11.61988831 0.02676392 C13.29997719 0.02862806 14.98006662 0.03005198 16.66015625 0.03105164 C21.09071849 0.03487709 25.52124815 0.04470761 29.95179749 0.05575562 C34.46941182 0.06596914 38.98703139 0.07054913 43.50465393 0.07559204 C52.37511098 0.08632986 61.24554381 0.10340661 70.11598206 0.12442017 C69.47172152 4.30130393 68.58248846 7.75684877 66.86598206 11.62442017 C64.4172062 17.30048443 62.32965498 23.05094552 60.32691956 28.89785767 C59.11598206 32.12442017 59.11598206 32.12442017 57.59254456 35.16348267 C56.12356467 38.10921482 55.14954672 41.00384994 54.11598206 44.12442017 C53.07937516 46.49953236 51.99333081 48.85351137 50.86598206 51.18692017 C50.30395081 52.37414673 49.74191956 53.56137329 49.16285706 54.78457642 C46.69627164 58.80925299 43.96478206 62.11691713 39.34602356 63.75038147 C36.34768237 64.25328495 33.46043631 64.24357656 30.41969299 64.23794556 C29.42395264 64.2381646 29.42395264 64.2381646 28.40809631 64.23838806 C26.22214955 64.23769349 24.03628886 64.22992248 21.85035706 64.22207642 C20.33149767 64.22021062 18.81263767 64.21878748 17.29377747 64.2177887 C13.30220367 64.21397626 9.31066644 64.20415687 5.31910706 64.19308472 C1.24359125 64.1828432 -2.83193047 64.1782852 -6.90745544 64.17324829 C-14.8996597 64.16253344 -22.89183427 64.14547233 -30.88401794 64.12442017 C-30.1802431 60.38811499 -29.18940801 57.12653308 -27.73167419 53.60879517 C-27.28952576 52.53395874 -26.84737732 51.45912231 -26.39183044 50.35171509 C-25.91487732 49.20420776 -25.43792419 48.05670044 -24.94651794 46.87442017 C-23.947001 44.44897627 -22.94832327 42.0231864 -21.95042419 39.59707642 C-21.56891708 38.67134575 -21.56891708 38.67134575 -21.17970276 37.72691345 C-18.09759897 30.23712946 -15.11698925 22.70671626 -12.14598083 15.17227173 C-6.12742866 0.01331859 -6.12742866 0.01331859 0 0 Z " fill="#030303" transform="translate(147.88401794433594,155.87557983398438)"/>
<path d="M0 0 C1 2 1 2 0.22265625 4.3828125 C-0.16019531 5.28773437 -0.54304687 6.19265625 -0.9375 7.125 C-1.31777344 8.03507812 -1.69804687 8.94515625 -2.08984375 9.8828125 C-2.39019531 10.58148438 -2.69054687 11.28015625 -3 12 C-3.33 12 -3.66 12 -4 12 C-4.29554656 4.31578947 -4.29554656 4.31578947 -2 1 C-1.34 0.67 -0.68 0.34 0 0 Z " fill="#80AB6A" transform="translate(235,113)"/>
<path d="M0 0 C0.33 0 0.66 0 1 0 C1.25 3.3125 1.25 3.3125 1 7 C-0.32 7.99 -1.64 8.98 -3 10 C-2.01 6.7 -1.02 3.4 0 0 Z " fill="#8CB077" transform="translate(158,114)"/>
<path d="M0 0 C0 3 0 3 -1.3125 4.38671875 C-2.875 5.59114583 -4.4375 6.79557292 -6 8 C-4.73058503 4.08597051 -3.48832306 2.20927127 0 0 Z " fill="#CECECE" transform="translate(172,64)"/>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

1
src/react-app-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

15
src/reportWebVitals.ts Normal file
View File

@ -0,0 +1,15 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

27
src/routes.tsx Normal file
View File

@ -0,0 +1,27 @@
import React from "react";
import { Routes as ReactRouterRoutes, Route } from "react-router-dom";
import LandingPage from "./components/homepage/Landingpage";
import CategoryPage from "./components/category/CategoryPage";
import ProductPage from "./components/product/ProductPage";
import CollectionPage from "./components/category/collectionpage";
import CartPage from "./components/cart/cart";
import AboutPage from "./components/cart/aboutus";
const Routes: React.FC = () => {
return (
<ReactRouterRoutes>
<Route path="/" element={<LandingPage />} />
<Route path="/cat" element={<CategoryPage />} />
<Route path="/collections/:collectionId" element={<CollectionPage />} />
<Route
path="/products/:collectionId/:productId"
element={<ProductPage />}
/>
<Route path="/cart" element={<CartPage />} />
<Route path="/aboutus" element={<AboutPage />} />
{/* <Route path="*" element={<NotFoundPage />} /> */}
</ReactRouterRoutes>
);
};
export default Routes;

5
src/setupTests.ts Normal file
View File

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

26
tsconfig.json Normal file
View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}

10252
yarn.lock Normal file

File diff suppressed because it is too large Load Diff