first commit
|
|
@ -0,0 +1,7 @@
|
|||
.env*
|
||||
.drone.yml
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
build/
|
||||
node_modules/
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
PORT=3001
|
||||
# REACT_APP_API_BASE_URL=http://localhost:3010
|
||||
REACT_APP_API_BASE_URL=https://kineticgreen-backend.navigolabs.com
|
||||
|
||||
|
|
@ -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*
|
||||
|
|
@ -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"]
|
||||
|
||||
|
|
@ -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 can’t go back!**
|
||||
|
||||
If you aren’t 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 you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t 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/).
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -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>
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
After Width: | Height: | Size: 2.3 MiB |
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -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[];
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 7.5 MiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
} */
|
||||
|
|
@ -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();
|
||||
|
|
@ -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 |
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="react-scripts" />
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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';
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||