Add motion animations to Button and CabinetMaintenance components; update form validation and styling
This commit is contained in:
parent
442b8e52dd
commit
bfe22a61a5
72
package-lock.json
generated
72
package-lock.json
generated
@ -13,6 +13,7 @@
|
|||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"i18next-http-backend": "^2.6.1",
|
"i18next-http-backend": "^2.6.1",
|
||||||
"lucide-react": "^0.446.0",
|
"lucide-react": "^0.446.0",
|
||||||
|
"motion": "^11.15.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@ -2857,6 +2858,33 @@
|
|||||||
"url": "https://github.com/sponsors/rawify"
|
"url": "https://github.com/sponsors/rawify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "11.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.15.0.tgz",
|
||||||
|
"integrity": "sha512-MLk8IvZntxOMg7lDBLw2qgTHHv664bYoYmnFTmE0Gm/FW67aOJk0WM3ctMcG+Xhcv+vh5uyyXwxvxhSeJzSe+w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^11.14.3",
|
||||||
|
"motion-utils": "^11.14.3",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@ -3928,6 +3956,44 @@
|
|||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/motion": {
|
||||||
|
"version": "11.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion/-/motion-11.15.0.tgz",
|
||||||
|
"integrity": "sha512-iZ7dwADQJWGsqsSkBhNHdI2LyYWU+hA1Nhy357wCLZq1yHxGImgt3l7Yv0HT/WOskcYDq9nxdedyl4zUv7UFFw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"framer-motion": "^11.15.0",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "11.14.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.14.3.tgz",
|
||||||
|
"integrity": "sha512-lW+D2wBy5vxLJi6aCP0xyxTxlTfiu+b+zcpVbGVFUxotwThqhdpPRSmX8xztAgtZMPMeU0WGVn/k1w4I+TbPqA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "11.14.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.14.3.tgz",
|
||||||
|
"integrity": "sha512-Xg+8xnqIJTpr0L/cidfTTBFkvRw26ZtGGuIhA94J9PQ2p4mEa06Xx7QVYZH0BP+EpMSaDlu+q0I0mmvwADPsaQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@ -5327,6 +5393,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"i18next-http-backend": "^2.6.1",
|
"i18next-http-backend": "^2.6.1",
|
||||||
"lucide-react": "^0.446.0",
|
"lucide-react": "^0.446.0",
|
||||||
|
"motion": "^11.15.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
31
src/App.jsx
31
src/App.jsx
@ -1,19 +1,38 @@
|
|||||||
import { Outlet } from "react-router-dom";
|
import { useOutlet } from "react-router";
|
||||||
|
import { useState } from "react";
|
||||||
import Header from "./components/Header";
|
import Header from "./components/Header";
|
||||||
import Footer from "./components/Footer";
|
import Footer from "./components/Footer";
|
||||||
import { ToastProvider } from "./components/Toast";
|
import { AnimatePresence } from "motion/react";
|
||||||
|
import { motion } from "motion/react";
|
||||||
|
import { useLocation } from "react-router";
|
||||||
|
|
||||||
|
const AnimatedOutlet = () => {
|
||||||
|
const o = useOutlet();
|
||||||
|
const [outlet] = useState(o);
|
||||||
|
|
||||||
|
return <div>{outlet}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const location = useLocation();
|
||||||
return (
|
return (
|
||||||
<ToastProvider>
|
|
||||||
<div className="flex flex-col min-h-screen">
|
<div className="flex flex-col min-h-screen">
|
||||||
<Header />
|
<Header />
|
||||||
<main className="flex flex-grow transition-color-mode md:p-7 2xl:p-12 bg-surface dark:bg-surface-dark">
|
<main className="overflow-hidden flex flex-grow transition-color-mode md:p-7 2xl:p-12 bg-surface dark:bg-surface-dark">
|
||||||
<Outlet />
|
<AnimatePresence mode="popLayout">
|
||||||
|
<motion.div
|
||||||
|
className="w-full ovwerflow-hidden"
|
||||||
|
key={location.pathname}
|
||||||
|
initial={{ opacity: 0, x: 50 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={{ opacity: 0, x: 50 }}
|
||||||
|
>
|
||||||
|
<AnimatedOutlet />
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</ToastProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { motion } from 'motion/react';
|
||||||
|
|
||||||
function Button({text, onClick}) {
|
function Button({text, onClick}) {
|
||||||
return (
|
return (
|
||||||
<button
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
className="px-12 py-2 text-lg text-white dark:text-primary-dark rounded-full bg-primary dark:bg-secondary-dark"
|
className="px-12 py-2 text-lg text-white dark:text-primary-dark rounded-full bg-primary dark:bg-secondary-dark"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</button>
|
</motion.button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,23 @@
|
|||||||
import { PropTypes } from 'prop-types';
|
import { PropTypes } from "prop-types";
|
||||||
import clsx from 'clsx';
|
import clsx from "clsx";
|
||||||
|
|
||||||
function FormBox({ title, children, alt = false }) {
|
function FormBox({ title, children, alt = false }) {
|
||||||
return (
|
return (
|
||||||
<form className={clsx(alt ? 'bg-secondary-variant dark:bg-secondary-variant-dark border-secondary-variant dark:border-secondary-variant-dark' : 'bg-surface-variant dark:bg-surface-variant-dark', 'transition-color-mode font-body border-secondary dark:border-secondary-dark border-2 p-4 rounded-3xl relative h-full')}>
|
<form
|
||||||
<label className={clsx(alt && 'bg-surface dark:bg-surface-dark border-3 border-secondary-variant dark:border-secondary-variant-dark', 'font-body absolute left-11 -top-4 bg-secondary dark:bg-secondary-dark text-primary dark:text-primary-dark font-medium py-1 px-4 rounded-full')}>
|
className={clsx(
|
||||||
|
alt
|
||||||
|
? "bg-secondary-variant dark:bg-secondary-variant-dark border-secondary-variant dark:border-secondary-variant-dark"
|
||||||
|
: "bg-surface-variant dark:bg-surface-variant-dark",
|
||||||
|
"transition-color-mode font-body border-secondary dark:border-secondary-dark border-2 p-4 rounded-3xl relative h-full"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className={clsx(
|
||||||
|
alt &&
|
||||||
|
"bg-surface dark:bg-surface-dark border-3 border-secondary-variant dark:border-secondary-variant-dark",
|
||||||
|
"font-body absolute left-11 -top-4 bg-secondary dark:bg-secondary-dark text-primary dark:text-primary-dark font-medium py-1 px-4 rounded-full"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{title}
|
{title}
|
||||||
</label>
|
</label>
|
||||||
{children}
|
{children}
|
||||||
@ -18,4 +31,4 @@ FormBox.propTypes = {
|
|||||||
alt: PropTypes.bool,
|
alt: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FormBox;
|
export default FormBox;
|
||||||
|
@ -1,33 +1,56 @@
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
function SubMenu({ items }) {
|
function SubMenu({ items, isVisible, onLinkClick }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className="absolute left-0 z-50 invisible transition-all duration-200 -translate-y-6 shadow-sm opacity-0 top-full border-t-3 border-primary dark:border-primary-dark bg-secondary dark:bg-secondary-dark rounded-2xl shadow-surface-variant-dark dark:shadow-primary group-hover:visible group-hover:translate-y-0 group-hover:opacity-100">
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"absolute left-0 z-50 shadow-sm top-full border-t-3 border-primary dark:border-primary-dark bg-secondary dark:bg-secondary-dark rounded-2xl shadow-surface-variant-dark dark:shadow-primary",
|
||||||
|
isVisible ? "block" : "hidden"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{items.map((subItem, index) => (
|
{items.map((subItem, index) => (
|
||||||
<div key={index} className="px-6 py-2 cursor-pointer text-nowrap hover:bg-white dark:hover:bg-secondary-variant-dark first:rounded-t-2xl second-last:rounded-b-2xl first:pt-4 last:pb-4">
|
<div
|
||||||
|
key={index}
|
||||||
|
className="px-6 py-2 cursor-pointer text-nowrap hover:bg-white dark:hover:bg-secondary-variant-dark first:rounded-t-2xl second-last:rounded-b-2xl first:pt-4 last:pb-4"
|
||||||
|
onClick={onLinkClick}
|
||||||
|
>
|
||||||
<Link to={subItem.path}>{t(subItem.name)}</Link>
|
<Link to={subItem.path}>{t(subItem.name)}</Link>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<svg className="absolute top-0 left-6 mt-[-13px] fill-primary dark:fill-primary-dark" width="16" height="13" viewBox="0 0 16 13" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
className="absolute top-0 left-6 mt-[-13px] fill-primary dark:fill-primary-dark"
|
||||||
|
width="16"
|
||||||
|
height="13"
|
||||||
|
viewBox="0 0 16 13"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<path d="M6.26795 1C7.03775 -0.333332 8.96225 -0.333334 9.73205 0.999999L14.9282 10C15.698 11.3333 14.7358 13 13.1962 13H2.80385C1.26425 13 0.301996 11.3333 1.0718 10L6.26795 1Z" />
|
<path d="M6.26795 1C7.03775 -0.333332 8.96225 -0.333334 9.73205 0.999999L14.9282 10C15.698 11.3333 14.7358 13 13.1962 13H2.80385C1.26425 13 0.301996 11.3333 1.0718 10L6.26795 1Z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
function MenuBar({ menuItems }) {
|
function MenuBar({ menuItems }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [activeMenu, setActiveMenu] = useState(null);
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between pt-5 pb-2 text-base font-body">
|
<div className="flex justify-between pt-5 pb-2 text-base font-body">
|
||||||
{menuItems.map((item, index) =>
|
{menuItems.map((item, index) => (
|
||||||
<div key={index} className="relative pb-3 cursor-pointer group">
|
<div
|
||||||
{t(item.name)}
|
key={index}
|
||||||
{item.submenu.length > 0 && <SubMenu items={item.submenu} />}
|
className="relative pb-3 cursor-pointer"
|
||||||
|
onMouseEnter={() => setActiveMenu(index)}
|
||||||
|
onMouseLeave={() => setActiveMenu(null)}
|
||||||
|
>
|
||||||
|
<Link to={item.path}>{t(item.name)}</Link>
|
||||||
|
{item.submenu.length > 0 && <SubMenu items={item.submenu} isVisible={ activeMenu === index } onLinkClick={() => setActiveMenu(null)}/>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -42,18 +65,20 @@ MenuBar.propTypes = {
|
|||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
path: PropTypes.string
|
path: PropTypes.string,
|
||||||
})
|
})
|
||||||
).isRequired
|
).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
SubMenu.propTypes = {
|
SubMenu.propTypes = {
|
||||||
items: PropTypes.arrayOf(
|
items: PropTypes.arrayOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
path: PropTypes.string.isRequired
|
path: PropTypes.string.isRequired,
|
||||||
})
|
})
|
||||||
).isRequired
|
).isRequired,
|
||||||
|
isVisible: PropTypes.bool.isRequired,
|
||||||
|
onLinkClick: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MenuBar;
|
export default MenuBar;
|
||||||
|
@ -3,33 +3,40 @@ import { useTranslation } from "react-i18next";
|
|||||||
import FormBox from "../components/FormBox";
|
import FormBox from "../components/FormBox";
|
||||||
import Button from "../components/Button";
|
import Button from "../components/Button";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { AnimatePresence, motion } from "motion/react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
function CabinetCreation() {
|
function CabinetCreation() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [cabinetId, setCabinetId] = useState({id: "", valid: true});
|
const [cabinetId, setCabinetId] = useState({ id: "", valid: true });
|
||||||
const [cabinetKeyId, setCabinetKeyId] = useState({id: "", valid: true});
|
const [cabinetKeyId, setCabinetKeyId] = useState({ id: "", valid: true });
|
||||||
const [noOfLockers, setNoOfLockers] = useState({number: 0, valid: true});
|
const [noOfLockers, setNoOfLockers] = useState({ number: 0, valid: true });
|
||||||
|
|
||||||
const handleNext = (e) => {
|
const handleNext = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const idRegex = /^[A-Z]{2}[0-9]{4}$/;
|
const idRegex = /^[A-Z]{2}[0-9]{4}$/;
|
||||||
if (!idRegex.test(cabinetId.id)) {
|
if (!idRegex.test(cabinetId.id)) {
|
||||||
setCabinetId({id: cabinetId.id, valid: false});
|
setCabinetId({ id: cabinetId.id, valid: false });
|
||||||
return;
|
return;
|
||||||
} else if (!idRegex.test(cabinetKeyId.id)) {
|
} else if (!idRegex.test(cabinetKeyId.id)) {
|
||||||
setCabinetKeyId({id: cabinetKeyId.id, valid: false});
|
setCabinetKeyId({ id: cabinetKeyId.id, valid: false });
|
||||||
return;
|
return;
|
||||||
} else if (noOfLockers.number === 0) {
|
} else if (noOfLockers.number === 0) {
|
||||||
setNoOfLockers({number: noOfLockers.number, valid: false});
|
setNoOfLockers({ number: noOfLockers.number, valid: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
navigate("register-lockers", {state: {cabinetId: cabinetId.id, cabinetKeyId: cabinetKeyId.id, noOfLockers: noOfLockers.number}});
|
navigate("register-lockers", {
|
||||||
|
state: {
|
||||||
|
cabinetId: cabinetId.id,
|
||||||
|
cabinetKeyId: cabinetKeyId.id,
|
||||||
|
noOfLockers: noOfLockers.number,
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-fit">
|
<motion.div className="w-full h-fit" initial={{ scale: 0.9 }} animate={{ scale: 1 }} exit={{ scale: 0 }}>
|
||||||
<FormBox title={t("cabinetCreation")}>
|
<FormBox title={t("cabinetCreation")}>
|
||||||
<div className="p-2 pt-7 flex flex-col gap-4">
|
<div className="p-2 pt-7 flex flex-col gap-4">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
@ -39,12 +46,32 @@ function CabinetCreation() {
|
|||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<input
|
<input
|
||||||
value={cabinetId.id}
|
value={cabinetId.id}
|
||||||
className={clsx("w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey", !cabinetId.valid && "border-error")}
|
className={clsx(
|
||||||
onChange={(e) => setCabinetId({id: e.target.value.toUpperCase(), valid: true })}
|
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
|
||||||
|
!cabinetId.valid && "border-error"
|
||||||
|
)}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCabinetId({
|
||||||
|
id: e.target.value.toUpperCase(),
|
||||||
|
valid: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
type="text"
|
type="text"
|
||||||
maxLength={6}
|
maxLength={6}
|
||||||
/>
|
/>
|
||||||
{!cabinetId.valid && <div className="text-sm text-error ml-3 pt-1">Invalid Cabinet Id</div>}
|
<AnimatePresence initial={false}>
|
||||||
|
{!cabinetId.valid && (
|
||||||
|
<motion.div
|
||||||
|
className="w-1/5 text-sm text-error ml-3 pt-1"
|
||||||
|
initial={{ opacity: 0,scale: 0 }}
|
||||||
|
animate={{ opacity: 1,scale: 1 }}
|
||||||
|
exit={{ opacity: 0,scale: 0 }}
|
||||||
|
key="cabinetIdError"
|
||||||
|
>
|
||||||
|
Invalid Cabinet Id
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
@ -52,14 +79,26 @@ function CabinetCreation() {
|
|||||||
{t("cabinetKeyId")}
|
{t("cabinetKeyId")}
|
||||||
</label>
|
</label>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<input
|
<input
|
||||||
value={cabinetKeyId.id}
|
value={cabinetKeyId.id}
|
||||||
className={clsx("w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey", !cabinetKeyId.valid && "border-error")}
|
className={clsx(
|
||||||
onChange={(e) => setCabinetKeyId({id: e.target.value.toUpperCase(), valid: true })}
|
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
|
||||||
type="text"
|
!cabinetKeyId.valid && "border-error"
|
||||||
maxLength={6}
|
)}
|
||||||
/>
|
onChange={(e) =>
|
||||||
{!cabinetKeyId.valid && <div className="text-sm text-error ml-3 pt-1">Invalid Key Id</div>}
|
setCabinetKeyId({
|
||||||
|
id: e.target.value.toUpperCase(),
|
||||||
|
valid: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
type="text"
|
||||||
|
maxLength={6}
|
||||||
|
/>
|
||||||
|
{!cabinetKeyId.valid && (
|
||||||
|
<div className="text-sm text-error ml-3 pt-1">
|
||||||
|
Invalid Key Id
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
@ -67,19 +106,33 @@ function CabinetCreation() {
|
|||||||
{t("noOfLockers")}
|
{t("noOfLockers")}
|
||||||
</label>
|
</label>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<input
|
<input
|
||||||
value={noOfLockers.number}
|
value={noOfLockers.number}
|
||||||
className={clsx("w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey", !noOfLockers.valid && "border-error")}
|
className={clsx(
|
||||||
onChange={(e) => setNoOfLockers({number: e.target.value, valid: true })}
|
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
|
||||||
type="number"
|
!noOfLockers.valid && "border-error"
|
||||||
/>
|
)}
|
||||||
{!noOfLockers.valid && <div className="text-sm text-error ml-3 pt-1">Invalid Number of Lockers</div>}
|
onChange={(e) =>
|
||||||
|
setNoOfLockers({ number: e.target.value, valid: true })
|
||||||
|
}
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
{!noOfLockers.valid && (
|
||||||
|
<div className="text-sm text-error ml-3 pt-1">
|
||||||
|
Invalid Number of Lockers
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button text={t("next")} onClick={(e) => {handleNext(e)}}/>
|
<Button
|
||||||
|
text={t("next")}
|
||||||
|
onClick={(e) => {
|
||||||
|
handleNext(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</FormBox>
|
</FormBox>
|
||||||
</div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,43 +1,68 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useToast } from "../hooks/useToast";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import FormBox from "../components/FormBox";
|
import FormBox from "../components/FormBox";
|
||||||
import Button from "../components/Button";
|
import Button from "../components/Button";
|
||||||
|
import { motion } from "motion/react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { AnimatePresence } from "motion/react";
|
||||||
|
|
||||||
function CabinetMaintenace() {
|
function CabinetMaintenace() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const showToast = useToast();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [operation, setOperation] = useState("");
|
const [operation, setOperation] = useState({ value: "", valid: true });
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleNext = (e) => {
|
||||||
if (operation === "") {
|
e.preventDefault();
|
||||||
showToast("Please select an operation", "error");
|
if (operation.value === "") {
|
||||||
return;
|
setOperation({ value: operation.value, valid: false });
|
||||||
}
|
}
|
||||||
navigate(operation);
|
navigate(operation.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-fit">
|
<div>
|
||||||
<FormBox title={t('cabinetMaintenance')}>
|
<FormBox title={t("cabinetMaintenance")}>
|
||||||
<div className="p-2">
|
<div className="p-2 pt-7">
|
||||||
<div className="my-5">
|
<div className="flex">
|
||||||
<label className="mr-4 text-lg text-black dark:text-white">{t('operation')}</label>
|
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[10%]">
|
||||||
<select
|
{t("operation")}
|
||||||
className="w-1/5 h-10 px-2 rounded-full dark:bg-grey dark:text-primary-dark border-2 border-grey text-grey focus:border-none focus:outline-none"
|
</label>
|
||||||
onChange={(e) => setOperation(e.target.value)}
|
<div className="w-full">
|
||||||
defaultValue={operation}
|
<select
|
||||||
>
|
className={clsx(
|
||||||
<option value="" disabled>
|
"w-1/5 h-10 px-2 rounded-full dark:bg-grey dark:text-primary-dark border-2 focus:outline-grey border-grey",
|
||||||
{t('select')}
|
!operation.valid && "border-error"
|
||||||
</option>
|
)}
|
||||||
<option value="create">{t('create')}</option>
|
onChange={(e) =>
|
||||||
</select>
|
setOperation({ value: e.target.value, valid: true })
|
||||||
|
}
|
||||||
|
defaultValue={operation.value}
|
||||||
|
value={operation.value}
|
||||||
|
>
|
||||||
|
<option value="" disabled>
|
||||||
|
{t("select")}
|
||||||
|
</option>
|
||||||
|
<option value="create">{t("create")}</option>
|
||||||
|
</select>
|
||||||
|
<AnimatePresence initial={false}>
|
||||||
|
{!operation.valid && (
|
||||||
|
<motion.div
|
||||||
|
className="w-1/5 text-sm text-error ml-3 pt-1"
|
||||||
|
initial={{ opacity: 0,scale: 0 }}
|
||||||
|
animate={{ opacity: 1,scale: 1 }}
|
||||||
|
exit={{ opacity: 0,scale: 0 }}
|
||||||
|
key="cabinetIdError"
|
||||||
|
>
|
||||||
|
Invalid Operation
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button text={t('next')} onClick={handleNext} />
|
|
||||||
</div>
|
</div>
|
||||||
|
<Button text={t("next")} onClick={(e) => handleNext(e)} />
|
||||||
</FormBox>
|
</FormBox>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user