title of page changes , product page done and content page completed

This commit is contained in:
print-signs 2023-10-23 16:25:48 +05:30
parent d40bcdbff4
commit 34948d795b
18 changed files with 2160 additions and 806 deletions

View File

@ -41,19 +41,22 @@
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@material-ui/core": "^4.12.4", "@material-ui/core": "^4.12.4",
"@material-ui/data-grid": "^4.0.0-alpha.37", "@material-ui/data-grid": "^4.0.0-alpha.37",
"@mui/icons-material": "^5.14.13", "@mui/icons-material": "^5.14.14",
"@mui/material": "^5.11.12", "@mui/material": "^5.11.12",
"@reduxjs/toolkit": "^1.9.2", "@reduxjs/toolkit": "^1.9.2",
"axios": "^0.25.0", "axios": "^0.25.0",
"bootstrap": "^5.1.3", "bootstrap": "^5.1.3",
"country-state-city": "^3.1.2", "country-state-city": "^3.1.2",
"md5": "^2.3.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"quill": "^1.3.7",
"react": "18.0.0", "react": "18.0.0",
"react-bootstrap": "^2.7.0", "react-bootstrap": "^2.7.0",
"react-datepicker": "^4.8.0", "react-datepicker": "^4.8.0",
"react-dom": "^18.0.0", "react-dom": "^18.0.0",
"react-hot-toast": "^2.4.0", "react-hot-toast": "^2.4.0",
"react-qr-code": "^2.0.11", "react-qr-code": "^2.0.11",
"react-quill": "^2.0.0",
"react-redux": "^7.2.9", "react-redux": "^7.2.9",
"react-router-dom": "^6.7.0", "react-router-dom": "^6.7.0",
"react-spinners": "^0.11.0", "react-spinners": "^0.11.0",
@ -67,6 +70,7 @@
}, },
"devDependencies": { "devDependencies": {
"auto-changelog": "~2.3.0", "auto-changelog": "~2.3.0",
"fuse.js": "^6.6.2",
"react-scripts": "^4.0.3", "react-scripts": "^4.0.3",
"sass": "^1.43.5" "sass": "^1.43.5"
}, },

View File

@ -7,26 +7,37 @@
* License MIT * License MIT
--> -->
<html lang="en"> <html lang="en">
<head>
<head> <meta charset="utf-8" />
<meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> name="viewport"
<meta name="description" content="CoreUI for React - Open Source Bootstrap Admin Template"> content="width=device-width, initial-scale=1, shrink-to-fit=no"
<meta name="author" content="Łukasz Holeczek"> />
<meta name="keyword" content="Bootstrap,Admin,Template,Open,Source,CSS,SCSS,HTML,RWD,Dashboard,React"> <meta
<title>SOLAR Sign Admin</title> name="description"
<!-- content="CoreUI for React - Open Source Bootstrap Admin Template"
/>
<meta name="author" content="Łukasz Holeczek" />
<meta
name="keyword"
content="Bootstrap,Admin,Template,Open,Source,CSS,SCSS,HTML,RWD,Dashboard,React"
/>
<title>The Solar Sign</title>
<!--
manifest.json provides metadata used when your web app is added to the manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
--> -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"> <link
<link rel="react" href="https://coreui.io/react/"> rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
<!-- <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script> --> />
<!-- <link rel="react" href="https://coreui.io/react/" />
<!-- <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script> -->
<!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. 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. Only files inside the `public` folder can be referenced from the HTML.
@ -40,14 +51,12 @@
<!-- <link href="/assets/libs/bootstrap-datepicker/css/bootstrap-datepicker.min.css" rel="stylesheet" /> --> <!-- <link href="/assets/libs/bootstrap-datepicker/css/bootstrap-datepicker.min.css" rel="stylesheet" /> -->
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" /> --> <!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" /> -->
</head> </head>
<body> <body>
<noscript> <noscript> You need to enable JavaScript to run this app. </noscript>
You need to enable JavaScript to run this app. <div id="root"></div>
</noscript> <!--
<div id="root"></div>
<!--
This HTML file is a template. This HTML file is a template.
If you open it directly in the browser, you will see an empty page. If you open it directly in the browser, you will see an empty page.
@ -57,9 +66,6 @@
To begin the development, run `npm start` or `yarn start`. To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`. To create a production bundle, use `npm run build` or `yarn build`.
--> -->
</body>
<!-- <script src="/assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script> -->
</body> </html>
<!-- <script src="/assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script> -->
</html>

View File

@ -107,6 +107,12 @@ const _nav = [
icon: <CIcon icon={cilContact} customClassName="nav-icon" />, icon: <CIcon icon={cilContact} customClassName="nav-icon" />,
to: "/contact/request", to: "/contact/request",
}, },
{
component: CNavItem,
name: "Content ",
icon: <CIcon icon={cilText} customClassName="nav-icon" />,
to: "/content",
},
], ],
}, },
{ {

View File

@ -1,38 +1,43 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from 'react-redux' import { useSelector, useDispatch } from "react-redux";
import { CSidebar, CSidebarBrand, CSidebarNav, CSidebarToggler } from '@coreui/react' import {
import CIcon from '@coreui/icons-react' CSidebar,
CSidebarBrand,
CSidebarNav,
CSidebarToggler,
} from "@coreui/react";
import CIcon from "@coreui/icons-react";
import { AppSidebarNav } from './AppSidebarNav' import { AppSidebarNav } from "./AppSidebarNav";
import { logoNegative } from 'src/assets/brand/logo-negative' import { logoNegative } from "src/assets/brand/logo-negative";
import { sygnet } from 'src/assets/brand/sygnet' import { sygnet } from "src/assets/brand/sygnet";
import SimpleBar from 'simplebar-react' import SimpleBar from "simplebar-react";
import 'simplebar/dist/simplebar.min.css' import "simplebar/dist/simplebar.min.css";
// sidebar nav config // sidebar nav config
import navigation from '../_nav' import navigation from "../_nav";
import { isAutheticated } from 'src/auth' import { isAutheticated } from "src/auth";
import axios from 'axios' import axios from "axios";
import { Link } from 'react-router-dom' import { Link } from "react-router-dom";
const AppSidebar = () => { const AppSidebar = () => {
const dispatch = useDispatch() const dispatch = useDispatch();
const unfoldable = useSelector((state) => state.sidebarUnfoldable) const unfoldable = useSelector((state) => state.sidebarUnfoldable);
const sidebarShow = useSelector((state) => state.sidebarShow) const sidebarShow = useSelector((state) => state.sidebarShow);
///----------------------// ///----------------------//
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false);
const token = isAutheticated() const token = isAutheticated();
// urlcreated images // urlcreated images
const [AppName, setAppName] = useState('') const [AppName, setAppName] = useState("");
const [HeaderlogoUrl, setHeaderlogoUrl] = useState('') const [HeaderlogoUrl, setHeaderlogoUrl] = useState("");
const [FooterlogoUrl, setFooterlogoUrl] = useState('') const [FooterlogoUrl, setFooterlogoUrl] = useState("");
const [AdminlogoUrl, setAdminlogoUrl] = useState('') const [AdminlogoUrl, setAdminlogoUrl] = useState("");
useEffect(() => { useEffect(() => {
async function getConfiguration() { async function getConfiguration() {
@ -40,16 +45,16 @@ const AppSidebar = () => {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
}) });
setAppName(configDetails.data.result[0]?.appName) setAppName(configDetails.data.result[0]?.appName);
configDetails.data.result.map((item) => { configDetails.data.result.map((item) => {
setHeaderlogoUrl(item?.logo[0]?.Headerlogo) setHeaderlogoUrl(item?.logo[0]?.Headerlogo);
setFooterlogoUrl(item?.logo[0]?.Footerlogo) setFooterlogoUrl(item?.logo[0]?.Footerlogo);
setAdminlogoUrl(item?.logo[0]?.Adminlogo) setAdminlogoUrl(item?.logo[0]?.Adminlogo);
}) });
} }
getConfiguration() getConfiguration();
}, []) }, []);
//---------------------------// //---------------------------//
return ( return (
@ -58,13 +63,25 @@ const AppSidebar = () => {
unfoldable={unfoldable} unfoldable={unfoldable}
visible={sidebarShow} visible={sidebarShow}
onVisibleChange={(visible) => { onVisibleChange={(visible) => {
dispatch({ type: 'set', sidebarShow: visible }) dispatch({ type: "set", sidebarShow: visible });
}} }}
> >
<CSidebarBrand className="d-none d-md-flex" style={{ background: 'rgb(140, 213, 213)' }} to="/"> <CSidebarBrand
className="d-none d-md-flex"
style={{ background: "rgb(140, 213, 213)" }}
to="/"
>
{/* <CIcon className="sidebar-brand-full" icon={logoNegative} height={35} /> */} {/* <CIcon className="sidebar-brand-full" icon={logoNegative} height={35} /> */}
{HeaderlogoUrl ? <Link to='/dashboard'><img src={HeaderlogoUrl} alt='' width='100%' /></Link> : { AppName } ? <h2>Solar Sign </h2> : ''} {HeaderlogoUrl ? (
<Link to="/dashboard">
<img src={HeaderlogoUrl} alt="" width="100%" />
</Link>
) : { AppName } ? (
<h2>The Solar Sign </h2>
) : (
""
)}
{/* <CIcon className="sidebar-brand-narrow" height={35} /> */} {/* <CIcon className="sidebar-brand-narrow" height={35} /> */}
<CIcon className="sidebar-brand-narrow" icon={sygnet} height={35} /> <CIcon className="sidebar-brand-narrow" icon={sygnet} height={35} />
</CSidebarBrand> </CSidebarBrand>
@ -75,10 +92,12 @@ const AppSidebar = () => {
</CSidebarNav> </CSidebarNav>
<CSidebarToggler <CSidebarToggler
className="d-none d-lg-flex" className="d-none d-lg-flex"
onClick={() => dispatch({ type: 'set', sidebarUnfoldable: !unfoldable })} onClick={() =>
dispatch({ type: "set", sidebarUnfoldable: !unfoldable })
}
/> />
</CSidebar> </CSidebar>
) );
} };
export default React.memo(AppSidebar) export default React.memo(AppSidebar);

View File

@ -83,6 +83,11 @@ import ViewHealthCareProvider from "./views/Business/ViewHealthCareProvider";
import Campaign from "./views/Campaigns/Campaign.js"; import Campaign from "./views/Campaigns/Campaign.js";
import AddCampaign from "./views/Campaigns/AddCampaign.js"; import AddCampaign from "./views/Campaigns/AddCampaign.js";
import Categories from "./views/Categories/categories"; import Categories from "./views/Categories/categories";
import Content from "./views/Content/content";
import EditPrivacyPolicy from "./views/Content/editPrivacyPolicy";
import EditTermsConditions from "./views/Content/editTermsConditions";
import EditShippingPolicy from "./views/Content/editShippingPolicy";
const routes = [ const routes = [
{ path: "/", exact: true, name: "Home" }, { path: "/", exact: true, name: "Home" },
{ {
@ -183,6 +188,30 @@ const routes = [
name: "AddContact Request", name: "AddContact Request",
element: AddContactRequest, element: AddContactRequest,
}, },
// Content ---- >
{
path: "/content",
name: "Content",
element: Content,
},
{
path: "/content/terms-and-conditions",
name: "Content",
element: EditTermsConditions,
},
{
path: "/content/privacy-policy",
name: "Content",
element: EditPrivacyPolicy,
},
{
path: "/content/shipping-policy",
name: "Content",
element: EditShippingPolicy,
},
// { path: '/complaint/view/:id', name: 'view Complain', element: ViewComplaint }, // { path: '/complaint/view/:id', name: 'view Complain', element: ViewComplaint },
//Complaints //Complaints
{ path: "/testimonials", name: "Testimonials", element: Testimonials }, { path: "/testimonials", name: "Testimonials", element: Testimonials },

View File

@ -3,7 +3,6 @@
$enable-ltr: true; $enable-ltr: true;
$enable-rtl: true; $enable-rtl: true;
// Import CoreUI for React components library // Import CoreUI for React components library
//@import "@coreui/coreui/scss/coreui"; //@import "@coreui/coreui/scss/coreui";
// Import Chart.js custom tooltips styles // Import Chart.js custom tooltips styles
@ -14,4 +13,10 @@ $enable-rtl: true;
@import "example"; @import "example";
// If you want to add custom CSS you can put it here. // If you want to add custom CSS you can put it here.
@import "custom"; @import "custom";
.container .ql-editor {
min-height: 5in;
background-color: white;
}

View File

@ -277,11 +277,23 @@ const Categories = () => {
placeholder="category name" placeholder="category name"
value={categoryName} value={categoryName}
fullWidth fullWidth
inputProps={{
maxLength: 25,
}}
style={{ style={{
padding: "1rem", padding: "1rem",
}} }}
onChange={(e) => setCategoryName(e.target.value)} onChange={(e) => setCategoryName(e.target.value)}
/> />
{categoryName ? (
<>
<small className="charLeft mt-2 ml-3 fst-italic">
{25 - categoryName.length} characters left
</small>
</>
) : (
<></>
)}
<Box <Box
p={2} p={2}
display={"flex"} display={"flex"}

View File

@ -0,0 +1,86 @@
import {
Button,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
} from "@mui/material";
import React from "react";
import { Link } from "react-router-dom";
export default function Content() {
const pages = [
{
name: "Terms & Conditions ",
action: "Edit",
path: "/content/terms-and-conditions",
},
{
name: "Privacy Policy ",
action: "Edit",
path: "/content/privacy-policy",
},
{
name: "Shipping Policy ",
action: "Edit",
path: "/content/shipping-policy",
},
];
return (
<div className="main-content">
<Typography variant="h6" fontWeight={"bold"}>
Content
</Typography>
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell style={{ fontWeight: "bold" }}>Page</TableCell>
<TableCell style={{ fontWeight: "bold" }} align="right">
Action
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{pages.map((row) => (
<TableRow
key={row.name}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="right">
{" "}
<Link to={row.path}>
<button
style={{
color: "white",
marginRight: "1rem",
}}
type="button"
className="
btn btn-info btn-sm
waves-effect waves-light
btn-table
mt-1
mx-1
"
>
{row.action}
</button>
</Link>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>
);
}

View File

@ -0,0 +1,160 @@
import { Typography } from "@material-ui/core";
import { Box, Button, TextField } from "@mui/material";
import React, { useEffect, useState } from "react";
import ReactrichTextEditor from "./reactrichTextEditor";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
import axios from "axios";
import { isAutheticated } from "src/auth";
import { useNavigate, useNavigation } from "react-router-dom";
const TOOLBAR_OPTIONS = [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ font: [] }],
[{ list: "ordered" }, { list: "bullet" }],
["bold", "italic", "underline"],
[{ color: [] }, { background: [] }],
[{ align: [] }],
];
export default function EditPrivacyPolicy() {
const [title, setTitle] = useState("Privacy Policy");
const [content, setContent] = useState("");
const [added, setAdded] = useState(false);
const [olderContent, setOlderContent] = useState("");
const navigation = useNavigate();
const token = isAutheticated();
const handleContentChange = (content, delta, source, editor) => {
setContent(editor.getHTML());
};
const getPrivacyPolicy = async () => {
const response = await axios.get("/api/content/privacy-and-policy", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.status === 200) {
// console.log(response);
setContent(response?.data?.privacyAndPolicy[0]?.privacyAndPolicyContent);
setOlderContent(
response?.data?.privacyAndPolicy[0]?.privacyAndPolicyContent
);
}
};
const addPrivacyPolicy = async () => {
const response = await axios.post(
"/api/content/privacy-and-policy",
{ content },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (response.status == 201) {
swal({
title: "Congratulations!!",
text: "Terms and condition added successfully!",
icon: "success",
button: "OK",
});
}
};
const handleCancelClick = () => {
setContent(olderContent);
};
const updateContent = async () => {
const response = await axios.patch(
"/api/content/privacy-and-policy-update",
{ content },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (response.status === 200) {
swal({
title: "Congratulations!!",
text: "Terms and condition updated successfully!",
icon: "success",
button: "OK",
});
} else {
swal({
title: "Sorry, please try again",
text: "Something went wrong!",
icon: "error",
button: "Retry",
dangerMode: true,
});
}
};
const handleSaveClick = async () => {
if (olderContent === undefined) {
await addPrivacyPolicy();
setAdded(true);
} else {
await updateContent();
setAdded(false);
}
// Reload terms and conditions
await getPrivacyPolicy();
};
useEffect(() => {
// addTermsandConditions();
getPrivacyPolicy();
}, [added]);
return (
<div>
<div style={{ display: "flex" }}>
<Button
variant="contained"
color="primary"
onClick={handleSaveClick}
style={{
fontWeight: "bold",
marginBottom: "1rem",
textTransform: "capitalize",
marginRight: "5px",
}}
>
Save
</Button>
<Button
variant="contained"
color="primary"
onClick={handleCancelClick}
style={{
fontWeight: "bold",
marginBottom: "1rem",
textTransform: "capitalize",
marginRight: "5px",
}}
>
Cancel
</Button>
</div>
<Box style={{ background: "#FFFFFF", color: "black", padding: "1rem" }}>
<Typography
style={{ margin: "0.5rem 0rem", fontWeight: "bold" }}
variant="h6"
>
{" "}
Privacy and policy:{" "}
</Typography>
<Typography style={{ margin: "0.5rem 0rem" }}>Body</Typography>
<ReactQuill
theme="snow"
value={content}
onChange={handleContentChange}
modules={{ toolbar: TOOLBAR_OPTIONS }}
/>
</Box>
</div>
);
}

View File

@ -0,0 +1,166 @@
import { Typography } from "@material-ui/core";
import { Box, Button, TextField } from "@mui/material";
import React, { useEffect, useState } from "react";
import ReactrichTextEditor from "./reactrichTextEditor";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
import axios from "axios";
import { isAutheticated } from "src/auth";
import { useNavigate, useNavigation } from "react-router-dom";
const TOOLBAR_OPTIONS = [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ font: [] }],
[{ list: "ordered" }, { list: "bullet" }],
["bold", "italic", "underline"],
[{ color: [] }, { background: [] }],
[{ align: [] }],
];
export default function EditShippingPolicy() {
const [title, setTitle] = useState("Shipping Policy");
const [content, setContent] = useState("");
const [added, setAdded] = useState(false);
const [olderContent, setOlderContent] = useState("");
const navigation = useNavigate();
const token = isAutheticated();
const handleContentChange = (content, delta, source, editor) => {
setContent(editor.getHTML());
};
const getShipping = async () => {
const response = await axios.get("/api/content/shipping-and-policy", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.status === 200) {
// console.log(response);
setContent(response?.data?.shipping[0]?.shippingContent);
setOlderContent(response?.data?.shipping[0]?.shippingContent);
}
};
const addShipping = async () => {
const response = await axios.post(
"/api/content/shipping-and-policy",
{ content },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (response.status == 201) {
swal({
title: "Congratulations!!",
text: "Terms and condition added successfully!",
icon: "success",
button: "OK",
});
}
};
const handleCancelClick = () => {
setContent(olderContent);
};
const updateContent = async () => {
const response = await axios.patch(
"/api/content/shipping-and-policy-update",
{ content },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (response.status === 200) {
swal({
title: "Congratulations!!",
text: "Terms and condition updated successfully!",
icon: "success",
button: "OK",
});
} else {
swal({
title: "Sorry, please try again",
text: "Something went wrong!",
icon: "error",
button: "Retry",
dangerMode: true,
});
}
};
const handleSaveClick = async () => {
if (olderContent === undefined) {
await addShipping();
setAdded(true);
} else {
await updateContent();
setAdded(false);
}
// Reload terms and conditions
await getShipping();
};
useEffect(() => {
// addTermsandConditions();
getShipping();
}, [added]);
return (
<div>
<div style={{ display: "flex" }}>
<Button
variant="contained"
color="primary"
onClick={handleSaveClick}
style={{
fontWeight: "bold",
marginBottom: "1rem",
textTransform: "capitalize",
marginRight: "5px",
}}
>
Save
</Button>
<Button
variant="contained"
color="primary"
onClick={handleCancelClick}
style={{
fontWeight: "bold",
marginBottom: "1rem",
textTransform: "capitalize",
marginRight: "5px",
}}
>
Cancel
</Button>
</div>
<Box style={{ background: "#FFFFFF", color: "black", padding: "1rem" }}>
{/* <TextField
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
variant="outlined"
size="small"
fullWidth
/> */}
<Typography
style={{ margin: "0.5rem 0rem", fontWeight: "bold" }}
variant="h6"
>
{" "}
Shipping and policy:{" "}
</Typography>
<Typography style={{ margin: "0.5rem 0rem" }}>Body</Typography>
<ReactQuill
theme="snow"
value={content}
onChange={handleContentChange}
modules={{ toolbar: TOOLBAR_OPTIONS }}
/>
</Box>
</div>
);
}

View File

@ -0,0 +1,167 @@
import { Typography } from "@material-ui/core";
import { Box, Button, TextField } from "@mui/material";
import React, { useEffect, useState } from "react";
import ReactrichTextEditor from "./reactrichTextEditor";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
import axios from "axios";
import { isAutheticated } from "src/auth";
import { useNavigate, useNavigation } from "react-router-dom";
const TOOLBAR_OPTIONS = [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ font: [] }],
[{ list: "ordered" }, { list: "bullet" }],
["bold", "italic", "underline"],
[{ color: [] }, { background: [] }],
[{ align: [] }],
];
export default function EditTermsConditions() {
const [title, setTitle] = useState("Terms and conditions");
const [content, setContent] = useState("");
const [added, setAdded] = useState(false);
const [olderContent, setOlderContent] = useState("");
const navigation = useNavigate();
const token = isAutheticated();
const handleContentChange = (content, delta, source, editor) => {
setContent(editor.getHTML());
};
const getTermsAndConditions = async () => {
const response = await axios.get("/api/content/terms-and-conditions", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.status === 200) {
// console.log(response);
setContent(response?.data?.termsAndCondition[0]?.termsAndContionContent);
setOlderContent(
response?.data?.termsAndCondition[0]?.termsAndContionContent
);
}
};
const addTermsandConditions = async () => {
const response = await axios.post(
"/api/content/terms-and-conditions",
{ content },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (response.status == 201) {
swal({
title: "Congratulations!!",
text: "Terms and condition added successfully!",
icon: "success",
button: "OK",
});
}
};
const handleCancelClick = () => {
setContent(olderContent);
};
const updateContent = async () => {
const response = await axios.patch(
"/api/content/terms-and-condition-update",
{ content },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (response.status === 200) {
swal({
title: "Congratulations!!",
text: "Terms and condition updated successfully!",
icon: "success",
button: "OK",
});
} else {
swal({
title: "Sorry, please try again",
text: "Something went wrong!",
icon: "error",
button: "Retry",
dangerMode: true,
});
}
};
const handleSaveClick = async () => {
if (olderContent === undefined) {
await addTermsandConditions();
setAdded(true);
} else {
await updateContent();
setAdded(false);
}
// Reload terms and conditions
await getTermsAndConditions();
};
useEffect(() => {
// addTermsandConditions();
getTermsAndConditions();
}, [added]);
return (
<div>
<div style={{ display: "flex" }}>
<Button
variant="contained"
color="primary"
onClick={handleSaveClick}
style={{
fontWeight: "bold",
marginBottom: "1rem",
textTransform: "capitalize",
marginRight: "5px",
}}
>
Save
</Button>
<Button
variant="contained"
color="primary"
onClick={handleCancelClick}
style={{
fontWeight: "bold",
marginBottom: "1rem",
textTransform: "capitalize",
marginRight: "5px",
}}
>
Cancel
</Button>
</div>
<Box style={{ background: "#FFFFFF", color: "black", padding: "1rem" }}>
{/* <TextField
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
variant="outlined"
size="small"
fullWidth
/> */}
<Typography
style={{ margin: "0.5rem 0rem", fontWeight: "bold" }}
variant="h6"
>
{" "}
Terms and policy:{" "}
</Typography>
<Typography style={{ margin: "0.5rem 0rem" }}>Body</Typography>
<ReactQuill
theme="snow"
value={content}
onChange={handleContentChange}
modules={{ toolbar: TOOLBAR_OPTIONS }}
/>
</Box>
</div>
);
}

View File

@ -0,0 +1,33 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import ReactQuill from "react-quill";
const TOOLBAR_OPTIONS = [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ font: [] }],
[{ list: "ordered" }, { list: "bullet" }],
["bold", "italic", "underline"],
[{ color: [] }, { background: [] }],
[{ align: [] }],
];
export default function ReactrichTextEditor() {
const [content, setContent] = useState("");
const handleContentChange = (content, delta, source, editor) => {
setContent(editor.getHTML());
};
// const wrapperRef = useCallback((wrapper) => {
// if (wrapper == null) return;
// wrapper.innerHTML = "";
// const editor = document.createElement("div");
// wrapper.append(editor);
// new Quill(editor, { theme: "snow", modules: { toolbar: TOOLBAR_OPTIONS } });
// }, []);
// return <div className="container" ref={wrapperRef}></div>;
return (
<ReactQuill
theme="snow"
value={content}
onChange={handleContentChange}
modules={{ toolbar: TOOLBAR_OPTIONS }}
/>
);
}

View File

View File

@ -4,6 +4,16 @@ import { Link, useNavigate } from "react-router-dom";
import swal from "sweetalert"; import swal from "sweetalert";
import axios from "axios"; import axios from "axios";
import { isAutheticated } from "src/auth"; import { isAutheticated } from "src/auth";
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
import DeleteSharpIcon from "@mui/icons-material/DeleteSharp";
import {
Box,
FormControl,
IconButton,
MenuItem,
Select,
TextField,
} from "@mui/material";
// import { WebsiteURL } from '../WebsiteURL' // import { WebsiteURL } from '../WebsiteURL'
const AddProduct = () => { const AddProduct = () => {
@ -15,33 +25,50 @@ const AddProduct = () => {
const [categories, setCategoies] = useState([]); const [categories, setCategoies] = useState([]);
const [imagesPreview, setImagesPreview] = useState([]); const [imagesPreview, setImagesPreview] = useState([]);
const [allimage, setAllImage] = useState([]); // const [allimage, setAllImage] = useState([]);
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [productImages, setProductImages] = useState([]);
const [price, setPrice] = useState("");
const [categoryName, setCategoryName] = useState("");
const [error, setError] = useState("");
useEffect(() => { const handleFileChange = (e) => {
const getAllTax = async () => { const files = e.target.files;
const res = await axios.get(`/api/tax/view_tax`, {
headers: { // Check the total number of selected files
"Access-Control-Allow-Origin": "*", if (productImages.length + files.length > 4) {
Authorization: `Bearer ${token}`, setError("You can only upload up to 4 images.");
}, return;
}); }
if (res.data) {
setAllTax(res.data); // Check file types and append to selectedFiles
const allowedTypes = ["image/jpeg", "image/png", "image/jpg"];
const selected = [];
for (let i = 0; i < files.length; i++) {
if (productImages.length + selected.length >= 4) {
break; // Don't allow more than 4 images
} }
};
getAllTax();
getCategories();
}, [token]);
const [data, setData] = useState({
image: [],
imageURL: [],
name: "",
description: "",
price: "", if (allowedTypes.includes(files[i].type)) {
category: "", selected.push(files[i]);
}); }
}
if (selected.length === 0) {
setError("Please upload only PNG, JPEG, or JPG files.");
} else {
setError("");
setProductImages([...productImages, ...selected]);
}
};
const handelDelete = (image) => {
const filtered = productImages.filter((item) => item !== image);
setProductImages(filtered);
};
// get All categories
const getCategories = async () => { const getCategories = async () => {
try { try {
const response = await axios.get("/api/category/getCategories", { const response = await axios.get("/api/category/getCategories", {
@ -63,108 +90,118 @@ const AddProduct = () => {
}); });
} }
}; };
// Get all tax
const handleChange = (e) => { const getAllTax = async () => {
if (e.target.id === "image") { const res = await axios.get(`/api/tax/view_tax`, {
if ( headers: {
e.target.files[0]?.type === "image/jpeg" || "Access-Control-Allow-Origin": "*",
e.target.files[0]?.type === "image/png" || Authorization: `Bearer ${token}`,
e.target.files[0]?.type === "image/jpg" },
) { });
if (imagesPreview.length > 3) { if (res.data) {
swal({ setAllTax(res.data);
title: "Warning",
text: "maximum Four image Upload ",
icon: "error",
button: "Close",
dangerMode: true,
});
return;
}
// only for file preview------------------------------------
const files = Array.from(e.target.files);
files.forEach((file) => {
const reader = new FileReader();
reader.onload = () => {
if (reader.readyState === 2) {
setImagesPreview((old) => [...old, reader.result]);
}
};
reader.readAsDataURL(file);
});
// -----------------------------------------------------------------------------
setData((prev) => ({
...prev,
image: [...data.image, ...e.target.files],
}));
return;
} else {
swal({
title: "Warning",
text: "Upload jpg, jpeg, png only.",
icon: "error",
button: "Close",
dangerMode: true,
});
setData((prev) => ({
...prev,
imageURL: "",
image: "",
}));
e.target.value = null;
return;
}
} }
setData((prev) => ({ ...prev, [e.target.id]: e.target.value }));
}; };
useEffect(() => {
getAllTax();
getCategories();
}, [token]);
const TaxRatechange = async (e) => { // const handleChange = (e) => {
let taxDetails = { // if (e.target.id === "image") {
name: e.target.value.slice(12, 16), // if (
rate: Number(e.target.value.slice(4, 6)), // e.target.files[0]?.type === "image/jpeg" ||
// e.target.files[0]?.type === "image/png" ||
// e.target.files[0]?.type === "image/jpg"
// ) {
// if (imagesPreview.length > 3) {
// swal({
// title: "Warning",
// text: "maximum Four image Upload ",
// icon: "error",
// button: "Close",
// dangerMode: true,
// });
// return;
// }
// // only for file preview------------------------------------
// const files = Array.from(e.target.files);
// files.forEach((file) => {
// const reader = new FileReader();
taxId: e.target.value.slice(24), // reader.onload = () => {
}; // if (reader.readyState === 2) {
// setImagesPreview((old) => [...old, reader.result]);
// }
// };
let trRate = taxDetails.rate / 100; // reader.readAsDataURL(file);
let PriceWithT = Number(data.price); // });
PriceWithT += +(PriceWithT * trRate).toFixed(); // // -----------------------------------------------------------------------------
//price_Level_2_With_Tax // setData((prev) => ({
let price_Level_2_With_Tax = Number(data.price_Level_2); // ...prev,
price_Level_2_With_Tax += +(price_Level_2_With_Tax * trRate).toFixed();
//
//price_Level_3_With_Tax
let price_Level_3_With_Tax = Number(data.price_Level_3);
price_Level_3_With_Tax += +(price_Level_3_With_Tax * trRate).toFixed();
setData((prev) => ({
...prev,
price_With_Tax: PriceWithT,
price_Level_2_With_Tax: price_Level_2_With_Tax, // image: [...data.image, ...e.target.files],
// }));
// return;
// } else {
// swal({
// title: "Warning",
// text: "Upload jpg, jpeg, png only.",
// icon: "error",
// button: "Close",
// dangerMode: true,
// });
// setData((prev) => ({
// ...prev,
// imageURL: "",
// image: "",
// }));
// e.target.value = null;
// return;
// }
// }
// setData((prev) => ({ ...prev, [e.target.id]: e.target.value }));
// };
price_Level_3_With_Tax: price_Level_3_With_Tax, // const TaxRatechange = async (e) => {
taxId: taxDetails.taxId, // let taxDetails = {
})); // name: e.target.value.slice(12, 16),
}; // rate: Number(e.target.value.slice(4, 6)),
// taxId: e.target.value.slice(24),
// };
// let trRate = taxDetails.rate / 100;
// let PriceWithT = Number(data.price);
// PriceWithT += +(PriceWithT * trRate).toFixed();
// //price_Level_2_With_Tax
// let price_Level_2_With_Tax = Number(data.price_Level_2);
// price_Level_2_With_Tax += +(price_Level_2_With_Tax * trRate).toFixed();
// //
// //price_Level_3_With_Tax
// let price_Level_3_With_Tax = Number(data.price_Level_3);
// price_Level_3_With_Tax += +(price_Level_3_With_Tax * trRate).toFixed();
// setData((prev) => ({
// ...prev,
// price_With_Tax: PriceWithT,
// price_Level_2_With_Tax: price_Level_2_With_Tax,
// price_Level_3_With_Tax: price_Level_3_With_Tax,
// taxId: taxDetails.taxId,
// }));
// };
// console.log(data.image.length) // console.log(data.image.length)
const handleSubmit = () => { const handleSubmit = () => {
if ( if (
data.name.trim() === "" || name == "" ||
data.description.trim() === "" || description == "" ||
data.price === "" || productImages.length == 0 ||
data.image === "" price == ""
// data.price_With_Tax === '' ||
// data.price_Level_2 === '' ||
// data.price_Level_2_With_Tax === '' ||
// data.price_Level_3 === '' ||
// data.price_Level_3_With_Tax === '' ||
// data.imageURL.trim() === ''
) { ) {
swal({ swal({
title: "Warning", title: "Warning",
@ -177,13 +214,13 @@ const AddProduct = () => {
} }
setLoading(true); setLoading(true);
const formData = new FormData(); const formData = new FormData();
formData.append("name", data.name); formData.append("name", name);
formData.append("description", data.description); formData.append("description", description);
formData.append("price", data.price); formData.append("price", price);
formData.append("category", data.category); formData.append("category", categoryName);
data.image.forEach((Singleimage) => { productImages.forEach((Singleimage) => {
// console.log(Singleimage) // console.log(Singleimage)
formData.append("image", Singleimage); formData.append("image", Singleimage);
}); });
@ -220,8 +257,8 @@ const AddProduct = () => {
}); });
}); });
}; };
console.log(data); // console.log(data);
console.log(productImages);
return ( return (
<div className="container"> <div className="container">
<div className="row"> <div className="row">
@ -285,14 +322,14 @@ const AddProduct = () => {
type="text" type="text"
className="form-control" className="form-control"
id="name" id="name"
value={data.name} value={name}
maxLength={25} maxLength={25}
onChange={(e) => handleChange(e)} onChange={(e) => setName(e.target.value)}
/> />
{data.name ? ( {name ? (
<> <>
<small className="charLeft mt-4 fst-italic"> <small className="charLeft mt-4 fst-italic">
{25 - data.name.length} characters left {25 - name.length} characters left
</small> </small>
</> </>
) : ( ) : (
@ -308,14 +345,14 @@ const AddProduct = () => {
type="text" type="text"
className="form-control" className="form-control"
id="description" id="description"
value={data.description} value={description}
maxLength="100" maxLength="100"
onChange={(e) => handleChange(e)} onChange={(e) => setDescription(e.target.value)}
/> />
{data.description ? ( {description ? (
<> <>
<small className="charLeft mt-4 fst-italic"> <small className="charLeft mt-4 fst-italic">
{100 - data.description.length} characters left {100 - description.length} characters left
</small> </small>
</> </>
) : ( ) : (
@ -327,21 +364,100 @@ const AddProduct = () => {
<label htmlFor="image" className="form-label"> <label htmlFor="image" className="form-label">
Product Image* Product Image*
</label> </label>
<input <Box>
type="file" <label htmlFor="upload-Image">
className="form-control" <TextField
id="image" style={{
accept="image/*" display: "none",
multiple width: "350px",
onChange={(e) => handleChange(e)} height: "350px",
/> borderRadius: "10%",
}}
fullWidth
id="upload-Image"
type="file"
accept=".jpg , .png ,.jpeg"
label="file"
multiple
variant="outlined"
onChange={(e) => handleFileChange(e)}
/>
<Box
style={{ borderRadius: "10%" }}
sx={{
margin: "1rem 0rem",
cursor: "pointer",
width: "140px",
height: "140px",
border: "2px solid grey",
// borderRadius: '50%',
"&:hover": {
background: "rgba(112,112,112,0.5)",
},
}}
>
<CloudUploadIcon
style={{
color: "grey",
margin: "auto",
fontSize: "5rem",
}}
fontSize="large"
/>
</Box>
</label>
</Box>
{error && <p style={{ color: "red" }}>{error}</p>}
<p className="pt-1 pl-2 text-secondary"> <p className="pt-1 pl-2 text-secondary">
Upload jpg, jpeg and png only* Upload jpg, jpeg and png only*
</p> </p>
<Box style={{ display: "flex" }}>
{productImages &&
productImages.map((image, i) => (
<Box marginRight={"2rem"}>
<img
src={URL.createObjectURL(image)}
alt="profileImage"
style={{
width: 70,
height: 70,
marginBottom: "1rem",
}}
/>
{/* <IconButton
onClick={() => handelDelete(image)}
sx={{
position: "absolute",
fontSize: "small",
"&:hover": {
background: "black",
},
background: "black",
}}
> */}
<DeleteSharpIcon
onClick={() => handelDelete(image)}
fontSize="small"
sx={{
color: "white",
position: "absolute",
cursor: "pointer",
padding: "0.2rem",
background: "black",
borderRadius: "50%",
}}
/>
{/* </IconButton> */}
</Box>
))}
</Box>
</div> </div>
<div> <div>
<strong className="fs-6 fst-italic"> <strong className="fs-6 fst-italic">
*Please Upload maximum four images *You cannot upload more than 4 images !!
</strong> </strong>
</div> </div>
@ -363,23 +479,23 @@ const AddProduct = () => {
<div className="card-body px-5"> <div className="card-body px-5">
<div className="mb-3 me-3"> <div className="mb-3 me-3">
<label htmlFor="title" className="form-label"> <label htmlFor="title" className="form-label">
Price (optional) Price*
</label> </label>
<input <input
type="number" type="number"
className="form-control" className="form-control"
id="price" id="price"
value={data.price} value={price}
onChange={(e) => handleChange(e)} onChange={(e) => setPrice(e.target.value)}
/> />
</div> </div>
<div> <div>
<label htmlFor="categorySelect">Select a Category:</label> <label htmlFor="categorySelect">Select a Category:</label>
<select {/* <select
id="category" id="category"
style={{ width: "100%" }} style={{ width: "100%" }}
onChange={handleChange} value={categoryName}
value={data.category} onChange={(e) => setCategoryName(e.target.value)}
> >
<option value={""}>None</option> <option value={""}>None</option>
{categories.map((category, index) => ( {categories.map((category, index) => (
@ -387,7 +503,44 @@ const AddProduct = () => {
{category.categoryName} {category.categoryName}
</option> </option>
))} ))}
</select> </select> */}
<FormControl fullWidth>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
fullWidth
value={categoryName}
onChange={(e) => setCategoryName(e.target.value)}
>
<MenuItem
style={{
width: "100%",
display: "flex",
justifyContent: "left",
textAlign: "left",
padding: "0.5rem",
}}
value={""}
>
None
</MenuItem>
{categories.map((category, i) => (
<MenuItem
style={{
width: "100%",
display: "flex",
justifyContent: "left",
textAlign: "left",
padding: "0.5rem",
}}
key={i}
value={category.categoryName}
>
{category.categoryName}
</MenuItem>
))}
</Select>
</FormControl>
</div> </div>
{allTax.length > 0 && ( {allTax.length > 0 && (

View File

@ -4,6 +4,17 @@ import { Link, useNavigate, useParams } from "react-router-dom";
import swal from "sweetalert"; import swal from "sweetalert";
import axios from "axios"; import axios from "axios";
import { isAutheticated } from "src/auth"; import { isAutheticated } from "src/auth";
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
import DeleteSharpIcon from "@mui/icons-material/DeleteSharp";
import {
Box,
FormControl,
IconButton,
MenuItem,
Select,
TextField,
} from "@mui/material";
// import { WebsiteURL } from '../WebsiteURL' // import { WebsiteURL } from '../WebsiteURL'
const EditProduct = () => { const EditProduct = () => {
@ -11,20 +22,18 @@ const EditProduct = () => {
const token = isAutheticated(); const token = isAutheticated();
const navigate = useNavigate(); const navigate = useNavigate();
const [data, setData] = useState({
image: [],
imageURL: [],
name: "",
description: "",
category: "",
price: "",
});
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [allTax, setAllTax] = useState([]); const [allTax, setAllTax] = useState([]);
const [categories, setCatgories] = useState([]); const [categories, setCatgories] = useState([]);
const [imagesPreview, setImagesPreview] = useState([]); const [imagesPreview, setImagesPreview] = useState([]);
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [productImages, setProductImages] = useState([]);
const [price, setPrice] = useState("");
const [categoryName, setCategoryName] = useState("");
const [error, setError] = useState("");
const [newUpdatedImages, setNewUpdatedImages] = useState([]);
//get Productdata //get Productdata
const getProduct = async () => { const getProduct = async () => {
@ -36,23 +45,22 @@ const EditProduct = () => {
}, },
}) })
.then((res) => { .then((res) => {
// console.log(res.data?.product?.image) setName(res?.data?.product.name);
// if (res.data?.product?.image) { setDescription(res.data.product.description);
// res.data?.product?.image.map(item => { setProductImages(res.data.product.image);
// }) setPrice(res.data.product.price);
setCategoryName(res.data.product.category);
// }
// setImagesPreview(res.data?.product?.image)
setData((prev) => ({
...prev,
...res.data?.product,
imageURL: res.data?.product?.image?.url,
}));
}) })
.catch((err) => {}); .catch((err) => {
swal({
title: error,
text: " Can not fetch the product ",
icon: "error",
button: "Retry",
dangerMode: true,
});
});
}; };
// console.log(imagesPreview)
const getCategories = async () => { const getCategories = async () => {
try { try {
@ -95,101 +103,12 @@ const EditProduct = () => {
getAllTax(); getAllTax();
}, [token]); }, [token]);
const handleChange = (e) => {
if (e.target.id === "image") {
if (
e.target.files[0]?.type === "image/jpeg" ||
e.target.files[0]?.type === "image/png" ||
e.target.files[0]?.type === "image/jpg"
) {
if (imagesPreview.length > 3) {
swal({
title: "Warning",
text: "maximum Four image Upload ",
icon: "error",
button: "Close",
dangerMode: true,
});
return;
}
// only for file preview------------------------------------
const files = Array.from(e.target.files);
files.forEach((file) => {
const reader = new FileReader();
reader.onload = () => {
if (reader.readyState === 2) {
setImagesPreview((old) => [...old, reader.result]);
}
};
reader.readAsDataURL(file);
});
// -----------------------------------------------------------------------------
setData((prev) => ({
...prev,
image: [...data.image, ...e.target.files],
}));
return;
} else {
swal({
title: "Warning",
text: "Upload jpg, jpeg, png only.",
icon: "error",
button: "Close",
dangerMode: true,
});
setData((prev) => ({
...prev,
imageURL: "",
image: "",
}));
e.target.value = null;
return;
}
}
setData((prev) => ({ ...prev, [e.target.id]: e.target.value }));
};
const TaxRatechange = async (e) => {
let taxDetails = {
name: e.target.value.slice(12, 16),
rate: Number(e.target.value.slice(4, 6)),
taxId: e.target.value.slice(24),
};
let trRate = taxDetails.rate / 100;
let PriceWithT = Number(data.price);
PriceWithT += +(PriceWithT * trRate).toFixed();
//price_Level_2_With_Tax
let price_Level_2_With_Tax = Number(data.price_Level_2);
price_Level_2_With_Tax += +(price_Level_2_With_Tax * trRate).toFixed();
//
//price_Level_3_With_Tax
let price_Level_3_With_Tax = Number(data.price_Level_3);
price_Level_3_With_Tax += +(price_Level_3_With_Tax * trRate).toFixed();
setData((prev) => ({
...prev,
price_With_Tax: PriceWithT,
price_Level_2_With_Tax: price_Level_2_With_Tax,
price_Level_3_With_Tax: price_Level_3_With_Tax,
taxId: taxDetails.taxId,
}));
};
// console.log(data.image.length)
const handleSubmit = () => { const handleSubmit = () => {
if ( if (
data.name.trim() === "" || name == "" ||
data.description.trim() === "" || description == "" ||
data.price === "" || price == "" ||
data.image === "" (productImages.length == 0 && newUpdatedImages.length == 0)
// data.price_With_Tax === '' || // data.price_With_Tax === '' ||
// data.price_Level_2 === '' || // data.price_Level_2 === '' ||
// data.price_Level_2_With_Tax === '' || // data.price_Level_2_With_Tax === '' ||
@ -208,21 +127,25 @@ const EditProduct = () => {
} }
setLoading(true); setLoading(true);
const formData = new FormData(); const formData = new FormData();
formData.append("name", data.name); formData.append("name", name);
formData.append("description", data.description); formData.append("description", description);
formData.append("price", data.price); formData.append("price", price);
formData.append("category", data.category); formData.append("category", categoryName);
data.image.forEach((Singleimage) => { newUpdatedImages.length > 0 &&
formData.append("image", Singleimage); newUpdatedImages.forEach((Singleimage) => {
}); formData.append("newImages", Singleimage);
});
formData.append("image", JSON.stringify(productImages));
axios axios
.put(`/api/product/update/${id}`, formData, { .patch(`/api/product/update/${id}`, formData, {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
"Content-Type": "multipart/formdata", "Content-Type": "multipart/form-data",
"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Origin": "*",
}, },
}) })
@ -238,6 +161,7 @@ const EditProduct = () => {
}) })
.catch((err) => { .catch((err) => {
setLoading(false); setLoading(false);
const message = err.response?.data?.message const message = err.response?.data?.message
? err.response?.data?.message ? err.response?.data?.message
: "Something went wrong!"; : "Something went wrong!";
@ -250,7 +174,63 @@ const EditProduct = () => {
}); });
}); });
}; };
const handleFileChange = (e) => {
const files = e.target.files;
// Check the total number of selected files
if (newUpdatedImages.length + files.length > 4 - productImages.length) {
setError("You can only upload up to 4 images.");
return;
}
// Check file types and append to selectedFiles
const allowedTypes = ["image/jpeg", "image/png", "image/jpg"];
const selected = [];
for (let i = 0; i < files.length; i++) {
if (
newUpdatedImages.length + selected.length >=
4 - productImages.length
) {
break; // Don't allow more than 4 images
}
if (allowedTypes.includes(files[i].type)) {
selected.push(files[i]);
}
}
if (selected.length === 0) {
setError("Please upload only PNG, JPEG, or JPG files.");
} else {
setError("");
setNewUpdatedImages([...newUpdatedImages, ...selected]);
}
};
const handelDelete = async (public_id) => {
const ary = public_id.split("/");
const res = await axios.delete(
`/api/product/deleteImage/jatinMor/product/${ary[2]}`,
{
headers: {
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
}
);
if (res) {
const filtered = productImages.filter(
(item) => item.public_id !== public_id
);
setProductImages(filtered);
}
};
const handellocalDelete = (image) => {
const filtered = productImages.filter((item) => item !== image);
setProductImages(filtered);
};
return ( return (
<div className="container"> <div className="container">
<div className="row"> <div className="row">
@ -314,14 +294,14 @@ const EditProduct = () => {
type="text" type="text"
className="form-control" className="form-control"
id="name" id="name"
value={data.name} value={name}
maxLength={25} maxLength={25}
onChange={(e) => handleChange(e)} onChange={(e) => setName(e.target.value)}
/> />
{data.name ? ( {name ? (
<> <>
<small className="charLeft mt-4 fst-italic"> <small className="charLeft mt-4 fst-italic">
{25 - data.name.length} characters left {25 - name.length} characters left
</small> </small>
</> </>
) : ( ) : (
@ -337,14 +317,14 @@ const EditProduct = () => {
type="text" type="text"
className="form-control" className="form-control"
id="description" id="description"
value={data.description} value={description}
maxLength="100" maxLength="100"
onChange={(e) => handleChange(e)} onChange={(e) => setDescription(e.target.value)}
/> />
{data.description ? ( {description ? (
<> <>
<small className="charLeft mt-4 fst-italic"> <small className="charLeft mt-4 fst-italic">
{100 - data.description.length} characters left {100 - description.length} characters left
</small> </small>
</> </>
) : ( ) : (
@ -352,40 +332,119 @@ const EditProduct = () => {
)} )}
</div> </div>
<div className="mb-3"> <Box>
<label htmlFor="image" className="form-label"> <label htmlFor="upload-Image">
Product Image* <TextField
style={{
display: "none",
width: "350px",
height: "350px",
borderRadius: "10%",
}}
fullWidth
id="upload-Image"
type="file"
accept=".jpg , .png ,.jpeg"
label="file"
multiple
variant="outlined"
onChange={(e) => handleFileChange(e)}
/>
<Box
style={{ borderRadius: "10%" }}
sx={{
margin: "1rem 0rem",
cursor: "pointer",
width: "140px",
height: "140px",
border: "2px solid grey",
// borderRadius: '50%',
"&:hover": {
background: "rgba(112,112,112,0.5)",
},
}}
>
<CloudUploadIcon
style={{
color: "grey",
margin: "auto",
fontSize: "5rem",
}}
fontSize="large"
/>
</Box>
</label> </label>
<input </Box>
type="file" {error && <p style={{ color: "red" }}>{error}</p>}
className="form-control"
id="image"
accept="image/*"
multiple
onChange={(e) => handleChange(e)}
/>
<p className="pt-1 pl-2 text-secondary">
Upload jpg, jpeg and png only*
</p>
</div>
<div> <div>
<strong className="fs-6 fst-italic"> <strong className="fs-6 fst-italic">
*Please Upload maximum four images *You cannot upload more than 4 images
</strong> </strong>
</div> </div>
{imagesPreview.length > 0 && ( <Box style={{ display: "flex" }}>
<div id="createProductFormImage" className="w-25 d-flex"> {productImages &&
{imagesPreview.map((image, index) => ( productImages.map((image, i) => (
<img <Box marginRight={"2rem"}>
className=" w-50 p-1 " <img
key={index} src={image.url}
src={image} alt="profileImage"
alt="Product Preview" style={{
/> width: 70,
height: 70,
marginBottom: "1rem",
}}
/>
{productImages.length + newUpdatedImages.length > 1 && (
<DeleteSharpIcon
onClick={() => handelDelete(image.public_id)}
fontSize="small"
sx={{
color: "white",
position: "absolute",
cursor: "pointer",
padding: "0.2rem",
background: "black",
borderRadius: "50%",
}}
/>
)}
{/* </IconButton> */}
</Box>
))} ))}
</div> {newUpdatedImages &&
)} newUpdatedImages.map((image, i) => (
<Box marginRight={"2rem"}>
<img
src={URL.createObjectURL(image)}
alt="profileImage"
style={{
width: 70,
height: 70,
marginBottom: "1rem",
}}
/>
{productImages.length + newUpdatedImages.length > 1 && (
<DeleteSharpIcon
onClick={() => handellocalDelete(image)}
fontSize="small"
sx={{
color: "white",
position: "absolute",
cursor: "pointer",
padding: "0.2rem",
background: "black",
borderRadius: "50%",
}}
/>
)}
{/* </IconButton> */}
</Box>
))}
</Box>
</div> </div>
</div> </div>
</div> </div>
@ -400,17 +459,17 @@ const EditProduct = () => {
type="number" type="number"
className="form-control" className="form-control"
id="price" id="price"
value={data.price} value={price}
onChange={(e) => handleChange(e)} onChange={(e) => setPrice(e.target.value)}
/> />
</div> </div>
<div> <div>
<label htmlFor="categorySelect">Select a Category:</label> <label htmlFor="categorySelect">Select a Category:</label>
<select {/* <select
id="category" id="category"
style={{ width: "100%" }} style={{ width: "100%" }}
value={data.category} value={categoryName}
onChange={handleChange} onChange={(e) => setCategoryName(e.target.value)}
> >
<option value={""}>None</option> <option value={""}>None</option>
{categories.map((category, index) => ( {categories.map((category, index) => (
@ -418,7 +477,44 @@ const EditProduct = () => {
{category.categoryName} {category.categoryName}
</option> </option>
))} ))}
</select> </select> */}
<FormControl fullWidth>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
fullWidth
value={categoryName}
onChange={(e) => setCategoryName(e.target.value)}
>
<MenuItem
style={{
width: "100%",
display: "flex",
justifyContent: "left",
textAlign: "left",
padding: "0.5rem",
}}
value={""}
>
None
</MenuItem>
{categories.map((category, i) => (
<MenuItem
style={{
width: "100%",
display: "flex",
justifyContent: "left",
textAlign: "left",
padding: "0.5rem",
}}
key={i}
value={category.categoryName}
>
{category.categoryName}
</MenuItem>
))}
</Select>
</FormControl>
</div> </div>
{allTax.length > 0 && ( {allTax.length > 0 && (

File diff suppressed because it is too large Load Diff

View File

@ -36,6 +36,7 @@ function ViewProduct() {
return strTime; return strTime;
} }
// console.log(product);
return ( return (
<div className=" main-content"> <div className=" main-content">
<div className=" my-3 page-content"> <div className=" my-3 page-content">
@ -45,13 +46,13 @@ function ViewProduct() {
<div className="col-12"> <div className="col-12">
<div className="page-title-box d-flex align-items-center justify-content-between"> <div className="page-title-box d-flex align-items-center justify-content-between">
<h4 className="mb-3">Product</h4> <h4 className="mb-3">Product</h4>
<Link to="/product/add"> <Link to="/products">
<button <button
type="button" type="button"
className="btn btn-info float-end mb-3 ml-4" className="btn btn-info float-end mb-3 ml-4"
> >
{" "} {" "}
+ Add Product Back
</button> </button>
</Link> </Link>
{/* <div className="page-title-right"> {/* <div className="page-title-right">
@ -78,7 +79,7 @@ function ViewProduct() {
<tr> <tr>
<th>Id</th>{" "} <th>Id</th>{" "}
<td> <td>
<h5>{product?._id}</h5> <h5>{product.uniqueId}</h5>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -106,7 +107,7 @@ function ViewProduct() {
</tr> </tr>
<tr> <tr>
<th>Base Price</th> <th>Base Price</th>
<td>{product?.price}</td> <td>${product?.price}</td>
</tr> </tr>
{/* <tr><th>Product Time</th><td>{product?.time}</td></tr> {/* <tr><th>Product Time</th><td>{product?.time}</td></tr>

View File

@ -1,5 +1,5 @@
import React, { useEffect } from 'react' import React, { useEffect } from "react";
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from "react-router-dom";
import { import {
CButton, CButton,
CCard, CCard,
@ -12,129 +12,122 @@ import {
CInputGroup, CInputGroup,
CInputGroupText, CInputGroupText,
CRow, CRow,
} from '@coreui/react' } from "@coreui/react";
import CIcon from '@coreui/icons-react' import CIcon from "@coreui/icons-react";
import { cilLockLocked, cilUser } from '@coreui/icons' import { cilLockLocked, cilUser } from "@coreui/icons";
import ClipLoader from "react-spinners/ClipLoader"; import ClipLoader from "react-spinners/ClipLoader";
import { useState } from 'react' import { useState } from "react";
import axios from 'axios' import axios from "axios";
import { useHistory } from 'react-router-dom' import { useHistory } from "react-router-dom";
const Login = () => { const Login = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [validForm, setValidForm] = useState(false) const [validForm, setValidForm] = useState(false);
const [auth, setAuth] = useState({ const [auth, setAuth] = useState({
email: "", email: "",
password: "" password: "",
}); });
const [errors, setErrors] = useState({ const [errors, setErrors] = useState({
emailError: '', emailError: "",
passwordError: '', passwordError: "",
});
})
const validEmailRegex = RegExp( const validEmailRegex = RegExp(
/^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i, /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i
) );
const validPasswordRegex = RegExp(/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[^\w\s]).{7,}$/) const validPasswordRegex = RegExp(
/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[^\w\s]).{7,}$/
);
const history = useNavigate(); const history = useNavigate();
// const handleChange = (e) => (event) => { // const handleChange = (e) => (event) => {
// setAuth({ ...auth, [e]: event.target.value }); // setAuth({ ...auth, [e]: event.target.value });
// }; // };
const validateForm = () => { const validateForm = () => {
let valid = true let valid = true;
Object.values(errors).forEach((val) => { Object.values(errors).forEach((val) => {
if (val.length > 0) { if (val.length > 0) {
valid = false valid = false;
return false return false;
} }
}) });
Object.values(auth).forEach((val) => { Object.values(auth).forEach((val) => {
if (val.length <= 0) { if (val.length <= 0) {
valid = false valid = false;
return false return false;
} }
}) });
return valid return valid;
} };
//cheking email and password //cheking email and password
useEffect(() => { useEffect(() => {
if (validateForm()) { if (validateForm()) {
setValidForm(true) setValidForm(true);
} else { } else {
setValidForm(false) setValidForm(false);
} }
}, [errors]) }, [errors]);
const handleChange = (e) => { const handleChange = (e) => {
const { name, value } = e.target const { name, value } = e.target;
switch (name) { switch (name) {
case 'email': case "email":
setErrors({ setErrors({
...errors, ...errors,
emailError: validEmailRegex.test(value) ? '' : 'Email is not valid!', emailError: validEmailRegex.test(value) ? "" : "Email is not valid!",
}) });
break break;
case 'password': case "password":
setErrors((errors) => ({ setErrors((errors) => ({
...errors, ...errors,
passwordError: validPasswordRegex.test(value) passwordError: validPasswordRegex.test(value)
? '' ? ""
: 'Password Shoud Be 8 Characters Long, Atleast One Uppercase, Atleast One Lowercase,Atleast One Digit, Atleast One Special Character', : "Password Shoud Be 8 Characters Long, Atleast One Uppercase, Atleast One Lowercase,Atleast One Digit, Atleast One Special Character",
})) }));
break break;
default: default:
break break;
} }
setAuth({ ...auth, [name]: value }) setAuth({ ...auth, [name]: value });
} };
const Login = async () => { const Login = async () => {
if (!(auth.email && auth.password)) { if (!(auth.email && auth.password)) {
return swal("Error!", "All fields are required", "error");
return swal('Error!', 'All fields are required', 'error')
} }
setLoading({ loading: true }) setLoading({ loading: true });
try { try {
const res = await axios.post("/api/v1/user/login/", auth); const res = await axios.post("/api/v1/user/login/", auth);
if (res.data.success == true) { if (res.data.success == true) {
localStorage.setItem("authToken", res.data.token) localStorage.setItem("authToken", res.data.token);
let response = await axios.get(`/api/v1/user/details`, { let response = await axios.get(`/api/v1/user/details`, {
headers: { headers: {
Authorization: `Bearer ${res.data.token}`, Authorization: `Bearer ${res.data.token}`,
}, },
}) });
// console.log(response.data) // console.log(response.data)
const data = response.data const data = response.data;
if (data.user.role === 'admin') { if (data.user.role === "admin") {
history('/dashboard') history("/dashboard");
setLoading(false); setLoading(false);
window.location.reload() window.location.reload();
} } else {
else { swal("Error!", "please try with admin credential!!", "error");
swal('Error!', 'please try with admin credential!!', 'error')
setLoading(false); setLoading(false);
} }
} else {
}
else {
setLoading(false); setLoading(false);
swal('Error!', 'Invalid Credentials', 'error') swal("Error!", "Invalid Credentials", "error");
} }
} catch (error) { } catch (error) {
setLoading(false); setLoading(false);
swal('Error!', 'Invalid Credentials', 'error') swal("Error!", "Invalid Credentials", "error");
} }
} };
return ( return (
<div className="bg-light min-vh-100 d-flex flex-row align-items-center"> <div className="bg-light min-vh-100 d-flex flex-row align-items-center">
@ -145,16 +138,27 @@ const Login = () => {
<CCard className="p-4"> <CCard className="p-4">
<CCardBody> <CCardBody>
<CForm> <CForm>
<h1>Login</h1> <h1>The Solar Sign</h1>
<p className="text-medium-emphasis">Sign In to Your SOLAR Sign Admin Dashboard Account.</p> <p className="text-medium-emphasis">
Sign In to Your SOLAR Sign Admin Dashboard Account.
</p>
<CInputGroup className="mb-3"> <CInputGroup className="mb-3">
<CInputGroupText> <CInputGroupText>
<CIcon icon={cilUser} /> <CIcon icon={cilUser} />
</CInputGroupText> </CInputGroupText>
<CFormInput type="email" placeholder="Email" onChange={handleChange} value={auth.email} name="email" autoComplete="email" /> <CFormInput
type="email"
placeholder="Email"
onChange={handleChange}
value={auth.email}
name="email"
autoComplete="email"
/>
</CInputGroup> </CInputGroup>
{errors.emailError && ( {errors.emailError && (
<p className="text-center py-2 text-danger">{errors.emailError}</p> <p className="text-center py-2 text-danger">
{errors.emailError}
</p>
)} )}
<CInputGroup className="mb-4"> <CInputGroup className="mb-4">
<CInputGroupText> <CInputGroupText>
@ -171,15 +175,20 @@ const Login = () => {
</CInputGroup> </CInputGroup>
{errors.passwordError && ( {errors.passwordError && (
<p className="text-center py-2 text-danger">{errors.passwordError}</p> <p className="text-center py-2 text-danger">
{errors.passwordError}
</p>
)} )}
<CButton color="primary" className="px-4" disabled={!validForm} onClick={Login}> <CButton
color="primary"
className="px-4"
disabled={!validForm}
onClick={Login}
>
<ClipLoader loading={loading} size={18} /> <ClipLoader loading={loading} size={18} />
{!loading && "Login"} {!loading && "Login"}
</CButton> </CButton>
<Link to="/"> <Link to="/">
<CButton color="dark" className="px-4 ms-2"> <CButton color="dark" className="px-4 ms-2">
Cancel Cancel
@ -188,12 +197,8 @@ const Login = () => {
<br /> <br />
<CButton color="link" className="px-0"> <CButton color="link" className="px-0">
<Link to="/password/forgot"> <Link to="/password/forgot">Forgot password.?</Link>
Forgot password.?
</Link>
</CButton> </CButton>
</CForm> </CForm>
</CCardBody> </CCardBody>
{/* <CButton color="" className="px-0"> {/* <CButton color="" className="px-0">
@ -202,18 +207,17 @@ const Login = () => {
</Link> </Link>
</CButton> */} </CButton> */}
</CCard> </CCard>
</CCardGroup> </CCardGroup>
</CCol> </CCol>
</CRow> </CRow>
</CContainer> </CContainer>
</div> </div>
) );
} };
export default Login export default Login;
// < Route path = "/" name = "Home" render = {(props) => ( // < Route path = "/" name = "Home" render = {(props) => (
// userdata && userdata.role === 'admin' ? <DefaultLayout {...props} /> : // userdata && userdata.role === 'admin' ? <DefaultLayout {...props} /> :
// <><Login {...props} /></> // <><Login {...props} /></>
// )} /> // )} />