diff --git a/day19/models/index.js b/day19/models/index.js index 62cf181..9be9726 100644 --- a/day19/models/index.js +++ b/day19/models/index.js @@ -16,9 +16,9 @@ const { DataTypes } = require("sequelize"); const config = { DB_DATABASE: "mysql", DB_USERNAME: "root", - DB_PASSWORD: "root", + DB_PASSWORD: process.env.DB_PASSWORD || "root", DB_ADAPTER: "mysql", - DB_NAME: "day_1", + DB_NAME: "day_19", DB_HOSTNAME: "localhost", DB_PORT: 3306, }; @@ -26,7 +26,7 @@ const config = { let db = {}; let sequelize = new Sequelize( - config.DB_DATABASE, + config.DB_NAME, config.DB_USERNAME, config.DB_PASSWORD, { @@ -51,8 +51,14 @@ let sequelize = new Sequelize( } ); -// sequelize.sync({ force: true }); -sequelize.sync(); +sequelize + .sync() + .then(() => { + console.log("Database & tables created!"); + }) + .catch((err) => { + console.log(err); + }); fs.readdirSync(__dirname) .filter((file) => { diff --git a/day19/package.json b/day19/package.json index 219c20e..cd71cf0 100644 --- a/day19/package.json +++ b/day19/package.json @@ -3,7 +3,8 @@ "version": "0.0.0", "private": true, "scripts": { - "start": "node ./bin/www" + "start": "node ./bin/www", + "dev": "node --watch --env-file=.env ./bin/www" }, "dependencies": { "cookie-parser": "~1.4.4", diff --git a/day20/.gitignore b/day20/.gitignore new file mode 100644 index 0000000..db0b720 --- /dev/null +++ b/day20/.gitignore @@ -0,0 +1,27 @@ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dev-dist + +*.local +release +config.php +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + \ No newline at end of file diff --git a/day20/.prettier b/day20/.prettier new file mode 100644 index 0000000..3fa6037 --- /dev/null +++ b/day20/.prettier @@ -0,0 +1,5 @@ + +{ + "tabWidth": 2, + "useTabs": false +} diff --git a/day20/README.md b/day20/README.md index b0d3525..f6fa2b7 100644 --- a/day20/README.md +++ b/day20/README.md @@ -1,22 +1,23 @@ # Day 20 Read: + - https://www.notion.so/How-to-Use-Baas-00f549dda3a84dc48b352c79222f1a3a - https://www.notion.so/Create-Manage-Projects-With-Wireframe-Tool-df67b882f0c14735a0192d69dc3ff777 - Request for Wireframe tool url from Project Manager. -1. login to Wireframe tool. Create SOW and Wireframe called . +1. login to Wireframe tool. Create SOW and Wireframe called . -2. Navigate to Wireframe side-menu click, Edit > Setting, create a project () from here according to specifications of wireframe document provided (inventory-app.pdf). +2. Navigate to Wireframe side-menu click, Edit > Setting, create a project () from here according to specifications of wireframe document provided (inventory-app.pdf). 3. Create Models. Switch to Models tab or Web/React tab (Manage Models). -4. Create Roles and set Permissions. (Web/React Tab > Manage Permissions). +4. Create Roles and set Permissions. (Web/React Tab > Manage Permissions). 5. Create React portal and marketing pages and then export React. (Web/React Tab). -6. Create Custom APIs and commit. (API tab). API code would be commited to http://23.29.118.76:3000/mkdlabs/.git +6. Create Custom APIs and commit. (API tab). API code would be commited to http://23.29.118.76:3000/mkdlabs/.git 7. Switch to Deployment Tab. Initialize deployment and create repositories. @@ -27,4 +28,3 @@ Read: 10. Clone backend repo on src/backend/custom 11. Write APIs, and test locally. - diff --git a/day20/index.html b/day20/index.html new file mode 100644 index 0000000..a5e0886 --- /dev/null +++ b/day20/index.html @@ -0,0 +1,26 @@ + + + + + + + + inventorylynx + + +
+
+ + + + diff --git a/day20/jsconfig.json b/day20/jsconfig.json new file mode 100644 index 0000000..5033f8d --- /dev/null +++ b/day20/jsconfig.json @@ -0,0 +1,17 @@ + + { + "compilerOptions": { + "jsx": "react", + "baseUrl": ".", + "paths": { + "Components/*": ["src/components/*"], + "Pages/*": ["src/pages/*"], + "Utils/*": ["src/utils/*"], + "Assets/*": ["src/assets/*"], + "Context/*": ["src/context/*"], + "Routes/*": ["src/routes/*"], + "Hooks/*": ["src/hooks/*"], + "Src/*": ["src/*"] + } + } +} diff --git a/day20/package.json b/day20/package.json new file mode 100644 index 0000000..3ad5527 --- /dev/null +++ b/day20/package.json @@ -0,0 +1,179 @@ + +{ + "name": "adminportal", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "vite", + "tw": "npx tailwindcss -i ./src/index.css -o ./src/output.css --watch", + "build": "vite build", + "commit": "git add . && git commit -m \"Update\" && git pull && git push", + "commit:script": "node git-script add,commit,pull,push message=\"Update | API SECTION RESTRUCTURE IN PROGRESS\" origin=wireframe", + "preview": "vite preview", + "generate-pwa-assets": "pwa-assets-generator --preset minimal public/mkd_logo.png" + }, + "dependencies": { + "@craftjs/core": "^0.2.0-beta.11", + "@editorjs/attaches": "^1.3.0", + "@editorjs/checklist": "^1.5.0", + "@editorjs/code": "^2.8.0", + "@editorjs/delimiter": "^1.3.0", + "@editorjs/editorjs": "^2.26.5", + "@editorjs/embed": "^2.5.3", + "@editorjs/header": "^2.7.0", + "@editorjs/image": "^2.8.1", + "@editorjs/inline-code": "^1.4.0", + "@editorjs/link": "^2.5.0", + "@editorjs/list": "^1.8.0", + "@editorjs/marker": "^1.3.0", + "@editorjs/nested-list": "^1.3.0", + "@editorjs/paragraph": "^2.9.0", + "@editorjs/personality": "^2.0.2", + "@editorjs/quote": "^2.5.0", + "@editorjs/raw": "^2.4.0", + "@editorjs/simple-image": "^1.5.1", + "@editorjs/table": "^2.2.1", + "@editorjs/underline": "^1.1.0", + "@editorjs/warning": "^1.3.0", + "@emotion/cache": "^11.11.0", + "@emotion/react": "^11.11.1", + "@emotion/serialize": "^1.1.2", + "@emotion/utils": "^1.2.1", + "@fontsource/inter": "^5.0.15", + "@fontsource/poppins": "^4.5.10", + "@fontsource/roboto-mono": "^5.0.16", + "@fortawesome/fontawesome-svg-core": "^6.4.0", + "@fortawesome/free-brands-svg-icons": "^6.4.0", + "@fortawesome/free-regular-svg-icons": "^6.4.0", + "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@fortawesome/react-fontawesome": "^0.2.0", + "@fullcalendar/core": "^5.11.3", + "@fullcalendar/daygrid": "^5.11.3", + "@fullcalendar/interaction": "^5.11.3", + "@fullcalendar/list": "^5.11.3", + "@fullcalendar/react": "^5.11.2", + "@fullcalendar/timegrid": "^5.11.3", + "@headlessui/react": "^1.7.14", + "@heroicons/react": "^2.0.18", + "@hookform/resolvers": "^3.1.0", + "@legendapp/state": "^0.23.4", + "@mantine/core": "^6.0.19", + "@mantine/hooks": "^6.0.19", + "@react-google-maps/api": "^2.19.2", + "@react-pdf-viewer/core": "^3.12.0", + "@splidejs/react-splide": "^0.7.12", + "@stripe/react-stripe-js": "^2.1.0", + "@stripe/stripe-js": "^1.52.1", + "@tailwindcss/forms": "^0.5.3", + "@tippyjs/react": "^4.2.6", + "@uppy/audio": "^1.1.1", + "@uppy/aws-s3": "^3.2.1", + "@uppy/aws-s3-multipart": "^3.4.1", + "@uppy/compressor": "^1.0.2", + "@uppy/core": "^3.7.1", + "@uppy/dashboard": "^3.4.1", + "@uppy/drag-drop": "^3.0.2", + "@uppy/drop-target": "^2.0.1", + "@uppy/dropbox": "^3.1.1", + "@uppy/facebook": "^3.1.3", + "@uppy/file-input": "^3.0.3", + "@uppy/golden-retriever": "^3.1.0", + "@uppy/google-drive": "^3.1.1", + "@uppy/image-editor": "^2.1.2", + "@uppy/instagram": "^3.1.3", + "@uppy/onedrive": "^3.1.1", + "@uppy/progress-bar": "^3.0.3", + "@uppy/react": "^3.1.2", + "@uppy/remote-sources": "^1.0.3", + "@uppy/screen-capture": "^3.1.1", + "@uppy/tus": "^3.4.0", + "@uppy/webcam": "^3.3.1", + "@uppy/xhr-upload": "^3.5.0", + "apexcharts": "^3.40.0", + "axios": "^1.5.0", + "bootstrap": "^5.2.3", + "codemirror": "^5.65.11", + "codemirror-console": "^3.0.4", + "codemirror-console-ui": "^3.0.4", + "emoji-picker-textarea": "^1.0.1", + "file-saver": "^2.0.5", + "framer-motion": "^10.16.4", + "fullcalendar": "^5.11.3", + "html-to-image": "^1.11.11", + "jodit-react": "^1.3.39", + "jszip": "^3.10.1", + "moment": "^2.29.4", + "nanoid": "^4.0.2", + "openai": "^4.24.1", + "papaparse": "^5.4.1", + "pdfjs-dist": "^3.4.120", + "pluralize": "^8.0.0", + "pretty-rating-react": "^2.2.0", + "qr-scanner": "^1.4.2", + "qrcode": "^1.5.3", + "react": "^18.2.0", + "react-addons-update": "^15.6.3", + "react-apexcharts": "^1.4.0", + "react-calendar": "^4.2.1", + "react-codemirror2": "^7.2.1", + "react-confirm-alert": "^3.0.6", + "react-contenteditable": "^3.3.7", + "react-dnd": "^10.0.2", + "react-dnd-html5-backend": "^16.0.1", + "react-dom": "^18.2.0", + "react-google-autocomplete": "^2.7.3", + "react-google-maps": "^9.4.5", + "react-hook-form": "^7.46.1", + "react-icons": "^4.11.0", + "react-infinite-scroll-component": "^6.1.0", + "react-input-emoji": "^5.4.1", + "react-loading-skeleton": "^3.3.1", + "react-modal": "^3.16.1", + "react-outside-click-handler": "^1.3.0", + "react-pdf": "^7.6.0", + "react-qr-reader": "^2.2.1", + "react-quill": "^2.0.0", + "react-ratings-declarative": "^3.4.1", + "react-router": "^6.15.0", + "react-router-dom": "^6.11.1", + "react-select": "^5.8.0", + "react-slick": "^0.29.0", + "react-spinners": "^0.13.8", + "react-timeago": "^7.2.0", + "react-toggle": "^4.1.3", + "redux": "^4.2.1", + "slick-carousel": "^1.8.1", + "swiper": "^9.3.1", + "tw-elements": "^1.0.0-beta2", + "twilio-video": "^2.27.0", + "uppy": "^3.20.0", + "use-debounce": "^9.0.4", + "xlsx": "^0.18.5", + "yup": "^1.2.0" + }, + "devDependencies": { + "@editorjs/link-autocomplete": "^0.1.0", + "@editorjs/opensea": "^1.0.2", + "@editorjs/translate-inline": "^1.0.0-rc.0", + "@types/react": "^18.2.6", + "@types/react-dom": "^18.2.4", + "@vite-pwa/assets-generator": "^0.0.8", + "@vitejs/plugin-react": "^4.0.0", + "@vitejs/plugin-react-refresh": "^1.3.6", + "autoprefixer": "^10.4.14", + "eslint": "^8.40.0", + "eslint-config-react-app": "^7.0.1", + "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^4.6.0", + "husky": "^8.0.3", + "lint-staged": "^13.2.2", + "postcss": "^8.4.23", + "prettier": "^2.8.8", + "prettier-plugin-tailwindcss": "^0.2.8", + "tailwindcss": "^3.3.2", + "vite": "^4.3.5", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-pwa": "^0.16.4" + } +} diff --git a/day20/postcss.config.js b/day20/postcss.config.js new file mode 100644 index 0000000..f231f2f --- /dev/null +++ b/day20/postcss.config.js @@ -0,0 +1,7 @@ + + module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/day20/src/App.css b/day20/src/App.css new file mode 100644 index 0000000..e69de29 diff --git a/day20/src/App.jsx b/day20/src/App.jsx new file mode 100644 index 0000000..29135d0 --- /dev/null +++ b/day20/src/App.jsx @@ -0,0 +1,34 @@ + + import React from "react"; + import {AuthProvider} from "Context/Auth"; + import {GlobalProvider} from "Context/Global"; + import Main from "./routes/Routes"; + import "@uppy/core/dist/style.css"; + import "@uppy/dashboard/dist/style.css"; + import { BrowserRouter as Router } from "react-router-dom"; + import "react-loading-skeleton/dist/skeleton.css"; + import { loadStripe } from "@stripe/stripe-js"; + import { Elements } from "@stripe/react-stripe-js"; + + + const stripePromise = loadStripe("pk_test_51Ll5ukBgOlWo0lDUrBhA2W7EX2MwUH9AR5Y3KQoujf7PTQagZAJylWP1UOFbtH4UwxoufZbInwehQppWAq53kmNC00UIKSmebO"); + + function App() { + return ( + + + + + +
+ + + + + + ); + } + + export default App; + + diff --git a/day20/src/assets/images/index.js b/day20/src/assets/images/index.js new file mode 100644 index 0000000..78772ff --- /dev/null +++ b/day20/src/assets/images/index.js @@ -0,0 +1,3 @@ + +// export { default as LoginBg } from "./login-bg.jpg"; +export { default as LoginBgNew } from "./login-new-bg.png"; diff --git a/day20/src/assets/images/login-new-bg.png b/day20/src/assets/images/login-new-bg.png new file mode 100644 index 0000000..5c7cf5b Binary files /dev/null and b/day20/src/assets/images/login-new-bg.png differ diff --git a/day20/src/assets/images/mkd_logo.png b/day20/src/assets/images/mkd_logo.png new file mode 100644 index 0000000..8945fb2 --- /dev/null +++ b/day20/src/assets/images/mkd_logo.png @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/day20/src/assets/svgs/CaretLeft.jsx b/day20/src/assets/svgs/CaretLeft.jsx new file mode 100644 index 0000000..67aad76 --- /dev/null +++ b/day20/src/assets/svgs/CaretLeft.jsx @@ -0,0 +1,24 @@ + + + import React from "react"; + +export const CaretLeft = ({ className = "" }) => { + return ( + + + + ); +}; diff --git a/day20/src/assets/svgs/CloseIcon.jsx b/day20/src/assets/svgs/CloseIcon.jsx new file mode 100644 index 0000000..7eb7697 --- /dev/null +++ b/day20/src/assets/svgs/CloseIcon.jsx @@ -0,0 +1,14 @@ + + import React from "react"; + + export const CloseIcon = ({ className = "" }) => { + return ( + + + + ); + }; + \ No newline at end of file diff --git a/day20/src/assets/svgs/DangerIcon.jsx b/day20/src/assets/svgs/DangerIcon.jsx new file mode 100644 index 0000000..70cd36d --- /dev/null +++ b/day20/src/assets/svgs/DangerIcon.jsx @@ -0,0 +1,13 @@ + + import React from 'react' + + export const DangerIcon = ( { className } ) => { + return ( + + + + + ) + } + + \ No newline at end of file diff --git a/day20/src/assets/svgs/Spinner.jsx b/day20/src/assets/svgs/Spinner.jsx new file mode 100644 index 0000000..20c7a65 --- /dev/null +++ b/day20/src/assets/svgs/Spinner.jsx @@ -0,0 +1,21 @@ + + import React, { useId } from "react"; + import MoonLoader from "react-spinners/MoonLoader"; + +const override = { + borderColor: "red", +}; + +export const Spinner = ({ size = 20, color = "#ffffff" }) => { + const id = useId(); + return ( + + ); +}; diff --git a/day20/src/assets/svgs/index.js b/day20/src/assets/svgs/index.js new file mode 100644 index 0000000..60e06e8 --- /dev/null +++ b/day20/src/assets/svgs/index.js @@ -0,0 +1,9 @@ + + import { lazy } from "react"; + + export const CloseIcon = lazy(() => import("./CloseIcon").then((module) => ({ default: module.CloseIcon }))); + export const DangerIcon = lazy(() => import("./DangerIcon").then((module) => ({ default: module.DangerIcon }))); + export const Spinner = lazy(() => import("./Spinner").then((module) => ({ default: module.Spinner }))); + export const CaretLeft = lazy(() => import("./CaretLeft").then((module) => ({ default: module.CaretLeft }))); + + \ No newline at end of file diff --git a/day20/src/components/AddButton/AddButton.jsx b/day20/src/components/AddButton/AddButton.jsx new file mode 100644 index 0000000..aa4ded3 --- /dev/null +++ b/day20/src/components/AddButton/AddButton.jsx @@ -0,0 +1,36 @@ + + + import React, { useState } from "react"; + import classes from "./AddButton.module.css"; + + const AddButton = ({ + onClick, + children = "Add New", + showPlus = true, + className, + showChildren = true, + }) => { + const [animate, setAnimate] = useState(false); + + const onClickHandle = () => { + if (onClick) { + onClick(); + } + setAnimate(true); + }; + + return ( + + ); + }; + + export default AddButton; + \ No newline at end of file diff --git a/day20/src/components/AddButton/AddButton.module.css b/day20/src/components/AddButton/AddButton.module.css new file mode 100644 index 0000000..6b0b7b6 --- /dev/null +++ b/day20/src/components/AddButton/AddButton.module.css @@ -0,0 +1,33 @@ + + .button { + position: relative; + border: none; + color: #ffffff; + text-align: center; + -webkit-transition-duration: 0.4s; /* Safari */ + transition-duration: 0.4s; + text-decoration: none; + overflow: hidden; + cursor: pointer; + } + .button:after { + content: ""; + background-color: #6752e0; + /* background: #4f46e5; */ + display: block; + position: absolute; + padding-top: 300%; + padding-left: 350%; + margin-left: -20px !important; + margin-top: -120%; + opacity: 0; + transition: all 0.8s; + } + + .button:active:after { + padding: 0; + margin: 0; + opacity: 1; + transition: 0s; + } + diff --git a/day20/src/components/AddButton/index.js b/day20/src/components/AddButton/index.js new file mode 100644 index 0000000..24c1845 --- /dev/null +++ b/day20/src/components/AddButton/index.js @@ -0,0 +1,2 @@ + + export {default as AddButton } from "./AddButton"; diff --git a/day20/src/components/AddTags/AddTags.jsx b/day20/src/components/AddTags/AddTags.jsx new file mode 100644 index 0000000..c9983eb --- /dev/null +++ b/day20/src/components/AddTags/AddTags.jsx @@ -0,0 +1,11 @@ + + import React from 'react'; + + const AddTags = ({tags, tag, setTagData}) => { + // console.log('addtag--->',{tags, tag, setTagData}) + return ( +
  • {tag}
  • + ); +}; + +export default AddTags; diff --git a/day20/src/components/AddTags/index.js b/day20/src/components/AddTags/index.js new file mode 100644 index 0000000..b3763e4 --- /dev/null +++ b/day20/src/components/AddTags/index.js @@ -0,0 +1,5 @@ + + import { lazy } from 'react' + + export const AddTagsTemplate = lazy(()=> import("./AddTagsTemplate")) + \ No newline at end of file diff --git a/day20/src/components/AdminHeader/AdminHeader.jsx b/day20/src/components/AdminHeader/AdminHeader.jsx new file mode 100644 index 0000000..89ec206 --- /dev/null +++ b/day20/src/components/AdminHeader/AdminHeader.jsx @@ -0,0 +1,180 @@ + +import React from "react"; +import { Link, NavLink } from "react-router-dom"; +import { PiUsersThreeFill } from "react-icons/pi"; +import MkdSDK from "Utils/MkdSDK"; +import { MdDashboard } from "react-icons/md"; +import { GlobalContext } from "Context/Global"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +let sdk = new MkdSDK(); + +const NAV_ITEMS = [ + { + to: "/admin/dashboard", + text: "Dashboard", + icon: , + value: "admin", + }, + + + { + to: "/admin/cms", + text: " Cms", + icon: , + value: "cms", + }, + + + { + to: "/admin/email", + text: " Emails", + icon: , + value: "email", + }, + + + { + to: "/admin/photo", + text: " Photos", + icon: , + value: "photo", + }, + + + { + to: "/admin/users", + text: " Users", + icon: , + value: "users", + }, + + + + { + to: "/admin/profile", + text: "Profile", + icon: , + value: "profile", + }, +]; + +export const AdminHeader = () => { + const { + state: { isOpen, path }, + dispatch: gobalDispatch, + } = React.useContext(GlobalContext); + const { state: authState, dispatch } = React.useContext(AuthContext); + const [openDropdown, setOpenDropdown] = React.useState(false); + const [isHovering, setIsHovering] = React.useState(false); + + // const handleMouseOver = () => { + // setIsHovering(true); + // }; + + // const handleMouseOut = () => { + // setIsHovering(false); + // }; + let toggleOpen = (open) => { + gobalDispatch({ + type: "OPEN_SIDEBAR", + payload: { isOpen: open }, + }); + }; + + React.useEffect(() => { + async function fetchData() { + try { + const result = await sdk.getProfile(); + dispatch({ + type: "UPDATE_PROFILE", + payload: result, + }); + } catch (error) { + console.log("Error", error); + tokenExpireError( + dispatch, + error.response.data.message + ? error.response.data.message + : error.message + ); + } + } + + fetchData(); + }, []); + // sidebar-holder + return ( + <> +
    +
    +
    + {isOpen && ( +
    + +

    + Baas Brand{" "} +

    + +
    + )} +
    + +
    +
    +
      + {NAV_ITEMS.map((item) => ( +
    • + +
      + {item.icon} + {isOpen && {item.text}} +
      +
      +
    • + ))} +
    +
    +
    +
    +
    + toggleOpen(!isOpen)}> + + + + +
    +
    +
    + + ); +}; + +export default AdminHeader; + diff --git a/day20/src/components/AdminHeader/index.js b/day20/src/components/AdminHeader/index.js new file mode 100644 index 0000000..8f8cfd9 --- /dev/null +++ b/day20/src/components/AdminHeader/index.js @@ -0,0 +1,5 @@ + + import { lazy } from 'react' + + export const AdminHeader = lazy(()=> import("./AdminHeader")) + \ No newline at end of file diff --git a/day20/src/components/AdminWrapper/AdminWrapper.jsx b/day20/src/components/AdminWrapper/AdminWrapper.jsx new file mode 100644 index 0000000..ae4362d --- /dev/null +++ b/day20/src/components/AdminWrapper/AdminWrapper.jsx @@ -0,0 +1,39 @@ + + + +import React, { Suspense, memo } from "react"; + +import {AdminHeader} from "Components/AdminHeader"; +import {TopHeader} from "Components/TopHeader"; +import { Spinner } from "Assets/svgs"; +const navigation = [] +const AdminWrapper = ({ children }) => { + return ( +
    +
    + +
    + + + +
    + } + > +
    + {children} +
    + +
    +
    + + ); +}; + +export default memo(AdminWrapper); + diff --git a/day20/src/components/AdminWrapper/index.js b/day20/src/components/AdminWrapper/index.js new file mode 100644 index 0000000..c98297f --- /dev/null +++ b/day20/src/components/AdminWrapper/index.js @@ -0,0 +1,4 @@ + + import {lazy} from 'react' + export const AdminWrapper = lazy( ()=> import('./AdminWrapper')) + \ No newline at end of file diff --git a/day20/src/components/BackButton/BackButton.jsx b/day20/src/components/BackButton/BackButton.jsx new file mode 100644 index 0000000..f503590 --- /dev/null +++ b/day20/src/components/BackButton/BackButton.jsx @@ -0,0 +1,19 @@ + + import React from "react"; +import { CaretLeft } from "Assets/svgs"; + +import { NavLink } from "react-router-dom"; + +const BackButton = ({ text = "back", link }) => { + return ( +
    + + + {text} + +
    + ); +}; + +export default BackButton; + \ No newline at end of file diff --git a/day20/src/components/BackButton/index.js b/day20/src/components/BackButton/index.js new file mode 100644 index 0000000..ed5e016 --- /dev/null +++ b/day20/src/components/BackButton/index.js @@ -0,0 +1,5 @@ + + import { lazy } from "react"; + +export const BackButton = lazy(()=> import("./BackButton")) + diff --git a/day20/src/components/Calendar/Calendar.jsx b/day20/src/components/Calendar/Calendar.jsx new file mode 100644 index 0000000..b5aad20 --- /dev/null +++ b/day20/src/components/Calendar/Calendar.jsx @@ -0,0 +1,160 @@ + + import { useState } from "react"; +// import { formatDate } from '@fullcalendar/core' +import '@fullcalendar/react/dist/vdom'; +import FullCalendar, { formatDate } from "@fullcalendar/react"; +import dayGridPlugin from "@fullcalendar/daygrid"; +import timeGridPlugin from "@fullcalendar/timegrid"; +import interactionPlugin from "@fullcalendar/interaction"; +import listPlugin from "@fullcalendar/list"; +import { Modal } from 'Components/Modal/Modal'; +import ModalPrompt from 'Components/Modal/ModalPrompt'; +import "./calendar.css" + + +const Calendar = ({defaulEvents, setEvents, isCalendarFullWidth = true}) => { + const [currentEvents, setCurrentEvents] = useState([]); + const [showAddEventmodal, setShowAddEventmodal] = useState(false); + const [showDeleteEventmodal, setShowDeleteEventmodal] = useState(false); + const [newEventName, setNewEventName] = useState(''); + const [currentSelectedDate, setCurrentSelectedDate] = useState(); + const [currentSelectedEvent, setCurrentSelectedEvent] = useState(); + + const handleDateClick = (selected) => { + setShowAddEventmodal(true) + setCurrentSelectedDate(selected) + // console.log(selected) + }; + + + const handleEventsSet = (events) => { + setCurrentEvents(events) + if(setEvents){ + setEvents(events) + } + }; + + const handleAddModalSubmit = () =>{ + const calendarApi = currentSelectedDate.view.calendar; + calendarApi.unselect(); + + if (newEventName) { + setShowAddEventmodal(false) + calendarApi.addEvent({ + id: `${currentSelectedDate.dateStr}-${newEventName}`, + title: newEventName, + start: currentSelectedDate.startStr, + end: currentSelectedDate.endStr, + allDay: currentSelectedDate.allDay, + }); + } + setNewEventName('') + + } + + const handleEventClick = (selected) => { + // console.log(selected) + setCurrentSelectedEvent(selected) + setShowDeleteEventmodal(true) + }; + + const handleDeletEventClick = () => { + currentSelectedEvent.event.remove() + setShowDeleteEventmodal(false) + }; + + + + return ( + <> + +
    +
    + {/* CALENDAR SIDEBAR */} +
    +
    Events
    +
      + {currentEvents.map((event) => ( +
    • +
      +
      + {event.title} +
      +

      + {formatDate(event.start, { + year: "numeric", + month: "short", + day: "numeric", + })} +

      +
      +
    • + ))} +
    +
    + + {/* CALENDAR */} +
    + handleEventsSet(events)} + initialEvents={defaulEvents} + /> +
    +
    +
    + + setShowAddEventmodal(false)} + modalHeader={true} + classes={'w-1/2 '} + > +
    + setNewEventName(e.target.value)} /> + +
    +
    + + { + showDeleteEventmodal && + {setShowDeleteEventmodal(false)}} + message="Are you sure you want to delete this event?" + title = "Delete Event" + loading = {false} + actionHandler={handleDeletEventClick} + /> + } + + ); +}; + +export default Calendar; + \ No newline at end of file diff --git a/day20/src/components/Calendar/calendar.css b/day20/src/components/Calendar/calendar.css new file mode 100644 index 0000000..1b314a6 --- /dev/null +++ b/day20/src/components/Calendar/calendar.css @@ -0,0 +1,5 @@ + + .calendar-full-width .fc.fc-media-screen.fc-direction-ltr{ + width:100%; + } + \ No newline at end of file diff --git a/day20/src/components/Calendar/index.js b/day20/src/components/Calendar/index.js new file mode 100644 index 0000000..5a39545 --- /dev/null +++ b/day20/src/components/Calendar/index.js @@ -0,0 +1,3 @@ + + export {default as Calendar} from "./Calendar" + \ No newline at end of file diff --git a/day20/src/components/CameraToUpload/CameraToUpload.jsx b/day20/src/components/CameraToUpload/CameraToUpload.jsx new file mode 100644 index 0000000..09661d7 --- /dev/null +++ b/day20/src/components/CameraToUpload/CameraToUpload.jsx @@ -0,0 +1,323 @@ + +import React, { memo, useEffect, useRef, useState } from "react"; +import MkdSDK from "Utils/MkdSDK"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import { GlobalContext, showToast } from "Context/Global"; +import { Spinner } from "Assets/svgs"; + +const sdk = new MkdSDK(); + +const CameraToUpload = ({ onSave = undefined, uploadSuccess = undefined }) => { + const { dispatch, state } = React.useContext(AuthContext); + const { dispatch: globalDispatch, state: globalState } = + React.useContext(GlobalContext); + + const photoTrayRef = useRef(null); + const videoRef = useRef(null); + const canvasRef = useRef(null); + const [profilePictures, setProfileProfilePictures] = useState(); + const [submitLoading, setSubmitLoading] = useState(false); + const [showCamera, setShowCamera] = useState(false); + const [useFrontCam, setUseFrontCam] = useState(true); + const [photos, setPhotos] = useState([]); + + const constraints = { + video: { + facingMode: { exact: useFrontCam ? "user" : "environment" }, + advanced: [{ zoom: 1 }], + }, + }; + + const handleCapture = () => { + const video = videoRef.current; + const canvas = canvasRef.current; + const aspectRatio = video.videoWidth / video.videoHeight; + + // Calculate the aspect ratio of the video + let canvasWidth = video.offsetWidth; + let canvasHeight = canvasWidth / aspectRatio; + + if (canvasHeight > video.offsetHeight) { + canvasHeight = video.offsetHeight; + canvasWidth = canvasHeight * aspectRatio; + } + + // Set the canvas size based on the aspect ratio of the video + canvas.width = canvasWidth; + canvas.height = canvasHeight; + + // Draw the current video frame on the canvas + const context = canvas.getContext("2d"); + context.drawImage(video, 0, 0, canvasWidth, canvasHeight); + + // Get the data URL of the canvas + // const dataUrl = canvasRef.current.toDataURL("image/png"); + // setProfileProfilePictures(dataUrl); + // fileUpload(dataUrl); + // stopCapture(); + // Draw the video frame on the canvas + // const context = canvas.getContext("2d"); + // context.drawImage(video, 0, 0, canvas.width, canvas.height); + + // Convert the canvas data to a blob + canvas.toBlob((blob) => { + // Upload the blob to the server + // const formData = new FormData(); + setPhotos((prev) => [...prev, blob]); + if (!photos.length) { + photoTrayRef.current.style.maxHeight = `150px`; + } + // formData.append("file", blob); + // fileUpload(blob); + // console.log(formData); + // stopCapture(); + // fetch('/upload', { method: 'POST', body: formData }); + }); + }; + + const handleStream = (stream) => { + const video = videoRef.current; + video.style.display = "block"; + video.srcObject = stream; + video.play(); + }; + async function uploadFunction(formData) { + try { + let uploadResult = await sdk.uploadImage(formData); + + if (!uploadResult.error) { + return uploadResult?.url; // Return the response data from the server + } + } catch (error) { + throw new Error(error.message); + } + } + + const handleUpload = async () => { + if (!uploadSuccess) { + throw new Error("uploadSuccess is not a function"); + } + setSubmitLoading(true); + try { + if (photos && photos.length) { + const uploadPromises = photos.map(async (item) => { + let formData = new FormData(); + formData.append("file", item); + + // Perform the upload operation for 'item' and return the result + return uploadFunction(formData); // Replace 'uploadFunction' with your actual upload logic + }); + const uploadResults = await Promise.all(uploadPromises); + // Process uploadResults if needed + console.log(uploadResults); + showToast(globalDispatch, "Upload Successful", 5000, "success"); + uploadSuccess(uploadResults); + } + setSubmitLoading(false); + } catch (error) { + setSubmitLoading(false); + tokenExpireError( + dispatch, + error?.response?.data?.message + ? error?.response?.data?.message + : error?.message + ); + showToast( + globalDispatch, + error?.response?.data?.message + ? error?.response?.data?.message + : error?.message, + 5000, + "error" + ); + console.log(error.message); + // Handle errors + } + }; + + const handleSave = () => { + if (onSave) { + onSave(photos); + } + }; + + const handleError = (error) => { + console.error("Error accessing camera:", error); + }; + + const startCapture = async () => { + try { + // Request access to the camera + const stream = await navigator.mediaDevices.getUserMedia(constraints); + + setShowCamera(true); + // Display the camera feed on the video element + handleStream(stream); + } catch (error) { + handleError(error); + } + }; + + const stopCapture = () => { + // Stop all tracks in the stream + if (videoRef.current.srcObject) { + videoRef.current.srcObject.getTracks().forEach((track) => track.stop()); + } + setPhotos(() => []); + // // Clear the stream from state + // setStream(null); + setShowCamera(false); + // Release the camera resources + videoRef.current.srcObject = null; + }; + const removeItem = (index) => { + const tempPhotos = [...photos]; + tempPhotos.splice(index, 1); + setPhotos(() => [...tempPhotos]); + }; + + useEffect(() => { + if (photos.length === 0) { + photoTrayRef.current.style.maxHeight = null; + } + // console.log(photos); + }, [photos.length]); + useEffect(() => { + startCapture(true); + }, [useFrontCam]); + + return ( + <> +
    +
    +
    + Take picture now + + Switch to Mobile to take picture + +
    +
    startCapture()} + className={`flex h-full w-[5.625rem] min-w-[5.625rem] items-center justify-center rounded-[0rem_1.25rem] bg-blue-600 md:hidden`} + > + camera +
    +
    +

    +
    + +
    +
    +
    +
    + + + ); +}; + +export default memo(CameraToUpload); diff --git a/day20/src/components/CameraToUpload/index.js b/day20/src/components/CameraToUpload/index.js new file mode 100644 index 0000000..d833601 --- /dev/null +++ b/day20/src/components/CameraToUpload/index.js @@ -0,0 +1,4 @@ + +import {lazy} from "react"; + +export const CameraToUpload = lazy(()=> import("./CameraToUpload")) diff --git a/day20/src/components/Chat/Chat.jsx b/day20/src/components/Chat/Chat.jsx new file mode 100644 index 0000000..e332c74 --- /dev/null +++ b/day20/src/components/Chat/Chat.jsx @@ -0,0 +1,509 @@ + + import React from "react"; + import MkdSDK from "Utils/MkdSDK"; + + import moment from "moment"; + import { ImagePreviewModal } from "Components/ImagePreviewModal"; + import { CreateNewRoomModal } from "Components/CreateNewRoomModal"; + import { + UserCircleIcon, + PaperAirplaneIcon, + ArrowLeftIcon, + PaperClipIcon, + } from "@heroicons/react/20/solid"; + import { FaceSmileIcon } from "@heroicons/react/24/outline"; + import { AuthContext, tokenExpireError } from "Context/Auth"; + import InputEmoji from "react-input-emoji"; + import { renderName } from "Components/CreateNewRoomModal"; + + const Chat = ({roles=[]}) => { + const { state } = React.useContext(AuthContext); + const { dispatch } = React.useContext(AuthContext); + const [rooms, setRooms] = React.useState([]); + const [messages, setMessages] = React.useState([]); + const [chatId, setChatId] = React.useState(); + const otherUserId = React.useRef(); + const currentRooms = React.useRef(); + const inputRef = React.useRef(null); + const [message, setMessage] = React.useState(""); + const [roomId, setRoomId] = React.useState(); + const [file, setFile] = React.useState(null); + const [previewModal, showPreviewModal] = React.useState(false); + const [screenSize, setScreenSize] = React.useState(window.innerWidth); + const [showContacts, setShowContacts] = React.useState(true); + const [createRoom, setCreateRoom] = React.useState(false); + const [filteredRooms, setFilteredRooms] = React.useState([]); + + function setDimension(e) { + if (e.currentTarget.innerWidth > 1024) { + setShowContacts(true); + } + setScreenSize(e.currentTarget.innerWidth); + } + + let sdk = new MkdSDK(); + + const handleClick = () => { + inputRef.current.click(); + }; + + const cancelFileUpload = () => { + showPreviewModal(false); + setFile(null); + inputRef.current.value = ""; + }; + + const formatDate = (time) => { + let currentTime = moment(new Date()); + let messageDate = moment(time); + if (currentTime.diff(messageDate, "days") > 1) { + return moment(messageDate).format("Do MMMM"); + } else { + return moment(messageDate).format("hh:mm A"); + } + }; + + const handleFileUpload = async (e) => { + e.preventDefault(); + const formData = new FormData(); + for (let i = 0; i < file.length; i++) { + formData.append("file", file[i]); + } + try { + const upload = await sdk.uploadImage(formData); + await sendImageAsMessage(upload); + } catch (err) { + console.log(err); + } + }; + + async function handleKeyDown(event) { + if (event.key === "Enter" && !event.shiftKey) { + event.preventDefault(); + sendMessage(); + } + } + + async function getRooms(filter) { + try { + if (filter) { + return setFilteredRooms( + rooms.filter((user) => + `${renderName(user).toLowerCase()}`.includes(filter.toLowerCase()) + ) + ); + } + + const chats = await sdk.getMyRoom(); + if (chats && chats.list && chats.list[0]) { + setRooms(chats.list); + setFilteredRooms(chats.list); + currentRooms.current = chats.list; + } + } catch (err) { + console.log("Error:", err); + } + } + + async function createNewRoom(otherUser) { + try { + const createdRoom = await sdk.createRoom({ + user_id: state.user, + other_user_id: otherUser.id, + }); + let newRoom = { + chat_id: createdRoom.chat_id, + create_at: new Date(), + email: otherUser.email, + first_name: otherUser.first_name, + id: createdRoom.room_id, + last_name: otherUser.last_name, + other_user_id: otherUser.id, + other_user_update_at: new Date(), + photo: otherUser.photo, + unread: 0, + update_at: new Date(), + user_id: state.user, + user_update_at: new Date(), + }; + + const updatedRoomList = [newRoom, ...rooms]; + setRooms(updatedRoomList); + setFilteredRooms(updatedRoomList); + currentRooms.current = updatedRoomList; + setCreateRoom(false); + + if (document.getElementById(`user-${otherUser.id}`)) { + document.getElementById(`user-${otherUser.id}`).click(); + } else { + setTimeout(() => { + document.querySelector(".container-chat").firstChild.click(); + }, 200); + } + } catch (err) { + setCreateRoom(false); + document.getElementById(`user-${otherUser.id}`).click(); + } + } + + async function getChats(room_id, chat_id) { + try { + setRoomId(room_id); + setChatId(chat_id); + let date = new Date().toISOString().split("T")[0]; + const messages = await sdk.getChats(room_id, chat_id, date); + if (messages && messages.model) { + setMessages(messages.model.reverse()); + } + } catch (err) { + console.log("Error:", err); + } + } + + async function sendMessage() { + try { + let date = new Date().toISOString().split("T")[0]; + await sdk.postMessage({ + room_id: roomId, + chat_id: chatId, + user_id: state.user, + message, + date, + }); + let newMessageObj = { + message: message, + user_id: state.user, + is_image: false, + timeStamp: new Date(), + }; + const updatedMessages = [...messages, newMessageObj]; + setMessages(updatedMessages); + setMessage(""); + } catch (err) { + console.log("Error:", err); + } + } + + async function sendImageAsMessage(upload) { + try { + let date = new Date().toISOString().split("T")[0]; + await sdk.postMessage({ + room_id: roomId, + chat_id: chatId, + user_id: state.user, + message: upload.url, + date, + is_image: true, + }); + let newMessageObj = { + message: upload.url, + user_id: state.user, + is_image: true, + timeStamp: new Date(), + }; + const updatedMessages = [...messages, newMessageObj]; + setMessages(updatedMessages); + showPreviewModal(false); + setFile(null); + inputRef.current.value = ""; + setMessage(""); + } catch (err) { + console.log("Error:", err); + } + } + + async function startPooling() { + try { + const pool = await sdk.startPooling(state.user); + if (pool.message) { + let newMessageObj = { + message: pool.message, + user_id: pool.user_id, + is_image: false, + timeStamp: new Date(), + }; + if (pool.user_id === otherUserId.current) { + setMessages((prevMessages) => [...prevMessages, newMessageObj]); + setTimeout(async () => { + await startPooling(); + }, 2000); + } else { + setTimeout(async () => { + await startPooling(); + }, 1000); + } + } else { + setTimeout(async () => { + startPooling(); + }, 1000); + } + } catch (err) { + console.log(err.message); + tokenExpireError(dispatch, err.message); + if (err.message === "TOKEN_EXPIRED") + window.location.replace(`/${state.role}/login/`); + else + setTimeout(async () => { + startPooling(); + }, 500); + } + } + + React.useEffect(() => { + (async function () { + await getRooms(); + await startPooling(); + })(); + }, []); + + React.useEffect(() => { + window.addEventListener("resize", setDimension); + + return () => { + window.removeEventListener("resize", setDimension); + }; + }, [screenSize]); + + return ( +
    +
    +
    +
    +

    Chat

    +
    +
    + {showContacts && ( + <> +
    + +
    +
    +
    + getRooms(e.target.value)} + /> +
    + +
    + {filteredRooms && + filteredRooms.map((room, idx) => ( +
    { + getChats(room.id, room.chat_id); + otherUserId.current = room.other_user_id; + otherUserId.currentRoom = room; + if (screenSize < 1024) { + setShowContacts(false); + } + }} + > +
    +
    + {room.photo ? ( + user-photo + ) : ( + + )} +
    +
    +
    +
    + + {renderName(room)} + +
    +
    +
    +
    + + {formatDate(room.update_at)} + +
    + {room.unread > 0 && ( +
    + + {room.unread} + +
    + )} +
    +
    + ))} +
    +
    + + )} + + {screenSize > 1023 || (screenSize < 1024 && !showContacts) ? ( +
    + {otherUserId?.current ? ( +
    +
    +

    + setShowContacts(true)} + > + + + Chatting with{" "} + {`${renderName( + otherUserId.currentRoom + )}`} +

    +
    + + {messages && ( +
    + {messages.map((message, idx) => ( +
    + {message?.user_id !== state.user && ( +
    +
    + +
    +
    + )} +
    +
    + {message.is_image ? ( + + ) : ( +

    + {message?.message} +

    + )} +
    +
    + + {moment(message.timestamp).format("hh:mm A")} + +
    +
    +
    + ))} +
    + )} + +
    +
    + {/*
    + + + +
    */} + +
    + {/* */} + {}} + placeholder="Type a message" + /> +
    +
    +
    + + { + setFile(e.target.files); + showPreviewModal(true); + }} + /> + + +
    +
    + +
    +
    +
    +
    +
    + ) : ( +
    + Select a Chat to view +
    + )} +
    + ) : null} +
    +
    +
    + {previewModal && file ? ( + + ) : null} + {createRoom && ( + + )} +
    + ); + }; + + export default Chat; + + diff --git a/day20/src/components/Chat/index.js b/day20/src/components/Chat/index.js new file mode 100644 index 0000000..960d765 --- /dev/null +++ b/day20/src/components/Chat/index.js @@ -0,0 +1,5 @@ + + import { lazy } from 'react' + + export const Chat = lazy(()=> import("./Chat")) + \ No newline at end of file diff --git a/day20/src/components/ChatBot/Chat.jsx b/day20/src/components/ChatBot/Chat.jsx new file mode 100644 index 0000000..942a4d1 --- /dev/null +++ b/day20/src/components/ChatBot/Chat.jsx @@ -0,0 +1,233 @@ +import { useContext, useEffect, useRef, useState } from "react"; +import { FiSend } from "react-icons/fi"; +import { BsPlusLg } from "react-icons/bs"; +import { RxHamburgerMenu } from "react-icons/rx"; + +import Message from "./Message"; +import { homePage } from "./ChatUtils"; + +import MkdSDK from "Utils/MkdSDK"; +import { GlobalContext } from "Context/Global"; +import { AuthContext, tokenExpireError } from "Context/Auth"; + +const Chat = (props) => { + const { toggleComponentVisibility, index, currentRoom } = props; + + const { state, dispatch } = useContext(GlobalContext); + const { dispatch: authDispatch } = useContext(AuthContext); + const [isLoading, setIsLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const [showEmptyChat, setShowEmptyChat] = useState(true); + const [conversation, setConversation] = useState([]); + const [message, setMessage] = useState(""); + const [generating, setGenerating] = useState(false); + const bottomOfChatRef = useRef(null); + + let sdk = new MkdSDK(); + + useEffect(() => { + if (bottomOfChatRef.current) { + bottomOfChatRef.current.scrollIntoView({ behavior: "smooth" }); + } + }, [conversation]); + + const sendMessage = async (e) => { + // Don't send empty messages + if (message.length < 1) { + setErrorMessage("Please enter a message."); + return; + } else { + setErrorMessage(""); + dispatch({ + type: "SETROOM", + payload: { position: props.index, value: message }, + }); + } + + setIsLoading(true); + + // Add the message to the conversation + setConversation([ + ...conversation, + { content: message, role: "user", content2: null, role2: "system" }, + ]); + + // Clear the message & remove empty chat + setMessage(""); + setShowEmptyChat(false); + + try { + setGenerating(true); + const response = await sdk.chatGPT(message); + + if (response.ok) { + const data = await response.json(); + setGenerating(false); + + // Add the message to the conversation + setConversation([ + ...conversation, + { + content: message, + role: "user", + content2: data.Answer, + role2: "system", + }, + ]); + } else { + console.error(response); + setGenerating(false); + + setErrorMessage(response.statusText); + } + + setIsLoading(false); + } catch (error) { + console.error(error); + setGenerating(false); + setErrorMessage(error.message); + tokenExpireError( + authDispatch, + error?.response?.data?.messsage + ? error?.response?.data?.messsage + : error?.message + ); + setIsLoading(false); + } + }; + + const handleKeypress = (e) => { + // It's triggers by pressing the enter key + if (e.keyCode == 13 && !e.shiftKey) { + sendMessage(e); + e.preventDefault(); + } + }; + + return ( +
    +
    + +

    New chat

    + +
    +
    +
    +
    +
    + {!showEmptyChat && conversation.length > 0 ? ( +
    + +
    +
    +
    + ) : null} + {showEmptyChat ? ( +
    +

    + ChatGPT +

    +
    + {homePage.map((item, index) => ( +
    +
    + + {item.icon} + +

    + {item.title} +

    +
    +
    + {item.details.map((details, index) => ( +
    + {details} +
    + ))} +
    +
    + ))} +
    +
    + ) : null} +
    +
    +
    +
    +
    +
    +
    +
    + + + ) + + case "image": + return ( + <> + preview + + + ) + + case "number": + return ( + setContentValue(e.target.value)} + defaultValue={contentValue} + /> + ) + + case "team-list": + return ( + + ) + + case "image-list": + return ( + + ) + + case "captioned-image-list": + return ( + + ) + + case "kvp": + return ( + + ) + + default: + break; + } +} + +export default DynamicContentType; + +const ImageList = ({contentValue, setContentValue}) => { + let itemsObj = [ + {key: '', value_type: 'image', value: null} + ]; + if (!empty(contentValue)) { + itemsObj = JSON.parse(contentValue); + } + const [items, setItems] = React.useState(itemsObj); + + + const handleImageChange = async (e) => { + const listKey = e.target.getAttribute('listkey'); + const formData = new FormData(); + formData.append('file', e.target.files[0]); + try { + const result = await sdk.uploadImage(formData); + setItems(oldItems => { + let updatedItems = oldItems.map((item, index) => { + if (index == listKey) { + item.value = result.url; + return item; + } + return item; + }); + return updatedItems; + }) + setContentValue(JSON.stringify(items)) + }catch(err) { + console.error(err); + } + } + + const handleKeyChange = (e) => { + const listKey = e.target.getAttribute('listkey'); + setItems(oldItems => { + let updatedItems = oldItems.map((item, index) => { + if (index == listKey) { + item.key = e.target.value; + return item; + } + return item; + }); + return updatedItems; + }) + setContentValue(JSON.stringify(items)) + + } + + return ( +
    + {items.map( (item, index) =>
    + preview +
    + + +
    + +
    ) + } + +
    + + ) +} + +const CaptionedImageList = ({setContentValue, contentValue}) => { + let itemsObj = [ + {key: '', value_type: 'image', value: null, caption: ''} + ] + + if (!empty(contentValue)) { + itemsObj = JSON.parse(contentValue); + } + const [items, setItems] = React.useState(itemsObj); + + const handleImageChange = async (e) => { + const listKey = e.target.getAttribute('listkey'); + const formData = new FormData(); + formData.append('file', e.target.files[0]); + try { + const result = await sdk.uploadImage(formData); + setItems(oldItems => { + let updatedItems = oldItems.map((item, index) => { + if (index == listKey) { + item.value = result.url; + return item; + } + return item; + }); + return updatedItems; + }) + setContentValue(JSON.stringify(items)) + }catch(err) { + console.error(err); + } + } + + const handleKeyChange = (e) => { + const listKey = e.target.getAttribute('listkey'); + setItems(oldItems => { + let updatedItems = oldItems.map((item, index) => { + if (index == listKey) { + item.key = e.target.value; + return item; + } + return item; + }); + return updatedItems; + }) + setContentValue(JSON.stringify(items)) + } + + const handleCaptionChange = (e) => { + const listKey = e.target.getAttribute('listkey'); + setItems(oldItems => { + let updatedItems = oldItems.map((item, index) => { + if (index == listKey) { + item.caption = e.target.value; + return item; + } + return item; + }); + return updatedItems; + }) + setContentValue(JSON.stringify(items)) + } + + return ( +
    + {items.map( (item, index) =>
    + preview +
    + + +
    + + +
    ) + } + +
    + + ) +} +const TeamList = ({setContentValue, contentValue}) => { + let itemsObj = [ + {name: '', image: null, title: ''} + ] + + if (!empty(contentValue)) { + itemsObj = JSON.parse(contentValue); + } + const [items, setItems] = React.useState(itemsObj); + + const handleImageChange = async (e) => { + const listKey = e.target.getAttribute('listkey'); + const formData = new FormData(); + formData.append('file', e.target.files[0]); + try { + const result = await sdk.uploadImage(formData); + setItems(oldItems => { + let updatedItems = oldItems.map((item, index) => { + if (index == listKey) { + item.image = result.url; + return item; + } + return item; + }); + return updatedItems; + }) + setContentValue(JSON.stringify(items)) + }catch(err) { + console.error(err); + } + } + + const handleNameChange = (e) => { + const listKey = e.target.getAttribute('listkey'); + setItems(oldItems => { + let updatedItems = oldItems.map((item, index) => { + if (index == listKey) { + item.name = e.target.value; + return item; + } + return item; + }); + return updatedItems; + }) + setContentValue(JSON.stringify(items)) + } + + const handleTitleChange = (e) => { + const listKey = e.target.getAttribute('listkey'); + setItems(oldItems => { + let updatedItems = oldItems.map((item, index) => { + if (index == listKey) { + item.title = e.target.value; + return item; + } + return item; + }); + return updatedItems; + }) + setContentValue(JSON.stringify(items)) + } + + return ( +
    + {items.map( (item, index) =>
    + + {/*
    */} + + + preview + + {/*
    */} + + +
    ) + } + +
    + + ) +} +const KeyValuePair = ({setContentValue, contentValue}) => { + let itemsObj = [ + {key: '', value_type: 'text', value: ''} + ] + + if (!empty(contentValue)) { + itemsObj = JSON.parse(contentValue); + } + + const [items, setItems] = React.useState(itemsObj); + const valueTypeMap = [ + { + key: "text", + value: "Text" + }, + { + key: "number", + value: "Number" + }, + { + key: "json", + value: "JSON Object" + }, + { + key: "url", + value: "URL" + } + ] + + + const handleKeyChange = (e) => { + const listKey = e.target.getAttribute('listkey'); + setItems(oldItems => { + let updatedItems = oldItems.map((item, index) => { + if (index == listKey) { + item.key = e.target.value; + return item; + } + return item; + }); + return updatedItems; + }) + setContentValue(JSON.stringify(items)) + } + + const handleValueChange = (e) => { + const listKey = e.target.getAttribute('listkey'); + setItems(oldItems => { + let updatedItems = oldItems.map((item, index) => { + if (index == listKey) { + item.value = e.target.value; + return item; + } + return item; + }); + return updatedItems; + }) + setContentValue(JSON.stringify(items)) + } + + const handleValueTypeChange = (e) => { + const listKey = e.target.getAttribute('listkey'); + setItems(oldItems => { + let updatedItems = oldItems.map((item, index) => { + if (index == listKey) { + item.value_type = e.target.value; + return item; + } + return item; + }); + return updatedItems; + }) + setContentValue(JSON.stringify(items)) + } + + return ( +
    + {items.map( (item, index) =>
    + + + + + +
    ) + } + +
    + + ) +} + + diff --git a/day20/src/components/DynamicContentType/index.js b/day20/src/components/DynamicContentType/index.js new file mode 100644 index 0000000..7ebb2ae --- /dev/null +++ b/day20/src/components/DynamicContentType/index.js @@ -0,0 +1,5 @@ + + import { lazy } from 'react' + + export const DynamicContentType = lazy(()=> import("./DynamicContentType")) + \ No newline at end of file diff --git a/day20/src/components/Editor/Editor.jsx b/day20/src/components/Editor/Editor.jsx new file mode 100644 index 0000000..e0627a2 --- /dev/null +++ b/day20/src/components/Editor/Editor.jsx @@ -0,0 +1,47 @@ + + import React, { useState } from "react"; + import ReactQuill from "react-quill"; + import EditorToolbar, { modules, formats } from "./EditorToolbars"; + const Editor = ({ + setValue, + errors, + name, + placeholder = "Write something awesome...", + initialContent = "", + }) => { + const [content, setContent] = useState(initialContent); + const editorStyle = { + // maxheight: '500px', + // minheight: '500px', + height: "500px", + // overFlow: 'auto', + + // set the height to 500 pixels + }; + const onSetContent = (content) => { + setContent(content); + setValue(name, content); + }; + + return ( + <> + + onSetContent(content)} + placeholder={placeholder} + modules={modules} + formats={formats} + style={editorStyle} + /> + {errors && errors?.content && ( +

    + {errors?.content?.message} +

    + )} + + ); + }; + + export default Editor; diff --git a/day20/src/components/Editor/EditorToolbars.jsx b/day20/src/components/Editor/EditorToolbars.jsx new file mode 100644 index 0000000..b2b5b69 --- /dev/null +++ b/day20/src/components/Editor/EditorToolbars.jsx @@ -0,0 +1,160 @@ + +import React from "react"; +import { Quill } from "react-quill"; + +// Custom Undo button icon component for Quill editor. You can import it directly +// from 'quill/assets/icons/undo.svg' but I found that a number of loaders do not +// handle them correctly +const CustomUndo = () => ( + + + + +); + +// Redo button icon component for Quill editor +const CustomRedo = () => ( + + + + +); + +// Undo and redo functions for Custom Toolbar +function undoChange () { + this.quill.history.undo(); +} +function redoChange () { + this.quill.history.redo(); +} + +// Add sizes to whitelist and register them +const Size = Quill.import( "formats/size" ); +Size.whitelist = [ "extra-small", "small", "medium", "large" ]; +Quill.register( Size, true ); + +// Add fonts to whitelist and register them +const Font = Quill.import( "formats/font" ); +Font.whitelist = [ + "arial", + "comic-sans", + "courier-new", + "georgia", + "helvetica", + "lucida" +]; +Quill.register( Font, true ); + +// Modules object for setting up the Quill editor +export const modules = { + toolbar: { + container: "#toolbar", + handlers: { + undo: undoChange, + redo: redoChange + } + }, + history: { + delay: 500, + maxStack: 100, + userOnly: true + } +}; + +// Formats objects for setting up the Quill editor +export const formats = [ + "header", + "font", + "size", + "bold", + "italic", + "underline", + "align", + "strike", + "script", + "blockquote", + "background", + "list", + "bullet", + "indent", + "link", + "image", + "color", + "code-block" +]; + +// Quill Toolbar component +const QuillToolbar = () => ( +
    + + + + + + + + )} + {actions?.view && ( + + )} + + {actions?.delete && ( + + )} +
    + ); + } else { + return null; + } + } + if (cell?.mappingExist) { + return ( +
    +
    {cell?.header}:
    +
    + {cell?.mappings[row[cell?.accessor]]} +
    +
    + ); + } + return ( +
    +
    {cell?.header}:
    +
    + {row[cell?.accessor]} +
    +
    + ); + })} +
    + + ); +}; + +export default MkdGridCard; diff --git a/day20/src/components/MkdGridView/MkdGridCards.jsx b/day20/src/components/MkdGridView/MkdGridCards.jsx new file mode 100644 index 0000000..0b32829 --- /dev/null +++ b/day20/src/components/MkdGridView/MkdGridCards.jsx @@ -0,0 +1,87 @@ +import React from "react"; +import ModalPrompt from "Components/Modal/ModalPrompt"; +import { useNavigate } from "react-router-dom"; +import { capitalize } from "Utils/utils"; +import { Spinner } from "Assets/svgs"; +import { colors } from "Utils/config"; +import MkdGridCard from "./MkdGridCard"; + +const MkdGridCards = ({ + table, + onSort, + getData, + loading, + columns, + actions, + tableRole, + deleteItem, + deleteLoading, + actionId = "id", + showDeleteModal, + currentTableData, + setShowDeleteModal, +}) => { + const [deleteId, setIdToDelete] = React.useState(null); + + const navigate = useNavigate(); + + const setDeleteId = async (id) => { + setShowDeleteModal(true); + setIdToDelete(id); + }; + + return ( + <> +
    + {loading ? ( +
    + +
    + ) : ( +
    +
    + {currentTableData.map((row, i) => { + return ( + + ); + })} +
    +
    + )} +
    + + {showDeleteModal ? ( + { + deleteItem(deleteId); + }} + closeModalFunction={() => { + setIdToDelete(null); + setShowDeleteModal(false); + }} + title={`Delete ${capitalize(table)}`} + message={`You are about to delete ${capitalize( + table + )} ${deleteId}, note that this action is irreversible`} + acceptText={`DELETE`} + rejectText={`CANCEL`} + loading={deleteLoading} + /> + ) : null} + + ); +}; + +export default MkdGridCards; diff --git a/day20/src/components/MkdGridView/MkdGridView.jsx b/day20/src/components/MkdGridView/MkdGridView.jsx new file mode 100644 index 0000000..956f500 --- /dev/null +++ b/day20/src/components/MkdGridView/MkdGridView.jsx @@ -0,0 +1,325 @@ +import React from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { getNonNullValue } from "Utils/utils"; +import {PaginationBar} from "Components/PaginationBar"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import { GlobalContext } from "Context/Global"; +import { MkdGridCards } from "Components/MkdGridView"; +import { MkdInput } from "Components/MkdInput"; + +let sdk = new MkdSDK(); +// const getSchemaStructure = (schema) => { +// return; +// }; +const getType = (type) => { + switch (type) { + case "varchar": + return "text"; + case "text": + return "textarea"; + case "mediumtext": + return "textarea"; + case "longtext": + return "textarea"; + case "tinyint": + return "number"; + case "int": + return "number"; + case "bigint": + return "number"; + case "float": + return "number"; + case "double": + return "number"; + case "image": + return "image"; + case "file": + return "file"; + case "date": + return "date"; + case "datetime": + return "datetime"; + default: + return "text"; + } +}; + +const MkdGridView = ({ + columns = [], + actions = { view: true, edit: true, delete: true }, + actionId = "id", + tableRole = "admin", + table = "user", + tableSchema = [], + hasFilter = true, + schemaFields = [], +}) => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + const [query, setQuery] = React.useState(""); + const [currentTableData, setCurrentTableData] = React.useState([]); + const [pageSize, setPageSize] = React.useState(10); + const [pageCount, setPageCount] = React.useState(0); + const [dataTotal, setDataTotal] = React.useState(0); + const [currentPage, setPage] = React.useState(0); + const [canPreviousPage, setCanPreviousPage] = React.useState(false); + const [canNextPage, setCanNextPage] = React.useState(false); + const [showDeleteModal, setShowDeleteModal] = React.useState(false); + const [deleteLoading, setDeleteLoading] = React.useState(false); + const [loading, setLoading] = React.useState(false); + + const schema = yup.object({}); + + const { + register, + handleSubmit, + setError, + reset, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + function onSort(columnIndex) { + console.log(columns[columnIndex]); + if (columns[columnIndex].isSorted) { + columns[columnIndex].isSortedDesc = !columns[columnIndex].isSortedDesc; + } else { + columns.map((i) => (i.isSorted = false)); + columns.map((i) => (i.isSortedDesc = false)); + columns[columnIndex].isSorted = true; + } + + (async function () { + await getData(0, pageSize); + })(); + } + + function updatePageSize(limit) { + (async function () { + setPageSize(limit); + await getData(0, limit); + })(); + } + + function previousPage() { + (async function () { + await getData(currentPage - 1 > 0 ? currentPage - 1 : 0, pageSize); + })(); + } + + function nextPage() { + (async function () { + await getData( + currentPage + 1 <= pageCount ? currentPage + 1 : 0, + pageSize + ); + })(); + } + + async function getData(pageNum, limitNum, currentTableData) { + try { + // sdk.setTable("nanny_profile"); + // let sortField = columns.filter(col => col.isSorted); + setLoading(true); + sdk.setTable(table); + const result = await sdk.callRestAPI( + { + payload: { + ...currentTableData, + }, + page: pageNum, + limit: limitNum, + }, + "PAGINATE" + ); + if (result) { + setLoading(false); + } + const { list, total, limit, num_pages, page } = result; + + setCurrentTableData(list); + setPageSize(limit); + setPageCount(num_pages); + setPage(page); + setDataTotal(total); + setCanPreviousPage(page > 1); + setCanNextPage(page + 1 <= num_pages); + setLoading(false); + } catch (error) { + setLoading(false); + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + } + + const deleteItem = async (id) => { + try { + setDeleteLoading(true); + sdk.setTable(table); + const result = await sdk.callRestAPI({ id }, "DELETE"); + if (!result?.error) { + setCurrentTableData((list) => + list.filter((x) => Number(x.user_id) !== Number(id)) + ); + setDeleteLoading(false); + setShowDeleteModal(false); + } + } catch (err) { + setDeleteLoading(false); + setShowDeleteModal(false); + tokenExpireError(dispatch, err?.message); + throw new Error(err); + } + }; + + const exportTable = async (id) => { + try { + sdk.setTable(table); + const result = await sdk.exportCSV(); + } catch (err) { + throw new Error(err); + } + }; + + const resetForm = async () => { + reset(); + await getData(0, pageSize); + }; + + const onSubmit = (_data) => { + let filter = {}; + for (const field of schemaFields) { + const [key] = field.split(":"); + filter[key] = getNonNullValue(_data[key]); + } + getData(1, pageSize, filter); + }; + + React.useEffect(() => { + // globalDispatch({ + // type: "SETPATH", + // payload: { + // path: "nanny_profile", + // }, + // }); + + (async function () { + await getData(1, pageSize); + })(); + }, []); + + return ( +
    + {hasFilter ? ( +
    +

    {table} Search

    +
    + {tableSchema && tableSchema.length + ? tableSchema + .map((item, key) => { + if (item.is_filter) { + if (Object.keys(item.mapping).length) { + return ( +
    + +
    + ); + } else { + return ( +
    + +
    + ); + } + } + }) + .filter(Boolean) + : null} +
    + + + +
    + ) : null} + +
    +
    +

    {table} Profile

    +
    + {/* + */} +
    +
    + +
    + +
    + ); +}; + +export default MkdGridView; diff --git a/day20/src/components/MkdGridView/index.js b/day20/src/components/MkdGridView/index.js new file mode 100644 index 0000000..b0c4191 --- /dev/null +++ b/day20/src/components/MkdGridView/index.js @@ -0,0 +1,5 @@ + +import {lazy} from "react" +export const MkdGridView = lazy(()=> import("./MkdGridView")) +export const MkdGridCards = lazy(()=> import("./MkdGridCards")) +export const MkdGridCard = lazy(()=> import("./MkdGridCard")) diff --git a/day20/src/components/MkdInput/MkdInput.jsx b/day20/src/components/MkdInput/MkdInput.jsx new file mode 100644 index 0000000..3b59ba0 --- /dev/null +++ b/day20/src/components/MkdInput/MkdInput.jsx @@ -0,0 +1,120 @@ + + import React from "react"; +import { StringCaser } from "Utils/utils"; + +const MkdInput = ({ + type = "text", + page, + cols = "30", + rows = "50", + name, + label, + errors, + register, + className, + placeholder, + options = [], + mapping = null, + disabled = false, +}) => { + return ( + <> +
    + + {type === "textarea" ? ( + + ) : type === "radio" || type === "checkbox" || type === "color" ? ( + + ) : type === "dropdown" || type === "select" ? ( + + ) : type === "mapping" ? ( + <> + {mapping ? ( + + ) : ( + `Please Pass the mapping e.g {key:value}` + )} + + ) : ( + + )} +

    + {errors[name]?.message} +

    +
    + + ); +}; + +export default MkdInput; + + \ No newline at end of file diff --git a/day20/src/components/MkdInput/index.js b/day20/src/components/MkdInput/index.js new file mode 100644 index 0000000..6a5f779 --- /dev/null +++ b/day20/src/components/MkdInput/index.js @@ -0,0 +1,3 @@ + + export { default as MkdInput } from "./MkdInput"; + \ No newline at end of file diff --git a/day20/src/components/MkdJsonQuiz/AnswerTypes/MultipleChoice.jsx b/day20/src/components/MkdJsonQuiz/AnswerTypes/MultipleChoice.jsx new file mode 100644 index 0000000..31a5393 --- /dev/null +++ b/day20/src/components/MkdJsonQuiz/AnswerTypes/MultipleChoice.jsx @@ -0,0 +1,20 @@ +import React from "react"; +import MkdJsonQuizOptions from "../MkdJsonQuizOptions"; +import { QuestionTypes } from "../MkdJsonQuiz"; + +const MultipleChoice = ({ updateQuestions, currentQuestion }) => { + // console.log("currentQuestion >>", currentQuestion); + return ( + <> + {currentQuestion && + currentQuestion?.type === QuestionTypes.multiple_choice ? ( + + ) : null} + + ); +}; + +export default MultipleChoice; diff --git a/day20/src/components/MkdJsonQuiz/AnswerTypes/ShortLongAnswer.jsx b/day20/src/components/MkdJsonQuiz/AnswerTypes/ShortLongAnswer.jsx new file mode 100644 index 0000000..4b6be5f --- /dev/null +++ b/day20/src/components/MkdJsonQuiz/AnswerTypes/ShortLongAnswer.jsx @@ -0,0 +1,67 @@ +import React, { useId, useState } from "react"; +import { QuestionTypes } from "../MkdJsonQuiz"; + +let timeout = null; + +const ShortLongAnswer = ({ updateQuestions, currentQuestion }) => { + // console.log("currentQuestion >>", currentQuestion); + const inputId = useId(); + const [inputValue, setInputValue] = useState(""); + + const getValue = () => { + return inputValue || currentQuestion?.answer; + }; + + function handleInput(e) { + const inputValue = e.target.value; + // setValue(inputValue); + setInputValue(inputValue); + + if (timeout) { + clearTimeout(timeout); + } + + timeout = setTimeout(() => { + const tempCurrentQuestion = { ...currentQuestion }; + tempCurrentQuestion["answer"] = inputValue; + updateQuestions(tempCurrentQuestion); + setInputValue(""); + }, 1000); // 500 milliseconds = half a second + } + + return ( + <> + {currentQuestion && + [QuestionTypes.short_answer, QuestionTypes.long_answer].includes( + currentQuestion?.type + ) ? ( +
    + {/*
    */} + {currentQuestion?.type === QuestionTypes.short_answer ? ( + handleInput(e)} + value={getValue()} + className="block w-full rounded-lg border border-blue-600 bg-white p-4 pl-3 text-sm text-black placeholder-black focus:border-blue-500 focus:ring-blue-500 dark:text-gray-400 dark:placeholder-gray-400" + /> + ) : ( + +

    + {errors.name?.message} +

    +
    + +
    + + +
    + + ) : ( + + )} +
    +
    +
    +
    + ); +}; + +export default BoardCardsPage; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/CardMember.jsx b/day20/src/components/MkdTrelloColumns/CardMember.jsx new file mode 100644 index 0000000..a7a447e --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/CardMember.jsx @@ -0,0 +1,214 @@ +import { AuthContext } from "Context/Auth"; +import { GlobalContext } from "Context/Global"; +import MkdSDK from "Utils/MkdSDK"; +import React from "react"; +import { useParams } from "react-router"; +import CardMemberCard from "./CardMemberCard"; + +let sdk = new MkdSDK(); + +const CardMember = ({ cardId }) => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [showForm, setShowForm] = React.useState(false); + const [users, setUsers] = React.useState([]); + const [cardMembers, setCardMembers] = React.useState([]); + + const { workspaceId } = useParams(); + + const getWorkspaceMember = async () => { + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/workspaces/${workspaceId}/members`, + {}, + "GET" + ); + console.log(result, "workspaces member"); + if (!result.error) { + setUsers(result.list); + } + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + }; + + const getCardUsers = async () => { + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/cards/${cardId}/members`, + {}, + "GET" + ); + console.log(result, "Cardmember result"); + // setUsers(result); + if (!result.error) { + setCardMembers(result.list); + } + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + }; + + const assigneMember = async (id) => { + console.log(id); + + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/cards/${cardId}/members/${id}/attach`, + {}, + "POST" + ); + console.log(result, "Assinged result"); + if (!result.error) { + showToast(globalDispatch, "User Added"); + getCardUsers(); + } + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + }; + + console.log(users, cardMembers, "users"); + + React.useEffect(() => { + (async function () { + await getWorkspaceMember(); + })(); + (async function () { + await getCardUsers(); + })(); + }, []); + + return ( +
    + {cardMembers.length === 0 ? ( +
    setShowForm(true)} + className=" relative mt-4 rounded-sm bg-[#c7c8d4] " + > +

    + add member +

    +
    + ) : ( + <> +
    +

    + + + + Members +

    +
    +
      + {cardMembers?.map((item, i) => ( + + ))} +
    • +

      setShowForm(true)} + className=" mr-[1px] flex h-[40px] w-[40px] cursor-pointer items-center justify-center rounded-full bg-[#b1b1c8] text-[20px] font-bold uppercase text-white shadow-[0_0_3px_0_#1111115e] hover:bg-[#9e9ecf] " + > + + + +

      +
    • +
    + + )} + + {showForm && ( +
    +
    setShowForm(false)} + className=" absolute left-0 top-0 h-full w-full bg-[#0000004f] " + /> +
    +
    +
    +

    Members

    + +
    +
    + {users.map((item, i) => ( +
    assigneMember(item.id)} + > +

    + {item.first_name ? item.first_name.substring(0, 1) : "N"} + {item.last_name ? item.last_name.substring(0, 1) : "/A"} +

    +

    + {" "} + {`${ + item.first_name + ? `${item.first_name} ${ + item.last_name ? item.last_name : "N/A" + }` + : "N/A" + }`} +

    +
    + ))} +
    +
    +
    +
    + )} +
    + ); +}; + +export default CardMember; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/CardMemberCard.jsx b/day20/src/components/MkdTrelloColumns/CardMemberCard.jsx new file mode 100644 index 0000000..b7b05d4 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/CardMemberCard.jsx @@ -0,0 +1,104 @@ +import { AuthContext } from "Context/Auth"; +import { GlobalContext } from "Context/Global"; +import MkdSDK from "Utils/MkdSDK"; +import React from "react"; + +let sdk = new MkdSDK(); + +const CardMemberCard = ({ cardId, item, getCardUsers }) => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [cardMemberProfilePopup, setCardMemberProfilePopup] = + React.useState(false); + + console.log(item); + const removeMember = async () => { + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/cards/${cardId}/members/${item.user_id}/detach`, + {}, + "DELETE" + ); + console.log(result, "attachment deleted"); + if (!result.error) { + showToast(globalDispatch, "User Removed"); + getCardUsers(); + setCardMemberProfilePopup(false); + } + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + }; + + return ( + <> +
  • +

    setCardMemberProfilePopup(true)} + className=" mr-[3px] flex h-[40px] w-[40px] cursor-pointer items-center justify-center rounded-full bg-[#DE350B] text-[16px] font-bold uppercase text-white shadow-[0_0_3px_0_#1111115e] hover:bg-[#b22b09] " + > + {item.first_name ? item.first_name.substring(0, 1) : "N"} + {item.last_name ? item.last_name.substring(0, 1) : "/A"} +

    +
  • + {cardMemberProfilePopup && ( +
    +
    setCardMemberProfilePopup(false)} + className=" absolute left-0 top-0 h-full w-full bg-[#0000004f] " + /> +
    +
    +
    +

    + {" "} + {item.first_name + ? `${item.first_name} ${ + item.last_name ? item.last_name : "" + }` + : "N/A"} + {} +

    + +
    + +
    +
      +
    • + Remove from card +
    • +
    +
    +
    +
    +
    + )} + + ); +}; + +export default CardMemberCard; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/Col.jsx b/day20/src/components/MkdTrelloColumns/Col.jsx new file mode 100644 index 0000000..f4e0888 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/Col.jsx @@ -0,0 +1,21 @@ +import React from "react"; + +const Col = ({ isOver, children }) => { + const className = isOver + ? "bg-[#f5eaea] min-h-[6.25rem] rounded-[.3125rem]" + : ""; + + return ( +
    + {children} +
    + ); +}; + +export default Col; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/Comment.jsx b/day20/src/components/MkdTrelloColumns/Comment.jsx new file mode 100644 index 0000000..7725f27 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/Comment.jsx @@ -0,0 +1,260 @@ +import React from "react"; +import moment from "moment"; +import MkdSDK from "Utils/MkdSDK"; +import { GlobalContext, showToast } from "Context/Global"; +import { AuthContext } from "Context/Auth"; +import LinkifyText from "./LinkifyText"; +import CommentReplyBox from "./CommentReplyBox"; + +let sdk = new MkdSDK(); + +const Comment = ({ cardId, comment, deleteComment, getComment }) => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [commentText, setCommentText] = React.useState(); + const [commentBoxShow, setCommentBoxShow] = React.useState(false); + const [replyCommentBoxShow, setReplyCommentBoxShow] = React.useState(false); + const [textareaHeight, setTextareaHeight] = React.useState(20); + const textareaRef = React.useRef(null); + const user_id = JSON.parse(localStorage.getItem("user")); + + const onSubmit = async (_data) => { + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/cards/${cardId}/comments/${comment.id}`, + { + comment: JSON.stringify(commentText.split(` +`)), + // attachment: "/uploads/1678393390494-334071424_1672975889824064_4323871217183033675_n.jpg", + }, + "PUT" + ); + console.log(result); + if (!result.error) { + showToast(globalDispatch, "Comment is Added"); + // getData(); + setCommentText(""); + setCommentBoxShow(!commentBoxShow); + getComment(); + } + } catch (error) { + console.log("Error", error); + tokenExpireError(dispatch, error.message); + } + }; + + React.useEffect(() => { + if (textareaRef.current) { + textareaRef.current.style.height = "0px"; + const scrollHeight = textareaRef.current.scrollHeight; + textareaRef.current.style.height = scrollHeight + "px"; + } + }, [commentText]); + + React.useEffect(() => { + setCommentText(JSON.parse(comment?.comment).join(` +`)); + if (textareaRef.current) { + textareaRef.current.style.height = "0px"; + const scrollHeight = textareaRef.current.scrollHeight; + textareaRef.current.style.height = scrollHeight + "px"; + } + }, [commentBoxShow]); + + // console.log(comment.comment, "commentsList"); + return ( +
    +
    + {/* */} + {!comment.parent_id && ( +

    + ys +

    + )} +
    +
    + {commentBoxShow ? ( +
    +
    + + +
    +
    + {commentText ? ( + + ) : ( + + )} + +
    +
      +
    • + + + +
    • +
    +
    +
    +
    + ) : ( +
    +
    + + Yea Sin + {" "} + + {moment(comment.update_at) + .startOf(new Date(), "hour") + .fromNow()} + +
    + + + + {/*

    +

    + {" "} + {comment?.attachment && ( + + + + + + )} +
    + + {comment?.comment} + +

    + */} +
      + {user_id === comment.user_id && ( + <> +
    • + +
    • +

      +
    • + +
    • +

      + + )} + +
    • + +
    • +
    +
    + )} + {replyCommentBoxShow && ( + <> + + + )} +
    +
    + ); +}; + +export default Comment; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/CommentForm.jsx b/day20/src/components/MkdTrelloColumns/CommentForm.jsx new file mode 100644 index 0000000..7a95860 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/CommentForm.jsx @@ -0,0 +1,197 @@ +import React from "react"; +import { AuthContext } from "Context/Auth"; +import { GlobalContext } from "Context/Global"; +import MkdSDK from "Utils/MkdSDK"; + +let sdk = new MkdSDK(); + +const CommentForm = ({ cardId, getComment }) => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [commentText, setCommentText] = React.useState(""); + const [commentBoxShow, setCommentBoxShow] = React.useState(false); + const [textareaHeight, setTextareaHeight] = React.useState(20); + const [attachment, setAttachment] = React.useState(""); + const textareaRef = React.useRef(null); + + const onSubmit = async (_data) => { + try { + let formData = new FormData(); + formData.append("file", attachment); + const attachmentUrl = await sdk.uploadImage(formData); + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/cards/${cardId}/comments`, + { + comment: JSON.stringify(commentText.split(` +`)), + attachment: attachmentUrl.url ? attachmentUrl.url : "", + }, + "POST" + ); + console.log(result); + if (!result.error) { + showToast(globalDispatch, "Comment is Added"); + // getData(); + setCommentText(""); + setCommentBoxShow(!commentBoxShow); + getComment(); + } + } catch (error) { + console.log("Error", error); + tokenExpireError(dispatch, error.message); + } + }; + + const attachmentHanle = async (e) => { + setAttachment(e.target.files[0]); + console.log(e.target.files[0].name); + + // try { + // let formData = new FormData(); + // formData.append("file", e.target.files[0]); + // const attachment = await sdk.uploadImage(formData); + // console.log(attachment); + // if (attachment.url) { + // setAttachmentUrl(attachment.url); + // const result = await sdk.callRawAPI( + // `/v2/api/lambda/pm/cards/${cardId}/attachments`, + // { + // name: e.target.files[0].name, + // attachment: attachment.url, + // }, + // "POST" + // ); + // console.log(result, "attachment uploaded"); + // if (!result.error) { + // setCommentText( + // `${commentText} [${e.target.files[0].name}] (${attachment.url})` + // ); + // } + // } + // } catch (error) { + // console.log("ERROR", error); + // tokenExpireError(dispatch, error.message); + // } + }; + + React.useEffect(() => { + if (textareaRef.current) { + textareaRef.current.style.height = "0px"; + const scrollHeight = textareaRef.current.scrollHeight; + textareaRef.current.style.height = scrollHeight + "px"; + } + }, [commentText]); + console.log(commentText.split(` +`)); + return ( +
    +
    + {/* */} +

    + ys +

    +
    + {commentBoxShow ? ( +
    +
    + + +
    +
    + {commentText ? ( + + ) : ( + + )} + +
    +
      +
    • + + + + +
    • +
    +
    +
    +
    + ) : ( + + )} +
    + ); +}; + +export default CommentForm; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/CommentReplyBox.jsx b/day20/src/components/MkdTrelloColumns/CommentReplyBox.jsx new file mode 100644 index 0000000..bd09317 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/CommentReplyBox.jsx @@ -0,0 +1,185 @@ +import React from "react"; + import { AuthContext } from "Context/Auth"; +import { GlobalContext } from "Context/Global"; +import MkdSDK from "Utils/MkdSDK"; + +const CommentReplyBox = ({ + replyCommentBoxShow, + setReplyCommentBoxShow, + parentId, + cardId, + getComment, +}) => { + let sdk = new MkdSDK(); + + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [commentText, setCommentText] = React.useState(); + const [commentBoxShow, setCommentBoxShow] = React.useState(false); + const [attachment, setAttachment] = React.useState(""); + // const [replyCommentBoxShow, setReplyCommentBoxShow] = React.useState(false); + const [textareaHeight, setTextareaHeight] = React.useState(20); + + const textareaRef = React.useRef(null); + const user_id = JSON.parse(localStorage.getItem("user")); + + const onSubmit = async (_data) => { + try { + let formData = new FormData(); + formData.append("file", attachment); + const attachment = await sdk.uploadImage(formData); + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/cards/${cardId}/comments`, + { + comment: JSON.stringify(commentText.split(` +`)), + parent_id: parentId, + attachment: attachment.url ? attachment.url : "", + }, + "POST" + ); + console.log(result); + if (!result.error) { + showToast(globalDispatch, "Comment is Added"); + // getData(); + setCommentText(""); + setReplyCommentBoxShow(!replyCommentBoxShow); + getComment(); + } + } catch (error) { + console.log("Error", error); + tokenExpireError(dispatch, error.message); + } + }; + + const attachmentHanle = async (e) => { + let sdk = new MkdSDK(); + console.log(e.target.files); + console.log(e.target.files[0].name); + setAttachment(e.target.files[0]); + + // try { + // let formData = new FormData(); + // formData.append("file", e.target.files[0]); + // const attachment = await sdk.uploadImage(formData); + // console.log(attachment); + // if (attachment.url) { + // setAttachmentUrl(attachment.url); + // const result = await sdk.callRawAPI( + // `/v2/api/lambda/pm/cards/${cardId}/attachments`, + // { + // name: e.target.files[0].name, + // attachment: attachment.url, + // }, + // "POST" + // ); + // console.log(result, "attachment uploaded"); + // if (!result.error) { + // setCommentText( + // `${commentText} [${e.target.files[0].name}] (${attachment.url})` + // ); + // } + // } + // } catch (error) { + // console.log("ERROR", error); + // tokenExpireError(dispatch, error.message); + // } + }; + + React.useEffect(() => { + if (textareaRef.current) { + textareaRef.current.style.height = "0px"; + const scrollHeight = textareaRef.current.scrollHeight; + textareaRef.current.style.height = scrollHeight + "px"; + } + }, [commentText]); + + return ( +
    +
    + + +
    +
    + {commentText ? ( + + ) : ( + + )} + +
    +
      +
    • + + + +
    • +
    +
    +
    +
    + ); +}; + +export default CommentReplyBox; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/CreateNewRoomModal.jsx b/day20/src/components/MkdTrelloColumns/CreateNewRoomModal.jsx new file mode 100644 index 0000000..83cacf5 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/CreateNewRoomModal.jsx @@ -0,0 +1,89 @@ +import React from "react"; + import { AuthContext } from "Context/Auth"; +import MkdSDK from "Utils/MkdSDK"; + +const CreateNewRoomModal = ({ createNewRoom, setCreateRoom }) => { + const [otherUsers, setOtherUsers] = React.useState(); + const [unfilteredUsers, setUnfilteredUsers] = React.useState(); + const [search, setSearch] = React.useState(""); + const { state } = React.useContext(AuthContext); + + let sdk = new MkdSDK(); + const getAllUsers = async () => { + try { + let users = await sdk.getAllUsers(); + + if (!users.error) { + setOtherUsers(users?.list.filter((user) => user.id !== state.user)); + setUnfilteredUsers( + users?.list.filter((user) => user.id !== state.user) + ); + } + } catch (err) { + console.log("Error", err); + } + }; + + const filterList = (value) => { + setSearch(value); + if (value.length > 0) { + setOtherUsers( + [...unfilteredUsers].filter((user) => + `${user.first_name.toLowerCase()} ${user.last_name.toLowerCase()}`.includes( + value.toLowerCase() + ) + ) + ); + } else { + setOtherUsers(unfilteredUsers); + } + }; + + React.useEffect(() => { + (async function () { + await getAllUsers(); + })(); + }, []); + + return ( + <> +
    +
    setCreateRoom(false)} + >
    +
    +
    +
    +
    + filterList(e.target.value)} + /> +
      + {otherUsers && + otherUsers.map((user) => ( +
    • createNewRoom(user)} + className={`w-full cursor-pointer bg-white px-4 py-2 hover:bg-gray-200 user-${user.id}`} + > + {user.first_name} {user.last_name} +
    • + ))} +
    +
    +
    +
    +
    +
    + + ); +}; + +export default CreateNewRoomModal; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/DropWrapper.jsx b/day20/src/components/MkdTrelloColumns/DropWrapper.jsx new file mode 100644 index 0000000..828a675 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/DropWrapper.jsx @@ -0,0 +1,34 @@ +import React from "react"; +import { useDrop } from "react-dnd"; +import { statuses } from "./data"; +import ITEM_TYPE from "./data/types"; + +const DropWrapper = ({ onDrop, children, status }) => { + const [{ isOver }, drop] = useDrop({ + accept: ITEM_TYPE, + canDrop: (item, monitor) => { + const itemIndex = statuses.findIndex((si) => si.status === item.status); + const statusIndex = statuses.findIndex((si) => si.status === status); + return [itemIndex + 1, itemIndex - 1, itemIndex].includes(statusIndex); + }, + drop: (item, monitor) => { + onDrop(item, monitor, status); + }, + collect: (monitor) => ({ + isOver: monitor.isOver(), + }), + }); + + return ( +
    + {React.cloneElement(children, { isOver })} +
    + ); +}; + +export default DropWrapper; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/InstandEditor.jsx b/day20/src/components/MkdTrelloColumns/InstandEditor.jsx new file mode 100644 index 0000000..7c7e609 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/InstandEditor.jsx @@ -0,0 +1,115 @@ +import React, {useContext} from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { AuthContext} from "Context/Auth"; + +const InstandEditor = ({ setShowInstandEditor, item, list, getData }) => { + const {dispatch} = useContext(AuthContext); + + const schema = yup.object({ + name: yup.string().required("Title is required"), + }); + const { + register, + handleSubmit, + setError, + reset, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + console.log(item, list); + + const onSubmit = async (_data) => { + let sdk = new MkdSDK(); + console.log(_data); + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/lists/${list.status}/cards/${item.id}`, + { name: _data.name }, + "PUT" + ); + console.log(result); + if (!result.error) { + getData(); + setShowInstandEditor(false); + } + } catch (error) { + console.log("Error", error); + tokenExpireError(dispatch, error.message); + } + }; + + React.useEffect(() => { + setValue("name", item.content); + }, []); + + return ( +
    +
    setShowInstandEditor(false)} + className="fixed left-0 top-0 z-[99999] h-full w-full bg-[#11111169]" + >
    +
    +
    + +

    {errors.name?.message}

    +
    + +
    + + +
    +
    +
    + ); +}; + +export default InstandEditor; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/Item.jsx b/day20/src/components/MkdTrelloColumns/Item.jsx new file mode 100644 index 0000000..eafeb93 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/Item.jsx @@ -0,0 +1,108 @@ +import React, { Fragment, useState, useRef } from "react"; +import { useDrag, useDrop } from "react-dnd"; +import Window from "./Window"; +import ITEM_TYPE from "./data/types"; +import InstandEditor from "./InstandEditor"; + +const Item = ({ item, index, moveItem, list, getData }) => { + const ref = useRef(null); + const [showInstandEditor, setShowInstandEditor] = React.useState(false); + const [show, setShow] = useState(false); + + const [, drop] = useDrop({ + accept: ITEM_TYPE, + hover(item, monitor) { + if (!ref.current) { + return; + } + const dragIndex = item.index; + const hoverIndex = index; + + if (dragIndex === hoverIndex) { + return; + } + + const hoveredRect = ref.current.getBoundingClientRect(); + const hoverMiddleY = (hoveredRect.bottom - hoveredRect.top) / 2; + const mousePosition = monitor.getClientOffset(); + const hoverClientY = mousePosition.y - hoveredRect.top; + + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { + return; + } + + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { + return; + } + + moveItem(dragIndex, hoverIndex); + item.index = hoverIndex; + }, + }); + + const [{ isDragging }, drag] = useDrag({ + type: ITEM_TYPE, + item: { type: ITEM_TYPE, ...item, index }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + }); + + drag(drop(ref)); + + const onOpen = () => setShow(true); + const onClose = () => setShow(false); + + return ( + +
    +
    + {/*
    */} +

    {item.content}

    +
    + +
    + {show && } + {showInstandEditor && ( + + )} + + ); +}; + +export default Item; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/LIstItems.jsx b/day20/src/components/MkdTrelloColumns/LIstItems.jsx new file mode 100644 index 0000000..f998cdb --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/LIstItems.jsx @@ -0,0 +1,257 @@ +import React from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import { GlobalContext, showToast } from "Context/Global"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import MkdSDK from "Utils/MkdSDK"; +import ListSetting from "./ListSetting"; +import DropWrapper from "./DropWrapper"; +import Item from "./Item"; +import Col from "./Col"; + +const LIstItems = ({ + list, + cardItems, + getData, + statuses, + items, + setItems, + setCurrentTableData, +}) => { + const [cardFormShow, setCardFormShow] = React.useState(false); + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + const schema = yup.object({ + name: yup.string().required("Title is required"), + }); + const { + register, + handleSubmit, + setError, + reset, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onDrop = (item, monitor, status) => { + const mapping = statuses.find((si) => si.status === status); + + setItems((prevState) => { + const newItems = prevState + .filter((i) => i?.id !== item?.id) + .concat({ ...item, status, icon: mapping?.icon }); + return [...newItems]; + }); + + // console.log(item, status, monitor, "now"); + updateCardListId(item, status); + }; + + const moveItem = (dragIndex, hoverIndex) => { + const item = items[dragIndex]; + setItems((prevState) => { + const newItems = prevState.filter((i, idx) => idx !== dragIndex); + newItems.splice(hoverIndex, 0, item); + return [...newItems]; + }); + }; + + const updateCardListId = async (item, status) => { + console.log(item, status); + let sdk = new MkdSDK(); + + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/lists/${status}/cards/${item.id}`, + { list_id: status }, + "PUT" + ); + console.log(result); + if (!result.error) { + setCurrentTableData(() => []); + getData(); + } + } catch (error) { + console.log("Error", error); + tokenExpireError(dispatch, error.message); + } + }; + const onSubmit = async (_data) => { + let sdk = new MkdSDK(); + console.log(_data); + + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/lists/${list.status}/cards`, + { + name: _data.name, + start_date: new Date().toISOString().split("T")[0], + due_date: new Date().toISOString().split("T")[0], + }, + "POST" + ); + if (!result.error) { + showToast(globalDispatch, "Added"); + setCurrentTableData(() => []); + getData(); + reset(); + setCardFormShow(!cardFormShow); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + console.log("Error", error); + setError("name", { + type: "manual", + message: error.message, + }); + tokenExpireError(dispatch, error.message); + } + }; + + return ( + <> +
    +
    +

    + {list.title.toUpperCase()}{" "} +

    + + +
    + + + {cardItems.map((i, idx) => ( + + ))} + {/* {items + .filter((i) => i.status == s.status) + .map((i, idx) => ( + + ))} */} + { + <> + {!cardFormShow && ( + + )} + + {cardFormShow && ( +
    +
    + +

    + {errors.name?.message} +

    +
    + +
    + + +
    +
    + )} + + } + +
    +
    + + ); +}; + +export default LIstItems; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/LinkifyText.jsx b/day20/src/components/MkdTrelloColumns/LinkifyText.jsx new file mode 100644 index 0000000..4aaa3b8 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/LinkifyText.jsx @@ -0,0 +1,64 @@ +import React from "react"; + +const LinkifyText = ({ comment, attachment }) => { + // React.useEffect(() => { + // JSON.parse(comment).map((com) => { + // let parts = com.match(/(?<=[).*?(?=])/g); + // let parts2 = com.match(/(([^)]+))/); + // console.log(com.split("["), "linked text"); + // }); + // }, []); + return ( +
    +
    + {" "} + {attachment && ( + + + + + + )} +
    + {comment + ? JSON.parse(comment).map((com, i) => ( + + {com.match(/(([^)]+))/) ? ( + <> + {com.split("[")[0]} + + {com.match(/(?<=[).*?(?=])/g)[0]} + + {com.split(")")[1]} + + ) : ( + com + )} +
    +
    + )) + : "N/A"} + {/* {comment?.comment ? comment?.comment : "N/A"} */} +
    + ); +}; + +export default LinkifyText; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/ListSetting.jsx b/day20/src/components/MkdTrelloColumns/ListSetting.jsx new file mode 100644 index 0000000..f1f85ea --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/ListSetting.jsx @@ -0,0 +1,387 @@ +import React from "react"; +import { useParams } from "react-router"; +import AlartMolad from "./AlartMolad"; +import { AuthContext } from "Context/Auth"; +import { GlobalContext } from "Context/Global"; +import MkdSDK from "Utils/MkdSDK"; + +const ListSetting = ({ cardFormShow, setCardFormShow, list, getData }) => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [showModal, setShowModal] = React.useState(false); + const [showMoveListModal, setShowMoveListModal] = React.useState(false); + const [showNameChangeModal, setShowNameChangeModal] = React.useState(false); + const [showDeleteModal, setShowDeleteModal] = React.useState(false); + const [showArchiveModal, setShowArchiveModal] = React.useState(false); + const [currentTableData, setCurrentTableData] = React.useState([]); + const [listName, setListName] = React.useState(list.title); + + const { id } = useParams(); + + async function getLists() { + let sdk = new MkdSDK(); + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/boards/${id}/lists?order_by=id&direction=asc`, + {}, + "GET" + ); + + const { list } = result; + setCurrentTableData(list); + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + } + + const updateListPosition = async (listId) => { + let sdk = new MkdSDK(); + console.log(listId); + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/boards/${id}/lists/${list.status}`, + { + position: listId, + }, + "PUT" + ); + console.log(result); + if (!result.error) { + getData(); + } + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + }; + const updateListName = async () => { + let sdk = new MkdSDK(); + if (listName) { + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/boards/${id}/lists/${list.status}`, + { + name: listName, + }, + "PUT" + ); + console.log(result); + if (!result.error) { + getData(); + setShowNameChangeModal(false); + } + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + } else { + showToast(globalDispatch, "Enter Name"); + } + }; + + const deleteList = async () => { + let sdk = new MkdSDK(); + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/lists/${list.status}`, + {}, + "DELETE" + ); + console.log(result); + if (!result.error) { + getData(); + setShowDeleteModal(false); + setShowModal(false); + } + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + }; + const archiveList = async () => { + let sdk = new MkdSDK(); + if (listName) { + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/lists/${list.status}`, + { + archive: true, + }, + "DELETE" + ); + console.log(result); + if (!result.error) { + getData(); + setShowArchiveModal(false); + setShowModal(false); + } + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + } else { + showToast(globalDispatch, "Enter Name"); + } + }; + + React.useEffect(() => { + (async function () { + await getLists(); + })(); + }, []); + + // console.log(s, "s"); + + return ( + <> + + {showModal && ( +
    +
    setShowModal(false)} + className=" absolute left-0 top-0 h-full w-full bg-[#0000004f] " + /> +
    +
    + List actions + +
    +
      +
    • { + setCardFormShow(!cardFormShow); + setShowModal(false); + }} + className=" cursor-pointer px-[12px] py-[6px] text-[#172b4d] hover:bg-[#ceecfc5d] " + > + Add Card... +
    • +
    • { + setShowMoveListModal(true); + // setShowModal(false); + }} + className=" cursor-pointer px-[12px] py-[6px] text-[#172b4d] hover:bg-[#ceecfc5d] " + > + Move List... +
    • +
    • { + setShowNameChangeModal(true); + // setShowModal(false); + }} + className=" cursor-pointer px-[12px] py-[6px] text-[#172b4d] hover:bg-[#ceecfc5d] " + > + Change Name +
    • +
    • { + setShowDeleteModal(true); + }} + className=" cursor-pointer px-[12px] py-[6px] text-[#172b4d] hover:bg-[#ceecfc5d] " + > + Delete +
    • +
    • { + setShowArchiveModal(true); + }} + className=" cursor-pointer px-[12px] py-[6px] text-[#172b4d] hover:bg-[#ceecfc5d] " + > + Archive +
    • +
    +
    +
    + )} + {showMoveListModal && ( +
    +
    setShowMoveListModal(false)} + className=" absolute left-0 top-0 h-full w-full bg-[#0000004f] " + /> +
    +
    + Move + +
    +
    + + +
    +
    +
    + )} + {showNameChangeModal && ( +
    +
    setShowNameChangeModal(false)} + className=" absolute left-0 top-0 h-full w-full bg-[#0000004f] " + /> +
    +
    + Change Name + +
    +
    +
    +
    + +
    + +
    + + {/* */} +
    +
    +
    +
    +
    + )} + {showDeleteModal && ( + + )} + {showArchiveModal && ( + + )} + + ); +}; + +export default ListSetting; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/Lists.jsx b/day20/src/components/MkdTrelloColumns/Lists.jsx new file mode 100644 index 0000000..2d2eb54 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/Lists.jsx @@ -0,0 +1,141 @@ +import React from "react"; +import { useDrag, useDrop } from "react-dnd"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import { GlobalContext } from "Context/Global"; +import MkdSDK from "Utils/MkdSDK"; +import LIstItems from "./LIstItems"; + +const style = { + border: "1px dashed gray", + padding: "0.5rem 1rem", + marginBottom: ".5rem", + backgroundColor: "white", + cursor: "move", +}; + +const ItemTypes = { + CARD: "card", +}; +const icons = { 1: "⭕️", 2: "🔆️", 3: "📝", 4: "✅" }; + +const Lists = ({ + list, + cardLIst, + getData, + statuses, + items, + setItems, + moveList, + setCurrentTableData, + id, + index, +}) => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [cardItems, setCardItems] = React.useState([]); + + const ref = React.useRef(null); + + const [{ handlerId }, drop] = useDrop({ + accept: ItemTypes.CARD, + collect(monitor) { + return { + handlerId: monitor.getHandlerId(), + }; + }, + hover(item, monitor) { + console.log(item, monitor, "monitor"); + if (!ref.current) { + return; + } + const dragIndex = item.index; + const hoverIndex = index; + if (dragIndex === hoverIndex) { + return; + } + const hoveredRect = ref.current.getBoundingClientRect(); + const hoverMiddleY = (hoveredRect.bottom - hoveredRect.top) / 2; + const mousePosition = monitor.getClientOffset(); + const hoverClientY = mousePosition.y - hoveredRect.top; + + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { + return; + } + + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { + return; + } + console.log(dragIndex, hoverIndex); + moveList(dragIndex, hoverIndex); + item.index = hoverIndex; + }, + }); + const [{ isDragging }, drag] = useDrag({ + type: ItemTypes.CARD, + item: { type: ItemTypes.CARD, id, index }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + }); + const opacity = isDragging ? 0 : 1; + drag(drop(ref)); + + const getCardsByList = async () => { + let sdk = new MkdSDK(); + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/lists/${list?.status}/cards`, + {}, + "GET" + ); + let cardsList = []; + result.list.map((item) => + cardsList.push({ + id: item.id, + icon: icons[item.list_id], + // status: "open", + status: item.list_id, + title: "Human Interest Form", + content: item.name, + }) + ); + setCardItems(cardsList); + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + }; + + // console.log(cardLIst, items, s); + + React.useEffect(() => { + // setItems(cardLIst); + // console.log(cardLIst, items); + (async function () { + await getCardsByList(); + })(); + }, [list?.status]); + + return ( +
    + +
    + ); +}; + +export default Lists; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/MkdTrelloColumns.jsx b/day20/src/components/MkdTrelloColumns/MkdTrelloColumns.jsx new file mode 100644 index 0000000..9636f76 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/MkdTrelloColumns.jsx @@ -0,0 +1,90 @@ +import React from "react"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { DndProvider } from "react-dnd"; +import BoardCardsPage from "./BoardCardsPage"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import { GlobalContext } from "Context/Global"; +import MkdSDK from "Utils/MkdSDK"; + +const icons = { 1: "⭕️", 2: "🔆️", 3: "📝", 4: "✅" }; + +const MkdTrelloColumns = ({ activeBoardId, handleBackButton }) => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [currentTableData, setCurrentTableData] = React.useState([]); + const [cardLIst, setCardLIst] = React.useState([]); + + async function getData() { + let sdk = new MkdSDK(); + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/boards/${0}/lists?order_by=id&direction=asc`, + {}, + "GET" + ); + + const { list } = result; + let lists = []; + list.map((item) => + lists.push({ + // status: "open", + status: item.id, + title: item.name, + // icon: "⭕️", + icon: icons[item.id], + color: "#EB5A46", + position: item.position, + }) + ); + setCurrentTableData(lists); + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + } + + React.useEffect(() => { + (async function () { + await getData(); + })(); + }, []); + + // console.log(currentTableData, "currentTableData"); + + return ( +
    + {/* + + + + */} + + {/* {cardLIst.length > 0 && ( */} + + {/* )} */} + +
    + ); +}; + +export default MkdTrelloColumns; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/Window.jsx b/day20/src/components/MkdTrelloColumns/Window.jsx new file mode 100644 index 0000000..35c3639 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/Window.jsx @@ -0,0 +1,288 @@ +import moment from "moment"; +import React from "react"; +import Modal from "react-modal"; +import AttachmentCard from "./AttachmentCard"; +import CardMember from "./CardMember"; +import Comment from "./Comment"; +import CommentForm from "./CommentForm"; +import { AuthContext } from "Context/Auth"; +import { GlobalContext } from "Context/Global"; +import MkdSDK from "Utils/MkdSDK"; + +let sdk = new MkdSDK(); + +const Window = ({ show, onClose, item }) => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [currentTableData, setCurrentTableData] = React.useState({}); + const [showAttachments, setShowAttachments] = React.useState(false); + const [attachmentsList, setAttachmentsList] = React.useState([]); + const [commentsList, setCommentsList] = React.useState([]); + + async function getData() { + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/cards/${item.id}`, + {}, + "GET" + ); + const result2 = await sdk.callRawAPI( + `/v2/api/lambda/pm/cards/${item.id}/attachments?order_by=id&direction=asc`, + {}, + "GET" + ); + console.log(result2, "result2 attachment"); + setAttachmentsList(result2.list); + const { model } = result; + setCurrentTableData(model); + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + } + async function getComment() { + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/cards/${item.id}/comments?order_by=id&direction=desc&limit=20`, + {}, + "GET" + ); + console.log(result, "commentsList result"); + setCommentsList(result.list); + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + } + + React.useEffect(() => { + (async function () { + await getData(); + })(); + (async function () { + await getComment(); + })(); + }, []); + + // console.log(currentTableData, "on popup card"); + + const attachmentHanle = async (e) => { + console.log(e.target.files); + + try { + let formData = new FormData(); + formData.append("file", e.target.files[0]); + const attachment = await sdk.uploadImage(formData); + console.log(attachment); + if (attachment.url) { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/cards/${item.id}/attachments`, + { + name: e.target.files[0].name, + attachment: attachment.url, + }, + "POST" + ); + console.log(result, "attachment uploaded"); + if (!result.error) { + showToast(globalDispatch, "Added"); + getData(); + } + } + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + }; + + const deleteAttachment = async (id) => { + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/attachments/${id}`, + {}, + "DELETE" + ); + console.log(result, "attachment deleted"); + if (!result.error) { + getData(); + } + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + }; + const deleteComment = async (id) => { + try { + const result = await sdk.callRawAPI( + `/v2/api/lambda/pm/comments/${id}`, + {}, + "DELETE" + ); + console.log(result, "Comment deleted"); + if (!result.error) { + getComment(); + } + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + }; + + Modal.setAppElement("body"); + + return ( + +
    +

    + {currentTableData.name} +

    + +
    +
    + {/*

    Description

    */} +

    {item.content}

    + {/*

    Status {item.icon}

    */} +

    {/* {item.name} */}

    +
    + + {/* Card member */} + + + +
    + {/* Attachment secttion */} +
    + {attachmentsList.length > 0 ? ( +
    +

    + + + + attachments +

    +
    + ) : ( +
    + +

    + add attachment +

    +
    + )} + + {attachmentsList?.map((attach, i) => ( + + ))} + {attachmentsList.length > 0 && ( + + )} +
    + + {/* Comment secttion */} +
    +
    + + + +

    Comments

    +
    + + + + {commentsList.length > 0 && + commentsList?.map((com, i) => ( + + ))} +
    +
    + ); +}; + +export default Window; + + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/data/types.js b/day20/src/components/MkdTrelloColumns/data/types.js new file mode 100644 index 0000000..aac2a46 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/data/types.js @@ -0,0 +1,4 @@ +const ITEM_TYPE = "ITEM"; + +export default ITEM_TYPE; + \ No newline at end of file diff --git a/day20/src/components/MkdTrelloColumns/index.js b/day20/src/components/MkdTrelloColumns/index.js new file mode 100644 index 0000000..e9ffb85 --- /dev/null +++ b/day20/src/components/MkdTrelloColumns/index.js @@ -0,0 +1,23 @@ + + export { default as AlartMolad } from "./AlartMolad"; + export { default as AttachmentCard } from "./AttachmentCard"; + export { default as AttachmentViewer } from "./AttachmentViewer"; + export { default as BoardCardsPage } from "./BoardCardsPage"; + export { default as CardMember } from "./CardMember"; + export { default as CardMemberCard } from "./CardMemberCard"; + export { default as Col } from "./Col"; + export { default as Comment } from "./Comment"; + export { default as CommentForm } from "./CommentForm"; + export { default as CommentReplyBox } from "./CommentReplyBox"; + export { default as CreateNewRoomModal } from "./CreateNewRoomModal"; + export { default as DropWrapper } from "./DropWrapper"; + export { default as InstandEditor } from "./InstandEditor"; + export { default as Item } from "./Item"; + export { default as LinkifyText } from "./LinkifyText"; + export { default as LIstItems } from "./LIstItems"; + export { default as Lists } from "./Lists"; + export { default as ListSetting } from "./ListSetting"; + export { default as MkdTrelloColumns } from "./MkdTrelloColumns"; + export { default as Window } from "./Window"; + + \ No newline at end of file diff --git a/day20/src/components/MkdWizardContainer/MkdWizardContainer.jsx b/day20/src/components/MkdWizardContainer/MkdWizardContainer.jsx new file mode 100644 index 0000000..371df80 --- /dev/null +++ b/day20/src/components/MkdWizardContainer/MkdWizardContainer.jsx @@ -0,0 +1,55 @@ + + import React, { useState } from "react"; + + const MkdWizardContainer = ({ children }) => { + const [activeId, setActiveId] = useState(1); + + const childrenArray = React.Children.toArray(children); + + const activeIndex = childrenArray.findIndex( + (child) => child.props.componentId === activeId + ); + + const handlePreviousClick = () => { + const newIndex = activeIndex - 1; + if (newIndex >= 0) { + setActiveId(childrenArray[newIndex].props.componentId); + } + }; + + const handleNextClick = () => { + const newIndex = activeIndex + 1; + if (newIndex < childrenArray.length) { + setActiveId(childrenArray[newIndex].props.componentId); + } + }; + + return ( +
    +
    + {childrenArray.map((child) => + child.props.componentId === activeId ? child : null + )} +
    +
    + + +
    +
    + ); + }; + + export default MkdWizardContainer; + \ No newline at end of file diff --git a/day20/src/components/MkdWizardContainer/index.js b/day20/src/components/MkdWizardContainer/index.js new file mode 100644 index 0000000..f08528d --- /dev/null +++ b/day20/src/components/MkdWizardContainer/index.js @@ -0,0 +1,5 @@ + + + import {lazy} from "react" + export const MkdWizardContainer = lazy(()=> import("./MkdWizardContainer")) + \ No newline at end of file diff --git a/day20/src/components/Modal/Modal.jsx b/day20/src/components/Modal/Modal.jsx new file mode 100644 index 0000000..d891127 --- /dev/null +++ b/day20/src/components/Modal/Modal.jsx @@ -0,0 +1,38 @@ + + import React, { memo } from "react"; +import { MdClose } from "react-icons/md"; + +const Modal = ({ children, title, modalCloseClick, modalHeader, classes, page = "" }) => { + return ( +
    +
    + {modalHeader && ( +
    +
    {title}
    +
    + +
    +
    + )} + +
    {children}
    +
    +
    + ); +}; + +const ModalMemo = memo(Modal); +export { ModalMemo as Modal }; + + \ No newline at end of file diff --git a/day20/src/components/Modal/ModalAlert.jsx b/day20/src/components/Modal/ModalAlert.jsx new file mode 100644 index 0000000..a579365 --- /dev/null +++ b/day20/src/components/Modal/ModalAlert.jsx @@ -0,0 +1,56 @@ + +// import { Close, danger, warning } from 'Assets/svgs' +import React from 'react' + + +const ModalAlert = ( { closeModalFunction, message, title, messageClasses, titleClasses, buttonText = "OK" } ) => { + return ( + + ) +} + +export default ModalAlert diff --git a/day20/src/components/Modal/ModalPrompt.jsx b/day20/src/components/Modal/ModalPrompt.jsx new file mode 100644 index 0000000..b2e55a8 --- /dev/null +++ b/day20/src/components/Modal/ModalPrompt.jsx @@ -0,0 +1,92 @@ + + import React from "react"; +import { CloseIcon, DangerIcon } from "Assets/svgs"; +import { InteractiveButton } from "Components/InteractiveButton"; + +// type Props = { +// closeModalFunction: () => void +// actionHandler: any +// message?: string +// title?: string +// rejectText?: string +// acceptText?: string +// titleClasses?: string +// messageClasses?: string +// } + +const ModalPrompt = ({ + open, + closeModalFunction, + actionHandler, + message, + title, + messageClasses = "font-normal text-base", + titleClasses = "text-center font-bold text-lg", + acceptText = "YES", + rejectText = "NO", + loading = false, + isInfo = false +}) => { + return ( + + ); +}; + +export default ModalPrompt; + + \ No newline at end of file diff --git a/day20/src/components/Modal/index.js b/day20/src/components/Modal/index.js new file mode 100644 index 0000000..6fab98b --- /dev/null +++ b/day20/src/components/Modal/index.js @@ -0,0 +1,8 @@ + + import { lazy } from "react"; + + export const Modal = lazy(() => import("./Modal").then((module) => ({ default: module.Modal }))); + export const ModalPrompt = lazy(() => import("./ModalPrompt")); + export const ModalAlert = lazy(() => import("./ModalAlert")); + + \ No newline at end of file diff --git a/day20/src/components/ModalSidebar/ModalSidebar.jsx b/day20/src/components/ModalSidebar/ModalSidebar.jsx new file mode 100644 index 0000000..650d2be --- /dev/null +++ b/day20/src/components/ModalSidebar/ModalSidebar.jsx @@ -0,0 +1,57 @@ + + import { useEffect, useRef } from 'react' + +const ModalSidebar = ( + { + customMinWidthInTw = "min-w-[30%]", + isModalActive = false, + closeModalFn = () => { }, + children + } +) => { + + const modalRef = useRef() + + useEffect(() => { + const handleClickOutside = (e) => { + if (modalRef.current && !modalRef.current.contains(e.target)) { + closeModalFn(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + useEffect(() => { + if (isModalActive && modalRef) { + modalRef.current.focus(); + } + }, [isModalActive]); + + return ( + + ) +} + +export default ModalSidebar + + \ No newline at end of file diff --git a/day20/src/components/ModalSidebar/index.js b/day20/src/components/ModalSidebar/index.js new file mode 100644 index 0000000..94eaa7f --- /dev/null +++ b/day20/src/components/ModalSidebar/index.js @@ -0,0 +1,5 @@ + + import { lazy } from 'react' + + export const ModalSidebar = lazy(()=> import("./ModalSidebar")) + \ No newline at end of file diff --git a/day20/src/components/MultiSelect/MultiSelect.jsx b/day20/src/components/MultiSelect/MultiSelect.jsx new file mode 100644 index 0000000..10537d2 --- /dev/null +++ b/day20/src/components/MultiSelect/MultiSelect.jsx @@ -0,0 +1,60 @@ + + import React, { useCallback } from "react"; + import { memo } from "react"; + import { CheckIcon, PlusIcon } from "@heroicons/react/24/solid"; + + const MultiSelect = ({ label, options, selected, setSelected }) => { + const toggleOption = useCallback( + (option) => { + let temp = selected && selected.length ? [...selected] : []; + console.log(temp, selected); + const index = temp.findIndex((i) => i === option); + if (index > -1) { + temp.splice(index, 1); + setSelected([...temp]); + } else { + console.log("option >>", option); + temp.push(option); + setSelected([...temp]); + } + }, + [selected] + ); + + return ( +
    +
    +
    {label}
    +
    + +
    + {options.map((option, key) => { + const isSelected = selected.includes(option); + return ( +
    toggleOption(option)} + > + {option} + {isSelected ? ( + + ) : ( + + )} +
    + ); + })} +
    +
    + ); + }; + + export default memo(MultiSelect); + diff --git a/day20/src/components/MultiSelect/index.js b/day20/src/components/MultiSelect/index.js new file mode 100644 index 0000000..36d4a17 --- /dev/null +++ b/day20/src/components/MultiSelect/index.js @@ -0,0 +1,5 @@ + + import { lazy } from 'react' + + export const MultiSelect = lazy(()=> import("./MultiSelect")) + \ No newline at end of file diff --git a/day20/src/components/MultipleAnnswer/MultipleAnswer.jsx b/day20/src/components/MultipleAnnswer/MultipleAnswer.jsx new file mode 100644 index 0000000..c3c5a69 --- /dev/null +++ b/day20/src/components/MultipleAnnswer/MultipleAnswer.jsx @@ -0,0 +1,74 @@ +import React, { useId, useState } from "react"; +import { PlusIcon, TrashIcon } from "@heroicons/react/24/solid"; + +const MultipleAnswer = () => { + const inputId = useId(); + const [inputValue, setInputValue] = useState([]); + const [currentValue, setCurrentValue] = useState(""); + + function handleInput(e) { + const inputValue = e.target.value; + setCurrentValue(inputValue); + } + + const handleValueInput = () => { + setInputValue((prev) => [currentValue, ...prev]); + setCurrentValue(""); + }; + const handleKeyDown = (e) => { + if (e.key === "Enter") { + handleValueInput(); + } + }; + + return ( + <> +
    +
    +
    + handleInput(e)} + onKeyDown={(e) => handleKeyDown(e)} + value={currentValue} + className="block w-full rounded-lg border border-blue-600 bg-white p-4 pl-3 text-sm text-black placeholder-black focus:border-blue-500 focus:ring-blue-500 dark:text-gray-400 dark:placeholder-gray-400" + /> + + + +
    + {inputValue?.length > 0 && + inputValue.map((input) => { + return ( +
    + {input} + + setInputValue((prev) => prev.filter((p) => p != input)) + } + > + + +
    + ); + })} +
    +
    + + ); +}; + +export default MultipleAnswer; diff --git a/day20/src/components/MultipleAnswer/index.js b/day20/src/components/MultipleAnswer/index.js new file mode 100644 index 0000000..762b69d --- /dev/null +++ b/day20/src/components/MultipleAnswer/index.js @@ -0,0 +1,6 @@ + + import { lazy } from "react"; + + export const MultipleAnswer = lazy(()=> import("./MultipleAnswer")) + + \ No newline at end of file diff --git a/day20/src/components/PaginationBar/PaginationBar.jsx b/day20/src/components/PaginationBar/PaginationBar.jsx new file mode 100644 index 0000000..5b76d71 --- /dev/null +++ b/day20/src/components/PaginationBar/PaginationBar.jsx @@ -0,0 +1,61 @@ + + import React from "react"; +const PaginationBar = ({ + currentPage, + pageCount, + pageSize, + canPreviousPage, + canNextPage, + updatePageSize, + previousPage, + nextPage, +}) => { + return ( + <> +
    +
    + + Page{" "} + + {+currentPage} of {pageCount} + {" "} + + +
    + {/* */} +
    + {" "} + {" "} +
    +
    + + ); +}; + +export default PaginationBar; + + \ No newline at end of file diff --git a/day20/src/components/PaginationBar/index.js b/day20/src/components/PaginationBar/index.js new file mode 100644 index 0000000..292c203 --- /dev/null +++ b/day20/src/components/PaginationBar/index.js @@ -0,0 +1,5 @@ + + import { lazy } from 'react' + + export const PaginationBar = lazy(()=> import("./PaginationBar")) + \ No newline at end of file diff --git a/day20/src/components/PublicHeader/PublicHeader.jsx b/day20/src/components/PublicHeader/PublicHeader.jsx new file mode 100644 index 0000000..49d0904 --- /dev/null +++ b/day20/src/components/PublicHeader/PublicHeader.jsx @@ -0,0 +1,18 @@ + + +import React, { useState } from "react"; +import { useNavigate } from "react-router"; +import { AuthContext } from "Context/Auth"; + +export const PublicHeader = () => { + const { state, dispatch } = React.useContext(AuthContext); + const [isOpen, setIsOpen] = useState(false); + const navigate = useNavigate(); + + return
    + +
    ; +}; + +export default PublicHeader; + diff --git a/day20/src/components/PublicHeader/index.js b/day20/src/components/PublicHeader/index.js new file mode 100644 index 0000000..bb63986 --- /dev/null +++ b/day20/src/components/PublicHeader/index.js @@ -0,0 +1,5 @@ + + import { lazy } from 'react' + + export const PublicHeader = lazy(()=> import("./PublicHeader")) + \ No newline at end of file diff --git a/day20/src/components/PublicWrapper/PublicWrapper.jsx b/day20/src/components/PublicWrapper/PublicWrapper.jsx new file mode 100644 index 0000000..327130d --- /dev/null +++ b/day20/src/components/PublicWrapper/PublicWrapper.jsx @@ -0,0 +1,26 @@ + +import React, { Suspense, memo } from "react"; +import {PublicHeader} from "Components/PublicHeader"; +import { Spinner } from "Assets/svgs"; + +const PublicWrapper = ({ children }) => { + return ( +
    + +
    + + +
    + } + > + {children} + +
    + {/*
    */} +
    + ); +}; + +export default memo(PublicWrapper); diff --git a/day20/src/components/PublicWrapper/index.js b/day20/src/components/PublicWrapper/index.js new file mode 100644 index 0000000..0b3922c --- /dev/null +++ b/day20/src/components/PublicWrapper/index.js @@ -0,0 +1,4 @@ + + import {lazy} from 'react' + export const PublicWrapper = lazy( ()=> import('./PublicWrapper')) + \ No newline at end of file diff --git a/day20/src/components/QrCodeGenerator/QrCodeGenerator.jsx b/day20/src/components/QrCodeGenerator/QrCodeGenerator.jsx new file mode 100644 index 0000000..300a4b8 --- /dev/null +++ b/day20/src/components/QrCodeGenerator/QrCodeGenerator.jsx @@ -0,0 +1,61 @@ + + import React from 'react' +import QRCode from "qrcode" + + +const QrCodeGenerator = ({setResult}) => { + const [textToGenerate, setTextToGenerate] = React.useState(''); + const [generatedQrcodeImageUrl, setGeneratedQrcodeImageUrl] = React.useState(); + + + async function generateQrCode(text){ + try { + const response = await QRCode.toDataURL(text) + setGeneratedQrcodeImageUrl(response) + console.log(response) + if(setResult){ + setResult(response) + } + } catch (error) { + console.error(error) + } + } + + + + return ( + <> +
    +
    +
    + + setTextToGenerate(e.target.value)} + className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" + /> +
    +
    + + { generatedQrcodeImageUrl && + + + + } +
    +
    + {generatedQrcodeImageUrl && Qr Code} +
    + + ); +} + +export default QrCodeGenerator diff --git a/day20/src/components/QrCodeGenerator/index.js b/day20/src/components/QrCodeGenerator/index.js new file mode 100644 index 0000000..bb1982d --- /dev/null +++ b/day20/src/components/QrCodeGenerator/index.js @@ -0,0 +1,2 @@ +import { lazy } from 'react' +export const QrCodeGenerator = lazy(()=> import("./QrCodeGenerator")) \ No newline at end of file diff --git a/day20/src/components/QrCodeReader/QrCodeReader.jsx b/day20/src/components/QrCodeReader/QrCodeReader.jsx new file mode 100644 index 0000000..5494381 --- /dev/null +++ b/day20/src/components/QrCodeReader/QrCodeReader.jsx @@ -0,0 +1,49 @@ + + import React,{useRef} from 'react' +import QrScanner from "qr-scanner" + +const QrCodeReader = ({setResult}) => { + const [scannedQrFile, setScannedQrFile] = React.useState(''); + const fileRef = useRef(null) + + function handleScanFileBtn(){ + fileRef.current.click() + } + + async function handleChangeScanFileBtn(e){ + const file = e.target.files[0]; + try{ + const result = await QrScanner.scanImage(file, {returnDetailedScanResult: true}) + await setScannedQrFile(result.data) + console.log(result) + if(setResult){ + setResult(result.data) + } + }catch(err){ + setScannedQrFile(err) + console.log(err) + } + + } + + return ( +
    +
    +
    + +
    +

    Scanned Code Result: {scannedQrFile && scannedQrFile}

    +
    +
    + ) +} + +export default QrCodeReader; diff --git a/day20/src/components/QrCodeReader/index.js b/day20/src/components/QrCodeReader/index.js new file mode 100644 index 0000000..76ed8dc --- /dev/null +++ b/day20/src/components/QrCodeReader/index.js @@ -0,0 +1,2 @@ +import { lazy } from 'react' +export const QrCodeReader = lazy(()=> import("./QrCodeReader")) \ No newline at end of file diff --git a/day20/src/components/RatingStar/RatingStar.jsx b/day20/src/components/RatingStar/RatingStar.jsx new file mode 100644 index 0000000..2304659 --- /dev/null +++ b/day20/src/components/RatingStar/RatingStar.jsx @@ -0,0 +1,98 @@ +import { StarIcon } from "@heroicons/react/24/solid"; +import React, { useEffect, useState } from "react"; + +const RatingStar = ({ rate = 0, onSetRate }) => { + const [rateValue, setRateValue] = useState(0); + const [hoveredIndex, setHoveredIndex] = useState(0); + + const handleClick = (number) => { + if (rateValue === number) { + onSetRate(0); + if (setRateValue) { + setRateValue(0); + } + } else { + onSetRate(number); + if (setRateValue) { + setRateValue(number); + } + } + }; + useEffect(() => { + setRateValue(rate); + }, [rate]); + + + const handleMouseOver = (e) => { + if (e.target.id) { + if (e.target.id.includes("path_")) { + setHoveredIndex(e.target.id.replace("path_", "").trim()); + } else { + setHoveredIndex(e.target.id); + } + } + }; + + const handleMouseOut = () => { + setHoveredIndex(0); + }; + + return ( + <> +
    + + = 1 ? "text-yellow-200" : "" + } ${rateValue >= 1 ? "text-yellow-400 " : "text-gray-500"}`} + onClick={() => handleClick(1)} + /> + + + = 2 ? "text-yellow-200" : "" + } ${ rateValue >= 2 ? "text-yellow-400 " : "text-gray-500" }`} + + onClick={() => handleClick(2)} + /> + + + = 3 ? "text-yellow-200" : "" + } ${rateValue >= 3 ? "text-yellow-400 " : "text-gray-500"}`} + onClick={() => handleClick(3)} + /> + + + = 4 ? "text-yellow-200" : "" + } ${rateValue >= 4 ? "text-yellow-400 " : "text-gray-500"}`} + onClick={() => handleClick(4)} + /> + + + = 5 ? "text-yellow-200" : "" + } ${rateValue === 5 ? "text-yellow-400 " : "text-gray-500"}`} + onClick={() => handleClick(5)} + /> + +
    + + ); +}; + +export default RatingStar; diff --git a/day20/src/components/RatingStar/index.js b/day20/src/components/RatingStar/index.js new file mode 100644 index 0000000..cf4c6fe --- /dev/null +++ b/day20/src/components/RatingStar/index.js @@ -0,0 +1,6 @@ + + import { lazy } from "react"; + + export const RatingStar = lazy(()=> import("./RatingStar")) + + \ No newline at end of file diff --git a/day20/src/components/SessionExpiredModal/SessionExpiredModal.jsx b/day20/src/components/SessionExpiredModal/SessionExpiredModal.jsx new file mode 100644 index 0000000..3175247 --- /dev/null +++ b/day20/src/components/SessionExpiredModal/SessionExpiredModal.jsx @@ -0,0 +1,100 @@ + + import { Dialog, Transition } from "@headlessui/react"; + import React, { Fragment } from "react"; + import { useEffect } from "react"; + import { useContext } from "react"; + import { useLocation, useNavigate } from "react-router"; + import { AuthContext } from "Context/Auth"; + + let modalTimeout; + export default function SessionExpiredModal() { + const { state, dispatch } = useContext(AuthContext); + const { pathname } = useLocation(); + const navigate = useNavigate(); + + const logout = () => { + dispatch({ type: "SESSION_EXPIRED", payload: false }); + dispatch({ type: "LOGOUT" }); + if (state.role) { + navigate(`${state?.role}/login?redirect_uri=${pathname}`); + } else { + window.location.href = `/login?redirect_uri=${pathname}`; + } + clearTimeout(modalTimeout); + }; + + useEffect(() => { + if (state.sessionExpired) { + modalTimeout = setTimeout(() => { + logout(); + }, 4000); + } + }, [state.sessionExpired]); + + + if (!state.sessionExpired) return null; + + return ( +
    + + {}} + > + +
    + + +
    +
    + + + + Session Expired + +
    +

    Your current login session has expired. Redirecting to login page shortly

    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    + ); +} diff --git a/day20/src/components/SessionExpiredModal/index.js b/day20/src/components/SessionExpiredModal/index.js new file mode 100644 index 0000000..02cc877 --- /dev/null +++ b/day20/src/components/SessionExpiredModal/index.js @@ -0,0 +1,4 @@ + + export { default as SessionExpiredModal } from "./SessionExpiredModal"; + + \ No newline at end of file diff --git a/day20/src/components/Skeleton/Skeleton.jsx b/day20/src/components/Skeleton/Skeleton.jsx new file mode 100644 index 0000000..742a551 --- /dev/null +++ b/day20/src/components/Skeleton/Skeleton.jsx @@ -0,0 +1,44 @@ + + import React from "react"; +import Skeleton from "react-loading-skeleton"; + +const SkeletonLoader = ({ + className = "", + count = 5, + counts = [2, 1, 3, 1, 1], + circle = false, +}) => { + return ( +
    + {/* */} + {Array.from({ length: count }).map((_, index) => ( + 1 + ? 25 + : index + 1 === count + ? 25 + : 80 + } + circle={circle} + style={{ marginBottom: "0.6rem" }} + /> + ))} +
    + ); +}; + +export default SkeletonLoader; + +{ + /* + + + */ +} + + diff --git a/day20/src/components/Skeleton/index.js b/day20/src/components/Skeleton/index.js new file mode 100644 index 0000000..dc09e78 --- /dev/null +++ b/day20/src/components/Skeleton/index.js @@ -0,0 +1,6 @@ + + import { lazy } from "react"; + + export const SkeletonLoader = lazy(()=> import("./Skeleton")) + + \ No newline at end of file diff --git a/day20/src/components/SnackBar/SnackBar.jsx b/day20/src/components/SnackBar/SnackBar.jsx new file mode 100644 index 0000000..02c45d1 --- /dev/null +++ b/day20/src/components/SnackBar/SnackBar.jsx @@ -0,0 +1,125 @@ + + import React from "react"; +import { GlobalContext } from "Context/Global"; +const SnackBar = () => { + const { + state: { globalMessage, toastStatus }, + dispatch, + } = React.useContext(GlobalContext); + const show = globalMessage.length > 0; + return show ? ( + + ) : null; +}; + +export default SnackBar; + diff --git a/day20/src/components/SnackBar/index.js b/day20/src/components/SnackBar/index.js new file mode 100644 index 0000000..5c096a8 --- /dev/null +++ b/day20/src/components/SnackBar/index.js @@ -0,0 +1,5 @@ + + import { lazy } from 'react' + + export const SnackBar = lazy(()=> import("./SnackBar")) + \ No newline at end of file diff --git a/day20/src/components/StripePayments/StripePayments.jsx b/day20/src/components/StripePayments/StripePayments.jsx new file mode 100644 index 0000000..30e42ba --- /dev/null +++ b/day20/src/components/StripePayments/StripePayments.jsx @@ -0,0 +1,76 @@ + +import React, { useState } from "react"; +import { + AdminStripeInvoicesListPageV2, + AdminStripePricesListPage, + AdminStripeSubscriptionsListPage, +} from "Pages/Admin/List"; +import { GlobalContext } from "Context/Global"; +import { LazyLoad } from "Components/LazyLoad"; + +const StripePayments = () => { + const [activeTab, setActiveTab] = useState("plans"); + const { state, dispatch } = React.useContext(GlobalContext); + + const TABS = { + plans: ( + + + + ), + subscriptions: ( + + + + ), + invoices: ( + + + + ), + }; + React.useEffect(() => { + dispatch({ + type: "SETPATH", + payload: { + path: "payments", + }, + }); + }, []); + + return ( +
    +
    +
    +
    setActiveTab("plans")} + > + Plans +
    +
    setActiveTab("subscriptions")} + > + Subscriptions +
    +
    setActiveTab("invoices")} + > + Invoices +
    +
    +
    +
    {TABS[activeTab]}
    +
    + ); +}; + +export default StripePayments; diff --git a/day20/src/components/StripePayments/index.js b/day20/src/components/StripePayments/index.js new file mode 100644 index 0000000..ecb76e7 --- /dev/null +++ b/day20/src/components/StripePayments/index.js @@ -0,0 +1,4 @@ + + import { lazy } from 'react' + export const StripePayments = lazy(()=> import("./StripePayments")) + \ No newline at end of file diff --git a/day20/src/components/TopHeader/TopHeader.jsx b/day20/src/components/TopHeader/TopHeader.jsx new file mode 100644 index 0000000..fd4784b --- /dev/null +++ b/day20/src/components/TopHeader/TopHeader.jsx @@ -0,0 +1,164 @@ + + import React, { useEffect, useState } from "react"; + import { GlobalContext } from "Context/Global"; + import { Link, useLocation } from "react-router-dom"; + import { BackButton } from "Components/BackButton"; + import { AuthContext } from "Context/Auth"; + import { StringCaser } from "Utils/utils"; + import { useProfile } from "Hooks/useProfile"; + + const TopHeader = () => { + const { state: globalState, dispatch: globalDispatch } = + React.useContext(GlobalContext); + const { state, dispatch } = React.useContext(AuthContext); + const [currentPath, setCurrentPath] = useState(""); + const { isOpen, showBackButton } = globalState; + const location = useLocation(); + const [profile] = useProfile(); + + let toggleOpen = (open) => { + dispatch({ + type: "OPEN_SIDEBAR", + payload: { isOpen: open }, + }); + }; + + useEffect(() => { + const pathArr = location.pathname.split("/"); + if (pathArr[1] !== "user" && pathArr[1] !== "admin") { + setCurrentPath(pathArr[1]); + } else { + setCurrentPath(pathArr[2]); + } + }, [location]); + return ( + //
    + // toggleOpen(!isOpen)}> + // + // + // + + // + //
    + +
    +
    + {showBackButton && } +

    + {StringCaser(currentPath, { casetype: "capitalize", separator: " " })} +

    +
    +
    + {/*
    + + + + +
    */} + +
    + +
      +
    • + + + + + + + + + + Account + +
    • +
    • + + dispatch({ + type: "LOGOUT", + }) + } + > + + + + + + Logout + +
    • +
    +
    +
    +
    + ); + }; + + export default TopHeader; + \ No newline at end of file diff --git a/day20/src/components/TopHeader/index.js b/day20/src/components/TopHeader/index.js new file mode 100644 index 0000000..4f72403 --- /dev/null +++ b/day20/src/components/TopHeader/index.js @@ -0,0 +1,5 @@ + + import { lazy } from 'react' + + export const TopHeader = lazy(()=> import("./TopHeader")) + \ No newline at end of file diff --git a/day20/src/components/UppyFileUpload/UppyFileUpload.jsx b/day20/src/components/UppyFileUpload/UppyFileUpload.jsx new file mode 100644 index 0000000..16dd019 --- /dev/null +++ b/day20/src/components/UppyFileUpload/UppyFileUpload.jsx @@ -0,0 +1,88 @@ +import React, { useState, useEffect } from "react"; +import Uppy from "@uppy/core"; +import Webcam from "@uppy/webcam"; +import Tus from "@uppy/tus"; +import GoogleDrive from "@uppy/google-drive"; +import Instagram from '@uppy/instagram'; +import Facebook from '@uppy/facebook'; +import { Dashboard, ProgressBar, FileInput } from "@uppy/react"; + +import "@uppy/core/dist/style.css"; +import "@uppy/dashboard/dist/style.css"; +import "@uppy/drag-drop/dist/style.css"; +import "@uppy/file-input/dist/style.css"; +import "@uppy/progress-bar/dist/style.css"; +import '@uppy/webcam/dist/style.min.css'; +import MkdSDK from "Utils/MkdSDK"; + +const sdk = new MkdSDK(); + +export default function UppyFileUpload({ companionUrl, setFileProcessedUrl }) { + const handleImageChange = async (file) => { + const formData = new FormData(); + formData.append("file", file); + try { + const result = await sdk.uploadImage(formData); + console.log(result.url); + if (setFileProcessedUrl) { + setFileProcessedUrl(result.url); + } + } catch (err) { + console.error(err); + } + }; + + const uppy = new Uppy({ id: "uppy1", autoProceed: true, debug: true }) + // .use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' }) + .use(GoogleDrive, { + companionUrl: companionUrl || "https://companion.uppy.io", + }) + .use(Webcam) + .use(Instagram, { + companionUrl: companionUrl || "https://companion.uppy.io", + }) + .use(Facebook, { + companionUrl: companionUrl || "https://companion.uppy.io", + }) + + const uppy3 = new Uppy({ autoProceed: false, allowMultipleUploadBatches: false }); + + uppy3.on("file-added", (file) => { + handleImageChange(file.data); + }); + + uppy.on("file-added", (file) => { + handleImageChange(file.data); + }); + + // uppy.on('complete', (file) => { + // console.log("Complete action", file) + // }); + + useEffect(() => { + return () => { + uppy.close({ reason: "unmount" }); + // uppy2.close({ reason: 'unmount' }); + }; + }, [uppy]); + + return ( +
    +

    File Uploader

    + + + +

    Progress Bar

    + + +

    File Input

    + +
    + ); +} diff --git a/day20/src/components/UppyFileUpload/index.js b/day20/src/components/UppyFileUpload/index.js new file mode 100644 index 0000000..908e496 --- /dev/null +++ b/day20/src/components/UppyFileUpload/index.js @@ -0,0 +1,5 @@ + + import { lazy } from 'react' + + export const UppyFileUpload = lazy(()=> import("./UppyFileUpload")) + \ No newline at end of file diff --git a/day20/src/components/video/index.js b/day20/src/components/video/index.js new file mode 100644 index 0000000..9d3f08a --- /dev/null +++ b/day20/src/components/video/index.js @@ -0,0 +1,5 @@ + + import {lazy} from 'react' + export const VideoItem = lazy( ()=> import('./VideoItem')) +export const Video = lazy( ()=> import('./Video')) + \ No newline at end of file diff --git a/day20/src/components/video/video.jsx b/day20/src/components/video/video.jsx new file mode 100644 index 0000000..2ef4e8b --- /dev/null +++ b/day20/src/components/video/video.jsx @@ -0,0 +1,234 @@ + +import { useState, useEffect } from "react"; +import TwilioVideo from "twilio-video"; +import VideoItem from "./VideoItem"; +import React from "react"; + +function Video() { + const [room, setRoom] = useState(null); + const [roomName, setRoomName] = useState("room"); + const [userName, setUserName] = useState("room"); + const [audio, setAudio] = useState(null); + const [video, setVideo] = useState(null); + const [error, setError] = useState(""); + + useEffect(() => { + const handleTabClose = (event) => { + event.preventDefault(); + console.log(room); + + console.log("beforeunload event triggered"); + document.getElementById("local-participant") && (document.getElementById("local-participant").style.display = "none"); + document.getElementById("remote-participant") && (document.getElementById("remote-participant").style.display = "none"); + document.getElementById("leave-btn") && (document.getElementById("leave-btn").style.display = "none"); + return room && room.disconnect(); + }; + + window.addEventListener("beforeunload", handleTabClose); + + return () => { + room && room.disconnect(); + window.removeEventListener("beforeunload", handleTabClose); + }; + }, [room]); + + const startMeeting = async () => { + try { + setError(""); + + function participantDisconnected(participant, room) { + document.getElementById("remote-participant") && (document.getElementById("remote-participant").style.display = "none"); + } + + function trackPublished(publication, participant) { + // If the TrackPublication is already subscribed to, then attach the Track to the DOM. + if (publication.track) { + if (track.kind === "video") setVideo(publication.track); + else setAudio(publication.track); + } + + // Once the TrackPublication is subscribed to, attach the Track to the DOM. + publication.on("subscribed", (track) => { + if (track.kind === "video") setVideo(publication.track); + else setAudio(publication.track); + document.getElementById("local-participant") && (document.getElementById("local-participant").style.display = "block"); + document.getElementById("remote-participant") && (document.getElementById("remote-participant").style.display = "block"); + document.getElementById("leave-btn") && (document.getElementById("leave-btn").style.display = "block"); + }); + + // Once the TrackPublication is unsubscribed from, detach the Track from the DOM. + // publication.on("unsubscribed", (track) => { + // detachTrack(track, participant); + // }); + } + + function participantConnected(participant, room) { + // Handle the TrackPublications already published by the Participant. + participant.tracks.forEach((publication) => { + trackPublished(publication, participant); + }); + + // Handle theTrackPublications that will be published by the Participant later. + participant.on("trackPublished", (publication) => { + trackPublished(publication, participant); + }); + } + + const res = await fetch("http://localhost:3048/v2/api/lambda/video/token", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + identity: userName + Math.random().toFixed(2), + room: roomName + }) + }); + const roomInfo = await res.json(); + + const connectOptions = { + // Available only in Small Group or Group Rooms only. Please set "Room Type" + // to "Group" or "Small Group" in your Twilio Console: + // https://www.twilio.com/console/video/configure + bandwidthProfile: { + video: { + dominantSpeakerPriority: "high", + mode: "collaboration", + clientTrackSwitchOffControl: "auto", + contentPreferencesMode: "auto" + } + }, + + // Available only in Small Group or Group Rooms only. Please set "Room Type" + // to "Group" or "Small Group" in your Twilio Console: + // https://www.twilio.com/console/video/configure + dominantSpeaker: true, + + // Comment this line if you are playing music. + maxAudioBitrate: 16000, + + // VP8 simulcast enables the media server in a Small Group or Group Room + // to adapt your encoded video quality for each RemoteParticipant based on + // their individual bandwidth constraints. This has no utility if you are + // using Peer-to-Peer Rooms, so you can comment this line. + preferredVideoCodecs: [{ codec: "VP8", simulcast: true }], + + // Capture 720p video @ 24 fps. + video: { height: 720, frameRate: 24, width: 1280 } + }; + + const dvcs = await navigator.mediaDevices.enumerateDevices(); + const audioDevice = dvcs.filter((dvc) => dvc.kind === "audioinput"); + const videoDevice = dvcs.filter((dvc) => dvc.kind === "videoinput"); + + connectOptions.audio = { + deviceId: { + exact: audioDevice.deviceId + } + }; + + // Add the specified Room name to ConnectOptions. + connectOptions.name = roomName; + + // Add the specified video device ID to ConnectOptions. + connectOptions.video.deviceId = { + exact: videoDevice.deviceId + }; + + const room = await TwilioVideo.connect(roomInfo.data, connectOptions); + setRoom(room); + document.getElementById("local-participant") && (document.getElementById("local-participant").style.display = "block"); + document.getElementById("leave-btn") && (document.getElementById("leave-btn").style.display = "block"); + + // Subscribe to the media published by RemoteParticipants already in the Room. + room.participants.forEach((participant) => { + participantConnected(participant, room); + }); + + // Subscribe to the media published by RemoteParticipants joining the Room later. + room.on("participantConnected", (participant) => { + participantConnected(participant, room); + }); + + room.on("participantDisconnected", (participant) => { + participantDisconnected(participant, room); + }); + + room.once("disconnected", (room, error) => { + participantDisconnected(); + }); + } catch (err) { + if (err.message === "Room contains too many Participants") setError("Room contains too many Participants."); + else setError("Something went wrong."); + document.getElementById("start-btn") && (document.getElementById("start-btn").style.display = "block"); + console.error(err); + } + }; + + return ( + <> +
    + { + setUserName(e.target.value); + }} + /> + { + setRoomName(e.target.value); + }} + /> + +
    + {error &&
    {error + " Please try again later!"}
    } + {room && ( +
    +
    + +
    + {audio && video && ( +
    + +
    + )} + +
    + )} + + ); +} + +export default Video; diff --git a/day20/src/components/video/videoItem.jsx b/day20/src/components/video/videoItem.jsx new file mode 100644 index 0000000..10665a9 --- /dev/null +++ b/day20/src/components/video/videoItem.jsx @@ -0,0 +1,29 @@ + +import { useRef, useEffect } from "react"; + +function VideoItem({ videoStream, audioStream, muted }) { + const videoRef = useRef(null); + const audioRef = useRef(null); + + useEffect(() => { + videoRef.current && videoStream?.attach(videoRef.current); + audioRef.current && audioStream?.attach(audioRef.current); + }, [videoStream, audioStream]); + + return ( + <> + + + + ); +} + +export default VideoItem; diff --git a/day20/src/context/Auth/AuthContext.jsx b/day20/src/context/Auth/AuthContext.jsx new file mode 100644 index 0000000..7ae6010 --- /dev/null +++ b/day20/src/context/Auth/AuthContext.jsx @@ -0,0 +1,127 @@ + + import React, { useReducer } from "react"; + import MkdSDK from "Utils/MkdSDK"; + const initialState = { + isAuthenticated: false, + user: null, + userDetails: { + firstName: null, + lastName: null, + photo: null, + }, + token: null, + role: null, + sessionExpired: null, + }; + export const AuthContext = React.createContext(initialState); + + const reducer = (state, action) => { + switch (action.type) { + case "LOGIN": + localStorage.setItem("user", Number(action.payload.user_id)); + localStorage.setItem("token", action.payload.token); + localStorage.setItem("role", action.payload.role); + return { + ...state, + isAuthenticated: true, + user: Number(localStorage.getItem("user")), + token: localStorage.getItem("token"), + role: localStorage.getItem("role"), + userDetails: { + firstName: action.payload.first_name, + lastName: action.payload.last_name, + photo: action.payload.photo, + }, + }; + case "UPDATE_PROFILE": + return { + ...state, + role: localStorage.getItem("role"), + profile: { ...action?.payload }, + }; + case "LOGOUT": + localStorage.removeItem("user"); + localStorage.removeItem("token"); + return { + ...state, + isAuthenticated: false, + user: null, + }; + case "SESSION_EXPIRED": + return { + ...state, + sessionExpired: action?.payload, + }; + default: + return state; + } + }; + + let sdk = new MkdSDK(); + + export const tokenExpireError = (dispatch, errorMessage) => { + /** + * either this or we pass the role as a parameter + */ + const role = localStorage.getItem("role"); + if (errorMessage === "TOKEN_EXPIRED") { + dispatch({ type: "SESSION_EXPIRED", payload: true }); + // dispatch({ + // type: "LOGOUT", + // }); + + // location.href = "/" + role + "/login"; + } + }; + + const AuthProvider = ({ children }) => { + const [state, dispatch] = useReducer(reducer, initialState); + + React.useEffect(() => { + const user = localStorage.getItem("user"); + const token = localStorage.getItem("token"); + const role = localStorage.getItem("role"); + + if (token) { + (async function () { + try { + const result = await sdk.check(role); + dispatch({ + type: "LOGIN", + payload: { + user_id: user, + token, + role: role, + }, + }); + } catch (error) { + if (role) { + dispatch({ + type: "LOGOUT", + }); + window.location.href = "/" + role + "/login"; + } else { + dispatch({ + type: "LOGOUT", + }); + window.location.href = "/"; + } + } + })(); + } + }, []); + + return ( + + {children} + + ); + }; + + export default AuthProvider; + \ No newline at end of file diff --git a/day20/src/context/Auth/index.js b/day20/src/context/Auth/index.js new file mode 100644 index 0000000..9357863 --- /dev/null +++ b/day20/src/context/Auth/index.js @@ -0,0 +1,3 @@ + + export {default as AuthProvider,AuthContext, tokenExpireError} from "./AuthContext" + \ No newline at end of file diff --git a/day20/src/context/Global/GlobalActions.jsx b/day20/src/context/Global/GlobalActions.jsx new file mode 100644 index 0000000..e6e5d2b --- /dev/null +++ b/day20/src/context/Global/GlobalActions.jsx @@ -0,0 +1,370 @@ + + import { tokenExpireError } from "Context/Auth"; + +import MkdSDK from "Utils/MkdSDK"; + +import { + REQUEST_FAILED, + REQUEST_LOADING, + REQUEST_SUCCESS, + RequestItems, + SET_GLOBAL_PROPERTY, +} from "./GlobalConstants"; + +import { showToast } from "./GlobalContext"; +import TreeSDK from "Utils/TreeSDK"; + +/** + * @param {any} dispatch - Global Dispatch + * @param {String | Number | Boolean | Object} data - + * @param {String} property - any propert name + */ + +export const setGLobalProperty = (dispatch, data, property) => { + // console.log("property >>", property); + dispatch({ + property, + type: SET_GLOBAL_PROPERTY, + payload: data, + }); +}; + +/** + * @param {any} dispatch - Global Dispatch + * @param {boolean} data - true || false + * @param { String } item - string of the needed action + */ + +export const setLoading = (dispatch, data, item, where) => { + // console.log("setLoading data >>", data, where); + dispatch({ + item, + type: REQUEST_LOADING, + payload: data, + }); +}; + +/** + * @param {any} dispatch - Global Dispatch + * @param {Array | any} data - any[] + * @param { String } item - string of the needed action + */ + +export const dataSuccess = (dispatch, data, item) => { + dispatch({ + item, + type: REQUEST_SUCCESS, + payload: data, + }); +}; +/** + * @param {any} dispatch - Global Dispatch + * @param {Array | any} data - any[] + * @param { String } item - string of the needed action + */ + +export const dataFailure = (dispatch, data, item) => { + dispatch({ + item, + type: REQUEST_FAILED, + payload: data, + }); +}; + +/** + * @param {any} globalDispatch - Global Dispatch + * @param {any} authDispatch - Auth Dispatch + * @param {String} table - table model + * @param {Number | String} id - id + * @param {String} method - method + */ + +export const getSingleModel = async ( + globalDispatch, + authDispatch, + table, + id, + method = "GET", + join = null +) => { + const sdk = new MkdSDK(); + setLoading(globalDispatch, true, RequestItems.viewModel); + try { + sdk.setTable(table.trim()); + const result = await sdk.callRestAPI( + { id: Number(id), ...{ ...(join ? { join: join } : null) } }, + method + ); + + if (!result?.error) { + dataSuccess( + globalDispatch, + { data: result?.model }, + RequestItems.viewModel + ); + } + setLoading(globalDispatch, false, RequestItems.viewModel); + // showToast(globalDispatch, "Project Saved", 4000, "info"); + } catch (error) { + const message = error?.response?.data?.message + ? error?.response?.data?.message + : error?.message; + setLoading(globalDispatch, false, RequestItems.viewModel); + dataFailure(globalDispatch, { message, id }, RequestItems.viewModel); + showToast(globalDispatch, message, 4000, "error"); + tokenExpireError(authDispatch, message); + } +}; + +/** + * @param {any} globalDispatch - Global Dispatch + * @param {any} authDispatch - Auth Dispatch + * @param {String} table - table model + * @param {Array[Number] | Array[String]} ids - list of ids + * @param { String } join - join table + */ + +export const getManyByIds = async ( + globalDispatch, + authDispatch, + table, + ids, + join = null +) => { + const tdk = new TreeSDK(); + setLoading(globalDispatch, true, RequestItems.listModel); + try { + // const filters = ids.map((id) => computeFilter("id", "in", id)); + const result = await tdk.getList(table, { + ...{ + ...(join ? { join: join } : null), + filter: [`id,in,${ids.join(",")}`], + }, + }); + + if (!result?.error) { + dataSuccess( + globalDispatch, + { data: result?.list }, + RequestItems.listModel + ); + } + setLoading(globalDispatch, false, RequestItems.listModel); + // showToast(globalDispatch, "Project Saved", 4000, "info"); + } catch (error) { + const message = error?.response?.data?.message + ? error?.response?.data?.message + : error?.message; + setLoading(globalDispatch, false, RequestItems.listModel); + dataFailure(globalDispatch, { message, id }, RequestItems.listModel); + showToast(globalDispatch, message, 4000, "error"); + tokenExpireError(authDispatch, message); + } +}; + +/** + * @param {any} globalDispatch - Global Dispatch + * @param {any} authDispatch - Auth Dispatch + * @param {String} table - table model + * @param { Object } payload - data to create + */ + +export const createRequest = async ( + globalDispatch, + authDispatch, + table, + payload +) => { + const tdk = new TreeSDK(); + setLoading(globalDispatch, true, RequestItems.createModel); + try { + const result = await tdk.create(table, payload); + + if (!result?.error) { + dataSuccess( + globalDispatch, + { message: result?.message }, + RequestItems.createModel + ); + setLoading(globalDispatch, false, RequestItems.createModel); + showToast(globalDispatch, result?.message, 4000, "success"); + return { error: false }; + } else { + setLoading(globalDispatch, false, RequestItems.createModel); + showToast(globalDispatch, result?.message, 4000, "error"); + return { error: true }; + } + } catch (error) { + const message = error?.response?.data?.message + ? error?.response?.data?.message + : error?.message; + setLoading(globalDispatch, false, RequestItems.createModel); + dataFailure(globalDispatch, { message }, RequestItems.createModel); + showToast(globalDispatch, message, 4000, "error"); + tokenExpireError(authDispatch, message); + return { error: true }; + } +}; + +/** + * @param {any} globalDispatch - Global Dispatch + * @param {any} authDispatch - Auth Dispatch + * @param {String} table - table model + * @param {String | Number} id - item id + * @param { Object } payload - data to create + */ + +export const updateRequest = async ( + globalDispatch, + authDispatch, + table, + id, + payload +) => { + const tdk = new TreeSDK(); + setLoading(globalDispatch, true, RequestItems.updateModel); + try { + const result = await tdk.update(table, id, payload); + + if (!result?.error) { + // dataSuccess(globalDispatch, { message: result?.message }, RequestItems.updateModel); + setLoading(globalDispatch, false, RequestItems.updateModel); + showToast(globalDispatch, result?.message, 4000, "success"); + return { error: false }; + } else { + setLoading(globalDispatch, false, RequestItems.updateModel); + showToast(globalDispatch, result?.message, 4000, "error"); + return { error: true }; + } + } catch (error) { + const message = error?.response?.data?.message + ? error?.response?.data?.message + : error?.message; + setLoading(globalDispatch, false, RequestItems.updateModel); + // dataFailure(globalDispatch, { message }, RequestItems.updateModel); + showToast(globalDispatch, message, 4000, "error"); + tokenExpireError(authDispatch, message); + return { error: true }; + } +}; + +/** + * @param {any} globalDispatch - Global Dispatch + * @param {any} authDispatch - Auth Dispatch + * @param {String} table - table model + * @param { Object } options - options are filter, join, size, direction and order query + * @param { Array } [options.filter=null] filter: [] + * @param { String } [options.join=null] join: "table1,table2,table3" - joins another table + * @param { Number } [options.size=10] size number of items per page + * @param { String } [options.order=id] order by any field | default is the id field + * @param { String } [options.direction=desc] direction, desc or asc | desc is the default + */ + +export const getList = async ( + globalDispatch, + authDispatch, + table, + options = {} +) => { + const tdk = new TreeSDK(); + setLoading(globalDispatch, true, table); + try { + const result = await tdk.getList(table, options); + + if (!result?.error) { + // dataSuccess(globalDispatch, { message: result?.message }, table); + setLoading(globalDispatch, false, table); + // showToast(globalDispatch, result?.message, 4000, "success"); + return { error: false, data: result?.list }; + } else { + setLoading(globalDispatch, false, table); + // showToast(globalDispatch, result?.message, 4000, "error"); + return { error: true }; + } + } catch (error) { + const message = error?.response?.data?.message + ? error?.response?.data?.message + : error?.message; + setLoading(globalDispatch, false, table); + // dataFailure(globalDispatch, { message }, table); + // showToast(globalDispatch, message, 4000, "error"); + tokenExpireError(authDispatch, message); + return { error: true }; + } +}; + +/** + * @param {any} globalDispatch - Global Dispatch + * @param {any} authDispatch - Auth Dispatch + * @param {String} endpoint - the api endpoint + * @param { Object } payload - payload data + * @param { String } state - a field in the global content to access the loading state + */ + +export const customCreateRequest = async ( + globalDispatch, + authDispatch, + endpoint, + payload, + state +) => { + const sdk = new MkdSDK(); + setLoading(globalDispatch, true, state); + try { + const result = await sdk.customCreateRequest(endpoint, payload); + + if (!result?.error) { + // dataSuccess(globalDispatch, { message: result?.message }, state); + setLoading(globalDispatch, false, state); + showToast(globalDispatch, result?.message, 4000, "success"); + return { error: false }; + } else { + setLoading(globalDispatch, false, state); + showToast(globalDispatch, result?.message, 4000, "error"); + return { error: true, validation: result?.validation }; + } + } catch (error) { + const message = error?.response?.data?.message + ? error?.response?.data?.message + : error?.message; + setLoading(globalDispatch, false, state); + // dataFailure(globalDispatch, { message }, state); + showToast(globalDispatch, message, 4000, "error"); + tokenExpireError(authDispatch, message); + return { error: true, validation: result?.validation }; + } +}; + +// BASIC SETUP ABOVE + +/** + * @param {any} globalDispatch - Global Dispatch + * @param {any} authDispatch - Auth Dispatch + * @param {Number | String} id - project id + */ + +export const getProject = async (globalDispatch, authDispatch, id) => { + const sdk = new MkdSDK(); + setLoading(globalDispatch, true, RequestItems.Project); + try { + const result = await sdk.getProject(id); + + if (!result?.error) { + dataSuccess(globalDispatch, result?.model, RequestItems.Project); + } + setLoading(globalDispatch, false, RequestItems.Project); + // showToast(globalDispatch, "Project Saved", 4000, "info"); + } catch (error) { + const message = error?.response?.data?.message + ? error?.response?.data?.message + : error?.message; + setLoading(globalDispatch, false, RequestItems.Project); + dataFailure(globalDispatch, { message, id }, RequestItems.Project); + showToast(globalDispatch, message, 4000, "error"); + tokenExpireError(authDispatch, message); + } +}; + +function computeFilter(field, operator, value) { + return `${field},${operator},${value}`; +} diff --git a/day20/src/context/Global/GlobalConstants.jsx b/day20/src/context/Global/GlobalConstants.jsx new file mode 100644 index 0000000..3f45253 --- /dev/null +++ b/day20/src/context/Global/GlobalConstants.jsx @@ -0,0 +1,16 @@ + + export const REQUEST_LOADING = "REQUEST_LOADING"; +export const REQUEST_SUCCESS = "REQUEST_SUCCESS"; +export const REQUEST_FAILED = "REQUEST_FAILED"; +export const SET_GLOBAL_PROPERTY = "SET_GLOBAL_PROPERTY"; + +const RequestItems = { + viewModel: "viewModel", + createModel: "createModel", + updateModel: "updateModel", + listModel: "listModel", +}; + +Object.freeze(RequestItems); + +export { RequestItems }; diff --git a/day20/src/context/Global/GlobalContext.jsx b/day20/src/context/Global/GlobalContext.jsx new file mode 100644 index 0000000..1033cb4 --- /dev/null +++ b/day20/src/context/Global/GlobalContext.jsx @@ -0,0 +1,224 @@ + + import React, { useReducer } from "react"; +// import { DefaultInitialState } from "./InitialStates"; +import { + REQUEST_FAILED, + REQUEST_LOADING, + REQUEST_SUCCESS, + SET_GLOBAL_PROPERTY, +} from "./GlobalConstants"; + +/** + * @typedef {Object} GlobalState + * @property {string} globalMessage - Toast Message. + * @property {"success"| "error" | "warning"} toastStatus - Toast State - "success" | "error" | "warning". + * @property {boolean} isOpen + * @property {string} path + * @property {any} projectRow + */ + +/** + * The Value of the Global State . + * @param {GlobalState} initialState + */ + +const initialState = { + globalMessage: "", + toastStatus: "success", + isOpen: true, + showBackButton: false, + path: "", + projectRow: null, + leftPanel: [], + middlePanel: [], + rightPanel: {}, + selectedComponent: 0, + rightComponentId: "", + selectedPageComponent: 0, + rooms: [], +}; + +export const GlobalContext = React.createContext(initialState); + +const reducer = (state, action) => { + switch (action.type) { + case "SNACKBAR": + return { + ...state, + globalMessage: action.payload.message, + toastStatus: action.payload.toastStatus, + }; + case "SETPATH": + return { + ...state, + path: action.payload.path, + }; + case "OPEN_SIDEBAR": + return { + ...state, + isOpen: action.payload.isOpen, + }; + case "SHOW_BACKBUTTON": + return { + ...state, + showBackButton: action.payload.showBackButton, + }; + case "SET_PROJECT_ROW": + return { + ...state, + projectRow: action.payload, + }; + case "SET_LEFT_PANEL": + return { + ...state, + leftPanel: action.payload, + }; + case "SET_MIDDLE_PANEL": + return { + ...state, + middlePanel: action.payload, + }; + case "SET_RIGHT_PANEL": + return { + ...state, + rightPanel: action.payload, + rightComponentId: action.rightComponentId, + }; + case "SET_SELECTED_COMPONENT": + return { + ...state, + selectedComponent: action.payload, + }; + case "SET_SELECTED_PAGE_COMPONENT": + return { + ...state, + selectedPageComponent: action.payload, + }; + case "SETROOM": + const existingRoom = state.rooms.find( + (rooms) => rooms.position === action.payload.position + ); + if (existingRoom) { + // Update the item in the array + const updatedItems = [...state?.rooms]; + updatedItems[action.payload.position] = { + position: action.payload.position, + value: action.payload.value, + }; + return { + ...state, + + rooms: updatedItems, + }; + } else { + return { + ...state, + rooms: [...state?.rooms, action.payload], + }; + } + case SET_GLOBAL_PROPERTY: + if (action.property.includes(".")) { + const [prop, field] = action.property.split("."); + return { + ...state, + [prop]: { ...state[prop], [field]: action?.payload }, + }; + } else { + return { + ...state, + [action.property]: action?.payload, + }; + } + case REQUEST_LOADING: + return { + ...state, + [action.item]: { + ...state[action?.item], + loading: action?.payload, + }, + }; + case REQUEST_SUCCESS: + return { + ...state, + [action.item]: { + ...state[action?.item], + ...action?.payload, + data: action?.payload?.data, + error: false, + success: true, + loading: false, + }, + }; + case REQUEST_FAILED: + return { + ...state, + [action.item]: { + ...state[action?.item], + ...action?.payload, + error: true, + success: false, + loading: false, + }, + }; + default: + return state; + } +}; + +/** + * @param {"success"| "error" | "warning"} toastStatus + * @param {any} dispatch + * @param {string} message + * @param {number} timeout + */ + +export const showToast = ( + dispatch, + message, + timeout = 3000, + toastStatus = "success" +) => { + dispatch({ + type: "SNACKBAR", + payload: { + message, + toastStatus, + }, + }); + + setTimeout(() => { + dispatch({ + type: "SNACKBAR", + payload: { + message: "", + }, + }); + }, timeout); +}; + +export const setGlobalProjectRow = (dispatch, data) => { + dispatch({ + type: "SET_PROJECT_ROW", + payload: data, + }); +}; + +const GlobalProvider = ({ children }) => { + const [state, dispatch] = useReducer(reducer, initialState); + // React.useEffect(() => { + + // }, []); + + return ( + + {children} + + ); +}; + +export default GlobalProvider; diff --git a/day20/src/context/Global/InitialGlobalStates.jsx b/day20/src/context/Global/InitialGlobalStates.jsx new file mode 100644 index 0000000..f8c19a1 --- /dev/null +++ b/day20/src/context/Global/InitialGlobalStates.jsx @@ -0,0 +1,14 @@ + +/** + * @property {boolean} loading + * @property {boolean} success + * @property {boolean} error + * @property {Array} data + * */ + +export const PropertyInitialState = { + loading: false, + success: false, + error: false, + data: [], +}; diff --git a/day20/src/context/Global/index.js b/day20/src/context/Global/index.js new file mode 100644 index 0000000..416ce1d --- /dev/null +++ b/day20/src/context/Global/index.js @@ -0,0 +1,28 @@ + + export { + default as GlobalProvider, + GlobalContext, + showToast, + setGlobalProjectRow, + } from "./GlobalContext"; + export { PropertyInitialState } from "./InitialGlobalStates"; + + export { + setGLobalProperty, + setLoading, + getSingleModel, + getManyByIds, + createRequest, + updateRequest, + getList, + customCreateRequest, + } from "./GlobalActions"; + + export { + RequestItems, + REQUEST_FAILED, + REQUEST_LOADING, + REQUEST_SUCCESS, + SET_GLOBAL_PROPERTY, + } from "./GlobalConstants"; + \ No newline at end of file diff --git a/day20/src/hooks/useProfile/index.js b/day20/src/hooks/useProfile/index.js new file mode 100644 index 0000000..232884e --- /dev/null +++ b/day20/src/hooks/useProfile/index.js @@ -0,0 +1,2 @@ + + export { default as useProfile } from "./useProfile"; diff --git a/day20/src/hooks/useProfile/useProfile.jsx b/day20/src/hooks/useProfile/useProfile.jsx new file mode 100644 index 0000000..94dc3fd --- /dev/null +++ b/day20/src/hooks/useProfile/useProfile.jsx @@ -0,0 +1,46 @@ + + import React from "react"; +import MkdSDK from "Utils/MkdSDK"; +import { AuthContext, tokenExpireError } from "Context/Auth"; + +const useProfile = (refresh = false) => { + const sdk = new MkdSDK(); + + const { + state: { profile: AuthProfile }, + dispatch, + } = React.useContext(AuthContext); + const [profile, setProfile] = React.useState(null); + + const getProfile = React.useCallback(() => { + (async () => { + try { + const result = await sdk.getProfile(); + console.log(result); + if (!result?.error) { + setProfile(() => result); + dispatch({ + type: "UPDATE_PROFILE", + payload: result, + }); + } + } catch (error) { + console.log(error.message); + tokenExpireError(dispatch, error.message); + } + })(); + }, [profile]); + + React.useEffect(() => { + if (!AuthProfile || refresh) { + getProfile(); + } else { + setProfile(AuthProfile); + } + // console.log("AuthProfile >>", AuthProfile); + }, [AuthProfile]); + + return [profile, setProfile]; +}; + +export default useProfile; diff --git a/day20/src/index.css b/day20/src/index.css new file mode 100644 index 0000000..6f6dd86 --- /dev/null +++ b/day20/src/index.css @@ -0,0 +1,3435 @@ + + * { + margin: 0; + padding: 0; + box-sizing: border-box; + scrollbar-width: .625rem; + scroll-padding: 6.25rem; + /* transition: all 0.5s ease-in-out; */ +} +html { + font-size: .875rem; +} +::-webkit-scrollbar { + width: .625rem; + border-radius: .75rem; +} + +::-webkit-scrollbar-thumb { + background-color: #a8a8a8; + border-radius: .75rem; +} +body { + position: relative; + 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; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} +.react-toggle { + touch-action: pan-x; + + display: inline-block; + position: relative; + cursor: pointer; + background-color: transparent; + border: 0; + padding: 0; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-tap-highlight-color: transparent; +} + +.react-toggle-screenreader-only { + border: 0; + clip: rect(0 0 0 0); + height: .0625rem; + margin: -0.0625rem; + overflow: hidden; + padding: 0; + position: absolute; + width: .0625rem; +} + +.react-toggle--disabled { + cursor: not-allowed; + opacity: 0.5; + -webkit-transition: opacity 0.25s; + transition: opacity 0.25s; +} + +.react-toggle-track { + width: 3.125rem; + height: 1.5rem; + padding: 0; + border-radius: 1.875rem; + background-color: #e4f1f7; + -webkit-transition: all 0.2s ease; + -moz-transition: all 0.2s ease; + transition: all 0.2s ease; +} + +.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track { + background-color: #eeeeee; +} + +.react-toggle--checked .react-toggle-track, +.react-toggle--checked .react-toggle-track:hover { + background-color: #4f46e5; +} + +.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track { + background-color: #4f46e5; +} + +.react-toggle-track-check { + position: absolute; + width: .875rem; + height: .625rem; + top: 0rem; + bottom: 0rem; + margin-top: auto; + margin-bottom: auto; + line-height: 0; + left: .5rem; + opacity: 0; + -webkit-transition: opacity 0.25s ease; + -moz-transition: opacity 0.25s ease; + transition: opacity 0.25s ease; +} + +.react-toggle--checked .react-toggle-track-check { + opacity: 1; + -webkit-transition: opacity 0.25s ease; + -moz-transition: opacity 0.25s ease; + transition: opacity 0.25s ease; +} + +.react-toggle-track-x { + position: absolute; + width: .625rem; + height: .625rem; + top: 0rem; + bottom: 0rem; + margin-top: auto; + margin-bottom: auto; + line-height: 0; + right: .625rem; + opacity: 1; + -webkit-transition: opacity 0.25s ease; + -moz-transition: opacity 0.25s ease; + transition: opacity 0.25s ease; +} + +.react-toggle--checked .react-toggle-track-x { + opacity: 0; +} + +.react-toggle-thumb { + transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms; + position: absolute; + top: .0625rem; + left: .0625rem; + width: 1.375rem; + height: 1.375rem; + border: .0625rem solid #fafafa; + border-radius: 50%; + background-color: #fafafa; + + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + + -webkit-transition: all 0.25s ease; + -moz-transition: all 0.25s ease; + transition: all 0.25s ease; +} + +.react-toggle--checked .react-toggle-thumb { + left: 1.6875rem; + border-color: #4f46e5; +} + +.react-toggle--focus .react-toggle-thumb { + -webkit-box-shadow: 0rem 0rem .1875rem .125rem #4f46e5; + -moz-box-shadow: 0rem 0rem .1875rem .125rem #4f46e5; + box-shadow: 0rem 0rem .125rem .1875rem #4f46e5; +} + +.react-toggle:active:not(.react-toggle--disabled) .react-toggle-thumb { + -webkit-box-shadow: 0rem 0rem .3125rem .3125rem #4f46e5; + -moz-box-shadow: 0rem 0rem .3125rem .3125rem #4f46e5; + box-shadow: 0rem 0rem .3125rem .3125rem #4f46e5; +} +.scrollbar-hide::-webkit-scrollbar { + display: none; +} +.scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; +} +.sidebar-holder { + width: 100%; + min-width: 15rem; + max-width: 15rem; + position: relative; + background: #151515; + color: #fff; + z-index: 2; + /* transition: all 0.3s; */ + min-height: 100vh; + overflow: hidden; + transition: 0.2s; +} +.open-nav { + min-width: 0rem !important; + max-width: 0rem !important; + width: 0 !important; + transition: 0.2s; + opacity: 0; +} + +.sidebar-list ul li a { + padding: .625rem; + display: block; + width: 100%; + font-size: .875rem; + font-weight: 600; + transition: 0.2s ease-in; + text-transform: capitalize; +} + +.sidebar-list .active-nav { + padding: .75rem; + color: #262626; + border-radius: .375rem; + background: #f4f4f4; +} + +.sidebar-list .active-nav:hover { + background: #f4f4f4; +} + +.sidebar-list ul li a:hover { + color: #262626; +} + +.page-header { + width: 100%; + padding: 1.25rem; + background: white; +} +.page-header span { + cursor: pointer; + display: block; + width: fit-content; + font-size: 1.25rem; +} + +.center-svg { + aspect-ratio: 1/1; + align-items: center; + justify-content: center; + line-height: 1.2em !important; +} + +.uppy-Dashboard-inner { + width: 100% !important; +} + +@tailwind base; +@tailwind components; +@tailwind utilities; + +@media screen and (max-width: 47.9375rem) { + .sidebar-holder { + width: 100%; + min-width: 12.5rem; + max-width: 12.5rem; + position: fixed; + top: 0; + left: 0; + } + .page-header span { + margin-left: auto; + } +} + +@media (max-width: 61.9988rem) { + .offcanvas-collapse { + position: fixed; + top: 3.5rem; /* Height of navbar */ + bottom: 0; + left: 100%; + width: 100%; + padding-right: 16px; + padding-left: 16px; + overflow-y: auto; + visibility: hidden; + background-color: #f8f9fa; + transition: transform 0.3s ease-in-out, visibility 0.3s ease-in-out; + } + .offcanvas-collapse.open { + visibility: visible; + transform: translateX(-100%); + } +} + +.bg-custom-light { + background-color: #e7e7e7; +} + +.bg-dim { + background-color: rgba(0, 0, 0, 0.6); +} + +.cover { + object-fit: cover; +} + +.opacity-6 { + opacity: 0.6; +} + +.opacity-5 { + opacity: 0.5; +} + +.image-vertical-scroller { + overflow-y: auto; + max-height: 28.75rem; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; +} + +.image-vertical-scroller::-webkit-scrollbar { + display: none; +} + +.h-scroller { + position: relative; + z-index: 2; + height: 60px; + overflow-y: hidden; +} + +.h-scroller .nav { + display: flex; + flex-wrap: nowrap; + padding-bottom: 16px; + margin-top: -0.0625rem; + overflow-x: auto; + color: rgba(255, 255, 255, 0.75); + text-align: center; + white-space: nowrap; + -webkit-overflow-scrolling: touch; +} + +.h-underline .h-link { + padding-top: 12px; + padding-bottom: 12px; + font-size: 14px; + color: #6c757d; +} + +.list-group { + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + border-radius: 4px; +} + +.list-group-flush { + border-radius: 0; +} +.list-group-flush > .list-group-item { + border-width: 0 0 .0625rem; +} +.list-group-flush > .list-group-item:last-child { + border-bottom-width: 0; +} + +.list-group-item { + position: relative; + display: block; + padding: 8px 16px; + color: #212529; + text-decoration: none; + background-color: #fff; + border: .0625rem solid rgba(0, 0, 0, 0.125); +} +.list-group-item:first-child { + border-top-left-radius: inherit; + border-top-right-radius: inherit; +} +.list-group-item:last-child { + border-bottom-right-radius: inherit; + border-bottom-left-radius: inherit; +} +.list-group-item.disabled, +.list-group-item:disabled { + color: #6c757d; + pointer-events: none; + background-color: #fff; +} +.list-group-item.active { + z-index: 2; + color: #fff; + background-color: #212529; + border-color: #212529; +} +.list-group-item + .list-group-item { + border-top-width: 0; +} +.list-group-item + .list-group-item.active { + margin-top: -0.0625rem; + border-top-width: .0625rem; +} + +[hidden] { + display: none; +} + +.btn { + display: inline-block; + line-height: 1.5; + text-align: center; + text-decoration: none; + vertical-align: middle; + cursor: pointer; + user-select: none; + border: .0625rem solid transparent; + padding: 6px 12px; + border-radius: 4px; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .btn { + transition: none; + } +} +.btn:hover { + color: #212529; +} +.btn-check:focus + .btn, +.btn:focus { + outline: 0; + box-shadow: 0 0 0 4px rgba(33, 37, 41, 0.25); +} +.btn:disabled, +.btn.disabled, +fieldset:disabled .btn { + pointer-events: none; + opacity: 0.65; +} + +.btn-primary { + color: #fff; + background-color: #212529; + border-color: #212529; +} +.btn-primary:hover { + color: #fff; + background-color: #1c1f23; + border-color: #1a1e21; +} +.btn-check:focus + .btn-primary, +.btn-primary:focus { + color: #fff; + background-color: #1c1f23; + border-color: #1a1e21; + box-shadow: 0 0 0 4px rgba(66, 70, 73, 0.5); +} +.btn-check:checked + .btn-primary, +.btn-check:active + .btn-primary, +.btn-primary:active, +.btn-primary.active, +.show > .btn-primary.dropdown-toggle { + color: #fff; + background-color: #1a1e21; + border-color: #191c1f; +} +.btn-check:checked + .btn-primary:focus, +.btn-check:active + .btn-primary:focus, +.btn-primary:active:focus, +.btn-primary.active:focus, +.show > .btn-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 4px rgba(66, 70, 73, 0.5); +} +.btn-primary:disabled, +.btn-primary.disabled { + color: #fff; + background-color: #212529; + border-color: #212529; +} + +.btn-secondary { + color: #fff; + background-color: #0d6efd; + border-color: #0d6efd; +} +.btn-secondary:hover { + color: #fff; + background-color: #0b5ed7; + border-color: #0a58ca; +} +.btn-check:focus + .btn-secondary, +.btn-secondary:focus { + color: #fff; + background-color: #0b5ed7; + border-color: #0a58ca; + box-shadow: 0 0 0 4px rgba(49, 132, 253, 0.5); +} +.btn-check:checked + .btn-secondary, +.btn-check:active + .btn-secondary, +.btn-secondary:active, +.btn-secondary.active, +.show > .btn-secondary.dropdown-toggle { + color: #fff; + background-color: #0a58ca; + border-color: #0a53be; +} +.btn-check:checked + .btn-secondary:focus, +.btn-check:active + .btn-secondary:focus, +.btn-secondary:active:focus, +.btn-secondary.active:focus, +.show > .btn-secondary.dropdown-toggle:focus { + box-shadow: 0 0 0 4px rgba(49, 132, 253, 0.5); +} +.btn-secondary:disabled, +.btn-secondary.disabled { + color: #fff; + background-color: #0d6efd; + border-color: #0d6efd; +} + +.btn-success { + color: #fff; + background-color: #198754; + border-color: #198754; +} +.btn-success:hover { + color: #fff; + background-color: #157347; + border-color: #146c43; +} +.btn-check:focus + .btn-success, +.btn-success:focus { + color: #fff; + background-color: #157347; + border-color: #146c43; + box-shadow: 0 0 0 4px rgba(60, 153, 110, 0.5); +} +.btn-check:checked + .btn-success, +.btn-check:active + .btn-success, +.btn-success:active, +.btn-success.active, +.show > .btn-success.dropdown-toggle { + color: #fff; + background-color: #146c43; + border-color: #13653f; +} +.btn-check:checked + .btn-success:focus, +.btn-check:active + .btn-success:focus, +.btn-success:active:focus, +.btn-success.active:focus, +.show > .btn-success.dropdown-toggle:focus { + box-shadow: 0 0 0 4px rgba(60, 153, 110, 0.5); +} +.btn-success:disabled, +.btn-success.disabled { + color: #fff; + background-color: #198754; + border-color: #198754; +} + +.btn-info { + color: #000; + background-color: #0dcaf0; + border-color: #0dcaf0; +} +.btn-info:hover { + color: #000; + background-color: #31d2f2; + border-color: #25cff2; +} +.btn-check:focus + .btn-info, +.btn-info:focus { + color: #000; + background-color: #31d2f2; + border-color: #25cff2; + box-shadow: 0 0 0 4px rgba(11, 172, 204, 0.5); +} +.btn-check:checked + .btn-info, +.btn-check:active + .btn-info, +.btn-info:active, +.btn-info.active, +.show > .btn-info.dropdown-toggle { + color: #000; + background-color: #3dd5f3; + border-color: #25cff2; +} +.btn-check:checked + .btn-info:focus, +.btn-check:active + .btn-info:focus, +.btn-info:active:focus, +.btn-info.active:focus, +.show > .btn-info.dropdown-toggle:focus { + box-shadow: 0 0 0 4px rgba(11, 172, 204, 0.5); +} +.btn-info:disabled, +.btn-info.disabled { + color: #000; + background-color: #0dcaf0; + border-color: #0dcaf0; +} + +.btn-warning { + color: #000; + background-color: #ffc107; + border-color: #ffc107; +} +.btn-warning:hover { + color: #000; + background-color: #ffca2c; + border-color: #ffc720; +} +.btn-check:focus + .btn-warning, +.btn-warning:focus { + color: #000; + background-color: #ffca2c; + border-color: #ffc720; + box-shadow: 0 0 0 4px rgba(217, 164, 6, 0.5); +} +.btn-check:checked + .btn-warning, +.btn-check:active + .btn-warning, +.btn-warning:active, +.btn-warning.active, +.show > .btn-warning.dropdown-toggle { + color: #000; + background-color: #ffcd39; + border-color: #ffc720; +} +.btn-check:checked + .btn-warning:focus, +.btn-check:active + .btn-warning:focus, +.btn-warning:active:focus, +.btn-warning.active:focus, +.show > .btn-warning.dropdown-toggle:focus { + box-shadow: 0 0 0 4px rgba(217, 164, 6, 0.5); +} +.btn-warning:disabled, +.btn-warning.disabled { + color: #000; + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-danger { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} +.btn-danger:hover { + color: #fff; + background-color: #bb2d3b; + border-color: #b02a37; +} +.btn-check:focus + .btn-danger, +.btn-danger:focus { + color: #fff; + background-color: #bb2d3b; + border-color: #b02a37; + box-shadow: 0 0 0 4px rgba(225, 83, 97, 0.5); +} +.btn-check:checked + .btn-danger, +.btn-check:active + .btn-danger, +.btn-danger:active, +.btn-danger.active, +.show > .btn-danger.dropdown-toggle { + color: #fff; + background-color: #b02a37; + border-color: #a52834; +} +.btn-check:checked + .btn-danger:focus, +.btn-check:active + .btn-danger:focus, +.btn-danger:active:focus, +.btn-danger.active:focus, +.show > .btn-danger.dropdown-toggle:focus { + box-shadow: 0 0 0 4px rgba(225, 83, 97, 0.5); +} +.btn-danger:disabled, +.btn-danger.disabled { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-light { + color: #000; + background-color: #f8f9fa; + border-color: #f8f9fa; +} +.btn-light:hover { + color: #000; + background-color: #f9fafb; + border-color: #f9fafb; +} +.btn-check:focus + .btn-light, +.btn-light:focus { + color: #000; + background-color: #f9fafb; + border-color: #f9fafb; + box-shadow: 0 0 0 4px rgba(211, 212, 213, 0.5); +} +.btn-check:checked + .btn-light, +.btn-check:active + .btn-light, +.btn-light:active, +.btn-light.active, +.show > .btn-light.dropdown-toggle { + color: #000; + background-color: #f9fafb; + border-color: #f9fafb; +} +.btn-check:checked + .btn-light:focus, +.btn-check:active + .btn-light:focus, +.btn-light:active:focus, +.btn-light.active:focus, +.show > .btn-light.dropdown-toggle:focus { + box-shadow: 0 0 0 4px rgba(211, 212, 213, 0.5); +} +.btn-light:disabled, +.btn-light.disabled { + color: #000; + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-dark { + color: #fff; + background-color: #212529; + border-color: #212529; +} +.btn-dark:hover { + color: #fff; + background-color: #1c1f23; + border-color: #1a1e21; +} +.btn-check:focus + .btn-dark, +.btn-dark:focus { + color: #fff; + background-color: #1c1f23; + border-color: #1a1e21; + box-shadow: 0 0 0 4px rgba(66, 70, 73, 0.5); +} +.btn-check:checked + .btn-dark, +.btn-check:active + .btn-dark, +.btn-dark:active, +.btn-dark.active, +.show > .btn-dark.dropdown-toggle { + color: #fff; + background-color: #1a1e21; + border-color: #191c1f; +} +.btn-check:checked + .btn-dark:focus, +.btn-check:active + .btn-dark:focus, +.btn-dark:active:focus, +.btn-dark.active:focus, +.show > .btn-dark.dropdown-toggle:focus { + box-shadow: 0 0 0 4px rgba(66, 70, 73, 0.5); +} +.btn-dark:disabled, +.btn-dark.disabled { + color: #fff; + background-color: #212529; + border-color: #212529; +} + +.btn-outline-primary { + color: #212529; + border-color: #212529; +} +.btn-outline-primary:hover { + color: #fff; + background-color: #212529; + border-color: #212529; +} +.btn-check:focus + .btn-outline-primary, +.btn-outline-primary:focus { + box-shadow: 0 0 0 4px rgba(33, 37, 41, 0.5); +} +.btn-check:checked + .btn-outline-primary, +.btn-check:active + .btn-outline-primary, +.btn-outline-primary:active, +.btn-outline-primary.active, +.btn-outline-primary.dropdown-toggle.show { + color: #fff; + background-color: #212529; + border-color: #212529; +} +.btn-check:checked + .btn-outline-primary:focus, +.btn-check:active + .btn-outline-primary:focus, +.btn-outline-primary:active:focus, +.btn-outline-primary.active:focus, +.btn-outline-primary.dropdown-toggle.show:focus { + box-shadow: 0 0 0 4px rgba(33, 37, 41, 0.5); +} +.btn-outline-primary:disabled, +.btn-outline-primary.disabled { + color: #212529; + background-color: transparent; +} + +.btn-outline-secondary { + color: #0d6efd; + border-color: #0d6efd; +} +.btn-outline-secondary:hover { + color: #fff; + background-color: #0d6efd; + border-color: #0d6efd; +} +.btn-check:focus + .btn-outline-secondary, +.btn-outline-secondary:focus { + box-shadow: 0 0 0 4px rgba(13, 110, 253, 0.5); +} +.btn-check:checked + .btn-outline-secondary, +.btn-check:active + .btn-outline-secondary, +.btn-outline-secondary:active, +.btn-outline-secondary.active, +.btn-outline-secondary.dropdown-toggle.show { + color: #fff; + background-color: #0d6efd; + border-color: #0d6efd; +} +.btn-check:checked + .btn-outline-secondary:focus, +.btn-check:active + .btn-outline-secondary:focus, +.btn-outline-secondary:active:focus, +.btn-outline-secondary.active:focus, +.btn-outline-secondary.dropdown-toggle.show:focus { + box-shadow: 0 0 0 4px rgba(13, 110, 253, 0.5); +} +.btn-outline-secondary:disabled, +.btn-outline-secondary.disabled { + color: #0d6efd; + background-color: transparent; +} + +.btn-outline-success { + color: #198754; + border-color: #198754; +} +.btn-outline-success:hover { + color: #fff; + background-color: #198754; + border-color: #198754; +} +.btn-check:focus + .btn-outline-success, +.btn-outline-success:focus { + box-shadow: 0 0 0 4px rgba(25, 135, 84, 0.5); +} +.btn-check:checked + .btn-outline-success, +.btn-check:active + .btn-outline-success, +.btn-outline-success:active, +.btn-outline-success.active, +.btn-outline-success.dropdown-toggle.show { + color: #fff; + background-color: #198754; + border-color: #198754; +} +.btn-check:checked + .btn-outline-success:focus, +.btn-check:active + .btn-outline-success:focus, +.btn-outline-success:active:focus, +.btn-outline-success.active:focus, +.btn-outline-success.dropdown-toggle.show:focus { + box-shadow: 0 0 0 4px rgba(25, 135, 84, 0.5); +} +.btn-outline-success:disabled, +.btn-outline-success.disabled { + color: #198754; + background-color: transparent; +} + +.btn-outline-info { + color: #0dcaf0; + border-color: #0dcaf0; +} +.btn-outline-info:hover { + color: #000; + background-color: #0dcaf0; + border-color: #0dcaf0; +} +.btn-check:focus + .btn-outline-info, +.btn-outline-info:focus { + box-shadow: 0 0 0 4px rgba(13, 202, 240, 0.5); +} +.btn-check:checked + .btn-outline-info, +.btn-check:active + .btn-outline-info, +.btn-outline-info:active, +.btn-outline-info.active, +.btn-outline-info.dropdown-toggle.show { + color: #000; + background-color: #0dcaf0; + border-color: #0dcaf0; +} +.btn-check:checked + .btn-outline-info:focus, +.btn-check:active + .btn-outline-info:focus, +.btn-outline-info:active:focus, +.btn-outline-info.active:focus, +.btn-outline-info.dropdown-toggle.show:focus { + box-shadow: 0 0 0 4px rgba(13, 202, 240, 0.5); +} +.btn-outline-info:disabled, +.btn-outline-info.disabled { + color: #0dcaf0; + background-color: transparent; +} + +.btn-outline-warning { + color: #ffc107; + border-color: #ffc107; +} +.btn-outline-warning:hover { + color: #000; + background-color: #ffc107; + border-color: #ffc107; +} +.btn-check:focus + .btn-outline-warning, +.btn-outline-warning:focus { + box-shadow: 0 0 0 4px rgba(255, 193, 7, 0.5); +} +.btn-check:checked + .btn-outline-warning, +.btn-check:active + .btn-outline-warning, +.btn-outline-warning:active, +.btn-outline-warning.active, +.btn-outline-warning.dropdown-toggle.show { + color: #000; + background-color: #ffc107; + border-color: #ffc107; +} +.btn-check:checked + .btn-outline-warning:focus, +.btn-check:active + .btn-outline-warning:focus, +.btn-outline-warning:active:focus, +.btn-outline-warning.active:focus, +.btn-outline-warning.dropdown-toggle.show:focus { + box-shadow: 0 0 0 4px rgba(255, 193, 7, 0.5); +} +.btn-outline-warning:disabled, +.btn-outline-warning.disabled { + color: #ffc107; + background-color: transparent; +} + +.btn-outline-danger { + color: #dc3545; + border-color: #dc3545; +} +.btn-outline-danger:hover { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} +.btn-check:focus + .btn-outline-danger, +.btn-outline-danger:focus { + box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.5); +} +.btn-check:checked + .btn-outline-danger, +.btn-check:active + .btn-outline-danger, +.btn-outline-danger:active, +.btn-outline-danger.active, +.btn-outline-danger.dropdown-toggle.show { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} +.btn-check:checked + .btn-outline-danger:focus, +.btn-check:active + .btn-outline-danger:focus, +.btn-outline-danger:active:focus, +.btn-outline-danger.active:focus, +.btn-outline-danger.dropdown-toggle.show:focus { + box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.5); +} +.btn-outline-danger:disabled, +.btn-outline-danger.disabled { + color: #dc3545; + background-color: transparent; +} + +.btn-outline-light { + color: #f8f9fa; + border-color: #f8f9fa; +} +.btn-outline-light:hover { + color: #000; + background-color: #f8f9fa; + border-color: #f8f9fa; +} +.btn-check:focus + .btn-outline-light, +.btn-outline-light:focus { + box-shadow: 0 0 0 4px rgba(248, 249, 250, 0.5); +} +.btn-check:checked + .btn-outline-light, +.btn-check:active + .btn-outline-light, +.btn-outline-light:active, +.btn-outline-light.active, +.btn-outline-light.dropdown-toggle.show { + color: #000; + background-color: #f8f9fa; + border-color: #f8f9fa; +} +.btn-check:checked + .btn-outline-light:focus, +.btn-check:active + .btn-outline-light:focus, +.btn-outline-light:active:focus, +.btn-outline-light.active:focus, +.btn-outline-light.dropdown-toggle.show:focus { + box-shadow: 0 0 0 4px rgba(248, 249, 250, 0.5); +} +.btn-outline-light:disabled, +.btn-outline-light.disabled { + color: #f8f9fa; + background-color: transparent; +} + +.btn-outline-dark { + color: #212529; + border-color: #212529; +} +.btn-outline-dark:hover { + color: #fff; + background-color: #212529; + border-color: #212529; +} +.btn-check:focus + .btn-outline-dark, +.btn-outline-dark:focus { + box-shadow: 0 0 0 4px rgba(33, 37, 41, 0.5); +} +.btn-check:checked + .btn-outline-dark, +.btn-check:active + .btn-outline-dark, +.btn-outline-dark:active, +.btn-outline-dark.active, +.btn-outline-dark.dropdown-toggle.show { + color: #fff; + background-color: #212529; + border-color: #212529; +} +.btn-check:checked + .btn-outline-dark:focus, +.btn-check:active + .btn-outline-dark:focus, +.btn-outline-dark:active:focus, +.btn-outline-dark.active:focus, +.btn-outline-dark.dropdown-toggle.show:focus { + box-shadow: 0 0 0 4px rgba(33, 37, 41, 0.5); +} +.btn-outline-dark:disabled, +.btn-outline-dark.disabled { + color: #212529; + background-color: transparent; +} + +.btn-link { + font-weight: 400; + color: #212529; + text-decoration: underline; +} +.btn-link:hover { + color: #1a1e21; +} +.btn-link:disabled, +.btn-link.disabled { + color: #6c757d; +} + +.btn-lg, +.btn-group-lg > .btn { + padding: 8px 16px; + font-size: 20px; + border-radius: 4.8px; +} + +.btn-sm, +.btn-group-sm > .btn { + padding: 4px 8px; + font-size: 14px; + border-radius: 3.2px; +} + +.rounded-pill { + border-radius: 800px; +} + +.form-check { + display: block; + min-height: 24px; + padding-left: 1.5em; + margin-bottom: 2px; +} +.form-check .form-check-input { + float: left; + margin-left: -1.5em; +} + +.form-check-input { + width: 1em; + height: 1em; + margin-top: 0.25em; + vertical-align: top; + background-color: #fff; + background-repeat: no-repeat; + background-position: center; + background-size: contain; + border: .0625rem solid rgba(0, 0, 0, 0.25); + appearance: none; + /* color-adjust: exact; */ +} +.form-check-input[type="checkbox"] { + border-radius: 0.25em; +} +.form-check-input[type="radio"] { + border-radius: 50%; +} +.form-check-input:active { + filter: brightness(90%); +} +.form-check-input:focus { + border-color: #909294; + outline: 0; + box-shadow: 0 0 0 4px rgba(33, 37, 41, 0.25); +} +.form-check-input:checked { + background-color: #212529; + border-color: #212529; +} +.form-check-input:checked[type="checkbox"] { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e"); +} +.form-check-input:checked[type="radio"] { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e"); +} +.form-check-input[type="checkbox"]:indeterminate { + background-color: #212529; + border-color: #212529; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e"); +} +.form-check-input:disabled { + pointer-events: none; + filter: none; + opacity: 0.5; +} +.form-check-input[disabled] ~ .form-check-label, +.form-check-input:disabled ~ .form-check-label { + opacity: 0.5; +} + +.form-switch { + padding-left: 2.5em; +} +.form-switch .form-check-input { + width: 2em; + margin-left: -2.5em; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e"); + background-position: left center; + border-radius: 2em; + transition: background-position 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-switch .form-check-input { + transition: none; + } +} +.form-switch .form-check-input:focus { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23909294'/%3e%3c/svg%3e"); +} +.form-switch .form-check-input:checked { + background-position: right center; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); +} + +.form-check-inline { + display: inline-block; + margin-right: 16px; +} +.form-check-input[disabled] ~ .form-check-label, +.form-check-input:disabled ~ .form-check-label { + opacity: 0.5; +} +.form-check-input.is-valid ~ .form-check-label { + color: #198754; +} +.form-check-input.is-invalid ~ .form-check-label { + color: #dc3545; +} +.is-invalid ~ .invalid-feedback, +.is-invalid ~ .invalid-tooltip { + display: block; +} + +.was-validated .form-control:invalid, +.form-control.is-invalid { + border-color: #dc3545; + padding-right: calc(1.5em + 12px); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 3px) center; + background-size: calc(0.75em + 6px) calc(0.75em + 6px); +} +.was-validated .form-control:invalid:focus, +.form-control.is-invalid:focus { + border-color: #dc3545; + box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.25); +} + +.was-validated textarea.form-control:invalid, +textarea.form-control.is-invalid { + padding-right: calc(1.5em + 12px); + background-position: top calc(0.375em + 3px) right + calc(0.375em + 3px); +} +.form-select { + display: block; + width: 100%; + padding: 6px 36px 6px 12px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #212529; + background-color: #fff; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 12px center; + background-size: 1rem .75rem; + border: .0625rem solid #ced4da; + border-radius: 4px; + appearance: none; +} +.form-select:focus { + border-color: #909294; + outline: 0; + box-shadow: 0 0 0 4px rgba(33, 37, 41, 0.25); +} +.form-select[multiple], +.form-select[size]:not([size="1"]) { + padding-right: 12px; + background-image: none; +} +.form-select:disabled { + background-color: #e9ecef; +} +.form-select:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #212529; +} + +.form-select-sm { + padding-top: 4px; + padding-bottom: 4px; + padding-left: 8px; + font-size: 14px; +} + +.form-select-lg { + padding-top: 8px; + padding-bottom: 8px; + padding-left: 16px; + font-size: 20px; +} + +.was-validated .form-select:invalid, +.form-select.is-invalid { + border-color: #dc3545; +} +.was-validated .form-select:invalid:not([multiple]):not([size]), +.was-validated .form-select:invalid:not([multiple])[size="1"], +.form-select.is-invalid:not([multiple]):not([size]), +.form-select.is-invalid:not([multiple])[size="1"] { + padding-right: 66px; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"), + url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + background-position: right 12px center, center right 36px; + background-size: 1rem .75rem, calc(0.75em + 6px) calc(0.75em + 6px); +} +.was-validated .form-select:invalid:focus, +.form-select.is-invalid:focus { + border-color: #dc3545; + box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.25); +} + +.was-validated .form-check-input:invalid, +.form-check-input.is-invalid { + border-color: #dc3545; +} +.was-validated .form-check-input:invalid:checked, +.form-check-input.is-invalid:checked { + background-color: #dc3545; +} +.was-validated .form-check-input:invalid:focus, +.form-check-input.is-invalid:focus { + box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.25); +} +.was-validated .form-check-input:invalid ~ .form-check-label, +.form-check-input.is-invalid ~ .form-check-label { + color: #dc3545; +} + +.form-check-inline .form-check-input ~ .invalid-feedback { + margin-left: 0.5em; +} + +.was-validated .input-group .form-control:invalid, +.input-group .form-control.is-invalid, +.was-validated .input-group .form-select:invalid, +.input-group .form-select.is-invalid { + z-index: 2; +} +.was-validated .input-group .form-control:invalid:focus, +.input-group .form-control.is-invalid:focus, +.was-validated .input-group .form-select:invalid:focus, +.input-group .form-select.is-invalid:focus { + z-index: 3; +} +.form-floating { + position: relative; +} +.form-floating > .form-control, +.form-floating > .form-select { + height: calc(56px + .125rem); + padding: 16px 12px; +} +.form-floating > label { + position: absolute; + top: 0; + left: 0; + height: 100%; + padding: 16px 12px; + pointer-events: none; + border: .0625rem solid transparent; + transform-origin: 0 0; + transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-floating > label { + transition: none; + } +} +.form-floating > .form-control::placeholder { + color: transparent; +} +.form-floating > .form-control:focus, +.form-floating > .form-control:not(:placeholder-shown) { + padding-top: 26px; + padding-bottom: 10px; +} +.form-floating > .form-control:-webkit-autofill { + padding-top: 26px; + padding-bottom: 10px; +} +.form-floating > .form-select { + padding-top: 26px; + padding-bottom: 10px; +} +.form-floating > .form-control:focus ~ label, +.form-floating > .form-control:not(:placeholder-shown) ~ label, +.form-floating > .form-select ~ label { + opacity: 0.65; + transform: scale(0.85) translateY(-8px) translateX(2.4px); +} +.form-floating > .form-control:-webkit-autofill ~ label { + opacity: 0.65; + transform: scale(0.85) translateY(-8px) translateX(2.4px); +} +.form-control { + display: block; + width: 100%; + padding: 6px 12px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #212529; + background-color: #fff; + background-clip: padding-box; + border: .0625rem solid #ced4da; + appearance: none; + border-radius: 4px; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-control { + transition: none; + } +} +.form-control[type="file"] { + overflow: hidden; +} +.form-control[type="file"]:not(:disabled):not([readonly]) { + cursor: pointer; +} +.form-control:focus { + color: #212529; + background-color: #fff; + border-color: #909294; + outline: 0; + box-shadow: 0 0 0 4px rgba(33, 37, 41, 0.25); +} +.form-control::-webkit-date-and-time-value { + height: 1.5em; +} +.form-control::placeholder { + color: #6c757d; + opacity: 1; +} +.form-control:disabled, +.form-control[readonly] { + background-color: #e9ecef; + opacity: 1; +} +.form-control::file-selector-button { + padding: 6px 12px; + margin: -6px -12px; + margin-inline-end: 12px; + color: #212529; + background-color: #e9ecef; + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: .0625rem; + border-radius: 0; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-control::file-selector-button { + transition: none; + } +} +.form-control:hover:not(:disabled):not([readonly])::file-selector-button { + background-color: #dde0e3; +} +.form-control::-webkit-file-upload-button { + padding: 6px 12px; + margin: -6px -12px; + margin-inline-end: 12px; + color: #212529; + background-color: #e9ecef; + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: .0625rem; + border-radius: 0; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-control::-webkit-file-upload-button { + transition: none; + } +} +.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button { + background-color: #dde0e3; +} + +.form-control-plaintext { + display: block; + width: 100%; + padding: 6px 0; + margin-bottom: 0; + line-height: 1.5; + color: #212529; + background-color: transparent; + border: solid transparent; + border-width: .0625rem 0; +} +.form-control-plaintext.form-control-sm, +.form-control-plaintext.form-control-lg { + padding-right: 0; + padding-left: 0; +} + +.form-control-sm { + min-height: calc(1.5em + 8px + .125rem); + padding: 4px 8px; + font-size: 14px; + border-radius: 3.2px; +} +.form-control-sm::file-selector-button { + padding: 4px 8px; + margin: -4px -8px; + margin-inline-end: 8px; +} +.form-control-sm::-webkit-file-upload-button { + padding: 4px 8px; + margin: -4px -8px; + margin-inline-end: 8px; +} + +.form-control-lg { + min-height: calc(1.5em + 16px + .125rem); + padding: 8px 16px; + font-size: 20px; + border-radius: 4.8px; +} +.form-control-lg::file-selector-button { + padding: 8px 16px; + margin: -8px -16px; + margin-inline-end: 16px; +} +.form-control-lg::-webkit-file-upload-button { + padding: 8px 16px; + margin: -8px -16px; + margin-inline-end: 16px; +} + +textarea.form-control { + min-height: calc(1.5em + 12px + .125rem); +} +textarea.form-control-sm { + min-height: calc(1.5em + 8px + .125rem); +} +textarea.form-control-lg { + min-height: calc(1.5em + 16px + .125rem); +} + +.form-control-color { + max-width: 48px; + height: auto; + padding: 6px; +} +.form-control-color:not(:disabled):not([readonly]) { + cursor: pointer; +} +.form-control-color::-moz-color-swatch { + height: 1.5em; + border-radius: 4px; +} +.form-control-color::-webkit-color-swatch { + height: 1.5em; + border-radius: 4px; +} +.form-floating > .form-control::placeholder { + color: transparent; +} +.form-floating > .form-control:focus, +.form-floating > .form-control:not(:placeholder-shown) { + padding-top: 26px; + padding-bottom: 10px; +} +.form-floating > .form-control:-webkit-autofill { + padding-top: 26px; + padding-bottom: 10px; +} +.form-floating > .form-select { + padding-top: 26px; + padding-bottom: 10px; +} +.form-floating > .form-control:focus ~ label, +.form-floating > .form-control:not(:placeholder-shown) ~ label, +.form-floating > .form-select ~ label { + opacity: 0.65; + transform: scale(0.85) translateY(-8px) translateX(2.4px); +} +.form-floating > .form-control:-webkit-autofill ~ label { + opacity: 0.65; + transform: scale(0.85) translateY(-8px) translateX(2.4px); +} + +.input-group { + position: relative; + display: flex; + flex-wrap: wrap; + align-items: stretch; + width: 100%; +} +.input-group > .form-control, +.input-group > .form-select { + position: relative; + flex: 1 1 auto; + width: 1%; + min-width: 0; +} +.input-group > .form-control:focus, +.input-group > .form-select:focus { + z-index: 3; +} +.form-control.is-valid { + border-color: #198754; + padding-right: calc(1.5em + 12px); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 3px) center; + background-size: calc(0.75em + 6px) calc(0.75em + 6px); +} +.was-validated .form-control:valid:focus, +.form-control.is-valid:focus { + border-color: #198754; + box-shadow: 0 0 0 4px rgba(25, 135, 84, 0.25); +} + +.was-validated textarea.form-control:valid, +textarea.form-control.is-valid { + padding-right: calc(1.5em + 12px); + background-position: top calc(0.375em + 3px) right + calc(0.375em + 3px); +} +.was-validated .input-group .form-control:valid, +.input-group .form-control.is-valid, +.was-validated .input-group .form-select:valid, +.input-group .form-select.is-valid { + z-index: 1; +} +.was-validated .input-group .form-control:valid:focus, +.input-group .form-control.is-valid:focus, +.was-validated .input-group .form-select:valid:focus, +.input-group .form-select.is-valid:focus { + z-index: 3; +} +.form-control.is-invalid { + border-color: #dc3545; + padding-right: calc(1.5em + 12px); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 3px) center; + background-size: calc(0.75em + 6px) calc(0.75em + 6px); +} +.was-validated .form-control:invalid:focus, +.form-control.is-invalid:focus { + border-color: #dc3545; + box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.25); +} + +.was-validated textarea.form-control:invalid, +textarea.form-control.is-invalid { + padding-right: calc(1.5em + 12px); + background-position: top calc(0.375em + 3px) right + calc(0.375em + 3px); +} +.was-validated .input-group .form-control:invalid, +.input-group .form-control.is-invalid, +.was-validated .input-group .form-select:invalid, +.input-group .form-select.is-invalid { + z-index: 2; +} +.was-validated .input-group .form-control:invalid:focus, +.input-group .form-control.is-invalid:focus, +.was-validated .input-group .form-select:invalid:focus, +.input-group .form-select.is-invalid:focus { + z-index: 3; +} +.breadcrumb { + display: flex; + flex-wrap: wrap; + /* padding: 0 0; */ + margin-bottom: 16px; + list-style: none; +} + +.breadcrumb-item + .breadcrumb-item { + padding-left: 8px; +} +.breadcrumb-item + .breadcrumb-item::before { + float: left; + padding-right: 8px; + color: #6c757d; + content: var(--bs-breadcrumb-divider, "/") + /* rtl: var(--bs-breadcrumb-divider, "/") */; +} +.breadcrumb-item.active { + color: #6c757d; +} +.link-secondary { + color: #0d6efd; +} +.link-secondary:hover, +.link-secondary:focus { + color: #0a58ca; +} +.nav { + display: flex; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav-link { + display: block; + padding: 8px 16px; + color: #212529; + text-decoration: none; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .nav-link { + transition: none; + } +} +.nav-link:hover, +.nav-link:focus { + color: #1a1e21; +} +.nav-link.disabled { + color: #6c757d; + pointer-events: none; + cursor: default; +} + +.nav-tabs { + border-bottom: .0625rem solid #dee2e6; +} +.nav-tabs .nav-link { + margin-bottom: -0.0625rem; + background: none; + border: .0625rem solid transparent; + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.nav-tabs .nav-link:hover, +.nav-tabs .nav-link:focus { + border-color: #e9ecef #e9ecef #dee2e6; + isolation: isolate; +} +.nav-tabs .nav-link.disabled { + color: #6c757d; + background-color: transparent; + border-color: transparent; +} +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + color: #495057; + background-color: #fff; + border-color: #dee2e6 #dee2e6 #fff; +} +.nav-tabs .dropdown-menu { + margin-top: -0.0625rem; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.nav-pills .nav-link { + background: none; + border: 0; + border-radius: 4px; +} +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: #fff; + background-color: #212529; +} + +.nav-fill > .nav-link, +.nav-fill .nav-item { + flex: 1 1 auto; + text-align: center; +} + +.nav-justified > .nav-link, +.nav-justified .nav-item { + flex-basis: 0; + flex-grow: 1; + text-align: center; +} + +.nav-fill .nav-item .nav-link, +.nav-justified .nav-item .nav-link { + width: 100%; +} +.accordion-button { + position: relative; + display: flex; + align-items: center; + width: 100%; + padding: 16px 20px; + font-size: 16px; + color: #212529; + text-align: left; + background-color: #fff; + border: 0; + border-radius: 0; + overflow-anchor: none; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, + border-radius 0.15s ease; +} +@media (prefers-reduced-motion: reduce) { + .accordion-button { + transition: none; + } +} +.accordion-button:not(.collapsed) { + color: #1e2125; + background-color: #e9e9ea; + box-shadow: inset 0 -0.0625rem 0 rgba(0, 0, 0, 0.125); +} +.accordion-button:not(.collapsed)::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%231e2125'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); + transform: rotate(-180deg); +} +.accordion-button::after { + flex-shrink: 0; + width: 20px; + height: 20px; + margin-left: auto; + content: ""; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-size: 20px; + transition: transform 0.2s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .accordion-button::after { + transition: none; + } +} +.accordion-button:hover { + z-index: 2; +} +.accordion-button:focus { + z-index: 3; + border-color: #909294; + outline: 0; + box-shadow: 0 0 0 4px rgba(33, 37, 41, 0.25); +} + +.accordion-header { + margin-bottom: 0; +} + +.accordion-item { + background-color: #fff; + border: .0625rem solid rgba(0, 0, 0, 0.125); +} +.accordion-item:first-of-type { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.accordion-item:first-of-type .accordion-button { + border-top-left-radius: calc(4px - .0625rem); + border-top-right-radius: calc(4px - .0625rem); +} +.accordion-item:not(:first-of-type) { + border-top: 0; +} +.accordion-item:last-of-type { + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.accordion-item:last-of-type .accordion-button.collapsed { + border-bottom-right-radius: calc(4px - .0625rem); + border-bottom-left-radius: calc(4px - .0625rem); +} +.accordion-item:last-of-type .accordion-collapse { + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} + +.accordion-body { + padding: 16px 20px; +} + +.accordion-flush .accordion-collapse { + border-width: 0; +} +.accordion-flush .accordion-item { + border-right: 0; + border-left: 0; + border-radius: 0; +} +.accordion-flush .accordion-item:first-child { + border-top: 0; +} +.accordion-flush .accordion-item:last-child { + border-bottom: 0; +} +.accordion-flush .accordion-item .accordion-button { + border-radius: 0; +} +.collapse:not(.show) { + display: none; +} +.accordion-item:last-of-type .accordion-button.collapsed { + border-bottom-right-radius: calc(4px - .0625rem); + border-bottom-left-radius: calc(4px - .0625rem); +} + +.row { + --bs-gutter-x: 24px; + --bs-gutter-y: 0; + display: flex; + flex-wrap: wrap; + margin-top: calc(var(--bs-gutter-y) * -1); + margin-right: calc(var(--bs-gutter-x) / -2); + margin-left: calc(var(--bs-gutter-x) / -2); +} +.row > * { + flex-shrink: 0; + width: 100%; + max-width: 100%; + padding-right: calc(var(--bs-gutter-x) / 2); + padding-left: calc(var(--bs-gutter-x) / 2); + margin-top: var(--bs-gutter-y); +} + +.col { + flex: 1 0 0%; +} + +.row-cols-auto > * { + flex: 0 0 auto; + width: auto; +} + +.row-cols-1 > * { + flex: 0 0 auto; + width: 100%; +} + +.row-cols-2 > * { + flex: 0 0 auto; + width: 50%; +} + +.row-cols-3 > * { + flex: 0 0 auto; + width: 33.3333333333%; +} + +.row-cols-4 > * { + flex: 0 0 auto; + width: 25%; +} + +.row-cols-5 > * { + flex: 0 0 auto; + width: 20%; +} + +.row-cols-6 > * { + flex: 0 0 auto; + width: 16.6666666667%; +} + +.flex-shrink-0 { + flex-shrink: 0; +} +.text-muted { + color: #6c757d; +} +.pagination { + display: flex; + padding-left: 0; + list-style: none; +} +.pagination-lg .page-link { + padding: 12px 24px; + font-size: 20px; +} +.pagination-lg .page-item:first-child .page-link { + border-top-left-radius: 4.8px; + border-bottom-left-radius: 4.8px; +} +.pagination-lg .page-item:last-child .page-link { + border-top-right-radius: 4.8px; + border-bottom-right-radius: 4.8px; +} + +.page-item:not(:first-child) .page-link { + margin-left: -0.0625rem; +} +.page-item.active .page-link { + z-index: 3; + color: #fff; + background-color: #212529; + border-color: #212529; +} +.page-item.disabled .page-link { + color: #6c757d; + pointer-events: none; + background-color: #fff; + border-color: #dee2e6; +} + +.page-link { + padding: 6px 12px; +} + +.page-item:first-child .page-link { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.page-item:last-child .page-link { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.pagination-sm .page-link { + padding: 4px 8px; + font-size: 14px; +} +.pagination-sm .page-item:first-child .page-link { + border-top-left-radius: 3.2px; + border-bottom-left-radius: 3.2px; +} +.pagination-sm .page-item:last-child .page-link { + border-top-right-radius: 3.2px; + border-bottom-right-radius: 3.2px; +} +.page-link { + position: relative; + display: block; + color: #212529; + text-decoration: none; + background-color: #fff; + border: .0625rem solid #dee2e6; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .page-link { + transition: none; + } +} +.page-link:hover { + z-index: 2; + color: #1a1e21; + background-color: #e9ecef; + border-color: #dee2e6; +} +.page-link:focus { + z-index: 3; + color: #1a1e21; + background-color: #e9ecef; + outline: 0; + box-shadow: 0 0 0 4px rgba(33, 37, 41, 0.25); +} + +.page-item:not(:first-child) .page-link { + margin-left: -0.0625rem; +} +.page-item.active .page-link { + z-index: 3; + color: #fff; + background-color: #212529; + border-color: #212529; +} +.page-item.disabled .page-link { + color: #6c757d; + pointer-events: none; + background-color: #fff; + border-color: #dee2e6; +} + +.page-link { + padding: 6px 12px; +} + +.page-item:first-child .page-link { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.page-item:last-child .page-link { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.page-item.active .page-link { + z-index: 3; + color: #fff; + background-color: #212529; + border-color: #212529; +} +.card { + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: .0625rem solid rgba(0, 0, 0, 0.125); + border-radius: 4px; +} +.card > hr { + margin-right: 0; + margin-left: 0; +} +.card > .list-group { + border-top: inherit; + border-bottom: inherit; +} +.card > .list-group:first-child { + border-top-width: 0; + border-top-left-radius: calc(4px - .0625rem); + border-top-right-radius: calc(4px - .0625rem); +} +.card > .list-group:last-child { + border-bottom-width: 0; + border-bottom-right-radius: calc(4px - .0625rem); + border-bottom-left-radius: calc(4px - .0625rem); +} +.card > .card-header + .list-group, +.card > .list-group + .card-footer { + border-top: 0; +} + +.card-body { + flex: 1 1 auto; + padding: 16px 16px; +} + +.card-title { + margin-bottom: 8px; +} + +.card-subtitle { + margin-top: -4px; + margin-bottom: 0; +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link:hover { + text-decoration: none; +} +.card-link + .card-link { + margin-left: 16px; +} + +.card-header { + padding: 8px 16px; + margin-bottom: 0; + background-color: rgba(0, 0, 0, 0.03); + border-bottom: .0625rem solid rgba(0, 0, 0, 0.125); +} +.card-header:first-child { + border-radius: calc(4px - .0625rem) calc(4px - .0625rem) 0 0; +} + +.card-footer { + padding: 8px 16px; + background-color: rgba(0, 0, 0, 0.03); + border-top: .0625rem solid rgba(0, 0, 0, 0.125); +} +.card-footer:last-child { + border-radius: 0 0 calc(4px - .0625rem) calc(4px - .0625rem); +} + +.card-header-tabs { + margin-right: -8px; + margin-bottom: -8px; + margin-left: -8px; + border-bottom: 0; +} + +.card-header-pills { + margin-right: -8px; + margin-left: -8px; +} + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 16px; + border-radius: calc(4px - .0625rem); +} + +.card-img, +.card-img-top, +.card-img-bottom { + width: 100%; +} + +.card-img, +.card-img-top { + border-top-left-radius: calc(4px - .0625rem); + border-top-right-radius: calc(4px - .0625rem); +} + +.card-img, +.card-img-bottom { + border-bottom-right-radius: calc(4px - .0625rem); + border-bottom-left-radius: calc(4px - .0625rem); +} + +.card-group > .card { + margin-bottom: 12px; +} +@media (min-width: 36rem) { + .card-group { + display: flex; + flex-flow: row wrap; + } + .card-group > .card { + flex: 1 0 0%; + margin-bottom: 0; + } + .card-group > .card + .card { + margin-left: 0; + border-left: 0; + } + .card-group > .card:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .card-group > .card:not(:last-child) .card-img-top, + .card-group > .card:not(:last-child) .card-header { + border-top-right-radius: 0; + } + .card-group > .card:not(:last-child) .card-img-bottom, + .card-group > .card:not(:last-child) .card-footer { + border-bottom-right-radius: 0; + } + .card-group > .card:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + .card-group > .card:not(:first-child) .card-img-top, + .card-group > .card:not(:first-child) .card-header { + border-top-left-radius: 0; + } + .card-group > .card:not(:first-child) .card-img-bottom, + .card-group > .card:not(:first-child) .card-footer { + border-bottom-left-radius: 0; + } +} +.card-group > .card { + margin-bottom: 12px; +} +@media (min-width: 36rem) { + .card-group { + display: flex; + flex-flow: row wrap; + } + .card-group > .card { + flex: 1 0 0%; + margin-bottom: 0; + } + .card-group > .card + .card { + margin-left: 0; + border-left: 0; + } + .card-group > .card:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .card-group > .card:not(:last-child) .card-img-top, + .card-group > .card:not(:last-child) .card-header { + border-top-right-radius: 0; + } + .card-group > .card:not(:last-child) .card-img-bottom, + .card-group > .card:not(:last-child) .card-footer { + border-bottom-right-radius: 0; + } + .card-group > .card:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + .card-group > .card:not(:first-child) .card-img-top, + .card-group > .card:not(:first-child) .card-header { + border-top-left-radius: 0; + } + .card-group > .card:not(:first-child) .card-img-bottom, + .card-group > .card:not(:first-child) .card-footer { + border-bottom-left-radius: 0; + } +} +.col { + flex: 1 0 0%; +} +.col-4 { + flex: 0 0 auto; + width: 33.3333333333%; +} +.rounded-start { + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; +} +.bg-dark { + background-color: #212529; +} +.col-1 { + flex: 0 0 auto; + width: 8.3333333333%; +} + +.col-2 { + flex: 0 0 auto; + width: 16.6666666667%; +} + +.col-3 { + flex: 0 0 auto; + width: 25%; +} + +.col-4 { + flex: 0 0 auto; + width: 33.3333333333%; +} + +.col-5 { + flex: 0 0 auto; + width: 41.6666666667%; +} + +.col-6 { + flex: 0 0 auto; + width: 50%; +} + +.col-7 { + flex: 0 0 auto; + width: 58.3333333333%; +} + +.col-8 { + flex: 0 0 auto; + width: 66.6666666667%; +} + +.col-9 { + flex: 0 0 auto; + width: 75%; +} + +.col-10 { + flex: 0 0 auto; + width: 83.3333333333%; +} + +.col-11 { + flex: 0 0 auto; + width: 91.6666666667%; +} + +.col-12 { + flex: 0 0 auto; + width: 100%; +} +.text-dark { + color: #212529; +} +.badge { + display: inline-block; + padding: 0.35em 0.65em; + font-size: 0.75em; + font-weight: 700; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 4px; +} +.badge:empty { + display: none; +} + +.btn .badge { + position: relative; + top: -0.0625rem; +} +.card-img, +.card-img-top, +.card-img-bottom { + width: 100%; +} + +.card-img, +.card-img-top { + border-top-left-radius: calc(4px - .0625rem); + border-top-right-radius: calc(4px - .0625rem); +} + +.lead { + font-size: 20px; + font-weight: 300; +} + +@media (min-width: 36rem) { + .row { + --bs-gutter-x: 40px; + --bs-gutter-y: 0; + display: flex; + flex-wrap: wrap; + margin-top: calc(var(--bs-gutter-y) * -1); + margin-right: calc(var(--bs-gutter-x) / -2); + margin-left: calc(var(--bs-gutter-x) / -2); + } + .col-sm { + flex: 1 0 0%; + } + + .row-cols-sm-auto > * { + flex: 0 0 auto; + width: auto; + } + + .row-cols-sm-1 > * { + flex: 0 0 auto; + width: 100%; + } + + .row-cols-sm-2 > * { + flex: 0 0 auto; + width: 50%; + } + + .row-cols-sm-3 > * { + flex: 0 0 auto; + width: 33.3333333333%; + } + + .row-cols-sm-4 > * { + flex: 0 0 auto; + width: 25%; + } + + .row-cols-sm-5 > * { + flex: 0 0 auto; + width: 20%; + } + + .row-cols-sm-6 > * { + flex: 0 0 auto; + width: 16.6666666667%; + } + + .col-sm-auto { + flex: 0 0 auto; + width: auto; + } + + .col-sm-1 { + flex: 0 0 auto; + width: 8.3333333333%; + } + + .col-sm-2 { + flex: 0 0 auto; + width: 16.6666666667%; + } + + .col-sm-3 { + flex: 0 0 auto; + width: 25%; + } + + .col-sm-4 { + flex: 0 0 auto; + width: 33.3333333333%; + } + + .col-sm-5 { + flex: 0 0 auto; + width: 41.6666666667%; + } + + .col-sm-6 { + flex: 0 0 auto; + width: 50%; + } + + .col-sm-7 { + flex: 0 0 auto; + width: 58.3333333333%; + } + + .col-sm-8 { + flex: 0 0 auto; + width: 66.6666666667%; + } + + .col-sm-9 { + flex: 0 0 auto; + width: 75%; + } + + .col-sm-10 { + flex: 0 0 auto; + width: 83.3333333333%; + } + + .col-sm-11 { + flex: 0 0 auto; + width: 91.6666666667%; + } + + .col-sm-12 { + flex: 0 0 auto; + width: 100%; + } + + .offset-sm-0 { + margin-left: 0; + } + + .offset-sm-1 { + margin-left: 8.3333333333%; + } + + .offset-sm-2 { + margin-left: 16.6666666667%; + } + + .offset-sm-3 { + margin-left: 25%; + } + + .offset-sm-4 { + margin-left: 33.3333333333%; + } + + .offset-sm-5 { + margin-left: 41.6666666667%; + } + + .offset-sm-6 { + margin-left: 50%; + } + + .offset-sm-7 { + margin-left: 58.3333333333%; + } + + .offset-sm-8 { + margin-left: 66.6666666667%; + } + + .offset-sm-9 { + margin-left: 75%; + } + + .offset-sm-10 { + margin-left: 83.3333333333%; + } + + .offset-sm-11 { + margin-left: 91.6666666667%; + } +} +@media (min-width: 48rem) { + .col-md { + flex: 1 0 0%; + } + + .row-cols-md-auto > * { + flex: 0 0 auto; + width: auto; + } + + .row-cols-md-1 > * { + flex: 0 0 auto; + width: 100%; + } + + .row-cols-md-2 > * { + flex: 0 0 auto; + width: 50%; + } + + .row-cols-md-3 > * { + flex: 0 0 auto; + width: 33.3333333333%; + } + + .row-cols-md-4 > * { + flex: 0 0 auto; + width: 25%; + } + + .row-cols-md-5 > * { + flex: 0 0 auto; + width: 20%; + } + + .row-cols-md-6 > * { + flex: 0 0 auto; + width: 16.6666666667%; + } + + .col-md-auto { + flex: 0 0 auto; + width: auto; + } + + .col-md-1 { + flex: 0 0 auto; + width: 8.3333333333%; + } + + .col-md-2 { + flex: 0 0 auto; + width: 16.6666666667%; + } + + .col-md-3 { + flex: 0 0 auto; + width: 25%; + } + + .col-md-4 { + flex: 0 0 auto; + width: 33.3333333333%; + } + + .col-md-5 { + flex: 0 0 auto; + width: 41.6666666667%; + } + + .col-md-6 { + flex: 0 0 auto; + width: 50%; + } + + .col-md-7 { + flex: 0 0 auto; + width: 58.3333333333%; + } + + .col-md-8 { + flex: 0 0 auto; + width: 66.6666666667%; + } + + .col-md-9 { + flex: 0 0 auto; + width: 75%; + } + + .col-md-10 { + flex: 0 0 auto; + width: 83.3333333333%; + } + + .col-md-11 { + flex: 0 0 auto; + width: 91.6666666667%; + } + + .col-md-12 { + flex: 0 0 auto; + width: 100%; + } + + .offset-md-0 { + margin-left: 0; + } + + .offset-md-1 { + margin-left: 8.3333333333%; + } + + .offset-md-2 { + margin-left: 16.6666666667%; + } + + .offset-md-3 { + margin-left: 25%; + } + + .offset-md-4 { + margin-left: 33.3333333333%; + } + + .offset-md-5 { + margin-left: 41.6666666667%; + } + + .offset-md-6 { + margin-left: 50%; + } + + .offset-md-7 { + margin-left: 58.3333333333%; + } + + .offset-md-8 { + margin-left: 66.6666666667%; + } + + .offset-md-9 { + margin-left: 75%; + } + + .offset-md-10 { + margin-left: 83.3333333333%; + } + + .offset-md-11 { + margin-left: 91.6666666667%; + } +} +@media (min-width: 62rem) { + .col-lg { + flex: 1 0 0%; + } + + .row-cols-lg-auto > * { + flex: 0 0 auto; + width: auto; + } + + .row-cols-lg-1 > * { + flex: 0 0 auto; + width: 100%; + } + + .row-cols-lg-2 > * { + flex: 0 0 auto; + width: 50%; + } + + .row-cols-lg-3 > * { + flex: 0 0 auto; + width: 33.3333333333%; + } + + .row-cols-lg-4 > * { + flex: 0 0 auto; + width: 25%; + } + + .row-cols-lg-5 > * { + flex: 0 0 auto; + width: 20%; + } + + .row-cols-lg-6 > * { + flex: 0 0 auto; + width: 16.6666666667%; + } + + .col-lg-auto { + flex: 0 0 auto; + width: auto; + } + + .col-lg-1 { + flex: 0 0 auto; + width: 8.3333333333%; + } + + .col-lg-2 { + flex: 0 0 auto; + width: 16.6666666667%; + } + + .col-lg-3 { + flex: 0 0 auto; + width: 25%; + } + + .col-lg-4 { + flex: 0 0 auto; + width: 33.3333333333%; + } + + .col-lg-5 { + flex: 0 0 auto; + width: 41.6666666667%; + } + + .col-lg-6 { + flex: 0 0 auto; + width: 50%; + } + + .col-lg-7 { + flex: 0 0 auto; + width: 58.3333333333%; + } + + .col-lg-8 { + flex: 0 0 auto; + width: 66.6666666667%; + } + + .col-lg-9 { + flex: 0 0 auto; + width: 75%; + } + + .col-lg-10 { + flex: 0 0 auto; + width: 83.3333333333%; + } + + .col-lg-11 { + flex: 0 0 auto; + width: 91.6666666667%; + } + + .col-lg-12 { + flex: 0 0 auto; + width: 100%; + } + + .offset-lg-0 { + margin-left: 0; + } + + .offset-lg-1 { + margin-left: 8.3333333333%; + } + + .offset-lg-2 { + margin-left: 16.6666666667%; + } + + .offset-lg-3 { + margin-left: 25%; + } + + .offset-lg-4 { + margin-left: 33.3333333333%; + } + + .offset-lg-5 { + margin-left: 41.6666666667%; + } + + .offset-lg-6 { + margin-left: 50%; + } + + .offset-lg-7 { + margin-left: 58.3333333333%; + } + + .offset-lg-8 { + margin-left: 66.6666666667%; + } + + .offset-lg-9 { + margin-left: 75%; + } + + .offset-lg-10 { + margin-left: 83.3333333333%; + } + + .offset-lg-11 { + margin-left: 91.6666666667%; + } +} +@media (min-width: 75rem) { + .col-xl { + flex: 1 0 0%; + } + + .row-cols-xl-auto > * { + flex: 0 0 auto; + width: auto; + } + + .row-cols-xl-1 > * { + flex: 0 0 auto; + width: 100%; + } + + .row-cols-xl-2 > * { + flex: 0 0 auto; + width: 50%; + } + + .row-cols-xl-3 > * { + flex: 0 0 auto; + width: 33.3333333333%; + } + + .row-cols-xl-4 > * { + flex: 0 0 auto; + width: 25%; + } + + .row-cols-xl-5 > * { + flex: 0 0 auto; + width: 20%; + } + + .row-cols-xl-6 > * { + flex: 0 0 auto; + width: 16.6666666667%; + } + + .col-xl-auto { + flex: 0 0 auto; + width: auto; + } + + .col-xl-1 { + flex: 0 0 auto; + width: 8.3333333333%; + } + + .col-xl-2 { + flex: 0 0 auto; + width: 16.6666666667%; + } + + .col-xl-3 { + flex: 0 0 auto; + width: 25%; + } + + .col-xl-4 { + flex: 0 0 auto; + width: 33.3333333333%; + } + + .col-xl-5 { + flex: 0 0 auto; + width: 41.6666666667%; + } + + .col-xl-6 { + flex: 0 0 auto; + width: 50%; + } + + .col-xl-7 { + flex: 0 0 auto; + width: 58.3333333333%; + } + + .col-xl-8 { + flex: 0 0 auto; + width: 66.6666666667%; + } + + .col-xl-9 { + flex: 0 0 auto; + width: 75%; + } + + .col-xl-10 { + flex: 0 0 auto; + width: 83.3333333333%; + } + + .col-xl-11 { + flex: 0 0 auto; + width: 91.6666666667%; + } + + .col-xl-12 { + flex: 0 0 auto; + width: 100%; + } + + .offset-xl-0 { + margin-left: 0; + } + + .offset-xl-1 { + margin-left: 8.3333333333%; + } + + .offset-xl-2 { + margin-left: 16.6666666667%; + } + + .offset-xl-3 { + margin-left: 25%; + } + + .offset-xl-4 { + margin-left: 33.3333333333%; + } + + .offset-xl-5 { + margin-left: 41.6666666667%; + } + + .offset-xl-6 { + margin-left: 50%; + } + + .offset-xl-7 { + margin-left: 58.3333333333%; + } + + .offset-xl-8 { + margin-left: 66.6666666667%; + } + + .offset-xl-9 { + margin-left: 75%; + } + + .offset-xl-10 { + margin-left: 83.3333333333%; + } + + .offset-xl-11 { + margin-left: 91.6666666667%; + } +} +@media (min-width: 87.5rem) { + .col-xxl { + flex: 1 0 0%; + } + + .row-cols-xxl-auto > * { + flex: 0 0 auto; + width: auto; + } + + .row-cols-xxl-1 > * { + flex: 0 0 auto; + width: 100%; + } + + .row-cols-xxl-2 > * { + flex: 0 0 auto; + width: 50%; + } + + .row-cols-xxl-3 > * { + flex: 0 0 auto; + width: 33.3333333333%; + } + + .row-cols-xxl-4 > * { + flex: 0 0 auto; + width: 25%; + } + + .row-cols-xxl-5 > * { + flex: 0 0 auto; + width: 20%; + } + + .row-cols-xxl-6 > * { + flex: 0 0 auto; + width: 16.6666666667%; + } + + .col-xxl-auto { + flex: 0 0 auto; + width: auto; + } + + .col-xxl-1 { + flex: 0 0 auto; + width: 8.3333333333%; + } + + .col-xxl-2 { + flex: 0 0 auto; + width: 16.6666666667%; + } + + .col-xxl-3 { + flex: 0 0 auto; + width: 25%; + } + + .col-xxl-4 { + flex: 0 0 auto; + width: 33.3333333333%; + } + + .col-xxl-5 { + flex: 0 0 auto; + width: 41.6666666667%; + } + + .col-xxl-6 { + flex: 0 0 auto; + width: 50%; + } + + .col-xxl-7 { + flex: 0 0 auto; + width: 58.3333333333%; + } + + .col-xxl-8 { + flex: 0 0 auto; + width: 66.6666666667%; + } + + .col-xxl-9 { + flex: 0 0 auto; + width: 75%; + } + + .col-xxl-10 { + flex: 0 0 auto; + width: 83.3333333333%; + } + + .col-xxl-11 { + flex: 0 0 auto; + width: 91.6666666667%; + } + + .col-xxl-12 { + flex: 0 0 auto; + width: 100%; + } + + .offset-xxl-0 { + margin-left: 0; + } + + .offset-xxl-1 { + margin-left: 8.3333333333%; + } + + .offset-xxl-2 { + margin-left: 16.6666666667%; + } + + .offset-xxl-3 { + margin-left: 25%; + } + + .offset-xxl-4 { + margin-left: 33.3333333333%; + } + + .offset-xxl-5 { + margin-left: 41.6666666667%; + } + + .offset-xxl-6 { + margin-left: 50%; + } + + .offset-xxl-7 { + margin-left: 58.3333333333%; + } + + .offset-xxl-8 { + margin-left: 66.6666666667%; + } + + .offset-xxl-9 { + margin-left: 75%; + } + + .offset-xxl-10 { + margin-left: 83.3333333333%; + } + + .offset-xxl-11 { + margin-left: 91.6666666667%; + } +} + +/* react carousle */ + +.react-carousle .slick-prev, +.react-carousle .slick-next { + position: absolute; + top: 0% !important; + display: block; + width: 2.5rem !important; + height: 9.375rem !important; + border-radius: .3125rem; + z-index: 99999 !important; + cursor: pointer; + color: black !important; + background: rgba(0, 0, 0, 0.619) !important; + /* border-radius: 50%; */ +} +.react-carousle .slick-prev { + left: 0 !important; +} +.slick-next { + right: 0 !important; +} +.react-carousle .slick-prev { + transform: translate(0%, 0%) !important; +} +.react-carousle .slick-next { + transform: translate(0%, 0%) !important; +} +.react-carousle button.slick-disabled { + opacity: 0.5; +} + +.react-carousle .slick-prev:before, +.react-carousle .slick-next:before { + font-size: 2.5rem !important; + position: absolute; + top: 57%; + left: 50%; + transform: translate(-50%, -50%); +} +.react-carousle .slick-list { + position: relative; + display: block; + overflow: unset !important; + margin: 0; + padding: 0; + /* overflow-x: visible !important; + overflow-y: scroll !important; */ +} +/* ::-webkit-scrollbar-track { + background-color: #f5f5f500; +} +::-webkit-scrollbar { + width: 0; + height: .625rem; + background-color: #821d1d00; +} +::-webkit-scrollbar-thumb { + background-color: rgba(245, 20, 20, 0); +} + +#react-carousle::-webkit-scrollbar { + width: 0; + height: 0rem; + background-color: #ce0f0ff2; +} */ + +#react-carousle .slick-dots { + top: -10% !important; + display: flex !important; + justify-content: flex-end !important; + height: .625rem; + text-align: end; +} +#react-carousle .slick-dots li button { + font-size: 0; + line-height: 0; + display: block; + width: 1.1875rem; + height: .1875rem !important; + margin: 0 0rem; + padding: 0 0 !important; + border-radius: 1.5rem !important; + padding: .3125rem; + cursor: pointer; + background: rgba(0, 0, 0, 0.346) !important; +} +#react-carousle .slick-dots li.slick-active button { + background: black !important; +} +#react-carousle .slick-dots li button:before { + opacity: 0 !important; +} +#react-carousle .slick-dots li { + margin: 0 .125rem !important; +} + +.react-calendar { + font-family: "General Sans" !important; + color: #344054; + border: .0625rem solid #eaecf0 !important; + width: 18.75rem; + z-index: 99; + position: relative; +} + +.react-calendar abbr:where([title]) { + text-decoration: none; + text-transform: none; +} + +.react-calendar__tile { + padding: .9375rem .5rem !important; + font-size: .875rem !important; +} + +.react-calendar__tile:enabled:hover { + background: none !important; +} + +.react-calendar__month-view__days__day--weekend { + color: #344054 !important; +} + +.react-calendar__tile--now { + background: none !important; + position: relative; +} + +.react-calendar__tile--now::after { + position: absolute; + bottom: .3125rem; + content: ""; + background-color: #00261c; + width: .3125rem; + height: .3125rem; + left: calc(50% - .1563rem); + border-radius: 50%; +} + +.react-calendar__tile--active { + background: linear-gradient( + 230.69deg, + #33d4b7 9.11%, + #0d9895 69.45% + ) !important; + color: white !important; +} + +.react-calendar__tile--active.react-calendar__tile--now::after { + background-color: white; +} + +.react-calendar__tile:enabled:hover.react-calendar__tile--active:hover { + background: linear-gradient( + 230.69deg, + #33d4b7 9.11%, + #0d9895 69.45% + ) !important; +} + +.react-calendar__navigation button:enabled:hover, +.react-calendar__navigation button:enabled:focus { + background: none !important; +} + +.react-calendar__month-view__days__day--neighboringMonth { + color: #98a2b3 !important; +} + +.custom-calendar .react-calendar_tile { + font-size: .75rem !important; + padding: .3125rem !important; + line-height: 1 !important; +} + +.custom-calendar .react-calendar__navigation { + margin-bottom: 0; +} + +.custom-calendar .react-calendar__tile:disabled { + /* position: relative; */ + background: transparent !important; + color: red !important; +} +.scheduling-calendar .react-calendar__tile abbr { + display: none; +} + +.scheduling-calendar .react-calendar__navigation { + display: grid !important; + grid-template-columns: 12.8125rem 2.5rem 15.625rem; + gap: .75rem; + align-items: center; + padding-inline: .3125rem; +} + +.scheduling-calendar .react-calendar__navigation__prev2-button { + display: none; +} + +.scheduling-calendar .react-calendar__navigation__next2-button { + display: none; +} + +.scheduling-calendar .react-calendar__navigation__label { + grid-column: 1/2; +} + +.scheduling-calendar .react-calendar__navigation__prev-button { + grid-column: 2/3; + border: .0625rem solid #d0d5dd; + grid-row: 1; + padding: .625rem 0; +} + +.scheduling-calendar .react-calendar__navigation__next-button { + grid-column: 3/4; +} +.react-calendar__navigation button.use-template:enabled:hover { + background: linear-gradient( + 230.69deg, + #33d4b7 9.11%, + #0d9895 69.45% + ) !important; +} + +.scheduling-calendar .react-calendar__month-view__weekdays { + display: grid !important; + grid-template-columns: repeat(7, 1fr); + border-bottom-width: .0625rem; +} + +.scheduling-calendar .react-calendar__month-view__weekdays__weekday { + justify-self: flex-start; + color: #475467; + font-size: 1rem; + font-weight: normal !important; +} + +.scheduling-calendar .react-calendar__tile { + padding: 0rem !important; + color: #667085 !important; +} + +.scheduling-calendar .react-calendar__month-view__days__day--neighboringMonth { + color: #98a2b3 !important; +} + +.scheduling-calendar .react-calendar__tile--active { + background: #f0f5f3 !important; + border-radius: unset !important; + border: .125rem solid #0d9895; + color: #667085 !important; + overflow: visible !important; +} + +.scheduling-calendar .react-calendar__tile:enabled:hover { + background: unset !important; +} + +.scheduling-calendar + .react-calendar__tile:enabled:hover.react-calendar__tile--active:hover { + background: #f0f5f3 !important; +} + +.scheduling-calendar .react-calendar__tile--now::after { + display: none !important; +} +h1 { + font-size: 2.5rem; + font-weight: 700; +} +h2 { + font-size: 1.875rem; + font-weight: 700; +} +h3 { + font-size: 1.5625rem; + font-weight: 700; +} +h4 { + font-size: 1.25rem; + font-weight: 700; +} +h5 { + font-size: 1.125rem; + font-weight: 700; +} +h6 { + font-size: 1rem; + font-weight: 700; +} + \ No newline at end of file diff --git a/day20/src/index.jsx b/day20/src/index.jsx new file mode 100644 index 0000000..fa12471 --- /dev/null +++ b/day20/src/index.jsx @@ -0,0 +1,26 @@ + + import React from "react"; + import ReactDOM from "react-dom/client"; + import "./output.css"; + import "./index.css"; + import "slick-carousel/slick/slick.css"; + import "slick-carousel/slick/slick-theme.css"; + import App from "./App"; + + import { library } from "@fortawesome/fontawesome-svg-core"; + import { fas } from "@fortawesome/free-solid-svg-icons"; + import { far } from "@fortawesome/free-regular-svg-icons"; + import { fab } from "@fortawesome/free-brands-svg-icons"; + + library.add(fas, far, fab); + + const root = ReactDOM.createRoot(document.getElementById("root")); + + root.render( + // + + // + ); + + + \ No newline at end of file diff --git a/day20/src/logo.svg b/day20/src/logo.svg new file mode 100644 index 0000000..2b2e4e5 --- /dev/null +++ b/day20/src/logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/day20/src/output.css b/day20/src/output.css new file mode 100644 index 0000000..89886ee --- /dev/null +++ b/day20/src/output.css @@ -0,0 +1,5132 @@ + + * { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +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; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; +} + +.scrollbar-hide::-webkit-scrollbar { + display: none; +} + +.scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.sidebar-holder { + width: 100%; + min-width: 15rem; + max-width: 15rem; + position: relative; + background: #151515; + color: #fff; + z-index: 2; + transition: all 0.3s; + min-height: 100vh; + overflow: hidden; + transition: 0.2s; +} + +.open-nav { + min-width: 0rem !important; + max-width: 0rem !important; + width: 0 !important; + transition: 0.2s; + opacity: 0; +} + +.sidebar-list ul li a { + padding: .625rem; + display: block; + width: 100%; + font-size: 1.125rem; + font-weight: 600; + transition: 0.2s ease-in; + text-transform: capitalize; +} + +.sidebar-list ul li a:hover { + color: #151515; + background: white; +} + +.page-header { + width: 100%; + padding: 1.25rem; + background: white; +} + +.page-header span { + cursor: pointer; + display: block; + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; + font-size: 1.25rem; +} + +.center-svg { + aspect-ratio: 1/1; + align-items: center; + justify-content: center; + line-height: 1.2em !important; +} + +.uppy-Dashboard-inner { + width: 100% !important; +} + +/* +! tailwindcss v3.0.24 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ""; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +*/ + +html { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: Inter, sans-serif; + /* 4 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: .0625rem; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font family by default. +2. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + line-height: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type="search"] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -0.125rem; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, +textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input:-ms-input-placeholder, +textarea:-ms-input-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* +Ensure the default browser behavior of the `hidden` attribute. +*/ + +[hidden] { + display: none; +} + +*, +::before, +::after { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0rem; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +.container { + width: 100%; +} + +@media (min-width: 40rem) { + .container { + max-width: 40rem; + } +} + +@media (min-width: 48rem) { + .container { + max-width: 48rem; + } +} + +@media (min-width: 64rem) { + .container { + max-width: 64rem; + } +} + +@media (min-width: 80rem) { + .container { + max-width: 80rem; + } +} + +@media (min-width: 96rem) { + .container { + max-width: 96rem; + } +} + +:root { + --bs-blue: #0d6efd; + --bs-indigo: #6610f2; + --bs-purple: #6f42c1; + --bs-pink: #d63384; + --bs-red: #dc3545; + --bs-orange: #fd7e14; + --bs-yellow: #ffc107; + --bs-green: #198754; + --bs-teal: #20c997; + --bs-cyan: #0dcaf0; + --bs-white: #fff; + --bs-gray: #6c757d; + --bs-gray-dark: #343a40; + --bs-gray-100: #f8f9fa; + --bs-gray-200: #e9ecef; + --bs-gray-300: #dee2e6; + --bs-gray-400: #ced4da; + --bs-gray-500: #adb5bd; + --bs-gray-600: #6c757d; + --bs-gray-700: #495057; + --bs-gray-800: #343a40; + --bs-gray-900: #212529; + --bs-primary: #0d6efd; + --bs-secondary: #6c757d; + --bs-success: #198754; + --bs-info: #0dcaf0; + --bs-warning: #ffc107; + --bs-danger: #dc3545; + --bs-light: #f8f9fa; + --bs-dark: #212529; + --bs-primary-rgb: 13, 110, 253; + --bs-secondary-rgb: 108, 117, 125; + --bs-success-rgb: 25, 135, 84; + --bs-info-rgb: 13, 202, 240; + --bs-warning-rgb: 255, 193, 7; + --bs-danger-rgb: 220, 53, 69; + --bs-light-rgb: 248, 249, 250; + --bs-dark-rgb: 33, 37, 41; + --bs-white-rgb: 255, 255, 255; + --bs-black-rgb: 0, 0, 0; + --bs-body-color-rgb: 33, 37, 41; + --bs-body-bg-rgb: 255, 255, 255; + --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", + "Noto Color Emoji"; + --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); + --bs-body-font-family: var(--bs-font-sans-serif); + --bs-body-font-size: 16px; + --bs-body-font-weight: 400; + --bs-body-line-height: 1.5; + --bs-body-color: #212529; + --bs-body-bg: #fff; +} + +.input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n + 3) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group.has-validation > .dropdown-toggle:nth-last-child(n + 4) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.is-invalid ~ .invalid-feedback { + display: block; +} + +.is-invalid ~ .invalid-tooltip { + display: block; +} + +.form-control.is-invalid { + border-color: #dc3545; + padding-right: calc(1.5em + 12px); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 3px) center; + background-size: calc(0.75em + 6px) calc(0.75em + 6px); +} + +.form-control.is-invalid:focus { + border-color: #dc3545; + box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.25); +} + +textarea.form-control.is-invalid { + padding-right: calc(1.5em + 12px); + background-position: top calc(0.375em + 3px) right calc(0.375em + 3px); +} + +.form-select.is-invalid { + border-color: #dc3545; +} + +.form-select.is-invalid:not([multiple]):not([size]) { + padding-right: 66px; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"), + url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + background-position: right 12px center, center right 36px; + background-size: 1rem .75rem, calc(0.75em + 6px) calc(0.75em + 6px); +} + +.form-select.is-invalid:not([multiple])[size="1"] { + padding-right: 66px; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"), + url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + background-position: right 12px center, center right 36px; + background-size: 1rem .75rem, calc(0.75em + 6px) calc(0.75em + 6px); +} + +.form-select.is-invalid:focus { + border-color: #dc3545; + box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.25); +} + +.form-check-input.is-invalid { + border-color: #dc3545; +} + +.form-check-input.is-invalid:checked { + background-color: #dc3545; +} + +.form-check-input.is-invalid:focus { + box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.25); +} + +.form-check-input.is-invalid ~ .form-check-label { + color: #dc3545; +} + +.input-group .form-control.is-invalid { + z-index: 2; +} + +.input-group .form-select.is-invalid { + z-index: 2; +} + +.input-group .form-control.is-invalid:focus { + z-index: 3; +} + +.input-group .form-select.is-invalid:focus { + z-index: 3; +} + +.btn.active { + box-shadow: none; +} + +.btn.active:focus { + box-shadow: none; +} + +.fade { + transition: opacity 0.15s linear; +} + +.fade:not(.show) { + opacity: 0; +} + +.collapse:not(.show) { + display: none; +} + +.collapsing { + height: 0; + overflow: hidden; + transition: height 0.35s ease; +} + +.collapsing.collapse-horizontal { + width: 0; + height: auto; + transition: width 0.35s ease; +} + +.dropdown-menu { + z-index: 1000; +} + +.dropdown-item.active { + color: #1f2937; + -webkit-text-decoration: none; + text-decoration: none; + background-color: #0d6efd; +} + +.dropdown-item:active { + color: #1f2937; + -webkit-text-decoration: none; + text-decoration: none; + background-color: #0d6efd; +} + +.dropdown-item:disabled { + color: #adb5bd; + pointer-events: none; + background-color: transparent; +} + +.dropdown-menu.show { + display: block; +} + +.dropdown-menu-dark .dropdown-item.active { + color: #fff; + background-color: #0d6efd; +} + +.dropdown-menu-dark .dropdown-item:active { + color: #fff; + background-color: #0d6efd; +} + +.dropdown-menu-dark .dropdown-item.disabled { + color: #adb5bd; +} + +.dropdown-menu-dark .dropdown-item:disabled { + color: #adb5bd; +} + +.nav-tabs .nav-link { + color: #4b5563; +} + +.nav-tabs .nav-link:hover { + isolation: isolate; +} + +.nav-tabs .nav-link:focus { + isolation: isolate; +} + +.nav-tabs .nav-link.disabled { + color: #9ca3af; + background-color: transparent; + border-color: transparent; +} + +.nav-tabs .nav-link.active { + color: #2563eb; + border-color: #2563eb; +} + +.nav-tabs .nav-item.show .nav-link { + color: #2563eb; + border-color: #2563eb; +} + +.nav-tabs .dropdown-menu { + margin-top: -0.0625rem; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.nav-pills .nav-link { + background: #f3f4f6; + color: #4b5563; + box-shadow: none; +} + +.nav-pills .nav-link.active { + background: #2563eb; + color: #fff; + box-shadow: 0 .25rem .375rem -0.0625rem rgba(0, 0, 0, 0.1), 0 .125rem .25rem -0.0625rem rgba(0, 0, 0, 0.06); +} + +.nav-pills .show > .nav-link { + background: #2563eb; + color: #fff; + box-shadow: 0 .25rem .375rem -0.0625rem rgba(0, 0, 0, 0.1), 0 .125rem .25rem -0.0625rem rgba(0, 0, 0, 0.06); +} + +.nav-pills .disabled { + color: #9ca3af; + background-color: rgba(243, 244, 246, 0.5); +} + +.nav-pills.menu-sidebar .nav-link { + background-color: transparent; + box-shadow: none; + padding: 0 .3125rem; + border-radius: 0; +} + +.nav-pills.menu-sidebar .nav-link.active { + color: #1266f1; + font-weight: 600; + border-left: 2px solid #1266f1; +} + +.nav-justified > .nav-link { + -ms-flex-basis: 0; + flex-basis: 0; +} + +.nav-justified .nav-item { + -ms-flex-basis: 0; + flex-basis: 0; +} + +.tab-content > .active { + display: block; +} + +.navbar-expand .navbar-nav { + flex-direction: row; +} + +.navbar-expand .navbar-nav .dropdown-menu { + position: absolute; +} + +.navbar-expand .navbar-nav .nav-link { + padding-right: 8px; + padding-left: 8px; +} + +.navbar-expand .offcanvas { + position: inherit; + bottom: 0; + z-index: 1000; + -ms-flex-grow: 1; + flex-grow: 1; + visibility: visible !important; + background-color: transparent; + border-right: 0; + border-left: 0; + transition: none; + transform: none; +} + +.navbar-light .navbar-nav .nav-link.disabled { + color: rgba(0, 0, 0, 0.3); +} + +.navbar-light .navbar-nav .show > .nav-link { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-nav .nav-link.active { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-dark .navbar-nav .nav-link.disabled { + color: rgba(255, 255, 255, 0.25); +} + +.navbar-dark .navbar-nav .show > .nav-link { + color: #fff; +} + +.navbar-dark .navbar-nav .nav-link.active { + color: #fff; +} + +.accordion-item:last-of-type .accordion-button.collapsed { + border-bottom-right-radius: calc(8px - .0625rem); + border-bottom-left-radius: calc(8px - .0625rem); +} + +.btn-close.disabled { + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + opacity: 0.25; +} + +.modal { + z-index: 1055; +} + +.modal-dialog { + margin: 8px; +} + +.modal.fade .modal-dialog { + transition: transform 0.3s ease-out; + transform: translate(0, -3.125rem); +} + +.modal.show .modal-dialog { + transform: none; +} + +.modal.modal-static .modal-dialog { + transform: scale(1.02); +} + +.modal-dialog-scrollable .modal-body { + overflow-y: auto; +} + +.modal-backdrop { + position: fixed; + top: 0; + left: 0; + z-index: 1050; + width: 100vw; + height: 100vh; + background-color: #000; +} + +.modal-backdrop.fade { + opacity: 0; +} + +.modal-backdrop.show { + opacity: 0.5; +} + +.modal-body { + flex: 1 1 auto; +} + +.modal-fullscreen .modal-body { + overflow-y: auto; +} + +.tooltip { + position: absolute; + z-index: 1080; + display: block; + margin: 0; + font-family: var(--bs-font-sans-serif); + font-style: normal; + font-weight: 400; + line-height: 1.5; + -webkit-text-align: start; + text-align: start; + -webkit-text-decoration: none; + text-decoration: none; + -webkit-text-shadow: none; + text-shadow: none; + -webkit-text-transform: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 14px; + word-wrap: break-word; + opacity: 0; +} + +.tooltip.show { + opacity: 1; +} + +.bs-tooltip-top .tooltip-arrow { + bottom: 0; +} + +.bs-tooltip-auto[data-popper-placement^="top"] .tooltip-arrow { + bottom: 0; +} + +.bs-tooltip-top .tooltip-arrow::before { + top: -0.0625rem; + border-width: 6.4px 6.4px 0; + border-top-color: #000; +} + +.bs-tooltip-auto[data-popper-placement^="top"] .tooltip-arrow::before { + top: -0.0625rem; + border-width: 6.4px 6.4px 0; + border-top-color: #000; +} + +.bs-tooltip-end .tooltip-arrow { + left: 0; + width: 6.4px; + height: 12.8px; +} + +.bs-tooltip-auto[data-popper-placement^="right"] .tooltip-arrow { + left: 0; + width: 6.4px; + height: 12.8px; +} + +.bs-tooltip-end .tooltip-arrow::before { + right: -0.0625rem; + border-width: 6.4px 6.4px 6.4px 0; + border-right-color: #000; +} + +.bs-tooltip-auto[data-popper-placement^="right"] .tooltip-arrow::before { + right: -0.0625rem; + border-width: 6.4px 6.4px 6.4px 0; + border-right-color: #000; +} + +.bs-tooltip-bottom .tooltip-arrow { + top: 0; +} + +.bs-tooltip-auto[data-popper-placement^="bottom"] .tooltip-arrow { + top: 0; +} + +.bs-tooltip-bottom .tooltip-arrow::before { + bottom: -0.0625rem; + border-width: 0 6.4px 6.4px; + border-bottom-color: #000; +} + +.bs-tooltip-auto[data-popper-placement^="bottom"] .tooltip-arrow::before { + bottom: -0.0625rem; + border-width: 0 6.4px 6.4px; + border-bottom-color: #000; +} + +.bs-tooltip-start .tooltip-arrow { + right: 0; + width: 6.4px; + height: 12.8px; +} + +.bs-tooltip-auto[data-popper-placement^="left"] .tooltip-arrow { + right: 0; + width: 6.4px; + height: 12.8px; +} + +.bs-tooltip-start .tooltip-arrow::before { + left: -0.0625rem; + border-width: 6.4px 0 6.4px 6.4px; + border-left-color: #000; +} + +.bs-tooltip-auto[data-popper-placement^="left"] .tooltip-arrow::before { + left: -0.0625rem; + border-width: 6.4px 0 6.4px 6.4px; + border-left-color: #000; +} + +.tooltip-inner { + max-width: 12.5rem; + font-size: .875rem; + padding: .375rem 1rem; + color: #fff; + -webkit-text-align: center; + text-align: center; + background-color: #6d6d6d; + border-radius: 4px; +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1070; + display: block; + max-width: 17.25rem; + font-family: var(--bs-font-sans-serif); + font-style: normal; + font-weight: 400; + line-height: 1.5; + -webkit-text-align: start; + text-align: start; + -webkit-text-decoration: none; + text-decoration: none; + -webkit-text-shadow: none; + text-shadow: none; + -webkit-text-transform: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 14px; + word-wrap: break-word; + background-color: #fff; + background-clip: padding-box; + border: 0; + border-radius: 8px; + box-shadow: 0 .625rem .9375rem -0.1875rem rgba(0, 0, 0, 0.1), 0 .25rem .375rem -0.125rem rgba(0, 0, 0, 0.05); +} + +.bs-popover-top > .popover-arrow { + bottom: calc(-8px - .0625rem); +} + +.bs-popover-auto[data-popper-placement^="top"] > .popover-arrow { + bottom: calc(-8px - .0625rem); +} + +.bs-popover-top > .popover-arrow::before { + bottom: 0; + border-width: 8px 8px 0; + border-top-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-auto[data-popper-placement^="top"] > .popover-arrow::before { + bottom: 0; + border-width: 8px 8px 0; + border-top-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-top > .popover-arrow::after { + bottom: .0625rem; + border-width: 8px 8px 0; + border-top-color: #fff; +} + +.bs-popover-auto[data-popper-placement^="top"] > .popover-arrow::after { + bottom: .0625rem; + border-width: 8px 8px 0; + border-top-color: #fff; +} + +.bs-popover-end > .popover-arrow { + left: calc(-8px - .0625rem); + width: 8px; + height: 16px; +} + +.bs-popover-auto[data-popper-placement^="right"] > .popover-arrow { + left: calc(-8px - .0625rem); + width: 8px; + height: 16px; +} + +.bs-popover-end > .popover-arrow::before { + left: 0; + border-width: 8px 8px 8px 0; + border-right-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-auto[data-popper-placement^="right"] > .popover-arrow::before { + left: 0; + border-width: 8px 8px 8px 0; + border-right-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-end > .popover-arrow::after { + left: .0625rem; + border-width: 8px 8px 8px 0; + border-right-color: #fff; +} + +.bs-popover-auto[data-popper-placement^="right"] > .popover-arrow::after { + left: .0625rem; + border-width: 8px 8px 8px 0; + border-right-color: #fff; +} + +.bs-popover-bottom > .popover-arrow { + top: calc(-8px - .0625rem); +} + +.bs-popover-auto[data-popper-placement^="bottom"] > .popover-arrow { + top: calc(-8px - .0625rem); +} + +.bs-popover-bottom > .popover-arrow::before { + top: 0; + border-width: 0 8px 8px 8px; + border-bottom-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-auto[data-popper-placement^="bottom"] > .popover-arrow::before { + top: 0; + border-width: 0 8px 8px 8px; + border-bottom-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-bottom > .popover-arrow::after { + top: .0625rem; + border-width: 0 8px 8px 8px; + border-bottom-color: #fff; +} + +.bs-popover-auto[data-popper-placement^="bottom"] > .popover-arrow::after { + top: .0625rem; + border-width: 0 8px 8px 8px; + border-bottom-color: #fff; +} + +.bs-popover-bottom .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 16px; + margin-left: -8px; + content: ""; + border-bottom: .0625rem solid #f0f0f0; +} + +.bs-popover-auto[data-popper-placement^="bottom"] .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 16px; + margin-left: -8px; + content: ""; + border-bottom: .0625rem solid #f0f0f0; +} + +.bs-popover-start > .popover-arrow { + right: calc(-8px - .0625rem); + width: 8px; + height: 16px; +} + +.bs-popover-auto[data-popper-placement^="left"] > .popover-arrow { + right: calc(-8px - .0625rem); + width: 8px; + height: 16px; +} + +.bs-popover-start > .popover-arrow::before { + right: 0; + border-width: 8px 0 8px 8px; + border-left-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-auto[data-popper-placement^="left"] > .popover-arrow::before { + right: 0; + border-width: 8px 0 8px 8px; + border-left-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-start > .popover-arrow::after { + right: .0625rem; + border-width: 8px 0 8px 8px; + border-left-color: #fff; +} + +.bs-popover-auto[data-popper-placement^="left"] > .popover-arrow::after { + right: .0625rem; + border-width: 8px 0 8px 8px; + border-left-color: #fff; +} + +.popover-header { + padding: 8px 16px; + margin-bottom: 0; + font-size: 16px; + background-color: #fff; + border-bottom: .0625rem solid rgba(0, 0, 0, 0.2); + border-top-left-radius: 8px; + border-top-right-radius: 8px; + font-weight: 500; +} + +.popover-header:empty { + display: none; +} + +.popover-body { + padding: 16px 16px; + color: #212529; +} + +.carousel.pointer-event { + touch-action: pan-y; +} + +.carousel-item { + display: none; + margin-right: -100%; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + transition: transform 0.6s ease-in-out; +} + +.carousel-item.active { + display: block; +} + +.carousel-item-next { + display: block; +} + +.carousel-item-prev { + display: block; +} + +.carousel-item-next:not(.carousel-item-start) { + transform: translateX(100%); +} + +.active.carousel-item-end { + transform: translateX(100%); +} + +.carousel-item-prev:not(.carousel-item-end) { + transform: translateX(-100%); +} + +.active.carousel-item-start { + transform: translateX(-100%); +} + +.carousel-fade .carousel-item { + opacity: 0; + transition-property: opacity; + transform: none; +} + +.carousel-fade .carousel-item.active { + z-index: 1; + opacity: 1; +} + +.carousel-fade .carousel-item-next.carousel-item-start { + z-index: 1; + opacity: 1; +} + +.carousel-fade .carousel-item-prev.carousel-item-end { + z-index: 1; + opacity: 1; +} + +.carousel-fade .active.carousel-item-start { + z-index: 0; + opacity: 0; + transition: opacity 0s 0.6s; +} + +.carousel-fade .active.carousel-item-end { + z-index: 0; + opacity: 0; + transition: opacity 0s 0.6s; +} + +.carousel-indicators { + z-index: 2; + margin-right: 15%; + margin-left: 15%; + list-style: none; +} + +.carousel-indicators [data-bs-target] { + box-sizing: content-box; + flex: 0 1 auto; + width: 1.875rem; + height: .1875rem; + padding: 0; + margin-right: .1875rem; + margin-left: .1875rem; + -webkit-text-indent: -62.4375rem; + text-indent: -62.4375rem; + cursor: pointer; + background-color: #fff; + background-clip: padding-box; + border: 0; + border-top: .625rem solid transparent; + border-bottom: .625rem solid transparent; + opacity: 0.5; + transition: opacity 0.6s ease; +} + +.carousel-indicators .active { + opacity: 1; +} + +.carousel-dark .carousel-indicators [data-bs-target] { + background-color: #000; +} + +.offcanvas { + z-index: 1045; +} + +.offcanvas-backdrop { + position: fixed; + top: 0; + left: 0; + z-index: 1040; + width: 100vw; + height: 100vh; + background-color: #000; +} + +.offcanvas-backdrop.fade { + opacity: 0; +} + +.offcanvas-backdrop.show { + opacity: 0.5; +} + +.offcanvas.show { + transform: none; +} + +.sticky-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; +} + +.vr { + display: inline-block; + align-self: stretch; + width: .0625rem; + min-height: 1em; + background-color: currentColor; + opacity: 0.25; +} + +.animation { + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + padding: auto; +} + +.fade-in { + -webkit-animation-name: _fade-in; + animation-name: _fade-in; +} + +.fade-out { + -webkit-animation-name: _fade-out; + animation-name: _fade-out; +} + +.animation.infinite { + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; +} + +.animation.delay-1s { + -webkit-animation-delay: 1s; + animation-delay: 1s; +} + +.animation.delay-2s { + -webkit-animation-delay: 2s; + animation-delay: 2s; +} + +.animation.delay-3s { + -webkit-animation-delay: 3s; + animation-delay: 3s; +} + +.animation.delay-4s { + -webkit-animation-delay: 4s; + animation-delay: 4s; +} + +.animation.delay-5s { + -webkit-animation-delay: 5s; + animation-delay: 5s; +} + +.animation.fast { + -webkit-animation-duration: 800ms; + animation-duration: 800ms; +} + +.animation.faster { + -webkit-animation-duration: 500ms; + animation-duration: 500ms; +} + +.animation.slow { + -webkit-animation-duration: 2s; + animation-duration: 2s; +} + +.animation.slower { + -webkit-animation-duration: 3s; + animation-duration: 3s; +} + +.slide-in-left { + -webkit-animation-name: _slide-in-left; + animation-name: _slide-in-left; +} + +.slide-in-right { + -webkit-animation-name: _slide-in-right; + animation-name: _slide-in-right; +} + +.slide-out-left { + -webkit-animation-name: _slide-out-left; + animation-name: _slide-out-left; +} + +.slide-out-right { + -webkit-animation-name: _slide-out-right; + animation-name: _slide-out-right; +} + +.ripple-surface { + position: relative; + overflow: hidden; + display: inline-block; + vertical-align: bottom; +} + +.ripple-surface-unbound { + overflow: visible; +} + +.ripple-wave { + background-image: radial-gradient(circle, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, transparent 70%); + border-radius: 50%; + opacity: 0.5; + pointer-events: none; + position: absolute; + touch-action: none; + transform: scale(0); + transition-property: transform, opacity; + transition-timing-function: cubic-bezier(0, 0, 0.15, 1), cubic-bezier(0, 0, 0.15, 1); + z-index: 999; +} + +.ripple-wave.active { + transform: scale(1); + opacity: 0; +} + +.btn .ripple-wave { + background-image: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%); +} + +.ripple-surface-primary .ripple-wave { + background-image: radial-gradient(circle, rgba(18, 102, 241, 0.2) 0, rgba(18, 102, 241, 0.3) 40%, rgba(18, 102, 241, 0.4) 50%, rgba(18, 102, 241, 0.5) 60%, rgba(18, 102, 241, 0) 70%); +} + +.ripple-surface-secondary .ripple-wave { + background-image: radial-gradient(circle, rgba(178, 60, 253, 0.2) 0, rgba(178, 60, 253, 0.3) 40%, rgba(178, 60, 253, 0.4) 50%, rgba(178, 60, 253, 0.5) 60%, rgba(178, 60, 253, 0) 70%); +} + +.ripple-surface-success .ripple-wave { + background-image: radial-gradient(circle, rgba(0, 183, 74, 0.2) 0, rgba(0, 183, 74, 0.3) 40%, rgba(0, 183, 74, 0.4) 50%, rgba(0, 183, 74, 0.5) 60%, rgba(0, 183, 74, 0) 70%); +} + +.ripple-surface-info .ripple-wave { + background-image: radial-gradient(circle, rgba(57, 192, 237, 0.2) 0, rgba(57, 192, 237, 0.3) 40%, rgba(57, 192, 237, 0.4) 50%, rgba(57, 192, 237, 0.5) 60%, rgba(57, 192, 237, 0) 70%); +} + +.ripple-surface-warning .ripple-wave { + background-image: radial-gradient(circle, rgba(255, 169, 0, 0.2) 0, rgba(255, 169, 0, 0.3) 40%, rgba(255, 169, 0, 0.4) 50%, rgba(255, 169, 0, 0.5) 60%, rgba(255, 169, 0, 0) 70%); +} + +.ripple-surface-danger .ripple-wave { + background-image: radial-gradient(circle, rgba(249, 49, 84, 0.2) 0, rgba(249, 49, 84, 0.3) 40%, rgba(249, 49, 84, 0.4) 50%, rgba(249, 49, 84, 0.5) 60%, rgba(249, 49, 84, 0) 70%); +} + +.ripple-surface-light .ripple-wave { + background-image: radial-gradient(circle, rgba(251, 251, 251, 0.2) 0, rgba(251, 251, 251, 0.3) 40%, rgba(251, 251, 251, 0.4) 50%, rgba(251, 251, 251, 0.5) 60%, rgba(251, 251, 251, 0) 70%); +} + +.ripple-surface-dark .ripple-wave { + background-image: radial-gradient(circle, rgba(38, 38, 38, 0.2) 0, rgba(38, 38, 38, 0.3) 40%, rgba(38, 38, 38, 0.4) 50%, rgba(38, 38, 38, 0.5) 60%, rgba(38, 38, 38, 0) 70%); +} + +.ripple-surface-white .ripple-wave { + background-image: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%); +} + +.ripple-surface-black .ripple-wave { + background-image: radial-gradient(circle, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, transparent 70%); +} + +.datepicker-toggle-button { + position: absolute; + outline: none; + border: none; + background-color: transparent; + right: .625rem; + top: 50%; + transform: translate(-50%, -50%); +} + +.datepicker-toggle-button:focus { + color: #2979ff; +} + +.datepicker-toggle-button:hover { + color: #2979ff; +} + +.datepicker-backdrop { + width: 100%; + height: 100%; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.4); + z-index: 1065; +} + +.datepicker-dropdown-container { + width: 20.5rem; + height: 23.75rem; + background-color: #fff; + border-radius: 8px; + box-shadow: 0 .625rem .9375rem -0.1875rem rgba(0, 0, 0, 0.07), 0 .25rem .375rem -0.125rem rgba(0, 0, 0, 0.05); + z-index: 1066; +} + +.datepicker-modal-container { + display: flex; + flex-direction: column; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 20.5rem; + height: 32rem; + background-color: #fff; + border-radius: 9.6px 9.6px 8px 8px; + box-shadow: 0 .625rem .9375rem -0.1875rem rgba(0, 0, 0, 0.07), 0 .25rem .375rem -0.125rem rgba(0, 0, 0, 0.05); + z-index: 1066; +} + +.datepicker-header { + height: 7.5rem; + padding-right: 1.5rem; + padding-left: 1.5rem; + background-color: #2979ff; + display: flex; + flex-direction: column; + border-radius: 8px 8px 0 0; +} + +.datepicker-title { + height: 2rem; + display: flex; + flex-direction: column; + justify-content: flex-end; +} + +.datepicker-title-text { + font-size: .625rem; + font-weight: 400; + -webkit-text-transform: uppercase; + text-transform: uppercase; + letter-spacing: .1062rem; + color: #fff; +} + +.datepicker-date { + height: 4.5rem; + display: flex; + flex-direction: column; + justify-content: flex-end; +} + +.datepicker-date-text { + font-size: 2.125rem; + font-weight: 400; + color: #fff; +} + +.datepicker-main { + position: relative; + height: 100%; +} + +.datepicker-date-controls { + padding: .625rem .75rem 0 .75rem; + display: flex; + justify-content: space-between; + color: rgba(0, 0, 0, 0.64); +} + +.datepicker-view-change-button { + padding: .625rem; + color: #666; + font-weight: 500; + font-size: 14.4px; + border-radius: .625rem; + box-shadow: none; + background-color: transparent; + margin: 0; + border: none; +} + +.datepicker-view-change-button:hover { + background-color: #eee; +} + +.datepicker-view-change-button:focus { + background-color: #eee; +} + +.datepicker-view-change-button:after { + content: ""; + display: inline-block; + width: 0; + height: 0; + border-left: .3125rem solid transparent; + border-right: .3125rem solid transparent; + border-top-width: .3125rem; + border-top-style: solid; + margin: 0 0 0 .3125rem; + vertical-align: middle; +} + +.datepicker-arrow-controls { + margin-top: .625rem; +} + +.datepicker-previous-button { + position: relative; + padding: 0; + width: 2.5rem; + height: 2.5rem; + line-height: 2.5rem; + border: none; + outline: none; + margin: 0; + color: rgba(0, 0, 0, 0.64); + background-color: transparent; + margin-right: 1.5rem; +} + +.datepicker-previous-button:hover { + background-color: #eee; + border-radius: 50%; +} + +.datepicker-previous-button:focus { + background-color: #eee; + border-radius: 50%; +} + +.datepicker-previous-button::after { + top: 0; + left: 0; + right: 0; + bottom: 0; + position: absolute; + content: ""; + margin: .9688rem; + border: 0 solid currentColor; + border-top-width: .125rem; + border-left-width: .125rem; + transform: translateX(.125rem) rotate(-45deg); +} + +.datepicker-next-button { + position: relative; + padding: 0; + width: 2.5rem; + height: 2.5rem; + line-height: 2.5rem; + border: none; + outline: none; + margin: 0; + color: rgba(0, 0, 0, 0.64); + background-color: transparent; +} + +.datepicker-next-button:hover { + background-color: #eee; + border-radius: 50%; +} + +.datepicker-next-button:focus { + background-color: #eee; + border-radius: 50%; +} + +.datepicker-next-button::after { + top: 0; + left: 0; + right: 0; + bottom: 0; + position: absolute; + content: ""; + margin: .9688rem; + border: 0 solid currentColor; + border-top-width: .125rem; + border-right-width: .125rem; + transform: translateX(-0.125rem) rotate(45deg); +} + +.datepicker-view { + padding-left: .75rem; + padding-right: .75rem; + outline: none; +} + +.datepicker-table { + margin-right: auto; + margin-left: auto; + width: 19rem; +} + +.datepicker-day-heading { + width: 2.5rem; + height: 2.5rem; + -webkit-text-align: center; + text-align: center; + font-size: .75rem; + font-weight: 400; +} + +.datepicker-cell { + -webkit-text-align: center; + text-align: center; +} + +.datepicker-cell.disabled { + color: #ccc; + cursor: default; + pointer-events: none; +} + +.datepicker-cell.disabled:hover { + cursor: default; +} + +.datepicker-cell:hover { + cursor: pointer; +} + +.datepicker-cell:not(.disabled):not(.selected):hover .datepicker-cell-content { + background-color: #d3d3d3; +} + +.datepicker-cell.selected .datepicker-cell-content { + background-color: #2979ff; + color: #fff; +} + +.datepicker-cell:not(.selected).focused .datepicker-cell-content { + background-color: #eee; +} + +.datepicker-cell.focused .datepicker-cell-content.selected { + background-color: #2979ff; +} + +.datepicker-cell.current .datepicker-cell-content { + border: .0625rem solid #000; +} + +.datepicker-small-cell { + width: 2.5rem; + height: 2.5rem; +} + +.datepicker-small-cell-content { + width: 2.25rem; + height: 2.25rem; + line-height: 2.25rem; + border-radius: 50%; + font-size: .8125rem; +} + +.datepicker-large-cell { + width: 4.75rem; + height: 2.625rem; +} + +.datepicker-large-cell-content { + width: 4.5rem; + height: 2.5rem; + line-height: 2.5rem; + padding: .0625rem .125rem; + border-radius: 62.4375rem; +} + +.datepicker-footer { + height: 3.5rem; + display: flex; + position: absolute; + width: 100%; + bottom: 0; + justify-content: flex-end; + align-items: center; + padding-left: .75rem; + padding-right: .75rem; +} + +.datepicker-footer-btn { + background-color: #fff; + color: #2979ff; + border: none; + cursor: pointer; + padding: 0 .625rem; + -webkit-text-transform: uppercase; + text-transform: uppercase; + font-size: 12.8px; + font-weight: 500; + height: 2.5rem; + line-height: 2.5rem; + letter-spacing: 1.6px; + border-radius: .625rem; + margin-bottom: .625rem; +} + +.datepicker-footer-btn:hover { + background-color: #eee; +} + +.datepicker-footer-btn:focus { + background-color: #eee; +} + +.datepicker-clear-btn { + margin-right: auto; +} + +.timepicker-wrapper { + touch-action: none; + z-index: 1065; + opacity: 0; + right: 0; + bottom: 0; + top: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.4); +} + +.timepicker-elements { + min-width: 19.375rem; + min-height: 20.3125rem; + background: #fff; + border-top-right-radius: 9.6px; + border-top-left-radius: 9.6px; +} + +.timepicker-head { + background-color: #2979ff; + height: 6.25rem; + border-top-right-radius: 8px; + border-top-left-radius: 8px; + padding: .625rem 1.5rem .625rem 3.125rem; +} + +.timepicker-button { + font-size: 12.8px; + min-width: 4rem; + box-sizing: border-box; + font-weight: 500; + line-height: 2.5rem; + border-radius: .625rem; + letter-spacing: 1.6px; + -webkit-text-transform: uppercase; + text-transform: uppercase; + color: #2979ff; + border: none; + background-color: transparent; + transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; + outline: none; + padding: 0 .625rem; + height: 2.5rem; + margin-bottom: .625rem; +} + +.timepicker-button:hover { + background-color: rgba(0, 0, 0, 0.08); +} + +.timepicker-button:focus { + outline: none; + background-color: rgba(0, 0, 0, 0.08); +} + +.timepicker-current { + font-size: 60px; + font-weight: 300; + line-height: 1.2; + letter-spacing: -0.00833em; + color: #fff; + opacity: 0.54; + border: none; + background: transparent; + padding: 0; +} + +.timepicker-current.active { + opacity: 1; +} + +.timepicker-current-wrapper { + direction: ltr; +} + +.timepicker-mode-wrapper { + margin-left: 1.25rem; + font-size: 1.125rem; + color: rgba(255, 255, 255, 0.54); +} + +.timepicker-mode-wrapper.active { + opacity: 1; +} + +.timepicker-clock-wrapper { + min-width: 19.375rem; + max-width: 20.3125rem; + min-height: 19.0625rem; + overflow-x: hidden; + height: 100%; +} + +.timepicker-clock { + position: relative; + border-radius: 100%; + width: 16.25rem; + height: 16.25rem; + cursor: default; + margin: 0 auto; + background-color: rgba(0, 0, 0, 0.07); +} + +.timepicker-time-tips-minutes.active { + color: #fff; + background-color: #2979ff; + font-weight: 400; +} + +.timepicker-time-tips-inner.active { + color: #fff; + background-color: #2979ff; + font-weight: 400; +} + +.timepicker-time-tips-hours.active { + color: #fff; + background-color: #2979ff; + font-weight: 400; +} + +.timepicker-time-tips-minutes.disabled { + color: #b3afaf; + pointer-events: none; + background-color: transparent; +} + +.timepicker-time-tips-inner.disabled { + color: #b3afaf; + pointer-events: none; + background-color: transparent; +} + +.timepicker-time-tips-hours.disabled { + color: #b3afaf; + pointer-events: none; + background-color: transparent; +} + +.timepicker-dot { + font-weight: 300; + line-height: 1.2; + letter-spacing: -0.00833em; + color: #fff; + font-size: 60px; + opacity: 0.54; + border: none; + background: transparent; + padding: 0; +} + +.timepicker-middle-dot { + top: 50%; + left: 50%; + width: .375rem; + height: .375rem; + transform: translate(-50%, -50%); + border-radius: 50%; + background-color: #2979ff; +} + +.timepicker-hand-pointer { + background-color: #2979ff; + bottom: 50%; + height: 40%; + left: calc(50% - .0625rem); + transform-origin: center bottom 0; + width: .125rem; +} + +.timepicker-time-tips.active { + color: #fff; +} + +.timepicker-circle { + top: -1.3125rem; + left: -0.9375rem; + width: .25rem; + border: .875rem solid #2979ff; + height: .25rem; + box-sizing: content-box; + border-radius: 100%; +} + +.timepicker-hour-mode { + padding: 0; + background-color: transparent; + border: none; + color: #fff; + opacity: 0.54; + cursor: pointer; +} + +.timepicker-hour { + cursor: pointer; +} + +.timepicker-minute { + cursor: pointer; +} + +.timepicker-hour-mode:hover { + background-color: rgba(0, 0, 0, 0.15); + outline: none; +} + +.timepicker-hour-mode:focus { + background-color: rgba(0, 0, 0, 0.15); + outline: none; +} + +.timepicker-hour:hover { + background-color: rgba(0, 0, 0, 0.15); + outline: none; +} + +.timepicker-hour:focus { + background-color: rgba(0, 0, 0, 0.15); + outline: none; +} + +.timepicker-minute:hover { + background-color: rgba(0, 0, 0, 0.15); + outline: none; +} + +.timepicker-minute:focus { + background-color: rgba(0, 0, 0, 0.15); + outline: none; +} + +.timepicker-hour-mode.active { + color: #fff; + opacity: 1; +} + +.timepicker-hour.active { + color: #fff; + opacity: 1; +} + +.timepicker-minute.active { + color: #fff; + opacity: 1; +} + +.timepicker-footer { + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + height: 3.5rem; + padding-left: .75rem; + padding-right: .75rem; + background-color: #fff; +} + +.timepicker-container { + max-height: calc(100% - 4rem); + overflow-y: auto; + box-shadow: 0 .625rem .9375rem -0.1875rem rgba(0, 0, 0, 0.07), 0 .25rem .375rem -0.125rem rgba(0, 0, 0, 0.05); +} + +.timepicker-icon-up.active { + opacity: 1; +} + +.timepicker-icon-down.active { + opacity: 1; +} + +.timepicker-toggle-button { + position: absolute; + outline: none; + border: none; + background-color: transparent; + right: .625rem; + top: 50%; + transform: translate(-50%, -50%); + transition: all 0.3s ease; + cursor: pointer; +} + +.timepicker-toggle-button:hover { + color: #2979ff; +} + +.timepicker-toggle-button:focus { + color: #2979ff; +} + +.timepicker-input:focus + .timepicker-toggle-button { + color: #2979ff; +} + +.timepicker-input:focus + .timepicker-toggle-button i { + color: #2979ff; +} + +.timepicker a.timepicker-toggle-button { + right: .0625rem; +} + +.timepicker-toggle-button.timepicker-icon { + right: .0625rem; +} + +.timepicker-modal .fade.show { + opacity: 1; +} + +.stepper { + position: relative; + padding: 0; + margin: 0; + width: 100%; + list-style: none; + overflow: hidden; + transition: height 0.2s ease-in-out; +} + +.stepper:not(.stepper-vertical) { + display: flex; + justify-content: space-between; +} + +.stepper:not(.stepper-vertical) .stepper-content { + position: absolute; + width: 100%; + padding: 16px; +} + +.stepper:not(.stepper-vertical) .stepper-step { + flex: auto; + height: 72px; +} + +.stepper:not(.stepper-vertical) .stepper-step:first-child .stepper-head { + padding-left: 24px; +} + +.stepper:not(.stepper-vertical) .stepper-step:last-child .stepper-head { + padding-right: 24px; +} + +.stepper:not(.stepper-vertical) .stepper-step:not(:first-child) .stepper-head:before { + flex: 1; + height: .0625rem; + width: 100%; + margin-right: 8px; + content: ""; + background-color: rgba(0, 0, 0, 0.1); +} + +.stepper:not(.stepper-vertical) .stepper-step:not(:last-child) .stepper-head:after { + flex: 1; + height: .0625rem; + width: 100%; + margin-left: 8px; + content: ""; + background-color: rgba(0, 0, 0, 0.1); +} + +.stepper:not(.stepper-vertical) .stepper-head-icon { + margin: 24px 8px 24px 0; +} + +.stepper.stepper-mobile { + justify-content: center; + align-items: flex-end; +} + +.stepper.stepper-mobile.stepper-progress-bar .stepper-head-icon { + display: none; +} + +.stepper.stepper-mobile .stepper-step { + flex: unset; + height: -webkit-fit-content; + height: -moz-fit-content; + height: fit-content; + margin: 16px 0 16px 0; +} + +.stepper.stepper-mobile .stepper-step:not(:last-child) .stepper-head:after { + margin-left: 0; +} + +.stepper.stepper-mobile .stepper-step:not(:first-child) .stepper-head:before { + margin-right: 0; +} + +.stepper.stepper-mobile .stepper-step:not(:last-child):not(:first-child) .stepper-head { + padding-left: 4px; + padding-right: 4px; +} + +.stepper.stepper-mobile .stepper-head-icon { + font-size: 0; + margin: 0; + height: 8px; + width: 8px; + z-index: 1; +} + +.stepper.stepper-mobile .stepper-head-text { + display: none; +} + +.stepper.stepper-mobile .stepper-content { + top: 40.96px; +} + +@media (prefers-reduced-motion: reduce) { + .form-control::-webkit-file-upload-button { + -webkit-transition: none; + transition: none; + } + .form-control::file-selector-button { + transition: none; + } + + .form-control::-webkit-file-upload-button { + -webkit-transition: none; + transition: none; + } + + .form-switch .form-check-input { + transition: none; + } + + .form-range::-webkit-slider-thumb { + -webkit-transition: none; + transition: none; + } + + .form-range::-moz-range-thumb { + -moz-transition: none; + transition: none; + } + + .form-floating > label { + transition: none; + } + + .fade { + transition: none; + } + + .collapsing { + transition: none; + } + + .collapsing.collapse-horizontal { + transition: none; + } + + .accordion-button::after { + transition: none; + } + + .modal.fade .modal-dialog { + transition: none; + } + + .carousel-item { + transition: none; + } + + .carousel-fade .active.carousel-item-start { + transition: none; + } + + .carousel-fade .active.carousel-item-end { + transition: none; + } + + .carousel-control-prev { + transition: none; + } + + .carousel-control-next { + transition: none; + } + + .carousel-indicators [data-bs-target] { + transition: none; + } + + .spinner-border { + -webkit-animation-duration: 1.5s; + animation-duration: 1.5s; + } + + .spinner-grow { + -webkit-animation-duration: 1.5s; + animation-duration: 1.5s; + } +} + +@media (min-width: 36rem) { + .navbar-expand-sm { + flex-wrap: nowrap; + justify-content: flex-start; + } + + .navbar-expand-sm .navbar-nav { + flex-direction: row; + } + + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; + } + + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: 8px; + padding-left: 8px; + } + + .navbar-expand-sm .navbar-nav-scroll { + overflow: visible; + } + + .navbar-expand-sm .navbar-collapse { + display: flex !important; + -ms-flex-basis: auto; + flex-basis: auto; + } + + .navbar-expand-sm .navbar-toggler { + display: none; + } + + .navbar-expand-sm .offcanvas-header { + display: none; + } + + .navbar-expand-sm .offcanvas { + position: inherit; + bottom: 0; + z-index: 1000; + -ms-flex-grow: 1; + flex-grow: 1; + visibility: visible !important; + background-color: transparent; + border-right: 0; + border-left: 0; + transition: none; + transform: none; + } + + .navbar-expand-sm .offcanvas-top { + height: auto; + border-top: 0; + border-bottom: 0; + } + + .navbar-expand-sm .offcanvas-bottom { + height: auto; + border-top: 0; + border-bottom: 0; + } + + .navbar-expand-sm .offcanvas-body { + display: flex; + -ms-flex-grow: 0; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } + + .modal-dialog { + max-width: 31.25rem; + margin: 28px auto; + } + + .modal-dialog-scrollable { + height: calc(100% - 56px); + } + + .modal-dialog-centered { + min-height: calc(100% - 56px); + } + + .modal-sm { + max-width: 18.75rem; + } + + .sticky-sm-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } +} + +@media (min-width: 48rem) { + .navbar-expand-md { + flex-wrap: nowrap; + justify-content: flex-start; + } + + .navbar-expand-md .navbar-nav { + flex-direction: row; + } + + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; + } + + .navbar-expand-md .navbar-nav .nav-link { + padding-right: 8px; + padding-left: 8px; + } + + .navbar-expand-md .navbar-nav-scroll { + overflow: visible; + } + + .navbar-expand-md .navbar-collapse { + display: flex !important; + -ms-flex-basis: auto; + flex-basis: auto; + } + + .navbar-expand-md .navbar-toggler { + display: none; + } + + .navbar-expand-md .offcanvas-header { + display: none; + } + + .navbar-expand-md .offcanvas { + position: inherit; + bottom: 0; + z-index: 1000; + -ms-flex-grow: 1; + flex-grow: 1; + visibility: visible !important; + background-color: transparent; + border-right: 0; + border-left: 0; + transition: none; + transform: none; + } + + .navbar-expand-md .offcanvas-top { + height: auto; + border-top: 0; + border-bottom: 0; + } + + .navbar-expand-md .offcanvas-bottom { + height: auto; + border-top: 0; + border-bottom: 0; + } + + .navbar-expand-md .offcanvas-body { + display: flex; + -ms-flex-grow: 0; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } + + .sticky-md-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } +} + +@media (min-width: 62rem) { + .navbar-expand-lg { + flex-wrap: nowrap; + justify-content: flex-start; + } + + .navbar-expand-lg .navbar-nav { + flex-direction: row; + } + + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; + } + + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: 8px; + padding-left: 8px; + } + + .navbar-expand-lg .navbar-nav-scroll { + overflow: visible; + } + + .navbar-expand-lg .navbar-collapse { + display: flex !important; + -ms-flex-basis: auto; + flex-basis: auto; + } + + .navbar-expand-lg .navbar-toggler { + display: none; + } + + .navbar-expand-lg .offcanvas-header { + display: none; + } + + .navbar-expand-lg .offcanvas { + position: inherit; + bottom: 0; + z-index: 1000; + -ms-flex-grow: 1; + flex-grow: 1; + visibility: visible !important; + background-color: transparent; + border-right: 0; + border-left: 0; + transition: none; + transform: none; + } + + .navbar-expand-lg .offcanvas-top { + height: auto; + border-top: 0; + border-bottom: 0; + } + + .navbar-expand-lg .offcanvas-bottom { + height: auto; + border-top: 0; + border-bottom: 0; + } + + .navbar-expand-lg .offcanvas-body { + display: flex; + -ms-flex-grow: 0; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } + + .modal-lg { + max-width: 50rem; + } + + .modal-xl { + max-width: 50rem; + } + + .sticky-lg-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } +} + +@media (min-width: 75rem) { + .navbar-expand-xl { + flex-wrap: nowrap; + justify-content: flex-start; + } + + .navbar-expand-xl .navbar-nav { + flex-direction: row; + } + + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; + } + + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: 8px; + padding-left: 8px; + } + + .navbar-expand-xl .navbar-nav-scroll { + overflow: visible; + } + + .navbar-expand-xl .navbar-collapse { + display: flex !important; + -ms-flex-basis: auto; + flex-basis: auto; + } + + .navbar-expand-xl .navbar-toggler { + display: none; + } + + .navbar-expand-xl .offcanvas-header { + display: none; + } + + .navbar-expand-xl .offcanvas { + position: inherit; + bottom: 0; + z-index: 1000; + -ms-flex-grow: 1; + flex-grow: 1; + visibility: visible !important; + background-color: transparent; + border-right: 0; + border-left: 0; + transition: none; + transform: none; + } + + .navbar-expand-xl .offcanvas-top { + height: auto; + border-top: 0; + border-bottom: 0; + } + + .navbar-expand-xl .offcanvas-bottom { + height: auto; + border-top: 0; + border-bottom: 0; + } + + .navbar-expand-xl .offcanvas-body { + display: flex; + -ms-flex-grow: 0; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } + + .modal-xl { + max-width: 71.25rem; + } + + .sticky-xl-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } +} + +@media (min-width: 87.5rem) { + .navbar-expand-xxl { + flex-wrap: nowrap; + justify-content: flex-start; + } + + .navbar-expand-xxl .navbar-nav { + flex-direction: row; + } + + .navbar-expand-xxl .navbar-nav .dropdown-menu { + position: absolute; + } + + .navbar-expand-xxl .navbar-nav .nav-link { + padding-right: 8px; + padding-left: 8px; + } + + .navbar-expand-xxl .navbar-nav-scroll { + overflow: visible; + } + + .navbar-expand-xxl .navbar-collapse { + display: flex !important; + -ms-flex-basis: auto; + flex-basis: auto; + } + + .navbar-expand-xxl .navbar-toggler { + display: none; + } + + .navbar-expand-xxl .offcanvas-header { + display: none; + } + + .navbar-expand-xxl .offcanvas { + position: inherit; + bottom: 0; + z-index: 1000; + -ms-flex-grow: 1; + flex-grow: 1; + visibility: visible !important; + background-color: transparent; + border-right: 0; + border-left: 0; + transition: none; + transform: none; + } + + .navbar-expand-xxl .offcanvas-top { + height: auto; + border-top: 0; + border-bottom: 0; + } + + .navbar-expand-xxl .offcanvas-bottom { + height: auto; + border-top: 0; + border-bottom: 0; + } + + .navbar-expand-xxl .offcanvas-body { + display: flex; + -ms-flex-grow: 0; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } + + .sticky-xxl-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } +} + +@media (max-width: 35.9988rem) { + .modal-fullscreen-sm-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + + .modal-fullscreen-sm-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + + .modal-fullscreen-sm-down .modal-header { + border-radius: 0; + } + + .modal-fullscreen-sm-down .modal-body { + overflow-y: auto; + } + + .modal-fullscreen-sm-down .modal-footer { + border-radius: 0; + } +} + +@media (max-width: 47.9988rem) { + .modal-fullscreen-md-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + + .modal-fullscreen-md-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + + .modal-fullscreen-md-down .modal-header { + border-radius: 0; + } + + .modal-fullscreen-md-down .modal-body { + overflow-y: auto; + } + + .modal-fullscreen-md-down .modal-footer { + border-radius: 0; + } +} + +@media (max-width: 61.9988rem) { + .modal-fullscreen-lg-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + + .modal-fullscreen-lg-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + + .modal-fullscreen-lg-down .modal-header { + border-radius: 0; + } + + .modal-fullscreen-lg-down .modal-body { + overflow-y: auto; + } + + .modal-fullscreen-lg-down .modal-footer { + border-radius: 0; + } +} + +@media (max-width: 74.9988rem) { + .modal-fullscreen-xl-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + + .modal-fullscreen-xl-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + + .modal-fullscreen-xl-down .modal-header { + border-radius: 0; + } + + .modal-fullscreen-xl-down .modal-body { + overflow-y: auto; + } + + .modal-fullscreen-xl-down .modal-footer { + border-radius: 0; + } +} + +@media (max-width: 87.4988rem) { + .modal-fullscreen-xxl-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + + .modal-fullscreen-xxl-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + + .modal-fullscreen-xxl-down .modal-header { + border-radius: 0; + } + + .modal-fullscreen-xxl-down .modal-body { + overflow-y: auto; + } + + .modal-fullscreen-xxl-down .modal-footer { + border-radius: 0; + } +} + +@media (prefers-reduced-motion) { + .animation { + transition: none !important; + -webkit-animation: unset !important; + animation: unset !important; + } +} + +@media screen and (min-width: 20rem) and (max-width: 51.25rem) and (orientation: landscape) { + .datepicker-modal-container .datepicker-header { + height: 100%; + } + + .datepicker-modal-container .datepicker-date { + margin-top: 6.25rem; + } + + .datepicker-modal-container .datepicker-day-cell { + width: 32x; + height: 32x; + } + + .datepicker-modal-container { + flex-direction: row; + width: 29.6875rem; + height: 22.5rem; + } + + .datepicker-modal-container.datepicker-day-cell { + width: 2.25rem; + height: 2.25rem; + } +} + +@media screen and (min-width: 20rem) and (max-width: 51.5625rem) and (orientation: landscape) { + .timepicker-elements { + flex-direction: row !important; + border-bottom-left-radius: 8px; + min-width: auto; + min-height: auto; + overflow-y: auto; + } + + .timepicker-head { + border-top-right-radius: 0; + border-bottom-left-radius: 0; + padding: .625rem; + padding-right: .625rem !important; + height: auto; + min-height: 19.0625rem; + } + + .timepicker-head-content { + flex-direction: column; + } + + .timepicker-mode-wrapper { + justify-content: space-around !important; + flex-direction: row !important; + } + + .timepicker-current { + font-size: 48px; + font-weight: 400; + } + + .timepicker-dot { + font-size: 48px; + font-weight: 400; + } +} + +@-webkit-keyframes _spinner-grow { + 0% { + transform: scale(0); + } + + 50% { + opacity: 1; + transform: none; + } +} + +@keyframes _spinner-grow { + 0% { + transform: scale(0); + } + + 50% { + opacity: 1; + transform: none; + } +} + +@-webkit-keyframes _fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes _fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@-webkit-keyframes _fade-out { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} + +@keyframes _fade-out { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} + +@-webkit-keyframes _fade-in-down { + from { + opacity: 0; + transform: translate3d(0, -100%, 0); + } + + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@keyframes _fade-in-down { + from { + opacity: 0; + transform: translate3d(0, -100%, 0); + } + + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@-webkit-keyframes _fade-in-left { + from { + opacity: 0; + transform: translate3d(-100%, 0, 0); + } + + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@keyframes _fade-in-left { + from { + opacity: 0; + transform: translate3d(-100%, 0, 0); + } + + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@-webkit-keyframes _fade-in-right { + from { + opacity: 0; + transform: translate3d(100%, 0, 0); + } + + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@keyframes _fade-in-right { + from { + opacity: 0; + transform: translate3d(100%, 0, 0); + } + + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@-webkit-keyframes _fade-in-up { + from { + opacity: 0; + transform: translate3d(0, 100%, 0); + } + + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@keyframes _fade-in-up { + from { + opacity: 0; + transform: translate3d(0, 100%, 0); + } + + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@-webkit-keyframes _fade-out-down { + from { + opacity: 1; + } + + to { + opacity: 0; + transform: translate3d(0, 100%, 0); + } +} + +@keyframes _fade-out-down { + from { + opacity: 1; + } + + to { + opacity: 0; + transform: translate3d(0, 100%, 0); + } +} + +@-webkit-keyframes _fade-out-left { + from { + opacity: 1; + } + + to { + opacity: 0; + transform: translate3d(-100%, 0, 0); + } +} + +@keyframes _fade-out-left { + from { + opacity: 1; + } + + to { + opacity: 0; + transform: translate3d(-100%, 0, 0); + } +} + +@-webkit-keyframes _fade-out-right { + from { + opacity: 1; + } + + to { + opacity: 0; + transform: translate3d(100%, 0, 0); + } +} + +@keyframes _fade-out-right { + from { + opacity: 1; + } + + to { + opacity: 0; + transform: translate3d(100%, 0, 0); + } +} + +@-webkit-keyframes _fade-out-up { + from { + opacity: 1; + } + + to { + opacity: 0; + transform: translate3d(0, -100%, 0); + } +} + +@keyframes _fade-out-up { + from { + opacity: 1; + } + + to { + opacity: 0; + transform: translate3d(0, -100%, 0); + } +} + +@-webkit-keyframes _slide-in-down { + from { + visibility: visible; + transform: translate3d(0, -100%, 0); + } + + to { + transform: translate3d(0, 0, 0); + } +} + +@keyframes _slide-in-down { + from { + visibility: visible; + transform: translate3d(0, -100%, 0); + } + + to { + transform: translate3d(0, 0, 0); + } +} + +@-webkit-keyframes _slide-in-left { + from { + visibility: visible; + transform: translate3d(-100%, 0, 0); + } + + to { + transform: translate3d(0, 0, 0); + } +} + +@keyframes _slide-in-left { + from { + visibility: visible; + transform: translate3d(-100%, 0, 0); + } + + to { + transform: translate3d(0, 0, 0); + } +} + +@-webkit-keyframes _slide-in-right { + from { + visibility: visible; + transform: translate3d(100%, 0, 0); + } + + to { + transform: translate3d(0, 0, 0); + } +} + +@keyframes _slide-in-right { + from { + visibility: visible; + transform: translate3d(100%, 0, 0); + } + + to { + transform: translate3d(0, 0, 0); + } +} + +@-webkit-keyframes _slide-in-up { + from { + visibility: visible; + transform: translate3d(0, 100%, 0); + } + + to { + transform: translate3d(0, 0, 0); + } +} + +@keyframes _slide-in-up { + from { + visibility: visible; + transform: translate3d(0, 100%, 0); + } + + to { + transform: translate3d(0, 0, 0); + } +} + +@-webkit-keyframes _slide-out-down { + from { + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + transform: translate3d(0, 100%, 0); + } +} + +@keyframes _slide-out-down { + from { + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + transform: translate3d(0, 100%, 0); + } +} + +@-webkit-keyframes _slide-out-left { + from { + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + transform: translate3d(-100%, 0, 0); + } +} + +@keyframes _slide-out-left { + from { + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + transform: translate3d(-100%, 0, 0); + } +} + +@-webkit-keyframes _slide-out-right { + from { + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + transform: translate3d(100%, 0, 0); + } +} + +@keyframes _slide-out-right { + from { + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + transform: translate3d(100%, 0, 0); + } +} + +@-webkit-keyframes _slide-out-up { + from { + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + transform: translate3d(0, -100%, 0); + } +} + +@keyframes _slide-out-up { + from { + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + transform: translate3d(0, -100%, 0); + } +} + +@-webkit-keyframes _slide-down { + from { + transform: translate3d(0, 0, 0); + } + + to { + transform: translate3d(0, 100%, 0); + } +} + +@keyframes _slide-down { + from { + transform: translate3d(0, 0, 0); + } + + to { + transform: translate3d(0, 100%, 0); + } +} + +@-webkit-keyframes _slide-left { + from { + transform: translate3d(0, 0, 0); + } + + to { + transform: translate3d(-100%, 0, 0); + } +} + +@keyframes _slide-left { + from { + transform: translate3d(0, 0, 0); + } + + to { + transform: translate3d(-100%, 0, 0); + } +} + +@-webkit-keyframes _slide-right { + from { + transform: translate3d(0, 0, 0); + } + + to { + transform: translate3d(100%, 0, 0); + } +} + +@keyframes _slide-right { + from { + transform: translate3d(0, 0, 0); + } + + to { + transform: translate3d(100%, 0, 0); + } +} + +@-webkit-keyframes _slide-up { + from { + transform: translate3d(0, 0, 0); + } + + to { + transform: translate3d(0, -100%, 0); + } +} + +@keyframes _slide-up { + from { + transform: translate3d(0, 0, 0); + } + + to { + transform: translate3d(0, -100%, 0); + } +} + +@-webkit-keyframes _zoom-in { + from { + opacity: 0; + transform: scale3d(0.3, 0.3, 0.3); + } + + 50% { + opacity: 1; + } +} + +@keyframes _zoom-in { + from { + opacity: 0; + transform: scale3d(0.3, 0.3, 0.3); + } + + 50% { + opacity: 1; + } +} + +@-webkit-keyframes _zoom-out { + from { + opacity: 1; + } + + 50% { + opacity: 0; + transform: scale3d(0.3, 0.3, 0.3); + } + + to { + opacity: 0; + } +} + +@keyframes _zoom-out { + from { + opacity: 1; + } + + 50% { + opacity: 0; + transform: scale3d(0.3, 0.3, 0.3); + } + + to { + opacity: 0; + } +} + +@-webkit-keyframes _tada { + from { + transform: scale3d(1, 1, 1); + } + + 10% { + transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); + } + + 20% { + transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); + } + + 30% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 50% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 70% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 90% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 40% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + 60% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + 80% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + to { + transform: scale3d(1, 1, 1); + } +} + +@keyframes _tada { + from { + transform: scale3d(1, 1, 1); + } + + 10% { + transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); + } + + 20% { + transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); + } + + 30% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 50% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 70% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 90% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 40% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + 60% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + 80% { + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + to { + transform: scale3d(1, 1, 1); + } +} + +@-webkit-keyframes _pulse { + from { + transform: scale3d(1, 1, 1); + } + + 50% { + transform: scale3d(1.05, 1.05, 1.05); + } + + to { + transform: scale3d(1, 1, 1); + } +} + +@keyframes _pulse { + from { + transform: scale3d(1, 1, 1); + } + + 50% { + transform: scale3d(1.05, 1.05, 1.05); + } + + to { + transform: scale3d(1, 1, 1); + } +} + +@-webkit-keyframes _show-up-clock { + 0% { + opacity: 0; + transform: scale(0.7); + } + + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes _show-up-clock { + 0% { + opacity: 0; + transform: scale(0.7); + } + + to { + opacity: 1; + transform: scale(1); + } +} + +.sr-only { + position: absolute; + width: .0625rem; + height: .0625rem; + padding: 0; + margin: -0.0625rem; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.visible { + visibility: visible; +} + +.static { + position: static; +} + +.fixed { + position: fixed; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.sticky { + position: -webkit-sticky; + position: sticky; +} + +.inset-0 { + top: 0rem; + right: 0rem; + bottom: 0rem; + left: 0rem; +} + +.top-0 { + top: 0rem; +} + +.right-0 { + right: 0rem; +} + +.bottom-0 { + bottom: 0rem; +} + +.top-5 { + top: 20px; +} + +.right-5 { + right: 20px; +} + +.z-10 { + z-index: 10; +} + +.m-auto { + margin: auto; +} + +.m-2 { + margin: 8px; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.my-auto { + margin-top: auto; + margin-bottom: auto; +} + +.my-2 { + margin-top: 8px; + margin-bottom: 8px; +} + +.my-4 { + margin-top: 16px; + margin-bottom: 16px; +} + +.mx-1 { + margin-left: 4px; + margin-right: 4px; +} + +.mb-4 { + margin-bottom: 16px; +} + +.mb-8 { + margin-bottom: 32px; +} + +.mr-4 { + margin-right: 16px; +} + +.ml-2 { + margin-left: 8px; +} + +.mt-3 { + margin-top: 12px; +} + +.mt-2 { + margin-top: 8px; +} + +.mt-4 { + margin-top: 16px; +} + +.ml-auto { + margin-left: auto; +} + +.mt-0 { + margin-top: 0rem; +} + +.mb-3 { + margin-bottom: 12px; +} + +.mb-2 { + margin-bottom: 8px; +} + +.mb-5 { + margin-bottom: 20px; +} + +.mb-10 { + margin-bottom: 40px; +} + +.mt-10 { + margin-top: 40px; +} + +.mt-8 { + margin-top: 32px; +} + +.mb-6 { + margin-bottom: 24px; +} + +.mr-2 { + margin-right: 8px; +} + +.ml-5 { + margin-left: 20px; +} + +.ml-6 { + margin-left: 24px; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.flex { + display: flex; +} + +.inline-flex { + display: inline-flex; +} + +.table { + display: table; +} + +.hidden { + display: none; +} + +.h-full { + height: 100%; +} + +.h-7 { + height: 28px; +} + +.h-fit { + height: -webkit-fit-content; + height: -moz-fit-content; + height: fit-content; +} + +.h-10 { + height: 40px; +} + +.h-12 { + height: 48px; +} + +.h-6 { + height: 24px; +} + +.h-14 { + height: 56px; +} + +.h-4 { + height: 16px; +} + +.h-40 { + height: 160px; +} + +.h-[70vh] { + height: 70vh; +} + +.h-[50vh] { + height: 50vh; +} + +.h-8 { + height: 32px; +} + +.h-5 { + height: 20px; +} + +.h-screen { + height: 100vh; +} + +.h-16 { + height: 64px; +} + +.max-h-[70vh] { + max-height: 70vh; +} + +.max-h-[80vh] { + max-height: 80vh; +} + +.max-h-60 { + max-height: 240px; +} + +.min-h-[60vh] { + min-height: 60vh; +} + +.min-h-screen { + min-height: 100vh; +} + +.w-full { + width: 100%; +} + +.w-7 { + width: 28px; +} + +.w-11/12 { + width: 91.666667%; +} + +.w-10 { + width: 40px; +} + +.w-12 { + width: 48px; +} + +.w-32 { + width: 128px; +} + +.w-6 { + width: 24px; +} + +.w-14 { + width: 56px; +} + +.w-4 { + width: 16px; +} + +.w-8 { + width: 32px; +} + +.w-5 { + width: 20px; +} + +.w-16 { + width: 64px; +} + +.min-w-full { + min-width: 100%; +} + +.max-w-lg { + max-width: 512px; +} + +.max-w-xs { + max-width: 320px; +} + +.max-w-sm { + max-width: 384px; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-auto { + flex: 1 1 auto; +} + +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.cursor-pointer { + cursor: pointer; +} + +.resize { + resize: both; +} + +.list-none { + list-style-type: none; +} + +.appearance-none { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.flex-row { + flex-direction: row; +} + +.flex-col { + flex-direction: column; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.content-center { + align-content: center; +} + +.items-end { + align-items: flex-end; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.justify-around { + justify-content: space-around; +} + +.justify-evenly { + justify-content: space-evenly; +} + +.gap-2 { + gap: 8px; +} + +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(8px * var(--tw-space-x-reverse)); + margin-left: calc(8px * calc(1 - var(--tw-space-x-reverse))); +} + +.divide-y > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(.0625rem * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(.0625rem * var(--tw-divide-y-reverse)); +} + +.divide-gray-200 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-divide-opacity)); +} + +.overflow-hidden { + overflow: hidden; +} + +.overflow-x-auto { + overflow-x: auto; +} + +.overflow-y-auto { + overflow-y: auto; +} + +.overflow-x-hidden { + overflow-x: hidden; +} + +.overflow-y-scroll { + overflow-y: scroll; +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.whitespace-nowrap { + white-space: nowrap; +} + +.whitespace-pre-line { + white-space: pre-line; +} + +.rounded-full { + border-radius: 624.9375rem; +} + +.rounded { + border-radius: 4px; +} + +.rounded-xl { + border-radius: 12px; +} + +.rounded-lg { + border-radius: 8px; +} + +.rounded-md { + border-radius: 6px; +} + +.rounded-t-lg { + border-top-left-radius: 8px; + border-top-right-radius: 8px; +} + +.border-2 { + border-width: .125rem; +} + +.border { + border-width: .0625rem; +} + +.border-b-2 { + border-bottom-width: .125rem; +} + +.border-b { + border-bottom-width: .0625rem; +} + +.border-t { + border-top-width: .0625rem; +} + +.border-gray-200 { + --tw-border-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-border-opacity)); +} + +.border-white { + --tw-border-opacity: 1; + border-color: rgb(255 255 255 / var(--tw-border-opacity)); +} + +.border-gray-300 { + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + +.border-red-500 { + --tw-border-opacity: 1; + border-color: rgb(239 68 68 / var(--tw-border-opacity)); +} + +.bg-blue-500 { + --tw-bg-opacity: 1; + background-color: rgb(59 130 246 / var(--tw-bg-opacity)); +} + +.bg-blue-900 { + --tw-bg-opacity: 1; + background-color: rgb(30 58 138 / var(--tw-bg-opacity)); +} + +.bg-gray-200 { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + +.bg-blue-400 { + --tw-bg-opacity: 1; + background-color: rgb(96 165 250 / var(--tw-bg-opacity)); +} + +.bg-transparent { + background-color: transparent; +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.bg-green-500 { + --tw-bg-opacity: 1; + background-color: rgb(34 197 94 / var(--tw-bg-opacity)); +} + +.bg-gray-400 { + --tw-bg-opacity: 1; + background-color: rgb(156 163 175 / var(--tw-bg-opacity)); +} + +.bg-gray-300 { + --tw-bg-opacity: 1; + background-color: rgb(209 213 219 / var(--tw-bg-opacity)); +} + +.bg-blue-600 { + --tw-bg-opacity: 1; + background-color: rgb(37 99 235 / var(--tw-bg-opacity)); +} + +.bg-black { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); +} + +.bg-slate-100 { + --tw-bg-opacity: 1; + background-color: rgb(241 245 249 / var(--tw-bg-opacity)); +} + +.bg-gray-50 { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity)); +} + +.bg-red-600 { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity)); +} + +.bg-slate-200 { + --tw-bg-opacity: 1; + background-color: rgb(226 232 240 / var(--tw-bg-opacity)); +} + +.bg-gray-800 { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +.object-cover { + -o-object-fit: cover; + object-fit: cover; +} + +.p-4 { + padding: 16px; +} + +.p-2 { + padding: 8px; +} + +.p-2.5 { + padding: 10px; +} + +.p-1.5 { + padding: 6px; +} + +.p-1 { + padding: 4px; +} + +.p-5 { + padding: 20px; +} + +.p-6 { + padding: 24px; +} + +.py-10 { + padding-top: 40px; + padding-bottom: 40px; +} + +NaNrem-5 { + padding-left: 20px; + padding-right: 20px; +} + +NaNrem-2 { + padding-left: 8px; + padding-right: 8px; +} + +.py-2 { + padding-top: 8px; + padding-bottom: 8px; +} + +.py-1 { + padding-top: 4px; + padding-bottom: 4px; +} + +NaNrem-6 { + padding-left: 24px; + padding-right: 24px; +} + +.py-4 { + padding-top: 16px; + padding-bottom: 16px; +} + +NaNrem-4 { + padding-left: 16px; + padding-right: 16px; +} + +.py-8 { + padding-top: 32px; + padding-bottom: 32px; +} + +.py-2.5 { + padding-top: 10px; + padding-bottom: 10px; +} + +.py-3 { + padding-top: 12px; + padding-bottom: 12px; +} + +.py-5 { + padding-top: 20px; + padding-bottom: 20px; +} + +NaNrem-3 { + padding-left: 12px; + padding-right: 12px; +} + +NaNrem-8 { + padding-left: 32px; + padding-right: 32px; +} + +NaNrem-1 { + padding-left: 4px; + padding-right: 4px; +} + +.pt-4 { + padding-top: 16px; +} + +.pr-6 { + padding-right: 24px; +} + +.pb-6 { + padding-bottom: 24px; +} + +.pl-4 { + padding-left: 16px; +} + +.pb-10 { + padding-bottom: 40px; +} + +.pr-0 { + padding-right: 0rem; +} + +.pr-2 { + padding-right: 8px; +} + +.pl-2 { + padding-left: 8px; +} + +.pt-6 { + padding-top: 24px; +} + +.pb-8 { + padding-bottom: 32px; +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +.align-baseline { + vertical-align: baseline; +} + +.align-text-bottom { + vertical-align: text-bottom; +} + +.text-2xl { + font-size: 24px; + line-height: 32px; +} + +.text-3xl { + font-size: 30px; + line-height: 36px; +} + +.text-xs { + font-size: 12px; + line-height: 16px; +} + +.text-xl { + font-size: 20px; + line-height: 28px; +} + +.text-7xl { + font-size: 72px; + line-height: 1; +} + +.text-sm { + font-size: 14px; + line-height: 20px; +} + +.text-lg { + font-size: 18px; + line-height: 28px; +} + +.text-base { + font-size: 16px; + line-height: 24px; +} + +.font-bold { + font-weight: 700; +} + +.font-medium { + font-weight: 500; +} + +.font-normal { + font-weight: 400; +} + +.uppercase { + text-transform: uppercase; +} + +.italic { + font-style: italic; +} + +.leading-6 { + line-height: 24px; +} + +.leading-tight { + line-height: 1.25; +} + +.tracking-wider { + letter-spacing: 0.05em; +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.text-black { + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); +} + +.text-gray-700 { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity)); +} + +.text-gray-800 { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity)); +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + +.text-gray-400 { + --tw-text-opacity: 1; + color: rgb(156 163 175 / var(--tw-text-opacity)); +} + +.text-gray-900 { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.text-slate-500 { + --tw-text-opacity: 1; + color: rgb(100 116 139 / var(--tw-text-opacity)); +} + +.text-gray-600 { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity)); +} + +.text-red-500 { + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity)); +} + +.text-blue-500 { + --tw-text-opacity: 1; + color: rgb(59 130 246 / var(--tw-text-opacity)); +} + +.text-red-400 { + --tw-text-opacity: 1; + color: rgb(248 113 113 / var(--tw-text-opacity)); +} + +.opacity-40 { + opacity: 0.4; +} + +.shadow-md { + --tw-shadow: 0 .25rem .375rem -0.0625rem rgb(0 0 0 / 0.1), 0 .125rem .25rem -0.125rem rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 .25rem .375rem -0.0625rem var(--tw-shadow-color), 0 .125rem .25rem -0.125rem var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow { + --tw-shadow: 0 .0625rem .1875rem 0 rgb(0 0 0 / 0.1), 0 .0625rem .125rem -0.0625rem rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 .0625rem .1875rem 0 var(--tw-shadow-color), 0 .0625rem .125rem -0.0625rem var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-lg { + --tw-shadow: 0 .625rem .9375rem -0.1875rem rgb(0 0 0 / 0.1), 0 .25rem .375rem -0.25rem rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 .625rem .9375rem -0.1875rem var(--tw-shadow-color), 0 .25rem .375rem -0.25rem var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-inner { + --tw-shadow: inset 0 .125rem .25rem 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: inset 0 .125rem .25rem 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.outline-none { + outline: .125rem solid transparent; + outline-offset: .125rem; +} + +.outline { + outline-style: solid; +} + +.ring-blue-600 { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(37 99 235 / var(--tw-ring-opacity)); +} + +.ring-indigo-600 { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(79 70 229 / var(--tw-ring-opacity)); +} + +.ring-offset-2 { + --tw-ring-offset-width: .125rem; +} + +.blur { + --tw-blur: blur(.5rem); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.transition-transform { + transition-property: transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition { + transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-text-decoration-color, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color, + -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-300 { + transition-duration: 300ms; +} + +.duration-150 { + transition-duration: 150ms; +} + +.ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +@media screen and (max-width: 47.9375rem) { + .sidebar-holder { + width: 100%; + min-width: 12.5rem; + max-width: 12.5rem; + position: fixed; + top: 0; + left: 0; + } + + .page-header span { + margin-left: auto; + } +} + +.hover:scale-105:hover { + --tw-scale-x: 1.05; + --tw-scale-y: 1.05; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.hover:bg-blue-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +} + +.hover:bg-gray-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + +.hover:bg-gray-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + +.hover:bg-green-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(22 163 74 / var(--tw-bg-opacity)); +} + +.hover:bg-red-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(185 28 28 / var(--tw-bg-opacity)); +} + +.hover:bg-gray-900:hover { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); +} + +.hover:text-gray-800:hover { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity)); +} + +.hover:text-gray-900:hover { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.hover:text-blue-800:hover { + --tw-text-opacity: 1; + color: rgb(30 64 175 / var(--tw-text-opacity)); +} + +.hover:shadow-lg:hover { + --tw-shadow: 0 .625rem .9375rem -0.1875rem rgb(0 0 0 / 0.1), 0 .25rem .375rem -0.25rem rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 .625rem .9375rem -0.1875rem var(--tw-shadow-color), 0 .25rem .375rem -0.25rem var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.focus:bg-blue-700:focus { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +} + +.focus:bg-green-600:focus { + --tw-bg-opacity: 1; + background-color: rgb(22 163 74 / var(--tw-bg-opacity)); +} + +.focus:bg-red-700:focus { + --tw-bg-opacity: 1; + background-color: rgb(185 28 28 / var(--tw-bg-opacity)); +} + +.focus:bg-gray-900:focus { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); +} + +.focus:shadow-lg:focus { + --tw-shadow: 0 .625rem .9375rem -0.1875rem rgb(0 0 0 / 0.1), 0 .25rem .375rem -0.25rem rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 .625rem .9375rem -0.1875rem var(--tw-shadow-color), 0 .25rem .375rem -0.25rem var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.focus:outline-none:focus { + outline: .125rem solid transparent; + outline-offset: .125rem; +} + +.focus:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(.125rem + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus:ring-0:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0rem + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus:ring-gray-300:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity)); +} + +.active:bg-blue-800:active { + --tw-bg-opacity: 1; + background-color: rgb(30 64 175 / var(--tw-bg-opacity)); +} + +.active:bg-green-700:active { + --tw-bg-opacity: 1; + background-color: rgb(21 128 61 / var(--tw-bg-opacity)); +} + +.active:bg-red-800:active { + --tw-bg-opacity: 1; + background-color: rgb(153 27 27 / var(--tw-bg-opacity)); +} + +.active:bg-gray-900:active { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); +} + +.active:shadow-lg:active { + --tw-shadow: 0 .625rem .9375rem -0.1875rem rgb(0 0 0 / 0.1), 0 .25rem .375rem -0.25rem rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 .625rem .9375rem -0.1875rem var(--tw-shadow-color), 0 .25rem .375rem -0.25rem var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.disabled:cursor-not-allowed:disabled { + cursor: not-allowed; +} + +@media (prefers-color-scheme: dark) { + .dark:text-gray-400 { + --tw-text-opacity: 1; + color: rgb(156 163 175 / var(--tw-text-opacity)); + } + + .dark:text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); + } + + .dark:hover:bg-gray-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); + } + + .dark:hover:text-white:hover { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); + } +} + +.property-grid { + display: grid; + grid-template-columns: repeat(2, 15.625rem); + -moz-column-gap: 1.9375rem; + column-gap: 1.9375rem; + row-gap: 5rem; + padding-inline: 16px; + min-width: 37.5rem; +} + +@media (min-width: 40rem) { + .property-grid { + grid-template-columns: repeat(2, minmax(18.25rem, 1fr)); + padding-inline: 0; + } + + .sm:ml-4 { + margin-left: 16px; + } + + .sm:flex { + display: flex; + } + + .sm:w-1/2 { + width: 50%; + } + + .sm:px-6 { + padding-left: 24px; + padding-right: 24px; + } + + .sm:text-left { + text-align: left; + } +} + +@media (min-width: 48rem) { + .md:h-52 { + height: 208px; + } + + .md:w-1/2 { + width: 50%; + } +} + +@media (min-width: 64rem) { + .property-grid { + grid-template-columns: repeat(3, minmax(18.25rem, 1fr)); + } + + .lg:block { + display: block; + } + + .lg:flex { + display: flex; + } + + .lg:hidden { + display: none; + } + + .lg:h-80 { + height: 320px; + } + + .lg:max-h-[60vh] { + max-height: 60vh; + } + + .lg:w-1/3 { + width: 33.333333%; + } + + .lg:w-1/2 { + width: 50%; + } + + .lg:px-6 { + padding-left: 24px; + padding-right: 24px; + } +} + +@media (min-width: 80rem) { + .property-grid { + grid-template-columns: repeat(4, minmax(18.25rem, 1fr)); + } + + .xl:w-1/4 { + width: 25%; + } + + .xl:w-1/2 { + width: 50%; + } +} + + + \ No newline at end of file diff --git a/day20/src/pages/404/NotFoundPage.jsx b/day20/src/pages/404/NotFoundPage.jsx new file mode 100644 index 0000000..92f3dfd --- /dev/null +++ b/day20/src/pages/404/NotFoundPage.jsx @@ -0,0 +1,33 @@ + + import React from "react"; + import {Loader} from "Components/Loader"; + + const NotFoundPage = () => { + const [ loading, setLoading ] = React.useState( true ); + + console.log( loading ); + + React.useEffect( () => { + const interval = setTimeout( () => { + setLoading( false ); + }, 5000 ); + // return () => clearInterval(interval); + }, [] ); + + return ( + <> + { loading ? ( + + ) : ( +
    + Not Found +
    + ) } + + ); + }; + + export default NotFoundPage; + + + \ No newline at end of file diff --git a/day20/src/pages/404/index.js b/day20/src/pages/404/index.js new file mode 100644 index 0000000..83428ac --- /dev/null +++ b/day20/src/pages/404/index.js @@ -0,0 +1,4 @@ + + import {lazy} from 'react' + export const NotFoundPage = lazy( ()=> import('./NotFoundPage')) + \ No newline at end of file diff --git a/day20/src/pages/Admin/Add/AddAdminCmsPage.jsx b/day20/src/pages/Admin/Add/AddAdminCmsPage.jsx new file mode 100644 index 0000000..4660417 --- /dev/null +++ b/day20/src/pages/Admin/Add/AddAdminCmsPage.jsx @@ -0,0 +1,182 @@ + + import React from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { useNavigate } from "react-router-dom"; +import { GlobalContext, showToast } from "Context/Global"; +import { tokenExpireError } from "Context/Auth"; +import { DynamicContentType } from "Components/DynamicContentType"; + +const AddAdminCmsPage = ({ setSidebar }) => { + const schema = yup + .object({ + page: yup.string().required(), + key: yup.string().required(), + type: yup.string().required(), + value: yup.string(), + }) + .required(); + + const selectType = [ + { key: "text", value: "Text" }, + { key: "image", value: "Image" }, + { key: "number", value: "Number" }, + { key: "kvp", value: "Key-Value Pair" }, + { key: "image-list", value: "Image List" }, + { key: "captioned-image-list", value: "Captioned Image List" }, + { key: "team-list", value: "Team List" }, + ]; + + const { dispatch } = React.useContext(GlobalContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + const [contentType, setContentType] = React.useState(selectType[0]?.key); + const [contentValue, setContentValue] = React.useState(''); + const [isUpdating, setIsUpdating] = React.useState(false); + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = async (data) => { + let sdk = new MkdSDK(); + setIsUpdating(true); + console.log(data); + try { + sdk.setTable("cms"); + + const result = await sdk.cmsAdd( + data.page, + data.key, + data.type, + contentValue + ); + if (!result.error) { + navigate("/admin/cms"); + showToast(globalDispatch, "Added"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + console.log("Error", error); + setError("page", { + type: "manual", + message: error.message, + }); + tokenExpireError(dispatch, error.message); + } + setIsUpdating(false); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "cms", + }, + }); + }, []); + + return ( +
    +
    +
    + {/* setSidebar(false)} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> + + */} + Add CMS Content +
    +
    + + +
    +
    + {/*

    Add CMS Content

    */} +
    +
    + + +
    +
    + + +

    {errors.key?.message}

    +
    +
    + + +
    + + + {/* */} + +
    + ); +}; + +export default AddAdminCmsPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Add/AddAdminEmailPage.jsx b/day20/src/pages/Admin/Add/AddAdminEmailPage.jsx new file mode 100644 index 0000000..15cbc4f --- /dev/null +++ b/day20/src/pages/Admin/Add/AddAdminEmailPage.jsx @@ -0,0 +1,188 @@ + + import React from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { useNavigate } from "react-router-dom"; +import { GlobalContext, showToast } from "Context/Global"; +import { tokenExpireError } from "Context/Auth"; +const AddAdminEmailPage = () => { + const [isUpdating, setIsUpdating] = React.useState(false); + + const schema = yup + .object({ + slug: yup.string().required(), + subject: yup.string().required(), + html: yup.string().required(), + tag: yup.string().required(), + }) + .required(); + + const { dispatch } = React.useContext(GlobalContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = async (data) => { + let sdk = new MkdSDK(); + setIsUpdating(true); + try { + sdk.setTable("email"); + + const result = await sdk.callRestAPI( + { + slug: data.slug, + subject: data.subject, + html: data.html, + tag: data.tag, + }, + "POST" + ); + if (!result.error) { + navigate("/admin/email"); + showToast(globalDispatch, "Added"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + console.log("Error", error); + setError("subject", { + type: "manual", + message: error.message, + }); + tokenExpireError(dispatch, error.message); + }; + setIsUpdating(false); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "email", + }, + }); + }, []); + + return ( +
    +
    +
    + {/* setSidebar(false)} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> + + */} + Add Email +
    +
    + + +
    +
    + {/*

    Add Email

    */} +
    +
    + + +
    +
    + + +

    + {errors.subject?.message} +

    +
    +
    + + +

    {errors.tag?.message}

    +
    +
    + + +

    {errors.html?.message}

    +
    + {/* */} +
    +
    + ); +}; + +export default AddAdminEmailPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Add/AddAdminPhotoPage.jsx b/day20/src/pages/Admin/Add/AddAdminPhotoPage.jsx new file mode 100644 index 0000000..0adcb57 --- /dev/null +++ b/day20/src/pages/Admin/Add/AddAdminPhotoPage.jsx @@ -0,0 +1,86 @@ + + import React, { useState } from "react"; +import Uppy from "@uppy/core"; +import XHRUpload from "@uppy/xhr-upload"; +import { Dashboard, useUppy } from "@uppy/react"; +import "@uppy/core/dist/style.css"; +import "@uppy/drag-drop/dist/style.css"; + +import MkdSDK from "Utils/MkdSDK"; +import { GlobalContext, showToast } from "Context/Global"; +import { useNavigate } from "react-router"; +let sdk = new MkdSDK(); + +const AddAdminPhotoPage = ({ setSidebar }) => { + const { dispatch } = React.useContext(GlobalContext); + const navigate = useNavigate(); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + const uppy = useUppy(() => { + let model = new Uppy(); + model.use(XHRUpload, { + id: "XHRUpload", + method: "post", + formData: true, + limit: 0, + fieldName: "file", + allowedMetaFields: ["caption", "size"], + headers: sdk.getHeader(), + endpoint: sdk.uploadUrl(), + }); + + model.on("file-added", (file) => { + model.setFileMeta(file.id, { + size: file.size, + caption: "", + }); + }); + + model.on("upload-success", async (file, response) => { + const httpStatus = response.status; // HTTP status code + const responseBody = response.body; + console.log("response", response); + showToast(globalDispatch, "Uploaded"); + navigate("/admin/photos"); + }); + + model.on("upload-error", (file, error, response) => { + const httpStatus = response.status; // HTTP status code + if (httpStatus == 401) { + tokenExpireError(dispatch, "TOKEN_EXPIRED"); + } + }); + return model; + }); + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "photos", + }, + }); + }, []); + + return ( +
    +
    +
    + {/* setSidebar(false)} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> + + */} + Add Photo +
    +
    + +
    +
    + {/*

    Add Photo

    */} + +
    + ); +}; + +export default AddAdminPhotoPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Add/AddAdminStripePricePage.jsx b/day20/src/pages/Admin/Add/AddAdminStripePricePage.jsx new file mode 100644 index 0000000..40d845d --- /dev/null +++ b/day20/src/pages/Admin/Add/AddAdminStripePricePage.jsx @@ -0,0 +1,394 @@ + + import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { useNavigate } from "react-router-dom"; +import { GlobalContext, showToast } from "Context/Global"; +import { tokenExpireError, AuthContext } from "Context/Auth"; + +const AddAdminStripePricePage = ({ setSidebar }) => { + const [priceType, setPriceType] = useState("one_time"); + const [selectProduct, setSelectProduct] = useState([]); + const [isUpdatingPrice, setIsUpdatingPrice] = useState(false); + + const schema = yup + .object({ + product_id: yup.string().required(), + name: yup.string().required(), + amount: yup.string().required(), + type: yup.string().required(), + interval: yup.string().when("type", { + is: "recurring", + then: (schema) => schema.required(), + otherwise: (schema) => schema.notRequired(), + }), + interval_count: yup.string(), + usage_type: yup.string().when("type", { + is: "recurring", + then: (schema) => schema.required(), + otherwise: (schema) => schema.notRequired(), + }), + usage_limit: yup.string(), + trial_days: yup.string(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + setValue, + trigger, + resetField, + getValues, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const selectType = [ + { key: 0, value: "", display: "Nothing Selected" }, + { key: 1, value: "one_time", display: "One Time" }, + { key: 2, value: "recurring", display: "Recurring" }, + ]; + + const selectUsageType = [ + { key: 0, value: "", display: "Nothing Selected" }, + { key: 1, value: "licenced", display: "Upfront" }, + { key: 2, value: "metered", display: "Metered" }, + ]; + + const selectInterval = [ + { key: 0, value: "", display: "Nothing Selected" }, + { key: 1, value: "day", display: "Day" }, + { key: 2, value: "week", display: "Week" }, + { key: 3, value: "month", display: "Month" }, + { key: 4, value: "year", display: "Year" }, + { key: 5, value: "lifetime", display: "Lifetime" }, + ]; + + const onSubmit = async (data) => { + let sdk = new MkdSDK(); + console.log(data); + setIsUpdatingPrice(true); + try { + const result = await sdk.addStripePrice(data); + if (!result.error) { + showToast(globalDispatch, "Price Added"); + navigate("/admin/prices"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + console.log(field); + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + console.log("Error", error); + + showToast(globalDispatch, error.message); + tokenExpireError(dispatch, error.message); + } + setIsUpdatingPrice(false); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "prices", + }, + }); + (async () => { + let sdk = new MkdSDK(); + const { list, error } = await sdk.getStripeProducts({ limit: "all" }); + if (error) { + showToast( + dispatch, + "Something went wrong while fetching products list" + ); + return; + } + setSelectProduct(list); + })(); + }, []); + return ( +
    +
    +
    + (/* setSidebar(false)} + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + > + + */) + Add Price +
    +
    + + +
    +
    +
    +
    + + +

    + {errors.product_id?.message} +

    +
    + +
    + + +

    {errors.name?.message}

    +
    + +
    + + +

    + {errors.amount?.message} +

    +
    + +
    + + +

    {errors.type?.message}

    +
    + {priceType === "recurring" ? ( +
    +
    + + +

    + {errors.interval?.message} +

    +
    +
    + + +

    + {errors.interval_count?.message} +

    +
    + +
    + + +

    + {errors.usage_type?.message} +

    +
    +
    + + +

    + {errors.trial_days?.message} +

    +
    +
    + + +

    + {errors.usage_limit?.message} +

    +
    +
    + ) : ( + "" + )} + + {/* */} +
    +
    + ); +}; + +export default AddAdminStripePricePage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Add/AddAdminUserPage.jsx b/day20/src/pages/Admin/Add/AddAdminUserPage.jsx new file mode 100644 index 0000000..f0535f3 --- /dev/null +++ b/day20/src/pages/Admin/Add/AddAdminUserPage.jsx @@ -0,0 +1,193 @@ + + import React from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { useNavigate } from "react-router-dom"; +import { GlobalContext, showToast } from "Context/Global"; +import { tokenExpireError } from "Context/Auth"; + +const AddAdminUserPage = ({ setSidebar }) => { + const schema = yup + .object({ + email: yup.string().email().required(), + password: yup.string().required(), + role: yup.string(), + }) + .required(); + + const { dispatch } = React.useContext(GlobalContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [isUpdating, setIsUpdating] = React.useState(false); + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const selectRole = [ + { name: "role", value: "admin" }, + { name: "role", value: "employee" }, + ]; + + const onSubmit = async (data) => { + let sdk = new MkdSDK(); + setIsUpdating(true); + try { + const result = await sdk.register(data.email, data.password, data.role); + if (!result.error) { + showToast(dispatch, "Added"); + navigate("/admin/users"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + console.log("Error", error); + setError("email", { + type: "manual", + message: error.message, + }); + tokenExpireError(dispatch, error.message); + } + setIsUpdating(false); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "users", + }, + }); + }, []); + return ( +
    +
    +
    + {/* setSidebar(false)} + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + > + + */} + Add User +
    +
    + + +
    +
    +
    +
    + + +

    {errors.email?.message}

    +
    +
    + + +
    +
    + + +

    + {errors.password?.message} +

    +
    + {/* */} +
    +
    + ); +}; + +export default AddAdminUserPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Add/AdminAddDepartmentTablePage.jsx b/day20/src/pages/Admin/Add/AdminAddDepartmentTablePage.jsx new file mode 100644 index 0000000..5496481 --- /dev/null +++ b/day20/src/pages/Admin/Add/AdminAddDepartmentTablePage.jsx @@ -0,0 +1,147 @@ + + import React from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate } from "react-router-dom"; + import { tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import ReactQuill from 'react-quill'; + import 'react-quill/dist/quill.snow.css'; + import { isImage, empty, isVideo, isPdf } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; + import {InteractiveButton} from "Components/InteractiveButton"; + import {SkeletonLoader }from"Components/Skeleton" + + const AddDepartmentPage = () => { + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const schema = yup + .object({ + + name: yup.string(), + }) + .required(); + + const { dispatch } = React.useContext(GlobalContext); + const [fileObj, setFileObj] = React.useState({}); + const [isSubmitLoading, setIsSubmitLoading] = React.useState(false); + + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + const [pageDetails, setPageDetails] = React.useState([]); + + const getPageDetails = async () => { + const result = await (new TreeSDK()).getList("table") + .catch(e => console.error(object)) + setPageDetails(result.list); + } + + const previewImage = (field, target) => { + let tempFileObj = fileObj; + tempFileObj[field] = { + file: target.files[0], + tempURL: URL.createObjectURL(target.files[0]), + }; + setFileObj({ ...tempFileObj }); + }; + + const onSubmit = async (_data) => { + let sdk = new MkdSDK(); + setIsSubmitLoading(true) + try { + for (let item in fileObj) { + let formData = new FormData(); + formData.append('file', fileObj[item].file); + let uploadResult = await sdk.uploadImage(formData); + _data[item] = uploadResult.url; + } + + sdk.setTable("department"); + + const result = await sdk.callRestAPI( + { + + name: _data.name, + + }, + "POST" + ); + if (!result.error) { + showToast(globalDispatch, "Added"); + navigate("/admin/department"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setIsSubmitLoading(false) + } catch (error) { + setIsSubmitLoading(false) + console.log("Error", error); + setError("name", { + type: "manual", + message: error.message, + }); + tokenExpireError(dispatch, error.message); + } + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "department", + }, + }); + }, []); + + return ( +
    +

    Add Department

    +
    + + + + + + Submit + + +
    + ); + }; + + export default AddDepartmentPage; + + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Add/AdminAddItemMovementHistoryTablePage.jsx b/day20/src/pages/Admin/Add/AdminAddItemMovementHistoryTablePage.jsx new file mode 100644 index 0000000..8589b21 --- /dev/null +++ b/day20/src/pages/Admin/Add/AdminAddItemMovementHistoryTablePage.jsx @@ -0,0 +1,190 @@ + + import React from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate } from "react-router-dom"; + import { tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import ReactQuill from 'react-quill'; + import 'react-quill/dist/quill.snow.css'; + import { isImage, empty, isVideo, isPdf } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; + import {InteractiveButton} from "Components/InteractiveButton"; + import {SkeletonLoader }from"Components/Skeleton" + + const AddItemMovementHistoryPage = () => { + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const schema = yup + .object({ + + product_id: yup.string(), + moved_from: yup.string(), + moved_to: yup.string(), + date: yup.string(), + }) + .required(); + + const { dispatch } = React.useContext(GlobalContext); + const [fileObj, setFileObj] = React.useState({}); + const [isSubmitLoading, setIsSubmitLoading] = React.useState(false); + + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + const [pageDetails, setPageDetails] = React.useState([]); + + const getPageDetails = async () => { + const result = await (new TreeSDK()).getList("table") + .catch(e => console.error(object)) + setPageDetails(result.list); + } + + const previewImage = (field, target) => { + let tempFileObj = fileObj; + tempFileObj[field] = { + file: target.files[0], + tempURL: URL.createObjectURL(target.files[0]), + }; + setFileObj({ ...tempFileObj }); + }; + + const onSubmit = async (_data) => { + let sdk = new MkdSDK(); + setIsSubmitLoading(true) + try { + for (let item in fileObj) { + let formData = new FormData(); + formData.append('file', fileObj[item].file); + let uploadResult = await sdk.uploadImage(formData); + _data[item] = uploadResult.url; + } + + sdk.setTable("item_movement_history"); + + const result = await sdk.callRestAPI( + { + + product_id: _data.product_id, + moved_from: _data.moved_from, + moved_to: _data.moved_to, + date: _data.date, + + }, + "POST" + ); + if (!result.error) { + showToast(globalDispatch, "Added"); + navigate("/admin/item_movement_history"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setIsSubmitLoading(false) + } catch (error) { + setIsSubmitLoading(false) + console.log("Error", error); + setError("product_id", { + type: "manual", + message: error.message, + }); + tokenExpireError(dispatch, error.message); + } + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "item_movement_history", + }, + }); + }, []); + + return ( +
    +

    Add Item Movement History

    +
    + + + + + + + + + + + + + + + + Submit + + +
    + ); + }; + + export default AddItemMovementHistoryPage; + + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Add/AdminAddItemsTablePage.jsx b/day20/src/pages/Admin/Add/AdminAddItemsTablePage.jsx new file mode 100644 index 0000000..6499c05 --- /dev/null +++ b/day20/src/pages/Admin/Add/AdminAddItemsTablePage.jsx @@ -0,0 +1,218 @@ + + import React from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate } from "react-router-dom"; + import { tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import ReactQuill from 'react-quill'; + import 'react-quill/dist/quill.snow.css'; + import { isImage, empty, isVideo, isPdf } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; + import {InteractiveButton} from "Components/InteractiveButton"; + import {SkeletonLoader }from"Components/Skeleton" + + const AddItemsPage = () => { + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const schema = yup + .object({ + + title: yup.string(), + quantity: yup.string(), + image: yup.string(), + price: yup.string(), + status: yup.string(), + location: yup.string(), + }) + .required(); + + const { dispatch } = React.useContext(GlobalContext); + const [fileObj, setFileObj] = React.useState({}); + const [isSubmitLoading, setIsSubmitLoading] = React.useState(false); + + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + const [pageDetails, setPageDetails] = React.useState([]); + + const getPageDetails = async () => { + const result = await (new TreeSDK()).getList("table") + .catch(e => console.error(object)) + setPageDetails(result.list); + } + + const previewImage = (field, target) => { + let tempFileObj = fileObj; + tempFileObj[field] = { + file: target.files[0], + tempURL: URL.createObjectURL(target.files[0]), + }; + setFileObj({ ...tempFileObj }); + }; + + const onSubmit = async (_data) => { + let sdk = new MkdSDK(); + setIsSubmitLoading(true) + try { + for (let item in fileObj) { + let formData = new FormData(); + formData.append('file', fileObj[item].file); + let uploadResult = await sdk.uploadImage(formData); + _data[item] = uploadResult.url; + } + + sdk.setTable("items"); + + const result = await sdk.callRestAPI( + { + + title: _data.title, + quantity: _data.quantity, + image: _data.image, + price: _data.price, + status: _data.status, + location: _data.location, + + }, + "POST" + ); + if (!result.error) { + showToast(globalDispatch, "Added"); + navigate("/admin/items"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setIsSubmitLoading(false) + } catch (error) { + setIsSubmitLoading(false) + console.log("Error", error); + setError("title", { + type: "manual", + message: error.message, + }); + tokenExpireError(dispatch, error.message); + } + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "items", + }, + }); + }, []); + + return ( +
    +

    Add Items

    +
    + + + + + + + + + + + + + + + + + + + + + + Submit + + +
    + ); + }; + + export default AddItemsPage; + + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Add/AdminAddLocationTablePage.jsx b/day20/src/pages/Admin/Add/AdminAddLocationTablePage.jsx new file mode 100644 index 0000000..8542333 --- /dev/null +++ b/day20/src/pages/Admin/Add/AdminAddLocationTablePage.jsx @@ -0,0 +1,147 @@ + + import React from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate } from "react-router-dom"; + import { tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import ReactQuill from 'react-quill'; + import 'react-quill/dist/quill.snow.css'; + import { isImage, empty, isVideo, isPdf } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; + import {InteractiveButton} from "Components/InteractiveButton"; + import {SkeletonLoader }from"Components/Skeleton" + + const AddLocationPage = () => { + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const schema = yup + .object({ + + name: yup.string(), + }) + .required(); + + const { dispatch } = React.useContext(GlobalContext); + const [fileObj, setFileObj] = React.useState({}); + const [isSubmitLoading, setIsSubmitLoading] = React.useState(false); + + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + const [pageDetails, setPageDetails] = React.useState([]); + + const getPageDetails = async () => { + const result = await (new TreeSDK()).getList("table") + .catch(e => console.error(object)) + setPageDetails(result.list); + } + + const previewImage = (field, target) => { + let tempFileObj = fileObj; + tempFileObj[field] = { + file: target.files[0], + tempURL: URL.createObjectURL(target.files[0]), + }; + setFileObj({ ...tempFileObj }); + }; + + const onSubmit = async (_data) => { + let sdk = new MkdSDK(); + setIsSubmitLoading(true) + try { + for (let item in fileObj) { + let formData = new FormData(); + formData.append('file', fileObj[item].file); + let uploadResult = await sdk.uploadImage(formData); + _data[item] = uploadResult.url; + } + + sdk.setTable("location"); + + const result = await sdk.callRestAPI( + { + + name: _data.name, + + }, + "POST" + ); + if (!result.error) { + showToast(globalDispatch, "Added"); + navigate("/admin/location"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setIsSubmitLoading(false) + } catch (error) { + setIsSubmitLoading(false) + console.log("Error", error); + setError("name", { + type: "manual", + message: error.message, + }); + tokenExpireError(dispatch, error.message); + } + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "location", + }, + }); + }, []); + + return ( +
    +

    Add Location

    +
    + + + + + + Submit + + +
    + ); + }; + + export default AddLocationPage; + + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Add/AdminAddUserTablePage.jsx b/day20/src/pages/Admin/Add/AdminAddUserTablePage.jsx new file mode 100644 index 0000000..5a7e5d4 --- /dev/null +++ b/day20/src/pages/Admin/Add/AdminAddUserTablePage.jsx @@ -0,0 +1,370 @@ + + import React from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate } from "react-router-dom"; + import { tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import ReactQuill from 'react-quill'; + import 'react-quill/dist/quill.snow.css'; + import { isImage, empty, isVideo, isPdf } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; + import {InteractiveButton} from "Components/InteractiveButton"; + import {SkeletonLoader }from"Components/Skeleton" + + const AddUserPage = () => { + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const schema = yup + .object({ + + oauth: yup.string(), + role: yup.string(), + first_name: yup.string(), + last_name: yup.string(), + email: yup.string(), + password: yup.string(), + type: yup.string(), + verify: yup.string(), + phone: yup.string(), + photo: yup.string(), + refer: yup.string(), + stripe_uid: yup.string(), + paypal_uid: yup.string(), + two_factor_authentication: yup.string(), + status: yup.string(), + department: yup.string(), + }) + .required(); + + const { dispatch } = React.useContext(GlobalContext); + const [fileObj, setFileObj] = React.useState({}); + const [isSubmitLoading, setIsSubmitLoading] = React.useState(false); + + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + const [pageDetails, setPageDetails] = React.useState([]); + + const getPageDetails = async () => { + const result = await (new TreeSDK()).getList("table") + .catch(e => console.error(object)) + setPageDetails(result.list); + } + + const previewImage = (field, target) => { + let tempFileObj = fileObj; + tempFileObj[field] = { + file: target.files[0], + tempURL: URL.createObjectURL(target.files[0]), + }; + setFileObj({ ...tempFileObj }); + }; + + const onSubmit = async (_data) => { + let sdk = new MkdSDK(); + setIsSubmitLoading(true) + try { + for (let item in fileObj) { + let formData = new FormData(); + formData.append('file', fileObj[item].file); + let uploadResult = await sdk.uploadImage(formData); + _data[item] = uploadResult.url; + } + + sdk.setTable("user"); + + const result = await sdk.callRestAPI( + { + + oauth: _data.oauth, + role: _data.role, + first_name: _data.first_name, + last_name: _data.last_name, + email: _data.email, + password: _data.password, + type: _data.type, + verify: _data.verify, + phone: _data.phone, + photo: _data.photo, + refer: _data.refer, + stripe_uid: _data.stripe_uid, + paypal_uid: _data.paypal_uid, + two_factor_authentication: _data.two_factor_authentication, + status: _data.status, + department: _data.department, + + }, + "POST" + ); + if (!result.error) { + showToast(globalDispatch, "Added"); + navigate("/admin/user"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setIsSubmitLoading(false) + } catch (error) { + setIsSubmitLoading(false) + console.log("Error", error); + setError("oauth", { + type: "manual", + message: error.message, + }); + tokenExpireError(dispatch, error.message); + } + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "user", + }, + }); + }, []); + + return ( +
    +

    Add User

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +

    + {errors.photo?.message} +

    +
    + + + + + + + + + + + + + + + + + + + + + + + Submit + + +
    + ); + }; + + export default AddUserPage; + + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Auth/AdminForgotPage.jsx b/day20/src/pages/Admin/Auth/AdminForgotPage.jsx new file mode 100644 index 0000000..d7b1865 --- /dev/null +++ b/day20/src/pages/Admin/Auth/AdminForgotPage.jsx @@ -0,0 +1,115 @@ + + import React, {useState } from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import { Link } from "react-router-dom"; + import MkdSDK from "Utils/MkdSDK"; + import { tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import { InteractiveButton } from "Components/InteractiveButton"; + +const AdminForgotPage = () => { + + const [submitLoading, setSubmitLoading] = useState(false); + + const schema = yup + .object({ + email: yup.string().email().required(), + }) + .required(); + + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const { dispatch } = React.useContext(GlobalContext); + + const onSubmit = async (data) => { + let sdk = new MkdSDK(); + try { + setSubmitLoading(true) + const result = await sdk.forgot(data.email, admin); + + if (!result.error) { + showToast(dispatch, "Reset Code Sent"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setSubmitLoading(false) + } catch (error) { + setSubmitLoading(false) + console.log("Error", error); + setError("email", { + type: "manual", + message: error?.response?.data?.message?error?.response?.data?.message:error?.message, + }); + tokenExpireError(dispatch, error?.response?.data?.message?error?.response?.data?.message:error?.message); + } + } + + return ( + <> +
    +
    +
    + + +

    + {errors && errors.email?.message} +

    +
    + +
    + + Forgot Password + + + Login? + +
    +
    +

    + © {new Date().getFullYear()} manaknightdigital inc. All rights + reserved. +

    +
    + + ); +} + + export default AdminForgotPage; diff --git a/day20/src/pages/Admin/Auth/AdminResetPage.jsx b/day20/src/pages/Admin/Auth/AdminResetPage.jsx new file mode 100644 index 0000000..cabf6b2 --- /dev/null +++ b/day20/src/pages/Admin/Auth/AdminResetPage.jsx @@ -0,0 +1,168 @@ + + import React, {useState} from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { Link } from "react-router-dom"; + import { useNavigate } from "react-router-dom"; + import { AuthContext, tokenExpireError } from "Context/Auth"; + import { showToast } from "Context/Global"; + import { InteractiveButton } from "Components/InteractiveButton"; + + const AdminResetPage = () => { + const { dispatch } = React.useContext(AuthContext); + const [submitLoading, setSubmitLoading] = useState(false); + const search = window.location.search; + const params = new URLSearchParams(search); + const token = params.get("token"); + + const schema = yup + .object({ + code: yup.string().required(), + password: yup.string().required(), + confirmPassword: yup + .string() + .oneOf([yup.ref("password"), null], "Passwords must match"), + }) + .required(); + + const navigate = useNavigate(); + + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = async (data) => { + let sdk = new MkdSDK(); + try { + setSubmitLoading(true) + const result = await sdk.reset(token, data.code, data.password); + if (!result.error) { + showToast(dispatch, "Password Reset"); + setTimeout(() => { + navigate(`/admin/login`); + }, 2000); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setSubmitLoading(false) + } catch (error) { + setSubmitLoading(false) + console.log("Error", error); + setError("code", { + type: "manual", + message: error?.response?.data?.message?error?.response?.data?.message:error?.message, + }); + tokenExpireError(dispatch, error?.response?.data?.message?error?.response?.data?.message:error?.message); + } + }; + + return ( + <> +
    +
    +
    + + +

    + {errors.code?.message} +

    +
    +
    + + +

    + {errors.password?.message} +

    +
    +
    + + +

    + {errors.confirmPassword?.message} +

    +
    +
    + + Reset Password + + + Login? + +
    +
    +

    + © {new Date().getFullYear()} manaknightdigital inc. All rights + reserved. +

    +
    + + ); + }; + + export default AdminResetPage; + + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Auth/CustomAdminLoginPage.jsx b/day20/src/pages/Admin/Auth/CustomAdminLoginPage.jsx new file mode 100644 index 0000000..ddc94e3 --- /dev/null +++ b/day20/src/pages/Admin/Auth/CustomAdminLoginPage.jsx @@ -0,0 +1,184 @@ + + import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { Link, useLocation } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; +import { InteractiveButton } from "Components/InteractiveButton"; +import { AuthContext } from "Context/Auth"; +import { GlobalContext, showToast } from "Context/Global"; +import { LoginBgNew } from "Assets/images"; + +let sdk = new MkdSDK(); + +const AdminLoginPage = () => { + const schema = yup + .object({ + email: yup.string().email().required(), + password: yup.string().required(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const { dispatch: GlobalDispatch } = React.useContext(GlobalContext); + + const [submitLoading, setSubmitLoading] = useState(false); + const [showPassword, setShowPassword] = useState(false); + const location = useLocation(); + const searchParams = new URLSearchParams(location.search); + const redirect_uri = searchParams.get("redirect_uri"); + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = async (data) => { + try { + setSubmitLoading(true); + const result = await sdk.login(data.email, data.password, "admin"); + if (!result.error) { + + dispatch({ + type: "LOGIN", + payload: result, + }); + showToast(GlobalDispatch, "Succesfully Logged In", 4000, "success"); + navigate(redirect_uri ?? "/admin/dashboard"); + + } else { + setSubmitLoading(false); + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + setSubmitLoading(false); + showToast( + GlobalDispatch, + error?.response?.data?.message + ? error?.response?.data?.message + : error?.message, + 4000, + "error" + ); + console.log("Error", error); + setError("email", { + type: "manual", + message: error?.response?.data?.message + ? error?.response?.data?.message + : error?.message, + }); + } + }; + + + + return ( +
    + +
    + +
    + + + + +
    Welcome Back
    +
    Don’t have account? Sign up here
    + +
    + + + + +
    + + + +
    +
    + + +

    + {errors?.email?.message} +

    +
    +
    + +
    + + setShowPassword(!showPassword)}> + { + showPassword ? ( + + + + ) : ( + + + + + ) + } + +
    +

    + {errors?.password?.message} +

    +
    +
    +
    + + Remember me +
    + Forgot password +
    + + Sign in + +
    + + + +
    + +
    +
    + ) +}; + +export default AdminLoginPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Auth/CustomAdminSignUpPage.jsx b/day20/src/pages/Admin/Auth/CustomAdminSignUpPage.jsx new file mode 100644 index 0000000..db0007b --- /dev/null +++ b/day20/src/pages/Admin/Auth/CustomAdminSignUpPage.jsx @@ -0,0 +1,156 @@ + +import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { Link, useLocation } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; +import { InteractiveButton } from "Components/InteractiveButton"; +import { AuthContext } from "Context/Auth"; +import { GlobalContext, showToast } from "Context/Global"; +import { LoginBgNew } from "Assets/images"; + +let sdk = new MkdSDK(); + +const AdminSignUpPage = () => { + const schema = yup + .object({ + email: yup.string().email().required(), + password: yup.string().required(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const { dispatch: GlobalDispatch } = React.useContext(GlobalContext); + + const [submitLoading, setSubmitLoading] = useState(false) + const location = useLocation() + const searchParams = new URLSearchParams(location.search) + const redirect_uri = searchParams.get('redirect_uri') + + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = async (data) => { + try { + setSubmitLoading(true) + const result = await sdk.register(data.email, data.password, "admin"); + if (!result.error) { + dispatch({ + type: "LOGIN", + payload: result, + }); + showToast(GlobalDispatch, "Succesfully Registered", 4000, "success") + navigate(redirect_uri ?? "/admin/dashboard"); + + } else { + setSubmitLoading(false) + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + setSubmitLoading(false) + console.log("Error", error); + showToast(GlobalDispatch, error?.message, 4000, "error") + setError("email", { + type: "manual", + message: error?.response?.data?.message ? error?.response?.data?.message : error?.message, + }); + } + }; + + return ( +
    +
    +
    +
    +

    Register

    +
    + + + +

    {errors.email?.message}

    +
    + +
    + + + +

    {errors.password?.message}

    +
    + + {/* Forgot Password */} + + + Register + +
    + + {/*
    OR
    */} +
    +
    +

    Already have an account? Login

    +
    +
    + {/*
    */} +

    © {new Date().getFullYear()} manaknightdigital inc. All rights reserved.

    +
    +
    +
    +
    + + +
    + + ); +}; + +export default AdminSignUpPage; diff --git a/day20/src/pages/Admin/Custom/CustomAdminAdminDashboardPage.jsx b/day20/src/pages/Admin/Custom/CustomAdminAdminDashboardPage.jsx new file mode 100644 index 0000000..e54dd38 --- /dev/null +++ b/day20/src/pages/Admin/Custom/CustomAdminAdminDashboardPage.jsx @@ -0,0 +1,50 @@ + + import React, { useState, useContext } from "react"; + import { LazyLoad } from "Components/LazyLoad"; + import { tokenExpireError, AuthContext } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + + + + + + + + + + + + + + + + const AdminDashboardPage = () => { + + + + const {state, dispatch} = useContext(AuthContext); + const {state:globalState, dispatch:globalDispatch} = useContext(GlobalContext); + + + + + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "{notes}", + }, + }); + }, []); + + + + return ( +
    + +
    + ); + }; + + export default AdminDashboardPage; \ No newline at end of file diff --git a/day20/src/pages/Admin/Edit/AdminEditDepartmentTablePage.jsx b/day20/src/pages/Admin/Edit/AdminEditDepartmentTablePage.jsx new file mode 100644 index 0000000..a1c7c93 --- /dev/null +++ b/day20/src/pages/Admin/Edit/AdminEditDepartmentTablePage.jsx @@ -0,0 +1,173 @@ + + import React, { useEffect, useState } from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate, useParams } from "react-router-dom"; + import { AuthContext, tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import ReactQuill from 'react-quill'; + import 'react-quill/dist/quill.snow.css'; + import { isImage, empty, isVideo, isPdf } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; +import {InteractiveButton} from "Components/InteractiveButton" +import { SkeletonLoader } from "Components/Skeleton"; + + + let sdk = new MkdSDK(); + + const EditDepartmentPage = () => { + const { dispatch } = React.useContext(AuthContext); + const schema = yup + .object({ + + name: yup.string(), + }) + .required(); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [fileObj, setFileObj] = React.useState({}); + const [isLoading, setIsLoading] = React.useState(true) + const [loading, setLoading] = React.useState(false) + + const navigate = useNavigate(); + + const [name, setName] = useState(''); + // const [id, setId] = useState(0); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const params = useParams(); + + useEffect(function () { + (async function () { + try { + setLoading(true) + + sdk.setTable("department"); + const result = await sdk.callRestAPI({ id: Number(params?.id)}, "GET"); + if (!result.error) { + + setValue('name', result.model.name); + + + setName(result.model.name); + setId(result.model.id); + setLoading(false) + + } + } catch (error) { + setLoading(false) + + console.log("error", error); + tokenExpireError(dispatch, error.message); + } + })(); + }, []); + + const previewImage = (field, target) => { + let tempFileObj = fileObj; + tempFileObj[field] = { + file: target.files[0], + tempURL: URL.createObjectURL(target.files[0]), + }; + setFileObj({ ...tempFileObj }); + }; + + + const onSubmit = async (_data) => { + setIsLoading(true) + try { + sdk.setTable("department"); + for (let item in fileObj) { + let formData = new FormData(); + formData.append('file', fileObj[item].file); + let uploadResult = await sdk.uploadImage(formData); + _data[item] = uploadResult.url; + + } + const result = await sdk.callRestAPI( + { + id: id, + + name: _data.name, + + + }, + "PUT" + ); + + if (!result.error) { + showToast(globalDispatch, "Updated"); + navigate("/admin/department"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setIsLoading(false) + } catch (error) { + setIsLoading(false) + console.log("Error", error); + setError("name", { + type: "manual", + message: error.message, + }); + } + }; + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "department", + }, + }); + }, []); + + return ( +
    +

    Edit Department

    + {loading? () : (
    + + + + + + Submit + + )} +
    + ); + }; + + export default EditDepartmentPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Edit/AdminEditItemMovementHistoryTablePage.jsx b/day20/src/pages/Admin/Edit/AdminEditItemMovementHistoryTablePage.jsx new file mode 100644 index 0000000..69af562 --- /dev/null +++ b/day20/src/pages/Admin/Edit/AdminEditItemMovementHistoryTablePage.jsx @@ -0,0 +1,225 @@ + + import React, { useEffect, useState } from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate, useParams } from "react-router-dom"; + import { AuthContext, tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import ReactQuill from 'react-quill'; + import 'react-quill/dist/quill.snow.css'; + import { isImage, empty, isVideo, isPdf } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; +import {InteractiveButton} from "Components/InteractiveButton" +import { SkeletonLoader } from "Components/Skeleton"; + + + let sdk = new MkdSDK(); + + const EditItemMovementHistoryPage = () => { + const { dispatch } = React.useContext(AuthContext); + const schema = yup + .object({ + + product_id: yup.string(), + moved_from: yup.string(), + moved_to: yup.string(), + date: yup.string(), + }) + .required(); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [fileObj, setFileObj] = React.useState({}); + const [isLoading, setIsLoading] = React.useState(true) + const [loading, setLoading] = React.useState(false) + + const navigate = useNavigate(); + + const [product_id, setProductId] = useState(0); + const [moved_from, setMovedFrom] = useState(''); + const [moved_to, setMovedTo] = useState(''); + const [date, setDate] = useState(''); + // const [id, setId] = useState(0); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const params = useParams(); + + useEffect(function () { + (async function () { + try { + setLoading(true) + + sdk.setTable("item_movement_history"); + const result = await sdk.callRestAPI({ id: Number(params?.id)}, "GET"); + if (!result.error) { + + setValue('product_id', result.model.product_id); + setValue('moved_from', result.model.moved_from); + setValue('moved_to', result.model.moved_to); + setValue('date', result.model.date); + + + setProductId(result.model.product_id); + setMovedFrom(result.model.moved_from); + setMovedTo(result.model.moved_to); + setDate(result.model.date); + setId(result.model.id); + setLoading(false) + + } + } catch (error) { + setLoading(false) + + console.log("error", error); + tokenExpireError(dispatch, error.message); + } + })(); + }, []); + + const previewImage = (field, target) => { + let tempFileObj = fileObj; + tempFileObj[field] = { + file: target.files[0], + tempURL: URL.createObjectURL(target.files[0]), + }; + setFileObj({ ...tempFileObj }); + }; + + + const onSubmit = async (_data) => { + setIsLoading(true) + try { + sdk.setTable("item_movement_history"); + for (let item in fileObj) { + let formData = new FormData(); + formData.append('file', fileObj[item].file); + let uploadResult = await sdk.uploadImage(formData); + _data[item] = uploadResult.url; + + } + const result = await sdk.callRestAPI( + { + id: id, + + product_id: _data.product_id, + moved_from: _data.moved_from, + moved_to: _data.moved_to, + date: _data.date, + + + }, + "PUT" + ); + + if (!result.error) { + showToast(globalDispatch, "Updated"); + navigate("/admin/item_movement_history"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setIsLoading(false) + } catch (error) { + setIsLoading(false) + console.log("Error", error); + setError("product_id", { + type: "manual", + message: error.message, + }); + } + }; + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "item_movement_history", + }, + }); + }, []); + + return ( +
    +

    Edit Item Movement History

    + {loading? () : (
    + + + + + + + + + + + + + + + + Submit + + )} +
    + ); + }; + + export default EditItemMovementHistoryPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Edit/AdminEditItemsTablePage.jsx b/day20/src/pages/Admin/Edit/AdminEditItemsTablePage.jsx new file mode 100644 index 0000000..b69c42b --- /dev/null +++ b/day20/src/pages/Admin/Edit/AdminEditItemsTablePage.jsx @@ -0,0 +1,259 @@ + + import React, { useEffect, useState } from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate, useParams } from "react-router-dom"; + import { AuthContext, tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import ReactQuill from 'react-quill'; + import 'react-quill/dist/quill.snow.css'; + import { isImage, empty, isVideo, isPdf } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; +import {InteractiveButton} from "Components/InteractiveButton" +import { SkeletonLoader } from "Components/Skeleton"; + + + let sdk = new MkdSDK(); + + const EditItemsPage = () => { + const { dispatch } = React.useContext(AuthContext); + const schema = yup + .object({ + + title: yup.string(), + quantity: yup.string(), + image: yup.string(), + price: yup.string(), + status: yup.string(), + location: yup.string(), + }) + .required(); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [fileObj, setFileObj] = React.useState({}); + const [isLoading, setIsLoading] = React.useState(true) + const [loading, setLoading] = React.useState(false) + + const navigate = useNavigate(); + + const [title, setTitle] = useState(''); + const [quantity, setQuantity] = useState(0); + const [image, setImage] = useState(''); + const [price, setPrice] = useState(''); + const [status, setStatus] = useState(''); + const [location, setLocation] = useState(''); + // const [id, setId] = useState(0); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const params = useParams(); + + useEffect(function () { + (async function () { + try { + setLoading(true) + + sdk.setTable("items"); + const result = await sdk.callRestAPI({ id: Number(params?.id)}, "GET"); + if (!result.error) { + + setValue('title', result.model.title); + setValue('quantity', result.model.quantity); + setValue('image', result.model.image); + setValue('price', result.model.price); + setValue('status', result.model.status); + setValue('location', result.model.location); + + + setTitle(result.model.title); + setQuantity(result.model.quantity); + setImage(result.model.image); + setPrice(result.model.price); + setStatus(result.model.status); + setLocation(result.model.location); + setId(result.model.id); + setLoading(false) + + } + } catch (error) { + setLoading(false) + + console.log("error", error); + tokenExpireError(dispatch, error.message); + } + })(); + }, []); + + const previewImage = (field, target) => { + let tempFileObj = fileObj; + tempFileObj[field] = { + file: target.files[0], + tempURL: URL.createObjectURL(target.files[0]), + }; + setFileObj({ ...tempFileObj }); + }; + + + const onSubmit = async (_data) => { + setIsLoading(true) + try { + sdk.setTable("items"); + for (let item in fileObj) { + let formData = new FormData(); + formData.append('file', fileObj[item].file); + let uploadResult = await sdk.uploadImage(formData); + _data[item] = uploadResult.url; + + } + const result = await sdk.callRestAPI( + { + id: id, + + title: _data.title, + quantity: _data.quantity, + image: _data.image, + price: _data.price, + status: _data.status, + location: _data.location, + + + }, + "PUT" + ); + + if (!result.error) { + showToast(globalDispatch, "Updated"); + navigate("/admin/items"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setIsLoading(false) + } catch (error) { + setIsLoading(false) + console.log("Error", error); + setError("title", { + type: "manual", + message: error.message, + }); + } + }; + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "items", + }, + }); + }, []); + + return ( +
    +

    Edit Items

    + {loading? () : (
    + + + + + + + + + + + + + + + + + + + + + + Submit + + )} +
    + ); + }; + + export default EditItemsPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Edit/AdminEditLocationTablePage.jsx b/day20/src/pages/Admin/Edit/AdminEditLocationTablePage.jsx new file mode 100644 index 0000000..bf24751 --- /dev/null +++ b/day20/src/pages/Admin/Edit/AdminEditLocationTablePage.jsx @@ -0,0 +1,173 @@ + + import React, { useEffect, useState } from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate, useParams } from "react-router-dom"; + import { AuthContext, tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import ReactQuill from 'react-quill'; + import 'react-quill/dist/quill.snow.css'; + import { isImage, empty, isVideo, isPdf } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; +import {InteractiveButton} from "Components/InteractiveButton" +import { SkeletonLoader } from "Components/Skeleton"; + + + let sdk = new MkdSDK(); + + const EditLocationPage = () => { + const { dispatch } = React.useContext(AuthContext); + const schema = yup + .object({ + + name: yup.string(), + }) + .required(); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [fileObj, setFileObj] = React.useState({}); + const [isLoading, setIsLoading] = React.useState(true) + const [loading, setLoading] = React.useState(false) + + const navigate = useNavigate(); + + const [name, setName] = useState(''); + // const [id, setId] = useState(0); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const params = useParams(); + + useEffect(function () { + (async function () { + try { + setLoading(true) + + sdk.setTable("location"); + const result = await sdk.callRestAPI({ id: Number(params?.id)}, "GET"); + if (!result.error) { + + setValue('name', result.model.name); + + + setName(result.model.name); + setId(result.model.id); + setLoading(false) + + } + } catch (error) { + setLoading(false) + + console.log("error", error); + tokenExpireError(dispatch, error.message); + } + })(); + }, []); + + const previewImage = (field, target) => { + let tempFileObj = fileObj; + tempFileObj[field] = { + file: target.files[0], + tempURL: URL.createObjectURL(target.files[0]), + }; + setFileObj({ ...tempFileObj }); + }; + + + const onSubmit = async (_data) => { + setIsLoading(true) + try { + sdk.setTable("location"); + for (let item in fileObj) { + let formData = new FormData(); + formData.append('file', fileObj[item].file); + let uploadResult = await sdk.uploadImage(formData); + _data[item] = uploadResult.url; + + } + const result = await sdk.callRestAPI( + { + id: id, + + name: _data.name, + + + }, + "PUT" + ); + + if (!result.error) { + showToast(globalDispatch, "Updated"); + navigate("/admin/location"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setIsLoading(false) + } catch (error) { + setIsLoading(false) + console.log("Error", error); + setError("name", { + type: "manual", + message: error.message, + }); + } + }; + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "location", + }, + }); + }, []); + + return ( +
    +

    Edit Location

    + {loading? () : (
    + + + + + + Submit + + )} +
    + ); + }; + + export default EditLocationPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Edit/AdminEditUserTablePage.jsx b/day20/src/pages/Admin/Edit/AdminEditUserTablePage.jsx new file mode 100644 index 0000000..e4dbf3c --- /dev/null +++ b/day20/src/pages/Admin/Edit/AdminEditUserTablePage.jsx @@ -0,0 +1,441 @@ + + import React, { useEffect, useState } from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate, useParams } from "react-router-dom"; + import { AuthContext, tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import ReactQuill from 'react-quill'; + import 'react-quill/dist/quill.snow.css'; + import { isImage, empty, isVideo, isPdf } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; +import {InteractiveButton} from "Components/InteractiveButton" +import { SkeletonLoader } from "Components/Skeleton"; + + + let sdk = new MkdSDK(); + + const EditUserPage = () => { + const { dispatch } = React.useContext(AuthContext); + const schema = yup + .object({ + + oauth: yup.string(), + role: yup.string(), + first_name: yup.string(), + last_name: yup.string(), + email: yup.string(), + password: yup.string(), + type: yup.string(), + verify: yup.string(), + phone: yup.string(), + photo: yup.string(), + refer: yup.string(), + stripe_uid: yup.string(), + paypal_uid: yup.string(), + two_factor_authentication: yup.string(), + status: yup.string(), + department: yup.string(), + }) + .required(); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [fileObj, setFileObj] = React.useState({}); + const [isLoading, setIsLoading] = React.useState(true) + const [loading, setLoading] = React.useState(false) + + const navigate = useNavigate(); + + const [oauth, setOauth] = useState(''); + const [role, setRole] = useState(''); + const [first_name, setFirstName] = useState(''); + const [last_name, setLastName] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [type, setType] = useState(0); + const [verify, setVerify] = useState(0); + const [phone, setPhone] = useState(''); + const [photo, setPhoto] = useState(''); + const [refer, setRefer] = useState(''); + const [stripe_uid, setStripeUid] = useState(''); + const [paypal_uid, setPaypalUid] = useState(''); + const [two_factor_authentication, setTwoFactorAuthentication] = useState(0); + const [status, setStatus] = useState(0); + const [department, setDepartment] = useState(''); + // const [id, setId] = useState(0); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const params = useParams(); + + useEffect(function () { + (async function () { + try { + setLoading(true) + + sdk.setTable("user"); + const result = await sdk.callRestAPI({ id: Number(params?.id)}, "GET"); + if (!result.error) { + + setValue('oauth', result.model.oauth); + setValue('role', result.model.role); + setValue('first_name', result.model.first_name); + setValue('last_name', result.model.last_name); + setValue('email', result.model.email); + setValue('password', result.model.password); + setValue('type', result.model.type); + setValue('verify', result.model.verify); + setValue('phone', result.model.phone); + setValue('photo', result.model.photo); + setValue('refer', result.model.refer); + setValue('stripe_uid', result.model.stripe_uid); + setValue('paypal_uid', result.model.paypal_uid); + setValue('two_factor_authentication', result.model.two_factor_authentication); + setValue('status', result.model.status); + setValue('department', result.model.department); + + + setOauth(result.model.oauth); + setRole(result.model.role); + setFirstName(result.model.first_name); + setLastName(result.model.last_name); + setEmail(result.model.email); + setPassword(result.model.password); + setType(result.model.type); + setVerify(result.model.verify); + setPhone(result.model.phone); + setPhoto(result.model.photo); + setRefer(result.model.refer); + setStripeUid(result.model.stripe_uid); + setPaypalUid(result.model.paypal_uid); + setTwoFactorAuthentication(result.model.two_factor_authentication); + setStatus(result.model.status); + setDepartment(result.model.department); + setId(result.model.id); + setLoading(false) + + } + } catch (error) { + setLoading(false) + + console.log("error", error); + tokenExpireError(dispatch, error.message); + } + })(); + }, []); + + const previewImage = (field, target) => { + let tempFileObj = fileObj; + tempFileObj[field] = { + file: target.files[0], + tempURL: URL.createObjectURL(target.files[0]), + }; + setFileObj({ ...tempFileObj }); + }; + + + const onSubmit = async (_data) => { + setIsLoading(true) + try { + sdk.setTable("user"); + for (let item in fileObj) { + let formData = new FormData(); + formData.append('file', fileObj[item].file); + let uploadResult = await sdk.uploadImage(formData); + _data[item] = uploadResult.url; + + } + const result = await sdk.callRestAPI( + { + id: id, + + oauth: _data.oauth, + role: _data.role, + first_name: _data.first_name, + last_name: _data.last_name, + email: _data.email, + password: _data.password, + type: _data.type, + verify: _data.verify, + phone: _data.phone, + photo: _data.photo, + refer: _data.refer, + stripe_uid: _data.stripe_uid, + paypal_uid: _data.paypal_uid, + two_factor_authentication: _data.two_factor_authentication, + status: _data.status, + department: _data.department, + + + }, + "PUT" + ); + + if (!result.error) { + showToast(globalDispatch, "Updated"); + navigate("/admin/user"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setIsLoading(false) + } catch (error) { + setIsLoading(false) + console.log("Error", error); + setError("oauth", { + type: "manual", + message: error.message, + }); + } + }; + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "user", + }, + }); + }, []); + + return ( +
    +

    Edit User

    + {loading? () : (
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +

    + {errors.photo?.message} +

    +
    + + + + + + + + + + + + + + + + + + + + + + + Submit + + )} +
    + ); + }; + + export default EditUserPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Edit/EditAdminCmsPage.jsx b/day20/src/pages/Admin/Edit/EditAdminCmsPage.jsx new file mode 100644 index 0000000..8ca9fd5 --- /dev/null +++ b/day20/src/pages/Admin/Edit/EditAdminCmsPage.jsx @@ -0,0 +1,271 @@ + + import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { useNavigate, useParams } from "react-router-dom"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import { GlobalContext, showToast } from "Context/Global"; + +let sdk = new MkdSDK(); + +const EditAdminUserPage = ({ activeId, setSidebar }) => { + const schema = yup + .object({ + email: yup.string().email().required(), + password: yup.string(), + role: yup.string(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const navigate = useNavigate(); + const params = useParams(); + const [oldEmail, setOldEmail] = useState(""); + const [id, setId] = useState(0); + const [isEditing, setIsEditing] = useState(false); + + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const selectRole = ["admin", "employee"]; + const selectStatus = [ + { key: "0", value: "Inactive" }, + { key: "2", value: "Suspend" }, + { key: "1", value: "Active" }, + ]; + + const onSubmit = async (data) => { + setIsEditing(true); + try { + if (oldEmail !== data.email) { + const emailresult = await sdk.updateEmailByAdmin(data.email, activeId); + if (!emailresult.error) { + showToast(globalDispatch, "Email Updated", 1000); + } else { + if (emailresult.validation) { + const keys = Object.keys(emailresult.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: emailresult.validation[field], + }); + } + } + } + } + + if (data.password.length > 0) { + const passwordresult = await sdk.updatePasswordByAdmin( + data.password, + activeId + ); + if (!passwordresult.error) { + showToast(globalDispatch, "Password Updated", 2000); + } else { + if (passwordresult.validation) { + const keys = Object.keys(passwordresult.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: passwordresult.validation[field], + }); + } + } + } + } + + sdk.setTable("user"); + + const result = await sdk.callRestAPI( + { activeId, email: data.email, role: data.role, status: data.status }, + "PUT" + ); + + if (!result.error) { + showToast(globalDispatch, "Added", 4000); + navigate("/admin/users"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + console.log("Error", error); + setError("email", { + type: "manual", + message: error.message, + }); + tokenExpireError(dispatch, error.message); + } + setIsEditing(false); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "users", + }, + }); + + (async function () { + try { + sdk.setTable("user"); + const result = await sdk.callRestAPI({ id: activeId }, "GET"); + + if (!result.error) { + setValue("email", result.model.email); + setValue("role", result.model.role); + setValue("status", result.model.status); + setOldEmail(result.model.email); + setId(result.model.id); + } + } catch (error) { + console.log("Error", error); + tokenExpireError(dispatch, error.message); + } + })(); + }, [activeId]); + return ( +
    +
    +
    + {/* setSidebar(false)} + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + > + + */} + Edit User +
    +
    + + +
    +
    +
    +
    + + +

    {errors.email?.message}

    +
    +
    + + +
    +
    + + +
    +
    + + +

    + {errors.password?.message} +

    +
    + {/* */} +
    +
    + ); +}; + +export default EditAdminUserPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Edit/EditAdminEmailPage.jsx b/day20/src/pages/Admin/Edit/EditAdminEmailPage.jsx new file mode 100644 index 0000000..2720238 --- /dev/null +++ b/day20/src/pages/Admin/Edit/EditAdminEmailPage.jsx @@ -0,0 +1,198 @@ + + + +import React, { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { GlobalContext, showToast } from "Context/Global"; +import { useNavigate, useParams } from "react-router-dom"; +import { AuthContext, tokenExpireError } from "Context/Auth"; + +let sdk = new MkdSDK(); + +const EditAdminEmailPage = ({ activeId, setSidebar }) => { + const schema = yup + .object({ + subject: yup.string().required(), + html: yup.string().required(), + tag: yup.string().required(), + }) + .required(); + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const navigate = useNavigate(); + const [id, setId] = useState(0); + const [slug, setSlug] = useState(""); + const [isEditing, setIsEditing] = useState(false); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const params = useParams(); + + useEffect(function () { + globalDispatch({ + type: "SETPATH", + payload: { + path: "email", + }, + }); + + (async function () { + try { + sdk.setTable("email"); + const result = await sdk.callRestAPI({ id: activeId }, "GET"); + if (!result.error) { + setValue("subject", result.model.subject); + setValue("html", result.model.html); + setValue("tag", result.model.tag); + setSlug(result.model.slug); + setId(result.model.id); + } + } catch (error) { + console.log("error", error); + tokenExpireError(dispatch, error.message); + } + })(); + }, []); + + const onSubmit = async (data) => { + setIsEditing(true); + try { + const result = await sdk.callRestAPI( + { id, slug, subject: data.subject, html: data.html, tag: data.tag }, + "PUT" + ); + + if (!result.error) { + showToast(globalDispatch, "Updated"); + navigate("/admin/email"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + console.log("Error", error); + setError("html", { + type: "manual", + message: error.message, + }); + tokenExpireError(dispatch, error.message); + } + setIsEditing(true); + }; + + return ( +
    + {/*

    Edit Email

    */} +
    +
    + {/* setSidebar(false)} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> + + */} + Edit Email +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +

    + {errors.subject?.message} +

    +
    +
    + + +

    {errors.tag?.message}

    +
    +
    + + +

    {errors.html?.message}

    +
    + +
    +
    + ); +}; + +export default EditAdminEmailPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Edit/EditAdminStripePricePage.jsx b/day20/src/pages/Admin/Edit/EditAdminStripePricePage.jsx new file mode 100644 index 0000000..f60c88f --- /dev/null +++ b/day20/src/pages/Admin/Edit/EditAdminStripePricePage.jsx @@ -0,0 +1,158 @@ + + import React, { useEffect, useState } from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "../../utils/MkdSDK"; + import { useNavigate, useParams } from "react-router-dom"; + import { AuthContext, tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import { FaLessThanEqual } from "react-icons/fa"; + + let sdk = new MkdSDK(); + + const EditAdminStripePricePage = ({ activeId, setSidebar }) => { + const schema = yup + .object({ + name: yup.string().required(), + status: yup.boolean().required(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const navigate = useNavigate(); + const params = useParams(); + const [id, setId] = useState(0); + const [isEditingPlan, setIsEditingPlan] = useState(false); + + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const selectStatus = [ + { key: "0", value: "Inactive" }, + { key: "1", value: "Active" }, + ]; + + const onSubmit = async (data) => { + setIsEditingPlan(true) + try { + const result = await sdk.updateStripePrice(activeId, data); + if (!result.error) { + showToast(globalDispatch, "Edited", 4000); + // navigate("/admin/prices"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + console.log("Error", error); + showToast(globalDispatch, error.message, 4000); + tokenExpireError(dispatch, error.message); + } + setIsEditingPlan(false) + }; + + async function getPrices() { + try { + const result = await sdk.getStripePrice(activeId); + + if (!result.error) { + const price = result.model.object; + setValue("name", price.nickname); + setValue("status", result.model.status); + setId(result.model.id); //set local id + } + } catch (error) { + console.log("Error", error); + tokenExpireError(dispatch, error.message); + } + } + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "prices", + }, + }); + + getPrices() + }, [activeId]); + + return ( +
    + {/* */} + {/*

    Edit Product

    */} +
    +
    + {/* setSidebar(false)} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> + + */} + Edit Plan +
    +
    + + +
    +
    +
    +
    + + +

    {errors.name?.message}

    +
    + +
    + + +
    + + {/* */} +
    +
    + ); + }; + + export default EditAdminStripePricePage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/Edit/EditAdminUserPage.jsx b/day20/src/pages/Admin/Edit/EditAdminUserPage.jsx new file mode 100644 index 0000000..8ca9fd5 --- /dev/null +++ b/day20/src/pages/Admin/Edit/EditAdminUserPage.jsx @@ -0,0 +1,271 @@ + + import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { useNavigate, useParams } from "react-router-dom"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import { GlobalContext, showToast } from "Context/Global"; + +let sdk = new MkdSDK(); + +const EditAdminUserPage = ({ activeId, setSidebar }) => { + const schema = yup + .object({ + email: yup.string().email().required(), + password: yup.string(), + role: yup.string(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const navigate = useNavigate(); + const params = useParams(); + const [oldEmail, setOldEmail] = useState(""); + const [id, setId] = useState(0); + const [isEditing, setIsEditing] = useState(false); + + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const selectRole = ["admin", "employee"]; + const selectStatus = [ + { key: "0", value: "Inactive" }, + { key: "2", value: "Suspend" }, + { key: "1", value: "Active" }, + ]; + + const onSubmit = async (data) => { + setIsEditing(true); + try { + if (oldEmail !== data.email) { + const emailresult = await sdk.updateEmailByAdmin(data.email, activeId); + if (!emailresult.error) { + showToast(globalDispatch, "Email Updated", 1000); + } else { + if (emailresult.validation) { + const keys = Object.keys(emailresult.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: emailresult.validation[field], + }); + } + } + } + } + + if (data.password.length > 0) { + const passwordresult = await sdk.updatePasswordByAdmin( + data.password, + activeId + ); + if (!passwordresult.error) { + showToast(globalDispatch, "Password Updated", 2000); + } else { + if (passwordresult.validation) { + const keys = Object.keys(passwordresult.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: passwordresult.validation[field], + }); + } + } + } + } + + sdk.setTable("user"); + + const result = await sdk.callRestAPI( + { activeId, email: data.email, role: data.role, status: data.status }, + "PUT" + ); + + if (!result.error) { + showToast(globalDispatch, "Added", 4000); + navigate("/admin/users"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + console.log("Error", error); + setError("email", { + type: "manual", + message: error.message, + }); + tokenExpireError(dispatch, error.message); + } + setIsEditing(false); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "users", + }, + }); + + (async function () { + try { + sdk.setTable("user"); + const result = await sdk.callRestAPI({ id: activeId }, "GET"); + + if (!result.error) { + setValue("email", result.model.email); + setValue("role", result.model.role); + setValue("status", result.model.status); + setOldEmail(result.model.email); + setId(result.model.id); + } + } catch (error) { + console.log("Error", error); + tokenExpireError(dispatch, error.message); + } + })(); + }, [activeId]); + return ( +
    +
    +
    + {/* setSidebar(false)} + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + > + + */} + Edit User +
    +
    + + +
    +
    +
    +
    + + +

    {errors.email?.message}

    +
    +
    + + +
    +
    + + +
    +
    + + +

    + {errors.password?.message} +

    +
    + {/* */} +
    +
    + ); +}; + +export default EditAdminUserPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/List/AdminCmsListPage.jsx b/day20/src/pages/Admin/List/AdminCmsListPage.jsx new file mode 100644 index 0000000..8126618 --- /dev/null +++ b/day20/src/pages/Admin/List/AdminCmsListPage.jsx @@ -0,0 +1,515 @@ + + + import React from "react"; +import { useForm } from "react-hook-form"; +import { useNavigate } from "react-router-dom"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import { GlobalContext } from "Context/Global"; +import { getNonNullValue } from "Utils/utils"; +import MkdSDK from "Utils/MkdSDK"; +import { ModalSidebar } from "Components/ModalSidebar"; +import { BiFilterAlt, BiSearch } from "react-icons/bi"; +import { AiOutlineClose, AiOutlinePlus } from "react-icons/ai"; +import { RiDeleteBin5Line } from "react-icons/ri"; +import EditAdminCmsPage from "../Edit/EditAdminCmsPage"; +import AddAdminCmsPage from "../Add/AddAdminCmsPage"; +import { AddButton } from "Components/AddButton"; +import SkeletonLoader from "Components/Skeleton/Skeleton"; +import { PaginationBar } from "Components/PaginationBar"; + +let sdk = new MkdSDK(); + +const columns = [ + { + header: "Page", + accessor: "page", + isSorted: false, + isSortedDesc: false, + mappingExist: false, + mappings: {}, + }, + { + header: "Identifier", + accessor: "content_key", + isSorted: false, + isSortedDesc: false, + mappingExist: false, + mappings: {}, + }, + { + header: "Content Type", + accessor: "content_type", + isSorted: false, + isSortedDesc: false, + mappingExist: false, + mappings: {}, + }, + { + header: "Action", + accessor: "", + }, +]; + +const AdminCmsListPage = () => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [query, setQuery] = React.useState(""); + const [data, setCurrentTableData] = React.useState([]); + const [pageSize, setPageSize] = React.useState(10); + const [pageCount, setPageCount] = React.useState(0); + const [dataTotal, setDataTotal] = React.useState(0); + const [currentPage, setPage] = React.useState(0); + const [canPreviousPage, setCanPreviousPage] = React.useState(false); + const [canNextPage, setCanNextPage] = React.useState(false); + const [showAddSidebar, setShowAddSidebar] = React.useState(false); + const [showEditSidebar, setShowEditSidebar] = React.useState(false); + const [isLoading, setIsLoading] = React.useState(false); + const [openFilter, setOpenFilter] = React.useState(false); + const [showFilterOptions, setShowFilterOptions] = React.useState(false); + const [selectedOptions, setSelectedOptions] = React.useState([]); + const [filterConditions, setFilterConditions] = React.useState([]); + const [searchValue, setSearchValue] = React.useState(""); + const [optionValue, setOptionValue] = React.useState("eq"); + const [activeEditId, setActiveEditId] = React.useState(); + const navigate = useNavigate(); + const dropdownFilterRef = React.useRef(null); + + const schema = yup.object({ + page: yup.string(), + key: yup.string(), + type: yup.string(), + }); + + const selectType = [ + { key: "", value: "All" }, + { key: "text", value: "Text" }, + { key: "image", value: "Image" }, + { key: "number", value: "Number" }, + ]; + + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + function onSort(columnIndex) { + console.log(columns[columnIndex]); + if (columns[columnIndex].isSorted) { + columns[columnIndex].isSortedDesc = !columns[columnIndex].isSortedDesc; + } else { + columns.map((i) => (i.isSorted = false)); + columns.map((i) => (i.isSortedDesc = false)); + columns[columnIndex].isSorted = true; + }; + + (async function () { + await getData(0, pageSize); + })(); + + } + + function updatePageSize(limit) { + (async function () { + setPageSize(limit); + await getData(0, limit); + })(); + } + + function previousPage() { + (async function () { + await getData(currentPage - 1 > 0 ? currentPage - 1 : 0, pageSize); + })(); + } + + function nextPage() { + (async function () { + await getData( + currentPage + 1 <= pageCount ? currentPage + 1 : 0, + pageSize + ); + })(); + } + + const addFilterCondition = (option, selectedValue, inputValue) => { + const input = + selectedValue === "eq" && isNaN(inputValue) + ? `"${inputValue}"` + : inputValue; + const condition = `${option},${selectedValue},${input}`; + setFilterConditions((prevConditions) => { + const newConditions = prevConditions.filter( + (condition) => !condition.includes(option) + ); + return [...newConditions, condition]; + }); + setSearchValue(inputValue); + }; + + async function getData(pageNum, limitNum, data) { + // setIsLoading(true); + try { + sdk.setTable("cms"); + const result = await sdk.callRestAPI( + { + payload: { ...data }, + page: pageNum, + limit: limitNum, + // sortId: sortField.length ? sortField[0].accessor : "", + // direction: sortField.length + // ? sortField[0].isSortedDesc + // ? "DESC" + // : "ASC" + // : "", + }, + "PAGINATE" + ); + + const { list, total, limit, num_pages, page } = result; + + setCurrentTableData(list); + setPageSize(limit); + setPageCount(num_pages); + setPage(page); + setDataTotal(total); + setCanPreviousPage(page > 1); + setCanNextPage(page + 1 <= num_pages); + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + }; + // setIsLoading(false); + } + + const onSubmit = (data) => { + let page = getNonNullValue(data.page); + let key = getNonNullValue(data.key); + let type = getNonNullValue(data.type); + let filter = { page, content_key: key, content_type: type }; + getData(0, 10, filter); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "cms", + }, + }); + + (async function () { + await getData(1, pageSize); + })(); + }, []); + + React.useEffect(() => { + if (!showAddSidebar) { + getData(1, pageSize, filterConditions) + } + }, [showAddSidebar]); + + const handleClickOutside = (event) => { + if (dropdownFilterRef.current && !dropdownFilterRef.current.contains(event.target)) { + setOpenFilter(false); + } + }; + + React.useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( +
    +
    +
    +
    +
    +
    setOpenFilter(!openFilter)} + > + + Filters + {selectedOptions.length > 0 && ( + + {selectedOptions.length} + + )} +
    + {openFilter && ( +
    +
    + {selectedOptions?.map((option, index) => ( +
    +
    + {option} +
    + + + + addFilterCondition( + option, + optionValue, + e.target.value + ) + } + /> + +
    + { + setSelectedOptions((prevOptions) => + prevOptions.filter((op) => op !== option) + ); + setFilterConditions((prevConditions) => { + return prevConditions.filter( + (condition) => !condition.includes(option) + ); + }); + }} + /> +
    + +
    + ))} + +
    +
    { + setShowFilterOptions(!showFilterOptions); + }} + > + + Add filter +
    + + {showFilterOptions && ( +
    +
      + {columns.slice(0, -1).map((column) => ( +
    • { + if (!selectedOptions.includes(column.header)) { + setSelectedOptions((prev) => [ + ...prev, + column.header, + ]); + } + setShowFilterOptions(false); + }} + > + {column.header} +
    • + ))} +
    +
    + )} + {selectedOptions.length > 0 && ( +
    { + setSelectedOptions([]); + setFilterConditions([]); + }} + className="inline-block cursor-pointer rounded px-6 py-2.5 font-medium leading-tight text-gray-600 transition duration-150 ease-in-out" + > + Clear all filter +
    + )} +
    +
    +
    + )} +
    +
    + + addFilterCondition("name", "cs", e.target?.value)} + /> + +
    +
    +
    + {/*
    +

    CMS Search

    +
    +
    + + +

    {errors.page?.message}

    +
    +
    + + +

    {errors.key?.message}

    +
    + +
    + + +

    +
    +
    + + +
    */} + setShowAddSidebar(true)} /> +
    + + {isLoading ? ( + + ) : ( +
    +
    + + + + {columns.map((column, i) => ( + + ))} + + + + {data?.length === 0 && ( +
    No data
    + )} + {data.map((row, i) => { + return ( + + {columns.map((cell, index) => { + if (cell.accessor == "") { + return ( + + ); + } + if (cell.mapping) { + return ( + + ); + } + return ( + + ); + })} + + ); + })} + +
    onSort(i)} > + {column.header} + {column.isSorted ? (column.isSortedDesc ? " ▼" : " ▲") : ""} +
    + + + {cell.mapping[row[cell.accessor]]} + + {row[cell.accessor]} +
    +
    +
    + )} + + setShowAddSidebar(false)} + > + + + setShowEditSidebar(false)} + > + + +
    + ); +}; + +export default AdminCmsListPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/List/AdminEmailListPage.jsx b/day20/src/pages/Admin/List/AdminEmailListPage.jsx new file mode 100644 index 0000000..29d94ab --- /dev/null +++ b/day20/src/pages/Admin/List/AdminEmailListPage.jsx @@ -0,0 +1,393 @@ + + import React from "react"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import MkdSDK from "Utils/MkdSDK"; +import { useNavigate } from "react-router-dom"; +import { GlobalContext } from "Context/Global"; +import Skeleton from "react-loading-skeleton"; +import { ModalSidebar } from "Components/ModalSidebar"; +import * as yup from "yup"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { BiFilterAlt, BiSearch } from "react-icons/bi"; +import { AiOutlineClose, AiOutlinePlus } from "react-icons/ai"; +import { RiDeleteBin5Line } from "react-icons/ri"; +import AddAdminEmailPage from "../Add/AddAdminEmailPage"; +import EditAdminEmailPage from "../Edit/EditAdminEmailPage"; +import { AddButton } from "Components/AddButton"; + + +let sdk = new MkdSDK(); + +const columns = [ + { + header: "ID", + accessor: "id", + }, + { + header: "Email Type", + accessor: "slug", + }, + { + header: "Subject", + accessor: "subject", + }, + { + header: "Tags", + accessor: "tag", + }, + { + header: "Action", + accessor: "", + }, + +]; + +const AdminEmailListPage = () => { + const { dispatch } = React.useContext(AuthContext); + const [data, setCurrentTableData] = React.useState([]); + const [loadingData, setLoadingData] = React.useState(false); + const [showAddSidebar, setShowAddSidebar] = React.useState(false); + const [showEditSidebar, setShowEditSidebar] = React.useState(false); + const [activeEditId, setActiveEditId] = React.useState(); + const [openFilter, setOpenFilter] = React.useState(false); + const [showFilterOptions, setShowFilterOptions] = React.useState(false); + const [selectedOptions, setSelectedOptions] = React.useState([]); + const [filterConditions, setFilterConditions] = React.useState([]); + const [searchValue, setSearchValue] = React.useState(""); + const [optionValue, setOptionValue] = React.useState("eq"); + const dropdownFilterRef = React.useRef(null); + + const navigate = useNavigate(); + + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + const schema = yup.object({ + page: yup.string(), + key: yup.string(), + type: yup.string(), + }); + + const { + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = (data) => { + let page = getNonNullValue(data.page); + let key = getNonNullValue(data.key); + let type = getNonNullValue(data.type); + let filter = { page, content_key: key, content_type: type }; + getData(0, 10, filter); + }; + + const addFilterCondition = (option, selectedValue, inputValue) => { + const input = + selectedValue === "eq" && isNaN(inputValue) + ? `"${inputValue}"` + : inputValue; + const condition = `${option},${selectedValue},${input}`; + setFilterConditions((prevConditions) => { + const newConditions = prevConditions.filter( + (condition) => !condition.includes(option) + ); + return [...newConditions, condition]; + }); + setSearchValue(inputValue); + }; + + async function getData() { + try { + sdk.setTable("email"); + const result = await sdk.callRestAPI({}, "GETALL"); + + const { list } = result; + + setCurrentTableData(list); + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + } + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "email", + }, + }); + + (async function () { + setLoadingData(true); + await getData(); + setLoadingData(false); + })(); + }, []); + + const handleClickOutside = (event) => { + if (dropdownFilterRef.current && !dropdownFilterRef.current.contains(event.target)) { + setOpenFilter(false); + } + }; + + React.useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( + <> +
    +
    + {/*

    Emails

    */} +
    +
    +
    +
    setOpenFilter(!openFilter)} + > + + Filters + {selectedOptions.length > 0 && ( + + {selectedOptions.length} + + )} +
    + {openFilter && ( +
    +
    + {selectedOptions?.map((option, index) => ( +
    +
    + {option} +
    + + + + addFilterCondition( + option, + optionValue, + e.target.value + ) + } + /> + +
    + { + setSelectedOptions((prevOptions) => + prevOptions.filter((op) => op !== option) + ); + setFilterConditions((prevConditions) => { + return prevConditions.filter( + (condition) => !condition.includes(option) + ); + }); + }} + /> +
    + +
    + ))} + +
    +
    { + setShowFilterOptions(!showFilterOptions); + }} + > + + Add filter +
    + + {showFilterOptions && ( +
    +
      + {columns.slice(0, -1).map((column) => ( +
    • { + if (!selectedOptions.includes(column.header)) { + setSelectedOptions((prev) => [ + ...prev, + column.header, + ]); + } + setShowFilterOptions(false); + }} + > + {column.header} +
    • + ))} +
    +
    + )} + {selectedOptions.length > 0 && ( +
    { + setSelectedOptions([]); + setFilterConditions([]); + }} + className="inline-block cursor-pointer rounded px-6 py-2.5 font-medium leading-tight text-gray-600 transition duration-150 ease-in-out" + > + Clear all filter +
    + )} +
    +
    +
    + )} +
    +
    + + addFilterCondition("name", "cs", e.target?.value)} + /> + +
    +
    +
    + setShowAddSidebar(true)} /> +
    +
    + + + + {columns.map((column, i) => ( + + ))} + + + + {data.length == 0 ? ( + + + + ) : null} + {data.map((row, i) => { + return ( + + {columns.map((cell, index) => { + if (cell.accessor == "") { + return ( + + ); + } + if (cell.mapping) { + return ( + + ); + } + return ( + + ); + })} + + ); + })} + +
    + {column.header} + + {column.isSorted + ? column.isSortedDesc + ? " ▼" + : " ▲" + : ""} + +
    + {loadingData && } +
    + + + {cell.mapping[row[cell.accessor]]} + + {row[cell.accessor]} +
    +
    +
    + setShowAddSidebar(false)} + > + + + setShowEditSidebar(false)} + > + + + + ); +}; + +export default AdminEmailListPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/List/AdminListDepartmentTablePage.jsx b/day20/src/pages/Admin/List/AdminListDepartmentTablePage.jsx new file mode 100644 index 0000000..4e75b41 --- /dev/null +++ b/day20/src/pages/Admin/List/AdminListDepartmentTablePage.jsx @@ -0,0 +1,146 @@ + + import React, { useRef } from "react"; + import MkdSDK from "Utils/MkdSDK"; + import { AuthContext } from "Context/Auth"; + import { GlobalContext } from "Context/Global"; + import { useNavigate } from "react-router-dom"; + import { LazyLoad } from "Components/LazyLoad"; + import { ModalSidebar } from "Components/ModalSidebar"; + import { MkdListTableV2 } from "Components/MkdListTable";; + + let sdk = new MkdSDK(); + + const columns = [ + + + + { + header: 'Id', + accessor: 'id', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Create At', + accessor: 'create_at', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Update At', + accessor: 'update_at', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Name', + accessor: 'name', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + { + header: "Action", + accessor: "", + }, + ]; + + const DepartmentListPage = () => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const navigate = useNavigate(); + + const [showAddSidebar, setShowAddSidebar] = React.useState(false); + const [showEditSidebar, setShowEditSidebar] = React.useState(false); + const [activeEditId, setActiveEditId] = React.useState(); + const refreshRef = useRef(null); + + const [selectedItems, setSelectedItems] = React.useState([]); + + const onToggleModal = (modal, toggle, ids = []) => { + switch (modal) { + case "add": + setShowAddSidebar(toggle); + // setSelectedItems(ids); + break; + case "edit": + setShowEditSidebar(toggle); + setSelectedItems(ids); + setActiveEditId(ids[0]); + break; + } + }; + + return ( + <> + <> +
    + + onToggleModal("edit", true, ids), + multiple: false, + }, + delete: { show: false, action: null, multiple: false }, + select: { show: false, action: null, multiple: false }, + add: { + show: true, + action: () => onToggleModal("add", true), + multiple: false, + children: "Add New", + showChildren: true, + }, + export: { show: false, action: null, multiple: true }, + }} + actionPostion={`ontable`} + refreshRef={refreshRef} + /> + +
    + + + {/* + setShowAddSidebar(false)} + > + + + + + + setShowEditSidebar(false)} + > + + + */} + + ); + }; + + export default DepartmentListPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/List/AdminListItemMovementHistoryTablePage.jsx b/day20/src/pages/Admin/List/AdminListItemMovementHistoryTablePage.jsx new file mode 100644 index 0000000..14bccd7 --- /dev/null +++ b/day20/src/pages/Admin/List/AdminListItemMovementHistoryTablePage.jsx @@ -0,0 +1,173 @@ + + import React, { useRef } from "react"; + import MkdSDK from "Utils/MkdSDK"; + import { AuthContext } from "Context/Auth"; + import { GlobalContext } from "Context/Global"; + import { useNavigate } from "react-router-dom"; + import { LazyLoad } from "Components/LazyLoad"; + import { ModalSidebar } from "Components/ModalSidebar"; + import { MkdListTableV2 } from "Components/MkdListTable";; + + let sdk = new MkdSDK(); + + const columns = [ + + + + { + header: 'Id', + accessor: 'id', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Create At', + accessor: 'create_at', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Update At', + accessor: 'update_at', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Product Id', + accessor: 'product_id', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Moved From', + accessor: 'moved_from', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Moved To', + accessor: 'moved_to', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Date', + accessor: 'date', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + { + header: "Action", + accessor: "", + }, + ]; + + const ItemMovementHistoryListPage = () => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const navigate = useNavigate(); + + const [showAddSidebar, setShowAddSidebar] = React.useState(false); + const [showEditSidebar, setShowEditSidebar] = React.useState(false); + const [activeEditId, setActiveEditId] = React.useState(); + const refreshRef = useRef(null); + + const [selectedItems, setSelectedItems] = React.useState([]); + + const onToggleModal = (modal, toggle, ids = []) => { + switch (modal) { + case "add": + setShowAddSidebar(toggle); + // setSelectedItems(ids); + break; + case "edit": + setShowEditSidebar(toggle); + setSelectedItems(ids); + setActiveEditId(ids[0]); + break; + } + }; + + return ( + <> + <> +
    + + onToggleModal("edit", true, ids), + multiple: false, + }, + delete: { show: false, action: null, multiple: false }, + select: { show: false, action: null, multiple: false }, + add: { + show: true, + action: () => onToggleModal("add", true), + multiple: false, + children: "Add New", + showChildren: true, + }, + export: { show: false, action: null, multiple: true }, + }} + actionPostion={`ontable`} + refreshRef={refreshRef} + /> + +
    + + + {/* + setShowAddSidebar(false)} + > + + + + + + setShowEditSidebar(false)} + > + + + */} + + ); + }; + + export default ItemMovementHistoryListPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/List/AdminListItemsTablePage.jsx b/day20/src/pages/Admin/List/AdminListItemsTablePage.jsx new file mode 100644 index 0000000..563ca06 --- /dev/null +++ b/day20/src/pages/Admin/List/AdminListItemsTablePage.jsx @@ -0,0 +1,191 @@ + + import React, { useRef } from "react"; + import MkdSDK from "Utils/MkdSDK"; + import { AuthContext } from "Context/Auth"; + import { GlobalContext } from "Context/Global"; + import { useNavigate } from "react-router-dom"; + import { LazyLoad } from "Components/LazyLoad"; + import { ModalSidebar } from "Components/ModalSidebar"; + import { MkdListTableV2 } from "Components/MkdListTable";; + + let sdk = new MkdSDK(); + + const columns = [ + + + + { + header: 'Id', + accessor: 'id', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Create At', + accessor: 'create_at', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Update At', + accessor: 'update_at', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Title', + accessor: 'title', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Quantity', + accessor: 'quantity', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Image', + accessor: 'image', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Price', + accessor: 'price', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Status', + accessor: 'status', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Location', + accessor: 'location', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + { + header: "Action", + accessor: "", + }, + ]; + + const ItemsListPage = () => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const navigate = useNavigate(); + + const [showAddSidebar, setShowAddSidebar] = React.useState(false); + const [showEditSidebar, setShowEditSidebar] = React.useState(false); + const [activeEditId, setActiveEditId] = React.useState(); + const refreshRef = useRef(null); + + const [selectedItems, setSelectedItems] = React.useState([]); + + const onToggleModal = (modal, toggle, ids = []) => { + switch (modal) { + case "add": + setShowAddSidebar(toggle); + // setSelectedItems(ids); + break; + case "edit": + setShowEditSidebar(toggle); + setSelectedItems(ids); + setActiveEditId(ids[0]); + break; + } + }; + + return ( + <> + <> +
    + + onToggleModal("edit", true, ids), + multiple: false, + }, + delete: { show: false, action: null, multiple: false }, + select: { show: false, action: null, multiple: false }, + add: { + show: true, + action: () => onToggleModal("add", true), + multiple: false, + children: "Add New", + showChildren: true, + }, + export: { show: false, action: null, multiple: true }, + }} + actionPostion={`ontable`} + refreshRef={refreshRef} + /> + +
    + + + {/* + setShowAddSidebar(false)} + > + + + + + + setShowEditSidebar(false)} + > + + + */} + + ); + }; + + export default ItemsListPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/List/AdminListLocationTablePage.jsx b/day20/src/pages/Admin/List/AdminListLocationTablePage.jsx new file mode 100644 index 0000000..6033f7d --- /dev/null +++ b/day20/src/pages/Admin/List/AdminListLocationTablePage.jsx @@ -0,0 +1,146 @@ + + import React, { useRef } from "react"; + import MkdSDK from "Utils/MkdSDK"; + import { AuthContext } from "Context/Auth"; + import { GlobalContext } from "Context/Global"; + import { useNavigate } from "react-router-dom"; + import { LazyLoad } from "Components/LazyLoad"; + import { ModalSidebar } from "Components/ModalSidebar"; + import { MkdListTableV2 } from "Components/MkdListTable";; + + let sdk = new MkdSDK(); + + const columns = [ + + + + { + header: 'Id', + accessor: 'id', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Create At', + accessor: 'create_at', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Update At', + accessor: 'update_at', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Name', + accessor: 'name', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + { + header: "Action", + accessor: "", + }, + ]; + + const LocationListPage = () => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const navigate = useNavigate(); + + const [showAddSidebar, setShowAddSidebar] = React.useState(false); + const [showEditSidebar, setShowEditSidebar] = React.useState(false); + const [activeEditId, setActiveEditId] = React.useState(); + const refreshRef = useRef(null); + + const [selectedItems, setSelectedItems] = React.useState([]); + + const onToggleModal = (modal, toggle, ids = []) => { + switch (modal) { + case "add": + setShowAddSidebar(toggle); + // setSelectedItems(ids); + break; + case "edit": + setShowEditSidebar(toggle); + setSelectedItems(ids); + setActiveEditId(ids[0]); + break; + } + }; + + return ( + <> + <> +
    + + onToggleModal("edit", true, ids), + multiple: false, + }, + delete: { show: false, action: null, multiple: false }, + select: { show: false, action: null, multiple: false }, + add: { + show: true, + action: () => onToggleModal("add", true), + multiple: false, + children: "Add New", + showChildren: true, + }, + export: { show: false, action: null, multiple: true }, + }} + actionPostion={`ontable`} + refreshRef={refreshRef} + /> + +
    + + + {/* + setShowAddSidebar(false)} + > + + + + + + setShowEditSidebar(false)} + > + + + */} + + ); + }; + + export default LocationListPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/List/AdminListUserTablePage.jsx b/day20/src/pages/Admin/List/AdminListUserTablePage.jsx new file mode 100644 index 0000000..5c8dce0 --- /dev/null +++ b/day20/src/pages/Admin/List/AdminListUserTablePage.jsx @@ -0,0 +1,281 @@ + + import React, { useRef } from "react"; + import MkdSDK from "Utils/MkdSDK"; + import { AuthContext } from "Context/Auth"; + import { GlobalContext } from "Context/Global"; + import { useNavigate } from "react-router-dom"; + import { LazyLoad } from "Components/LazyLoad"; + import { ModalSidebar } from "Components/ModalSidebar"; + import { MkdListTableV2 } from "Components/MkdListTable";; + + let sdk = new MkdSDK(); + + const columns = [ + + + + { + header: 'Id', + accessor: 'id', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Oauth', + accessor: 'oauth', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Role', + accessor: 'role', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'First Name', + accessor: 'first_name', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Last Name', + accessor: 'last_name', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Email', + accessor: 'email', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Password', + accessor: 'password', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Type', + accessor: 'type', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Verify', + accessor: 'verify', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Phone', + accessor: 'phone', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Photo', + accessor: 'photo', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Refer', + accessor: 'refer', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Stripe Uid', + accessor: 'stripe_uid', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Paypal Uid', + accessor: 'paypal_uid', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Two Factor Authentication', + accessor: 'two_factor_authentication', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Status', + accessor: 'status', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Create At', + accessor: 'create_at', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Update At', + accessor: 'update_at', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + + { + header: 'Department', + accessor: 'department', + isSorted: false, + isSortedDesc: false, + mappingExist : false, + mappings: { } + }, + { + header: "Action", + accessor: "", + }, + ]; + + const UserListPage = () => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const navigate = useNavigate(); + + const [showAddSidebar, setShowAddSidebar] = React.useState(false); + const [showEditSidebar, setShowEditSidebar] = React.useState(false); + const [activeEditId, setActiveEditId] = React.useState(); + const refreshRef = useRef(null); + + const [selectedItems, setSelectedItems] = React.useState([]); + + const onToggleModal = (modal, toggle, ids = []) => { + switch (modal) { + case "add": + setShowAddSidebar(toggle); + // setSelectedItems(ids); + break; + case "edit": + setShowEditSidebar(toggle); + setSelectedItems(ids); + setActiveEditId(ids[0]); + break; + } + }; + + return ( + <> + <> +
    + + onToggleModal("edit", true, ids), + multiple: false, + }, + delete: { show: false, action: null, multiple: false }, + select: { show: false, action: null, multiple: false }, + add: { + show: true, + action: () => onToggleModal("add", true), + multiple: false, + children: "Add New", + showChildren: true, + }, + export: { show: false, action: null, multiple: true }, + }} + actionPostion={`ontable`} + refreshRef={refreshRef} + /> + +
    + + + {/* + setShowAddSidebar(false)} + > + + + + + + setShowEditSidebar(false)} + > + + + */} + + ); + }; + + export default UserListPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/List/AdminPhotoListPage.jsx b/day20/src/pages/Admin/List/AdminPhotoListPage.jsx new file mode 100644 index 0000000..b36e447 --- /dev/null +++ b/day20/src/pages/Admin/List/AdminPhotoListPage.jsx @@ -0,0 +1,450 @@ + + import React from "react"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import { useForm } from "react-hook-form"; +import { useNavigate } from "react-router-dom"; +import MkdSDK from "Utils/MkdSDK"; +import { GlobalContext, showToast } from "Context/Global"; +import { getNonNullValue } from "Utils/utils"; +import { PaginationBar } from "Components/PaginationBar"; +import Skeleton from "react-loading-skeleton"; +import { ModalSidebar } from "Components/ModalSidebar"; +import { BiFilterAlt, BiSearch } from "react-icons/bi"; +import { AiOutlineClose, AiOutlinePlus } from "react-icons/ai"; +import { RiDeleteBin5Line } from "react-icons/ri"; +import AddAdminPhotoPage from "../Add/AddAdminPhotoPage"; +import { AddButton } from "Components/AddButton"; + +let sdk = new MkdSDK(); + +const columns = [ + { + header: "Photos", + accessor: "url", + }, + { + header: "Create at", + accessor: "create_at", + }, + { + header: "Action", + accessor: "", + }, + +]; + +const filterColumns = [ + { + header: "Date", + accessor: "create_at", + }, + { + header: "ID", + accessor: "id", + }, + { + header: "User ID", + accessor: "user_id", + }, + { + header: "Action", + accessor: "", + }, +] + +const AdminPhotoListPage = () => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const [query, setQuery] = React.useState(""); + const [data, setCurrentTableData] = React.useState([]); + const [pageSize, setPageSize] = React.useState(3); + const [pageCount, setPageCount] = React.useState(0); + const [dataTotal, setDataTotal] = React.useState(0); + const [currentPage, setPage] = React.useState(0); + const [canPreviousPage, setCanPreviousPage] = React.useState(false); + const [canNextPage, setCanNextPage] = React.useState(false); + const [loadingData, setLoadingData] = React.useState(false); + const [showAddSidebar, setShowAddSidebar] = React.useState(false); + const [openFilter, setOpenFilter] = React.useState(false); + const [showFilterOptions, setShowFilterOptions] = React.useState(false); + const [selectedOptions, setSelectedOptions] = React.useState([]); + const [filterConditions, setFilterConditions] = React.useState([]); + const [searchValue, setSearchValue] = React.useState(""); + const [optionValue, setOptionValue] = React.useState("eq"); + const navigate = useNavigate(); + const globalContext = React.useContext(GlobalContext); + const dropdownFilterRef = React.useRef(null); + + const schema = yup.object({ + date: yup.string(), + id: yup.string(), + user_id: yup.string(), + }); + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + function updatePageSize(limit) { + (async function () { + setPageSize(limit); + await getData(0, limit); + })(); + } + function previousPage() { + (async function () { + await getData(currentPage - 1 > 0 ? currentPage - 1 : 0, pageSize); + })(); + } + function nextPage() { + (async function () { + await getData( + currentPage + 1 <= pageCount ? currentPage + 1 : 0, + pageSize + ); + })(); + } + + async function getData(pageNum, limitNum, data) { + try { + sdk.setTable("photo"); + const result = await sdk.callRestAPI( + { + payload: { ...data }, + page: pageNum, + limit: limitNum, + }, + "PAGINATE" + ); + + const { list, total, limit, num_pages, page } = result; + + setCurrentTableData(list); + setPageSize(limit); + setPageCount(num_pages); + setPage(page); + setDataTotal(total); + setCanPreviousPage(page > 1); + setCanNextPage(page + 1 <= num_pages); + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + }; + + const addFilterCondition = (option, selectedValue, inputValue) => { + const input = + selectedValue === "eq" && isNaN(inputValue) + ? `"${inputValue}"` + : inputValue; + const condition = `${option},${selectedValue},${input}`; + setFilterConditions((prevConditions) => { + const newConditions = prevConditions.filter( + (condition) => !condition.includes(option) + ); + return [...newConditions, condition]; + }); + setSearchValue(inputValue); + }; + + const onSubmit = (data) => { + let create_at = getNonNullValue(data.date); + let id = getNonNullValue(data.id); + let user_id = getNonNullValue(data.user_id); + let filter = { create_at, id, user_id }; + getData(0, 50, filter); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "photos", + }, + }); + + (async function () { + setLoadingData(true); + await getData(0, 50); + setLoadingData(false); + })(); + }, []); + + async function deleteImage(id) { + sdk.setTable("photo"); + const result = await sdk.callRestAPI({ id }, "DELETE"); + showToast(globalDispatch, "Deleted"); + await getData(0, 50); + } + + const handleClickOutside = (event) => { + if (dropdownFilterRef.current && !dropdownFilterRef.current.contains(event.target)) { + setOpenFilter(false); + } + }; + + React.useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( +
    +
    +
    +
    +
    +
    setOpenFilter(!openFilter)} + > + + Filters + {selectedOptions.length > 0 && ( + + {selectedOptions.length} + + )} +
    + {openFilter && ( +
    +
    + {selectedOptions?.map((option, index) => ( +
    +
    + {option} +
    + + + + addFilterCondition( + option, + optionValue, + e.target.value + ) + } + /> + +
    + { + setSelectedOptions((prevOptions) => + prevOptions.filter((op) => op !== option) + ); + setFilterConditions((prevConditions) => { + return prevConditions.filter( + (condition) => !condition.includes(option) + ); + }); + }} + /> +
    + +
    + ))} + +
    +
    { + setShowFilterOptions(!showFilterOptions); + }} + > + + Add filter +
    + + {showFilterOptions && ( +
    +
      + {columns.slice(0, -1).map((column) => ( +
    • { + if (!selectedOptions.includes(column.header)) { + setSelectedOptions((prev) => [ + ...prev, + column.header, + ]); + } + setShowFilterOptions(false); + }} + > + {column.header} +
    • + ))} +
    +
    + )} + {selectedOptions.length > 0 && ( +
    { + setSelectedOptions([]); + setFilterConditions([]); + }} + className="inline-block cursor-pointer rounded px-6 py-2.5 font-medium leading-tight text-gray-600 transition duration-150 ease-in-out" + > + Clear all filter +
    + )} +
    +
    +
    + )} +
    +
    + + addFilterCondition("name", "cs", e.target?.value)} + /> + +
    +
    +
    + {/*

    Photos

    */} + setShowAddSidebar(true)} /> +
    + +
    +
    + + + + {columns.map((column, index) => ( + + ))} + + + + {data.length == 0 ? ( + + + + ) : null} + {data.map((row, i) => { + return ( + + {columns.map((cell, index) => { + if (cell.accessor == "") { + return ( + + ); + } + if (cell.mapping) { + return ( + + ); + } + return ( + + ); + })} + + ); + })} + +
    + {column.header} + + {column.isSorted + ? column.isSortedDesc + ? " ▼" + : " ▲" + : ""} + +
    + {loadingData && } +
    + + + {cell.mapping[row[cell.accessor]]} + + {cell.accessor == "url" ? : row[cell.accessor]} +
    +
    +
    + + setShowAddSidebar(false)} + > + + +
    + ); +}; + +export default AdminPhotoListPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/List/AdminStripeInvoicesListPageV2.jsx b/day20/src/pages/Admin/List/AdminStripeInvoicesListPageV2.jsx new file mode 100644 index 0000000..eb44ea2 --- /dev/null +++ b/day20/src/pages/Admin/List/AdminStripeInvoicesListPageV2.jsx @@ -0,0 +1,477 @@ + + import React from "react"; + import { AuthContext, tokenExpireError } from "Context/Auth"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate } from "react-router-dom"; + import { GlobalContext } from "Context/Global"; + import { yupResolver } from "@hookform/resolvers/yup"; + import { useForm } from "react-hook-form"; + import * as yup from "yup"; + import { getNonNullValue } from "Utils/utils"; + import { PaginationBar } from "Components/PaginationBar"; + import { BiFilterAlt, BiSearch } from "react-icons/bi"; + import { AiOutlineClose, AiOutlinePlus } from "react-icons/ai"; + import { RiDeleteBin5Line } from "react-icons/ri"; + import { SkeletonLoader } from "Components/Skeleton"; + + const columns = [ + { + header: "Status", + accessor: "status", + }, + { + header: "Currency", + accessor: "currency", + }, + { + header: "Amount due", + accessor: "amount_due", + type: "currency", + }, + { + header: "Amount paid", + accessor: "amount_paid", + type: "currency", + }, + { + header: "Amount remaining", + accessor: "amount_remaining", + type: "currency", + }, + { + header: "Created at", + accessor: "created_at", + type: "timestamp", + }, + ]; + + const AdminStripeInvoicesListPageV2 = () => { + const sdk = new MkdSDK(); + + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const { dispatch } = React.useContext(AuthContext); + const [query, setQuery] = React.useState(""); + const [data, setCurrentTableData] = React.useState([]); + const [pageSize, setPageSize] = React.useState(10); + const [pageCount, setPageCount] = React.useState(0); + const [dataTotal, setDataTotal] = React.useState(0); + const [currentPage, setPage] = React.useState(0); + const [canPreviousPage, setCanPreviousPage] = React.useState(false); + const [canNextPage, setCanNextPage] = React.useState(false); + const [openFilter, setOpenFilter] = React.useState(false); + const [showFilterOptions, setShowFilterOptions] = React.useState(false); + const [selectedOptions, setSelectedOptions] = React.useState([]); + const [filterConditions, setFilterConditions] = React.useState([]); + const [searchValue, setSearchValue] = React.useState(""); + const [optionValue, setOptionValue] = React.useState("eq"); + const [isLoading, setIsLoading] = React.useState(false); + const navigate = useNavigate(); + const dropdownFilterRef = React.useRef(null); + + const schema = yup.object({ + customer_email: yup.string(), + }); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const selectStatus = [{ key: "", value: "All" }]; + + const addFilterCondition = (option, selectedValue, inputValue) => { + const input = + selectedValue === "eq" && isNaN(inputValue) + ? `"${inputValue}"` + : inputValue; + const condition = `${option},${selectedValue},${input}`; + setFilterConditions((prevConditions) => { + const newConditions = prevConditions.filter( + (condition) => !condition.includes(option) + ); + return [...newConditions, condition]; + }); + setSearchValue(inputValue); + }; + + function updatePageSize(limit) { + (async function () { + setPageSize(limit); + await getData(1, limit); + })(); + } + function previousPage() { + (async function () { + await getData(currentPage - 1 > 1 ? currentPage - 1 : 1, pageSize); + })(); + } + + function nextPage() { + (async function () { + await getData( + currentPage + 1 <= pageCount ? currentPage + 1 : 1, + pageSize + ); + })(); + } + + async function getData(pageNum, limitNum, filterParams) { + setIsLoading(true); + try { + const { list, total, limit, num_pages, page, error, message } = + await sdk.getStripeInvoicesV2( + { page: pageNum, limit: limitNum }, + `filter=${filterParams.toString()}` + ); + + if (error) { + showToast(globalDispatch, message, 5000); + return; + } + + setCurrentTableData(list); + setPageSize(+limit); + setPageCount(+num_pages); + setPage(+page); + setDataTotal(+total); + setCanPreviousPage(+page > 1); + setCanNextPage(+page + 1 <= +num_pages); + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + setIsLoading(false); + } + + const onSubmit = (data) => { + const customer_email = getNonNullValue(data.customer_email); + getData(1, pageSize, { customer_email, product_name }); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "invoices", + }, + }); + + const delay = 700; + const timeoutId = setTimeout(async () => { + await getData(1, pageSize, filterConditions); + }, delay); + + return () => { + clearTimeout(timeoutId); + }; + }, [searchValue, filterConditions, optionValue]); + + const handleClickOutside = (event) => { + if (dropdownFilterRef.current && !dropdownFilterRef.current.contains(event.target)) { + setOpenFilter(false); + } + }; + + React.useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( + <> + {/*
    +

    Search

    +
    +
    + + +

    { errors.customer_email?.message }

    +
    +
    +
    + + + +
    +
    */} +
    +
    +
    +
    +
    setOpenFilter(!openFilter)} + > + + Filters + {selectedOptions.length > 0 && ( + + {selectedOptions.length} + + )} +
    + {openFilter && ( +
    +
    + {selectedOptions?.map((option, index) => ( +
    +
    + {option} +
    + + + + addFilterCondition( + option, + optionValue, + e.target.value + ) + } + /> + +
    + { + setSelectedOptions((prevOptions) => + prevOptions.filter((op) => op !== option) + ); + setFilterConditions((prevConditions) => { + return prevConditions.filter( + (condition) => !condition.includes(option) + ); + }); + }} + /> +
    + +
    + ))} + +
    +
    { + setShowFilterOptions(!showFilterOptions); + }} + > + + Add filter +
    + + {showFilterOptions && ( +
    +
      + {columns.slice(0, -1).map((column) => ( +
    • { + if (!selectedOptions.includes(column.header)) { + setSelectedOptions((prev) => [ + ...prev, + column.header, + ]); + } + setShowFilterOptions(false); + }} + > + {column.header} +
    • + ))} +
    +
    + )} + {selectedOptions.length > 0 && ( +
    { + setSelectedOptions([]); + setFilterConditions([]); + }} + className="inline-block cursor-pointer rounded px-6 py-2.5 font-medium leading-tight text-gray-600 transition duration-150 ease-in-out" + > + Clear all filter +
    + )} +
    +
    +
    + )} +
    +
    + + addFilterCondition("name", "cs", e.target?.value)} + /> + +
    +
    +
    +
    + {isLoading ? ( + + ) : ( +
    + + + + {columns.map((column, index) => ( + + ))} + + + + {data.map((row, i) => { + return ( + + {columns.map((cell, index) => { + if (cell.accessor == "") { + return ( + + ); + } + if (cell.mapping) { + return ( + + ); + } + if (cell.type === "timestamp") { + return ( + + ); + } else if (cell.type === "currency") { + return ( + + ); + } else if (cell.type === "metadata") { + return ( + + ); + } + return ( + + ); + })} + + ); + })} + +
    + {column.header} + + {column.isSorted + ? column.isSortedDesc + ? " ▼" + : " ▲" + : ""} + +
    + {cell.mapping[row[cell.accessor]]} + + {new Date(row[cell.accessor] * 1000).toLocaleString( + "en-US" + )} + + ${Number(row[cell.accessor] / 100).toFixed(2)} + + {row[cell.pre_accessor][cell.accessor] ?? "n/a"} + + {row[cell.accessor]} +
    +
    + )} + + + + ); + }; + + export default AdminStripeInvoicesListPageV2; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/List/AdminStripePricesListPage.jsx b/day20/src/pages/Admin/List/AdminStripePricesListPage.jsx new file mode 100644 index 0000000..27b78aa --- /dev/null +++ b/day20/src/pages/Admin/List/AdminStripePricesListPage.jsx @@ -0,0 +1,538 @@ + + import React, { useEffect, useState } from "react"; + import { AuthContext, tokenExpireError } from "Context/Auth"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate } from "react-router-dom"; + import { GlobalContext, showToast } from "Context/Global"; + import { yupResolver } from "@hookform/resolvers/yup"; + import { useForm } from "react-hook-form"; + import * as yup from "yup"; + import { getNonNullValue } from "Utils/utils"; + import { PaginationBar } from "Components/PaginationBar"; + import { AddButton } from "Components/AddButton"; + import { BiFilterAlt, BiSearch } from "react-icons/bi"; + import { AiOutlineClose, AiOutlinePlus } from "react-icons/ai"; + import { RiDeleteBin5Line } from "react-icons/ri"; + import { SkeletonLoader } from "Components/Skeleton"; + import { ModalSidebar } from "Components/ModalSidebar"; + import { + AddAdminStripePricePage, + } from "Pages/Admin/Add"; + import { + EditAdminStripePricePage, + } from "Pages/Admin/Edit"; + + let sdk = new MkdSDK(); + + const columns = [ + { + header: "Stripe Id", + accessor: "stripe_id", + }, + { + header: "Product", + accessor: "product_name", + }, + { + header: "Nickname", + accessor: "name", + }, + { + header: "Type", + accessor: "type", + mapping: { + one_time: "One Time", + recurring: "Recurring", + lifetime: "Lifetime", + }, + }, + { + header: "Price", + accessor: "amount", + }, + { + header: "Trial", + accessor: "trial_days", + }, + { + header: "Status", + accessor: "status", + mapping: { + 0: "Inactive", + 1: "Active", + }, + }, + { + header: "Action", + accessor: "", + }, + ]; + + const AdminStripePricesListPage = () => { + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const { dispatch } = React.useContext(AuthContext); + const [query, setQuery] = React.useState(""); + const [data, setCurrentTableData] = React.useState([]); + const [pageSize, setPageSize] = React.useState(10); + const [pageCount, setPageCount] = React.useState(0); + const [dataTotal, setDataTotal] = React.useState(0); + const [currentPage, setPage] = React.useState(0); + const [canPreviousPage, setCanPreviousPage] = React.useState(false); + const [canNextPage, setCanNextPage] = React.useState(false); + const [openFilter, setOpenFilter] = React.useState(false); + const [showFilterOptions, setShowFilterOptions] = React.useState(false); + const [selectedOptions, setSelectedOptions] = React.useState([]); + const [filterConditions, setFilterConditions] = React.useState([]); + const [searchValue, setSearchValue] = React.useState(""); + const [optionValue, setOptionValue] = React.useState("eq"); + const [isLoading, setIsLoading] = React.useState(false); + const [showEditSidebar, setShowEditSidebar] = useState(false); + const [showAddSidebar, setShowAddSidebar] = useState(false); + const [activeEditId, setActiveEditId] = useState(); + const navigate = useNavigate(); + const dropdownFilterRef = React.useRef(null); + + const schema = yup.object({ + stripe_id: yup.string(), + name: yup.string(), + status: yup.string(), + product_name: yup.string(), + amount: yup.string(), + type: yup.string(), + }); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const selectStatus = [ + { key: "", value: "All" }, + { key: 0, value: "Archived" }, + { key: 1, value: "Active" }, + ]; + const typeStatus = [ + { key: "", value: "All" }, + { key: "one_time", value: "One time" }, + { key: "recurring", value: "Recurring" }, + ]; + + function copyToClipboard(text) { + navigator.clipboard.writeText(text); + showToast(globalDispatch, "Copied to clipboard"); + } + + const addFilterCondition = (option, selectedValue, inputValue) => { + const input = + selectedValue === "eq" && isNaN(inputValue) + ? `"${inputValue}"` + : inputValue; + const condition = `${option},${selectedValue},${input}`; + setFilterConditions((prevConditions) => { + const newConditions = prevConditions.filter( + (condition) => !condition.includes(option) + ); + return [...newConditions, condition]; + }); + setSearchValue(inputValue); + }; + + function updatePageSize(limit) { + (async function () { + setPageSize(limit); + await getData(1, limit); + })(); + } + function previousPage() { + (async function () { + await getData(currentPage - 1 > 1 ? currentPage - 1 : 1, pageSize); + })(); + } + + function nextPage() { + (async function () { + await getData( + currentPage + 1 <= pageCount ? currentPage + 1 : 1, + pageSize + ); + })(); + } + + async function getData(pageNum, limitNum, filterParams) { + setIsLoading(true); + try { + const result = await sdk.getStripePrices( + { page: pageNum, limit: limitNum }, + `filter=${filterParams.toString()}` + ); + const { list, total, limit, num_pages, page } = result; + + setCurrentTableData(list); + setPageSize(+limit); + setPageCount(+num_pages); + setPage(+page); + setDataTotal(+total); + setCanPreviousPage(+page > 1); + setCanNextPage(+page + 1 <= +num_pages); + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + setIsLoading(false); + } + + const onSubmit = (data) => { + const stripe_id = getNonNullValue(data.stripe_id); + const product_name = getNonNullValue(data.product_name); + const name = getNonNullValue(data.name); + const amount = getNonNullValue(data.amount); + const type = getNonNullValue(data.type); + const status = getNonNullValue(data.status); + getData(1, pageSize, { + stripe_id, + product_name, + name, + amount, + type, + status, + }); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "prices", + }, + }); + + const delay = 700; + const timeoutId = setTimeout(async () => { + await getData(1, pageSize, filterConditions); + }, delay); + + return () => { + clearTimeout(timeoutId); + }; + }, [searchValue, filterConditions, optionValue]); + + useEffect(() => { + if (!showEditSidebar) { + getData(1, pageSize, filterConditions); + } + }, [showEditSidebar, activeEditId, showAddSidebar]); + + const handleClickOutside = (event) => { + if (dropdownFilterRef.current && !dropdownFilterRef.current.contains(event.target)) { + setOpenFilter(false); + } + }; + + React.useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( + <> +
    +
    +
    +
    +
    setOpenFilter(!openFilter)} + > + + Filters + {selectedOptions.length > 0 && ( + + {selectedOptions.length} + + )} +
    + {openFilter && ( +
    +
    + {selectedOptions?.map((option, index) => ( +
    +
    + {option} +
    + + + + addFilterCondition( + option, + optionValue, + e.target.value + ) + } + /> + +
    + { + setSelectedOptions((prevOptions) => + prevOptions.filter((op) => op !== option) + ); + setFilterConditions((prevConditions) => { + return prevConditions.filter( + (condition) => !condition.includes(option) + ); + }); + }} + /> +
    + +
    + ))} + +
    +
    { + setShowFilterOptions(!showFilterOptions); + }} + > + + Add filter +
    + + {showFilterOptions && ( +
    +
      + {columns.slice(0, -1).map((column) => ( +
    • { + if (!selectedOptions.includes(column.header)) { + setSelectedOptions((prev) => [ + ...prev, + column.header, + ]); + } + setShowFilterOptions(false); + }} + > + {column.header} +
    • + ))} +
    +
    + )} + {selectedOptions.length > 0 && ( +
    { + setSelectedOptions([]); + setFilterConditions([]); + }} + className="inline-block cursor-pointer rounded px-6 py-2.5 font-medium leading-tight text-gray-600 transition duration-150 ease-in-out" + > + Clear all filter +
    + )} +
    +
    +
    + )} +
    +
    + + addFilterCondition("name", "cs", e.target?.value)} + /> + +
    +
    +
    + setShowAddSidebar(true)} /> +
    + {isLoading ? ( + + ) : ( +
    + + + + {columns.map((column, index) => ( + + ))} + + + + {data.map((row, i) => { + return ( + + {columns.map((cell, index) => { + if (cell.accessor == "") { + return ( + + ); + } + if (cell.accessor === "stripe_id") { + return ( + + ); + } + if (cell.mapping && cell.accessor === "status") { + return ( + + ); + } + if (cell.mapping) { + return ( + + ); + } + return ( + + ); + })} + + ); + })} + +
    + {column.header} + + {column.isSorted + ? column.isSortedDesc + ? " ▼" + : " ▲" + : ""} + +
    + + +
    + {row[cell.accessor]} + copyToClipboard(row[cell.accessor])}> + + + + + + + + + + + +
    +
    + {row[cell.accessor] === 1 ? ( + + {cell.mapping[row[cell.accessor]]} + + ) : ( + + {cell.mapping[row[cell.accessor]]} + + )} + + {cell.mapping[row[cell.accessor]]} + + {row[cell.accessor]} +
    +
    + )} + + setShowEditSidebar(false)} + > + + + + setShowAddSidebar(false)} + > + + + + ); + }; + + export default AdminStripePricesListPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/List/AdminStripeSubscriptionsListPage.jsx b/day20/src/pages/Admin/List/AdminStripeSubscriptionsListPage.jsx new file mode 100644 index 0000000..ea36c20 --- /dev/null +++ b/day20/src/pages/Admin/List/AdminStripeSubscriptionsListPage.jsx @@ -0,0 +1,642 @@ + + import React from "react"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import MkdSDK from "Utils/MkdSDK"; +import { useNavigate } from "react-router-dom"; +import { GlobalContext, showToast } from "Context/Global"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { useForm } from "react-hook-form"; +import * as yup from "yup"; +import { getNonNullValue } from "Utils/utils"; +import { PaginationBar } from "Components/PaginationBar"; +// import {AddButton} from "Components/AddButton"; +import { BiFilterAlt, BiSearch } from "react-icons/bi"; +import { AiOutlineClose, AiOutlinePlus } from "react-icons/ai"; +import { RiDeleteBin5Line } from "react-icons/ri"; +import { SkeletonLoader } from "Components/Skeleton"; + +let sdk = new MkdSDK(); + +const columns = [ + { + header: "Customer", + accessor: "userEmail", + }, + { + header: "Plan", + accessor: "planName", + }, + { + header: "Starts", + accessor: "currentPeriodStart", + type: "timestamp", + }, + { + header: "Ends", + accessor: "currentPeriodEnd", + type: "timestamp", + }, + { + header: "type", + accessor: "planType", + mapping: { + recurring: "Recurring", + life_time: "Lifetime", + }, + }, + { + header: "Usage Type", + accessor: "isMetered", + mapping: { + 0: "Upfront", + 1: "Metered", + }, + }, + { + header: "Price", + accessor: "planAmount", + type: "currency", + }, + { + header: "Has Trial", + accessor: "trialDays", + }, + { + header: "Status", + accessor: "status", + }, + { + header: "Action", + accessor: "", + }, +]; +const AdminStripeSubscriptionsListPage = () => { + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const { dispatch } = React.useContext(AuthContext); + const [query, setQuery] = React.useState(""); + const [data, setCurrentTableData] = React.useState([]); + const [pageSize, setPageSize] = React.useState(10); + const [pageCount, setPageCount] = React.useState(0); + const [dataTotal, setDataTotal] = React.useState(0); + const [currentPage, setPage] = React.useState(0); + const [canPreviousPage, setCanPreviousPage] = React.useState(false); + const [canNextPage, setCanNextPage] = React.useState(false); + const [openFilter, setOpenFilter] = React.useState(false); + const [showFilterOptions, setShowFilterOptions] = React.useState(false); + const [selectedOptions, setSelectedOptions] = React.useState([]); + const [filterConditions, setFilterConditions] = React.useState([]); + const [searchValue, setSearchValue] = React.useState(""); + const [optionValue, setOptionValue] = React.useState("eq"); + const [isLoading, setIsLoading] = React.useState(false); + const navigate = useNavigate(); + const dropdownFilterRef = React.useRef(null); + + const schema = yup.object({ + customer_email: yup.string(), + plan_name: yup.string(), + sub_status: yup.string(), + plan_type: yup.string(), + }); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const selectStatus = [ + { key: "", value: "All" }, + { key: "active", value: "Active" }, + { key: "trialing", value: "Trialing" }, + { key: "canceled", value: "Canceled" }, + ]; + + const typeStatus = [ + { key: "", value: "All" }, + { key: "recurring", value: "Recurring" }, + { key: "lifetime", value: "Lifetime" }, + ]; + + const addFilterCondition = (option, selectedValue, inputValue) => { + const input = + selectedValue === "eq" && isNaN(inputValue) + ? `"${inputValue}"` + : inputValue; + const condition = `${option},${selectedValue},${input}`; + setFilterConditions((prevConditions) => { + const newConditions = prevConditions.filter( + (condition) => !condition.includes(option) + ); + return [...newConditions, condition]; + }); + setSearchValue(inputValue); + }; + + function updatePageSize(limit) { + (async function () { + setPageSize(limit); + await getData(1, limit); + })(); + } + function previousPage() { + (async function () { + await getData(currentPage - 1 > 1 ? currentPage - 1 : 1, pageSize); + })(); + } + + function nextPage() { + (async function () { + await getData( + currentPage + 1 <= pageCount ? currentPage + 1 : 1, + pageSize + ); + })(); + } + + async function getData(pageNum, limitNum, filterParams) { + setIsLoading(true); + + try { + const result = await sdk.getStripeSubscriptions( + { page: pageNum, limit: limitNum }, + `filter=${filterParams.toString()}` + ); + const { list, total, limit, num_pages, page } = result; + + setCurrentTableData(list); + setPageSize(+limit); + setPageCount(+num_pages); + setPage(+page); + setDataTotal(+total); + setCanPreviousPage(+page > 1); + setCanNextPage(+page + 1 <= +num_pages); + } catch (error) { + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + setIsLoading(false); + } + + const onSubmit = (data) => { + const customer_email = getNonNullValue(data.customer_email); + const plan_name = getNonNullValue(data.plan_name); + const sub_status = getNonNullValue(data.sub_status); + const plan_type = getNonNullValue(data.plan_type); + getData(1, pageSize, { + customer_email, + plan_name, + sub_status, + plan_type, + }); + }; + + const cancelCustomerSubscription = async (id) => { + console.log(id); + try { + const result = await sdk.adminCancelStripeSubscription(id, {}); + showToast(globalDispatch, result.message, 3000); + getData(1, pageSize); + } catch (error) { + console.log("ERROR", error); + showToast(globalDispatch, error.message); + tokenExpireError(dispatch, error.message); + } + }; + + const createCharge = async (subId, quantity) => { + console.log(subId); + try { + const result = await sdk.adminCreateUsageCharge(subId, quantity); + showToast(globalDispatch, result.message, 3000); + getData(1, pageSize); + } catch (error) { + console.log("ERROR", error); + showToast(globalDispatch, error.message); + tokenExpireError(dispatch, error.message); + } + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "subscriptions", + }, + }); + + const delay = 700; + const timeoutId = setTimeout(async () => { + await getData(1, pageSize, filterConditions); + }, delay); + + return () => { + clearTimeout(timeoutId); + }; + }, [searchValue, filterConditions, optionValue]); + + const handleClickOutside = (event) => { + if (dropdownFilterRef.current && !dropdownFilterRef.current.contains(event.target)) { + setOpenFilter(false); + } + }; + + React.useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( + <> + {/*
    +

    Search

    +
    +
    + + +

    {errors.customer_email?.message}

    +
    + +
    + + +

    {errors.plan_name?.message}

    +
    + +
    + + +

    +
    + +
    + + +

    +
    +
    + +
    + + + +
    +
    */} +
    +
    +
    +
    +
    setOpenFilter(!openFilter)} + > + + Filters + {selectedOptions.length > 0 && ( + + {selectedOptions.length} + + )} +
    + {openFilter && ( +
    +
    + {selectedOptions?.map((option, index) => ( +
    +
    + {option} +
    + + + + addFilterCondition( + option, + optionValue, + e.target.value + ) + } + /> + +
    + { + setSelectedOptions((prevOptions) => + prevOptions.filter((op) => op !== option) + ); + setFilterConditions((prevConditions) => { + return prevConditions.filter( + (condition) => !condition.includes(option) + ); + }); + }} + /> +
    + +
    + ))} + +
    +
    { + setShowFilterOptions(!showFilterOptions); + }} + > + + Add filter +
    + + {showFilterOptions && ( +
    +
      + {columns.slice(0, -1).map((column) => ( +
    • { + if (!selectedOptions.includes(column.header)) { + setSelectedOptions((prev) => [ + ...prev, + column.header, + ]); + } + setShowFilterOptions(false); + }} + > + {column.header} +
    • + ))} +
    +
    + )} + {selectedOptions.length > 0 && ( +
    { + setSelectedOptions([]); + setFilterConditions([]); + }} + className="inline-block cursor-pointer rounded px-6 py-2.5 font-medium leading-tight text-gray-600 transition duration-150 ease-in-out" + > + Clear all filter +
    + )} +
    +
    +
    + )} +
    +
    + + addFilterCondition("name", "cs", e.target?.value)} + /> + +
    +
    +
    +
    + + {isLoading ? ( + + ) : ( +
    + + + + {columns.map((column, index) => ( + + ))} + + + + {data.map((row, i) => { + return ( + + {columns.map((cell, index) => { + if (cell.accessor == "") { + return ( + + ); + } + if (cell.mapping) { + return ( + + ); + } + if ( + row.planType === "recurring" && + cell.type === "timestamp" + ) { + return ( + + ); + } else if ( + row.planType === "lifetime" && + cell.type === "timestamp" + ) { + if (cell.accessor === "currentPeriodStart") { + return ( + + ); + } else if (cell.accessor === "currentPeriodEnd") { + return ( + + ); + } + } else if (cell.type == "currency") { + return ( + + ); + } + return ( + + ); + })} + + ); + })} + +
    + {column.header} + + {column.isSorted + ? column.isSortedDesc + ? " ▼" + : " ▲" + : ""} + +
    + {row.status !== "canceled" ? ( + + ) : ( + "" + )} + {row.isMetered === 1 ? ( + + ) : ( + "" + )} + + {cell.mapping[row[cell.accessor]]} + + {new Date( + row[cell.accessor] * 1000 + ).toLocaleDateString("en-US", { + dateStyle: "medium", + })} + + {new Date( + row.createdAt * 1000 + ).toLocaleDateString("en-US", { + dateStyle: "medium", + })} + + Infinity + + ${+(row[cell.accessor] ?? 0)} + + {row[cell.accessor]} +
    +
    + )} + + + ); +}; + +export default AdminStripeSubscriptionsListPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/List/AdminUserListPage.jsx b/day20/src/pages/Admin/List/AdminUserListPage.jsx new file mode 100644 index 0000000..a58c955 --- /dev/null +++ b/day20/src/pages/Admin/List/AdminUserListPage.jsx @@ -0,0 +1,473 @@ + + import React from "react"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate } from "react-router-dom"; + import { AuthContext, tokenExpireError } from "Context/Auth"; + import { GlobalContext } from "Context/Global"; + import { yupResolver } from "@hookform/resolvers/yup"; + import { useForm } from "react-hook-form"; + import * as yup from "yup"; + import { getNonNullValue } from "Utils/utils"; + import { PaginationBar } from "Components/PaginationBar"; + import { AddButton } from "Components/AddButton"; + import { SkeletonLoader } from "Components/Skeleton"; + import { BiFilterAlt, BiSearch } from "react-icons/bi"; + import { AiOutlineClose, AiOutlinePlus } from "react-icons/ai"; + import { RiDeleteBin5Line } from "react-icons/ri"; + import { ModalSidebar } from "Components/ModalSidebar"; + import { AddAdminUserPage, EditAdminUserPage } from "Src/routes/LazyLoad"; + + let sdk = new MkdSDK(); + + const columns = [ + { + header: "Email", + accessor: "email", + }, + { + header: "Role", + accessor: "role", + }, + { + header: "Status", + accessor: "status", + mapping: ["Inactive", "Active", "Suspend"], + }, + { + header: "Action", + accessor: "", + }, + ]; + const AdminUserListPage = () => { + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const { dispatch } = React.useContext(AuthContext); + const [query, setQuery] = React.useState(""); + const [data, setCurrentTableData] = React.useState([]); + const [pageSize, setPageSize] = React.useState(10); + const [pageCount, setPageCount] = React.useState(0); + const [dataTotal, setDataTotal] = React.useState(0); + const [currentPage, setPage] = React.useState(0); + const [canPreviousPage, setCanPreviousPage] = React.useState(false); + const [canNextPage, setCanNextPage] = React.useState(false); + const [openFilter, setOpenFilter] = React.useState(false); + const [showFilterOptions, setShowFilterOptions] = React.useState(false); + const [selectedOptions, setSelectedOptions] = React.useState([]); + const [filterConditions, setFilterConditions] = React.useState([]); + const [searchValue, setSearchValue] = React.useState(""); + const [optionValue, setOptionValue] = React.useState("eq"); + const [loading, setLoading] = React.useState(true); + const [showEditSidebar, setShowEditSidebar] = React.useState(false); + const [showAddSidebar, setShowAddSidebar] = React.useState(false); + const [activeEditId, setActiveEditId] = React.useState(); + const navigate = useNavigate(); + const dropdownFilterRef = React.useRef(null); + + const schema = yup.object({ + id: yup.string(), + email: yup.string(), + role: yup.string(), + status: yup.string(), + }); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const selectRole = ["", "admin", "employee"]; + const selectStatus = [ + { key: "", value: "All" }, + { key: "0", value: "Inactive" }, + { key: "1", value: "Active" }, + { key: "2", value: "Suspend" }, + ]; + + function updatePageSize(limit) { + (async function () { + setPageSize(limit); + await getData(0, limit); + })(); + } + function previousPage() { + (async function () { + await getData(currentPage - 1 > 0 ? currentPage - 1 : 0, pageSize); + })(); + } + + function nextPage() { + (async function () { + await getData( + currentPage + 1 <= pageCount ? currentPage + 1 : 0, + pageSize + ); + })(); + } + + const addFilterCondition = (option, selectedValue, inputValue) => { + const input = + selectedValue === "eq" && isNaN(inputValue) + ? `"${inputValue}"` + : inputValue; + const condition = `${option},${selectedValue},${input}`; + setFilterConditions((prevConditions) => { + const newConditions = prevConditions.filter( + (condition) => !condition.includes(option) + ); + return [...newConditions, condition]; + }); + setSearchValue(inputValue); + }; + + async function getData(pageNum, limitNum, data) { + setLoading(true); + try { + sdk.setTable("user"); + const result = await sdk.callRestAPI( + { + payload: { + ...data, + }, + page: pageNum, + limit: limitNum, + filter: [...filterConditions], + }, + "PAGINATE" + ); + if (result) { + setLoading(false); + } + const { list, total, limit, num_pages, page } = result; + + setCurrentTableData(list); + setPageSize(limit); + setPageCount(num_pages); + setPage(page); + setDataTotal(total); + setCanPreviousPage(page > 1); + setCanNextPage(page + 1 <= num_pages); + } catch (error) { + setLoading(false); + console.log("ERROR", error); + tokenExpireError(dispatch, error.message); + } + } + + const onSubmit = (data) => { + const email = getNonNullValue(data.email); + const role = getNonNullValue(data.role); + const status = getNonNullValue(data.status); + const id = getNonNullValue(data.id); + getData(0, pageSize, { email, role, status, id }); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "users", + }, + }); + + const delay = 700; + const timeoutId = setTimeout(async () => { + await getData(1, pageSize); + }, delay); + + return () => { + clearTimeout(timeoutId); + }; + }, [searchValue, filterConditions, optionValue]); + + const handleClickOutside = (event) => { + if (dropdownFilterRef.current && !dropdownFilterRef.current.contains(event.target)) { + setOpenFilter(false); + } + }; + + React.useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( +
    +
    +
    +
    +
    +
    setOpenFilter(!openFilter)} + > + + Filters + {selectedOptions.length > 0 && ( + + {selectedOptions.length} + + )} +
    + {openFilter && ( +
    +
    + {selectedOptions?.map((option, index) => ( +
    +
    + {option} +
    + + + + addFilterCondition( + option, + optionValue, + e.target.value + ) + } + /> + +
    + { + setSelectedOptions((prevOptions) => + prevOptions.filter((op) => op !== option) + ); + setFilterConditions((prevConditions) => { + return prevConditions.filter( + (condition) => !condition.includes(option) + ); + }); + }} + /> +
    + +
    + ))} + +
    +
    { + setShowFilterOptions(!showFilterOptions); + }} + > + + Add filter +
    + + {showFilterOptions && ( +
    +
      + {columns.slice(0, -1).map((column) => ( +
    • { + if (!selectedOptions.includes(column.header)) { + setSelectedOptions((prev) => [ + ...prev, + column.header, + ]); + } + setShowFilterOptions(false); + }} + > + {column.header} +
    • + ))} +
    +
    + )} + {selectedOptions.length > 0 && ( +
    { + setSelectedOptions([]); + setFilterConditions([]); + }} + className="inline-block cursor-pointer rounded px-6 py-2.5 font-medium leading-tight text-gray-600 transition duration-150 ease-in-out" + > + Clear all filter +
    + )} +
    +
    +
    + )} +
    +
    + + addFilterCondition("name", "cs", e.target?.value)} + /> + +
    +
    +
    + setShowAddSidebar(true)} /> +
    + {loading ? ( + + ) : ( +
    + + + + {columns.map((column, index) => ( + + ))} + + + + {data.map((row, i) => { + return ( + + {columns.map((cell, index) => { + if (cell.accessor == "") { + return ( + + ); + } + if (cell.mapping && cell.accessor === "status") { + return ( + + ); + } + if (cell.mapping) { + return ( + + ); + } + return ( + + ); + })} + + ); + })} + +
    + {column.header} + + {column.isSorted + ? column.isSortedDesc + ? " ▼" + : " ▲" + : ""} + +
    + + + {row[cell.accessor] === 1 ? ({cell.mapping[row[cell.accessor]]}) : ({cell.mapping[row[cell.accessor]]})} + + {cell.mapping[row[cell.accessor]]} + + {row[cell.accessor]} +
    + {loading && ( + <> +

    Loading...

    + + )} + {!loading && data.length === 0 && ( + <> +

    + You Don't have any User +

    + + )} +
    + )} + + setShowAddSidebar(false)} + > + + + setShowEditSidebar(false)} + > + + +
    + ); + }; + + export default AdminUserListPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/View/AdminDashboardPage.jsx b/day20/src/pages/Admin/View/AdminDashboardPage.jsx new file mode 100644 index 0000000..81729c5 --- /dev/null +++ b/day20/src/pages/Admin/View/AdminDashboardPage.jsx @@ -0,0 +1,28 @@ + + import React from "react"; + import { AuthContext } from "Context/Auth"; + import { GlobalContext } from "Context/Global"; + + const AdminDashboardPage = () => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "admin", + }, + }); + }, []); + return ( + <> +
    + Dashboard +
    + + ); + }; + + export default AdminDashboardPage; + \ No newline at end of file diff --git a/day20/src/pages/Admin/View/AdminViewDepartmentTablePage.jsx b/day20/src/pages/Admin/View/AdminViewDepartmentTablePage.jsx new file mode 100644 index 0000000..aa40bf5 --- /dev/null +++ b/day20/src/pages/Admin/View/AdminViewDepartmentTablePage.jsx @@ -0,0 +1,72 @@ + + import React from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate, useParams } from "react-router-dom"; + import { tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import { isImage, empty, isVideo } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; + import {SkeletonLoader} from "Components/Skeleton" + + let sdk = new MkdSDK(); + + const ViewDepartmentPage = () => { + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + const { dispatch } = React.useContext(GlobalContext); + const [viewModel, setViewModel] = React.useState({}); + const [loading, setLoading] = React.useState(true) + + + + const params = useParams(); + + React.useEffect(function () { + (async function () { + try { + setLoading(true) + sdk.setTable("department"); + const result = await sdk.callRestAPI({ id: Number(params?.id), join: "", }, "GET"); + if (!result.error) { + + setViewModel(result.model); + setLoading(false) + + } + } catch (error) { + setLoading(false) + + console.log("error", error); + tokenExpireError(dispatch, error.message); + } + })(); + }, []); + return ( +
    + {loading ? () :( + <> +

    View Department

    + + + +
    +
    +
    Name
    +
    {viewModel?.name}
    +
    +
    + + + + ) + } +
    + ); + }; + + export default ViewDepartmentPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/View/AdminViewItemMovementHistoryTablePage.jsx b/day20/src/pages/Admin/View/AdminViewItemMovementHistoryTablePage.jsx new file mode 100644 index 0000000..6bb00c2 --- /dev/null +++ b/day20/src/pages/Admin/View/AdminViewItemMovementHistoryTablePage.jsx @@ -0,0 +1,99 @@ + + import React from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate, useParams } from "react-router-dom"; + import { tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import { isImage, empty, isVideo } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; + import {SkeletonLoader} from "Components/Skeleton" + + let sdk = new MkdSDK(); + + const ViewItemMovementHistoryPage = () => { + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + const { dispatch } = React.useContext(GlobalContext); + const [viewModel, setViewModel] = React.useState({}); + const [loading, setLoading] = React.useState(true) + + + + const params = useParams(); + + React.useEffect(function () { + (async function () { + try { + setLoading(true) + sdk.setTable("item_movement_history"); + const result = await sdk.callRestAPI({ id: Number(params?.id), join: "", }, "GET"); + if (!result.error) { + + setViewModel(result.model); + setLoading(false) + + } + } catch (error) { + setLoading(false) + + console.log("error", error); + tokenExpireError(dispatch, error.message); + } + })(); + }, []); + return ( +
    + {loading ? () :( + <> +

    View Item Movement History

    + + + +
    +
    +
    Product Id
    +
    {viewModel?.product_id}
    +
    +
    + + + +
    +
    +
    Moved From
    +
    {viewModel?.moved_from}
    +
    +
    + + + +
    +
    +
    Moved To
    +
    {viewModel?.moved_to}
    +
    +
    + + + +
    +
    +
    Date
    +
    {viewModel?.date}
    +
    +
    + + + + ) + } +
    + ); + }; + + export default ViewItemMovementHistoryPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/View/AdminViewItemsTablePage.jsx b/day20/src/pages/Admin/View/AdminViewItemsTablePage.jsx new file mode 100644 index 0000000..f483fe6 --- /dev/null +++ b/day20/src/pages/Admin/View/AdminViewItemsTablePage.jsx @@ -0,0 +1,117 @@ + + import React from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate, useParams } from "react-router-dom"; + import { tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import { isImage, empty, isVideo } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; + import {SkeletonLoader} from "Components/Skeleton" + + let sdk = new MkdSDK(); + + const ViewItemsPage = () => { + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + const { dispatch } = React.useContext(GlobalContext); + const [viewModel, setViewModel] = React.useState({}); + const [loading, setLoading] = React.useState(true) + + + + const params = useParams(); + + React.useEffect(function () { + (async function () { + try { + setLoading(true) + sdk.setTable("items"); + const result = await sdk.callRestAPI({ id: Number(params?.id), join: "", }, "GET"); + if (!result.error) { + + setViewModel(result.model); + setLoading(false) + + } + } catch (error) { + setLoading(false) + + console.log("error", error); + tokenExpireError(dispatch, error.message); + } + })(); + }, []); + return ( +
    + {loading ? () :( + <> +

    View Items

    + + + +
    +
    +
    Title
    +
    {viewModel?.title}
    +
    +
    + + + +
    +
    +
    Quantity
    +
    {viewModel?.quantity}
    +
    +
    + + + +
    +
    +
    Image
    +
    {viewModel?.image}
    +
    +
    + + + +
    +
    +
    Price
    +
    {viewModel?.price}
    +
    +
    + + + +
    +
    +
    Status
    +
    {viewModel?.status}
    +
    +
    + + + +
    +
    +
    Location
    +
    {viewModel?.location}
    +
    +
    + + + + ) + } +
    + ); + }; + + export default ViewItemsPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/View/AdminViewLocationTablePage.jsx b/day20/src/pages/Admin/View/AdminViewLocationTablePage.jsx new file mode 100644 index 0000000..0c7e5da --- /dev/null +++ b/day20/src/pages/Admin/View/AdminViewLocationTablePage.jsx @@ -0,0 +1,72 @@ + + import React from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate, useParams } from "react-router-dom"; + import { tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import { isImage, empty, isVideo } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; + import {SkeletonLoader} from "Components/Skeleton" + + let sdk = new MkdSDK(); + + const ViewLocationPage = () => { + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + const { dispatch } = React.useContext(GlobalContext); + const [viewModel, setViewModel] = React.useState({}); + const [loading, setLoading] = React.useState(true) + + + + const params = useParams(); + + React.useEffect(function () { + (async function () { + try { + setLoading(true) + sdk.setTable("location"); + const result = await sdk.callRestAPI({ id: Number(params?.id), join: "", }, "GET"); + if (!result.error) { + + setViewModel(result.model); + setLoading(false) + + } + } catch (error) { + setLoading(false) + + console.log("error", error); + tokenExpireError(dispatch, error.message); + } + })(); + }, []); + return ( +
    + {loading ? () :( + <> +

    View Location

    + + + +
    +
    +
    Name
    +
    {viewModel?.name}
    +
    +
    + + + + ) + } +
    + ); + }; + + export default ViewLocationPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/View/AdminViewUserTablePage.jsx b/day20/src/pages/Admin/View/AdminViewUserTablePage.jsx new file mode 100644 index 0000000..ed98ec3 --- /dev/null +++ b/day20/src/pages/Admin/View/AdminViewUserTablePage.jsx @@ -0,0 +1,207 @@ + + import React from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { useNavigate, useParams } from "react-router-dom"; + import { tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import { isImage, empty, isVideo } from "Utils/utils"; + import {MkdInput} from "Components/MkdInput"; + import {SkeletonLoader} from "Components/Skeleton" + + let sdk = new MkdSDK(); + + const ViewUserPage = () => { + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + const { dispatch } = React.useContext(GlobalContext); + const [viewModel, setViewModel] = React.useState({}); + const [loading, setLoading] = React.useState(true) + + + + const params = useParams(); + + React.useEffect(function () { + (async function () { + try { + setLoading(true) + sdk.setTable("user"); + const result = await sdk.callRestAPI({ id: Number(params?.id), join: "", }, "GET"); + if (!result.error) { + + setViewModel(result.model); + setLoading(false) + + } + } catch (error) { + setLoading(false) + + console.log("error", error); + tokenExpireError(dispatch, error.message); + } + })(); + }, []); + return ( +
    + {loading ? () :( + <> +

    View User

    + + + +
    +
    +
    Oauth
    +
    {viewModel?.oauth}
    +
    +
    + + + +
    +
    +
    Role
    +
    {viewModel?.role}
    +
    +
    + + + +
    +
    +
    First Name
    +
    {viewModel?.first_name}
    +
    +
    + + + +
    +
    +
    Last Name
    +
    {viewModel?.last_name}
    +
    +
    + + + +
    +
    +
    Email
    +
    {viewModel?.email}
    +
    +
    + + + +
    +
    +
    Password
    +
    {viewModel?.password}
    +
    +
    + + + +
    +
    +
    Type
    +
    {viewModel?.type}
    +
    +
    + + + +
    +
    +
    Verify
    +
    {viewModel?.verify}
    +
    +
    + + + +
    +
    +
    Phone
    +
    {viewModel?.phone}
    +
    +
    + + + +
    +
    +
    Photo
    +
    {viewModel?.photo}
    +
    +
    + + + +
    +
    +
    Refer
    +
    {viewModel?.refer}
    +
    +
    + + + +
    +
    +
    Stripe Uid
    +
    {viewModel?.stripe_uid}
    +
    +
    + + + +
    +
    +
    Paypal Uid
    +
    {viewModel?.paypal_uid}
    +
    +
    + + + +
    +
    +
    Two Factor Authentication
    +
    {viewModel?.two_factor_authentication}
    +
    +
    + + + +
    +
    +
    Status
    +
    {viewModel?.status}
    +
    +
    + + + +
    +
    +
    Department
    +
    {viewModel?.department}
    +
    +
    + + + + ) + } +
    + ); + }; + + export default ViewUserPage; + + \ No newline at end of file diff --git a/day20/src/pages/Admin/View/CustomAdminProfilePage.jsx b/day20/src/pages/Admin/View/CustomAdminProfilePage.jsx new file mode 100644 index 0000000..b9cff20 --- /dev/null +++ b/day20/src/pages/Admin/View/CustomAdminProfilePage.jsx @@ -0,0 +1,696 @@ + +import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { GlobalContext, showToast } from "Context/Global"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import { InteractiveButton } from "Components/InteractiveButton"; +import ModalPrompt from "Components/Modal/ModalPrompt"; +import { SkeletonLoader } from "Components/Skeleton"; +import { FaCloudUploadAlt, FaEye, FaEyeSlash } from "react-icons/fa"; + +let sdk = new MkdSDK(); +const AdminProfilePage = () => { + const schema = yup + .object({ + email: yup.string().email().required(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const [oldEmail, setOldEmail] = useState(""); + const [fileObj, setFileObj] = React.useState({}); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [oldPhoto, setOldPhoto] = useState(""); + const [isUploadedPhoto, setIsUploadedPhoto] = useState(false); + const [submitLoading, setSubmitLoading] = useState(false); + const [activeTab, setActiveTab] = useState("Profile"); + const [defaultValues, setDefaultValues] = useState({}); + const [loading, setLoading] = useState(true); + const [showPassword, setShowPassword] = useState(false); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const togglePasswordVisibility = () => { + setShowPassword(!showPassword); + }; + + const previewImage = (field, target, multiple = false) => { + setIsUploadedPhoto(true); + let tempFileObj = fileObj; + if (multiple) { + if (tempFileObj[field]) { + tempFileObj[field] = [ + ...tempFileObj[field], + { + file: target.files[0], + tempFile: { + url: URL.createObjectURL(target.files[0]), + name: target.files[0].name, + type: target.files[0].type, + }, + }, + ]; + } else { + tempFileObj[field] = [ + { + file: target.files[0], + tempFile: { + url: URL.createObjectURL(target.files[0]), + name: target.files[0].name, + type: target.files[0].type, + }, + }, + ]; + } + } else { + tempFileObj[field] = { + file: target.files[0], + tempURL: URL.createObjectURL(target.files[0]), + }; + } + setFileObj({ ...tempFileObj }); + }; + + async function fetchData() { + setLoading(true); + try { + const result = await sdk.getProfile(); + if (!result.error) { + setDefaultValues(result); + setValue("email", result?.email); + setValue("first_name", result?.first_name); + setValue("last_name", result?.last_name); + setOldEmail(result?.email); + setOldPhoto(result?.photo); + dispatch({ + type: "UPDATE_PROFILE", + payload: result, + }); + setLoading(false); + } + } catch (error) { + tokenExpireError( + dispatch, + error.response.data.message + ? error.response.data.message + : error.message + ); + } + } + + const onSubmit = async (data) => { + if ( + defaultValues.email === data.email && + defaultValues.first_name === data.first_name && + defaultValues.last_name === data.last_name && + !isUploadedPhoto && + !data.password + ) { + closeModal(); + return showToast(globalDispatch, "No Changes Available", 1000); + } + + setDefaultValues(data); + try { + setSubmitLoading(true); + if (fileObj && fileObj["photo"] && fileObj["photo"]?.file) { + let formData = new FormData(); + formData.append("file", fileObj["photo"]?.file); + let uploadResult = await sdk.uploadImage(formData); + data["photo"] = uploadResult.url; + showToast(globalDispatch, "Profile Photo Updated", 1000); + } + + const result = await sdk.updateProfile({ + first_name: data.first_name || defaultValues?.first_name, + last_name: data.last_name || defaultValues?.last_name, + photo: data.photo || oldPhoto, + }); + + if (!result.error) { + showToast(globalDispatch, "Profile Updated", 4000); + closeModal(); + fetchData(); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + closeModal(); + } + if (oldEmail !== data.email) { + const emailresult = await sdk.updateEmail(data.email); + if (!emailresult.error) { + showToast(globalDispatch, "Email Updated", 1000); + } else { + if (emailresult.validation) { + const keys = Object.keys(emailresult.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: emailresult.validation[field], + }); + } + } + } + closeModal(); + } + + if (data.password?.length > 0) { + const passwordresult = await sdk.updatePassword(data.password); + if (!passwordresult.error) { + showToast(globalDispatch, "Password Updated", 2000); + } else { + if (passwordresult.validation) { + const keys = Object.keys(passwordresult.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: passwordresult.validation[field], + }); + } + } + } + } + data.photo = ""; + await fetchData(); + setSubmitLoading(false); + } catch (error) { + setSubmitLoading(false); + setError("email", { + type: "manual", + message: error.response.data.message + ? error.response.data.message + : error.message, + }); + tokenExpireError( + dispatch, + error.response.data.message + ? error.response.data.message + : error.message + ); + } + }; + + const onDeleteProfile = async () => { + setFileObj({}); + setOldPhoto(""); + setIsUploadedPhoto(true); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "profile", + }, + }); + + fetchData(); + }, []); + + const openModalEdit = () => { + setIsEditModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + setIsEditModalOpen(false); + setOldPhoto(defaultValues?.photo); + setFileObj({}); + setIsUploadedPhoto(false); + }; + + return ( +
    +
    +
    +
    setActiveTab("Profile")} + > + Profile +
    +
    setActiveTab("Security")} + > + Security +
    +
    +
    +
    + {/* Profile Tab */} + {activeTab === "Profile" && ( +
    +
    +

    + {errors.photo?.message} +

    +
    +
    +

    + Personal Details +

    + {!loading ? ( +

    + Edit +

    + ) : ( +
    + +
    + )} +
    + +
    +
    +

    + Profile Picture +

    + + {loading ? ( +
    +
    + +
    +
    + ) : defaultValues?.photo ? ( +
    + +
    + ) : ( +
    +
    + No Image +
    +
    + )} +
    +
    + +
    +
    +

    + First Name +

    +

    + {defaultValues?.first_name} +

    +
    +
    + +
    +
    +

    + Last Name +

    +

    + {defaultValues?.last_name} +

    +
    +
    +
    +
    +

    Email

    +

    + {oldEmail} +

    +
    +
    +
    +
    +
    + )} + + {/* Security tab */} + {activeTab === "Security" && ( +
    +
    +
    +
    + +
    + +
    + {showPassword ? () : ()} +
    +
    +

    + {errors.password?.message} +

    +
    +
    + + Update + +
    +
    +
    +
    + )} + + {isModalOpen && ( + + )} + + {isEditModalOpen && ( + + )} +
    +
    + ); +}; + +export const EditInfoModal = (props) => { + const { + title, + isOpen, + onClose, + handleSubmit, + onSubmit, + register, + submitLoading, + errors, + oldPhoto, + fileObj, + onDeleteProfile, + previewImage, + oldEmail, + } = props; + const [emailConfirm, setEmailConfirm] = useState(false); + const [values, setValues] = useState({ + email: "", + }); + + React.useEffect(() => { + setValues({ ...values, email: oldEmail }); + setEmailConfirm(false); + }, []); + + const handleChange = (prop) => (event) => { + if (prop === "email") { + setValues({ ...values, [prop]: event.target.value }); + } + }; + + return ( +
    +
    +
    +
    +
    +
    + +
    +
    +
    + {title} +
    + +
    +
    +
    + + {oldPhoto ? ( +
    +
    + + +
    + + X + +
    +
    +
    + ) : ( +
    +
    + {fileObj["photo"]?.tempURL ? ( + + ) : ( + + )} +
    +
    + )} +
    + + +

    + {errors?.id?.message} +

    +
    +
    + + +

    + {errors?.id?.message} +

    +
    + {emailConfirm && oldEmail !== values.email ? ( +
    +
    + + + +
    +
    +

    + We've send an email to: {values?.email} +

    +

    +

    + In order to complete the email update click the + confirmation link. +

    +

    + (the link expires in 24 hours) +

    +
    +
    + ) : ( +
    + + +

    + {errors?.id?.message} +

    +
    + )} +
    + +
    + + + setEmailConfirm(true)} + > + Save + +
    +
    +
    +
    +
    +
    + ); +}; + +export default AdminProfilePage; + diff --git a/day20/src/pages/MagicLogin/MagicLoginVerifyPage.jsx b/day20/src/pages/MagicLogin/MagicLoginVerifyPage.jsx new file mode 100644 index 0000000..a03f7a7 --- /dev/null +++ b/day20/src/pages/MagicLogin/MagicLoginVerifyPage.jsx @@ -0,0 +1,61 @@ + +import React from "react"; +import MkdSDK from "Utils/MkdSDK"; +import { Link, useParams, useSearchParams } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; +import { AuthContext } from "Context/Auth"; +import { GlobalContext, showToast } from "Context/Global"; + +const MagicLoginVerifyPage = () => { + + + const { dispatch } = React.useContext(AuthContext); + const { dispatch:GlobalDispatch } = React.useContext(GlobalContext); + + const navigate = useNavigate(); + const [search, setSearch] = useSearchParams(); + + + const login = async () => { + let sdk = new MkdSDK(); + try { + let token = search.get('token') ?? null; + const result = await sdk.magicLoginVerify(token); + if (!result.error) { + dispatch({ + type: "LOGIN", + payload: result, + }); + navigate(`/${result.role}/dashboard`); + } else { + navigate('/user/login') + } + } catch (error) { + navigate('/user/login') + // console.log("Error", error); + // setError("email", { + // type: "manual", + // message: error.message, + // }); + } + }; + + React.useEffect(() => { + ( async () => { + await login(); + })() + }); + + return ( + <> +
    + + + + +
    + + ); +}; + +export default MagicLoginVerifyPage; diff --git a/day20/src/pages/MagicLogin/UserMagicLoginPage.jsx b/day20/src/pages/MagicLogin/UserMagicLoginPage.jsx new file mode 100644 index 0000000..a1acb31 --- /dev/null +++ b/day20/src/pages/MagicLogin/UserMagicLoginPage.jsx @@ -0,0 +1,107 @@ + +import React, {useContext} from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { Link, useParams } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; +import { AuthContext } from "Context/Auth"; +import { GlobalContext, showToast } from "Context/Global"; + +const UserMagicLoginPage = () => { + const schema = yup + .object({ + email: yup.string().email().required(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const { dispatch:GlobalDispatch } = React.useContext(GlobalContext); + + const [attemptingLogin, setAttemptingLogin] = React.useState(false) + + const params = useParams(); + + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = async (data) => { + let sdk = new MkdSDK(); + try { + setAttemptingLogin(true) + const result = await sdk.magicLoginAttempt(data.email, params?.role); + + if (!result.error) { + setAttemptingLogin(false) + console.log(result); + showToast(GlobalDispatch, 'Please check your mail to complete login attempt') + + // dispatch({ + // type: "LOGIN", + // payload: result, + // }); + // navigate("/user/dashboard"); + } + + + } catch (error) { + setAttemptingLogin(false) + console.log("Error", error); + setError("email", { + type: "manual", + message: error.message, + }); + } + }; + + return ( +
    +
    +
    + + +

    {errors.email?.message}

    +
    + + +
    + + +
    +
    +

    + © {new Date().getFullYear()} manaknightdigital inc. All rights + reserved. +

    +
    + ); +}; + +export default UserMagicLoginPage; diff --git a/day20/src/pages/Member/Auth/CustomMemberLoginPage.jsx b/day20/src/pages/Member/Auth/CustomMemberLoginPage.jsx new file mode 100644 index 0000000..5a4f76c --- /dev/null +++ b/day20/src/pages/Member/Auth/CustomMemberLoginPage.jsx @@ -0,0 +1,184 @@ + + import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { Link, useLocation } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; +import { InteractiveButton } from "Components/InteractiveButton"; +import { AuthContext } from "Context/Auth"; +import { GlobalContext, showToast } from "Context/Global"; +import { LoginBgNew } from "Assets/images"; + +let sdk = new MkdSDK(); + +const MemberLoginPage = () => { + const schema = yup + .object({ + email: yup.string().email().required(), + password: yup.string().required(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const { dispatch: GlobalDispatch } = React.useContext(GlobalContext); + + const [submitLoading, setSubmitLoading] = useState(false); + const [showPassword, setShowPassword] = useState(false); + const location = useLocation(); + const searchParams = new URLSearchParams(location.search); + const redirect_uri = searchParams.get("redirect_uri"); + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = async (data) => { + try { + setSubmitLoading(true); + const result = await sdk.login(data.email, data.password, "member"); + if (!result.error) { + + dispatch({ + type: "LOGIN", + payload: result, + }); + showToast(GlobalDispatch, "Succesfully Logged In", 4000, "success"); + navigate(redirect_uri ?? "/member/dashboard"); + + } else { + setSubmitLoading(false); + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + setSubmitLoading(false); + showToast( + GlobalDispatch, + error?.response?.data?.message + ? error?.response?.data?.message + : error?.message, + 4000, + "error" + ); + console.log("Error", error); + setError("email", { + type: "manual", + message: error?.response?.data?.message + ? error?.response?.data?.message + : error?.message, + }); + } + }; + + + + return ( +
    + +
    + +
    + + + + +
    Welcome Back
    +
    Don’t have account? Sign up here
    + +
    + + + + +
    + + + +
    +
    + + +

    + {errors?.email?.message} +

    +
    +
    + +
    + + setShowPassword(!showPassword)}> + { + showPassword ? ( + + + + ) : ( + + + + + ) + } + +
    +

    + {errors?.password?.message} +

    +
    +
    +
    + + Remember me +
    + Forgot password +
    + + Sign in + +
    + + + +
    + +
    +
    + ) +}; + +export default MemberLoginPage; + + \ No newline at end of file diff --git a/day20/src/pages/Member/Auth/CustomMemberSignUpPage.jsx b/day20/src/pages/Member/Auth/CustomMemberSignUpPage.jsx new file mode 100644 index 0000000..d332ac4 --- /dev/null +++ b/day20/src/pages/Member/Auth/CustomMemberSignUpPage.jsx @@ -0,0 +1,156 @@ + +import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { Link, useLocation } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; +import { InteractiveButton } from "Components/InteractiveButton"; +import { AuthContext } from "Context/Auth"; +import { GlobalContext, showToast } from "Context/Global"; +import { LoginBgNew } from "Assets/images"; + +let sdk = new MkdSDK(); + +const MemberSignUpPage = () => { + const schema = yup + .object({ + email: yup.string().email().required(), + password: yup.string().required(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const { dispatch: GlobalDispatch } = React.useContext(GlobalContext); + + const [submitLoading, setSubmitLoading] = useState(false) + const location = useLocation() + const searchParams = new URLSearchParams(location.search) + const redirect_uri = searchParams.get('redirect_uri') + + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = async (data) => { + try { + setSubmitLoading(true) + const result = await sdk.register(data.email, data.password, "member"); + if (!result.error) { + dispatch({ + type: "LOGIN", + payload: result, + }); + showToast(GlobalDispatch, "Succesfully Registered", 4000, "success") + navigate(redirect_uri ?? "/member/dashboard"); + + } else { + setSubmitLoading(false) + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + setSubmitLoading(false) + console.log("Error", error); + showToast(GlobalDispatch, error?.message, 4000, "error") + setError("email", { + type: "manual", + message: error?.response?.data?.message ? error?.response?.data?.message : error?.message, + }); + } + }; + + return ( +
    +
    +
    +
    +

    Register

    +
    + + + +

    {errors.email?.message}

    +
    + +
    + + + +

    {errors.password?.message}

    +
    + + {/* Forgot Password */} + + + Register + +
    + + {/*
    OR
    */} +
    +
    +

    Already have an account? Login

    +
    +
    + {/*
    */} +

    © {new Date().getFullYear()} manaknightdigital inc. All rights reserved.

    +
    +
    +
    +
    + + +
    + + ); +}; + +export default MemberSignUpPage; diff --git a/day20/src/pages/Member/Auth/MemberForgotPage.jsx b/day20/src/pages/Member/Auth/MemberForgotPage.jsx new file mode 100644 index 0000000..b906c68 --- /dev/null +++ b/day20/src/pages/Member/Auth/MemberForgotPage.jsx @@ -0,0 +1,115 @@ + + import React, {useState } from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import { Link } from "react-router-dom"; + import MkdSDK from "Utils/MkdSDK"; + import { tokenExpireError } from "Context/Auth"; + import { GlobalContext, showToast } from "Context/Global"; + import { InteractiveButton } from "Components/InteractiveButton"; + +const MemberForgotPage = () => { + + const [submitLoading, setSubmitLoading] = useState(false); + + const schema = yup + .object({ + email: yup.string().email().required(), + }) + .required(); + + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const { dispatch } = React.useContext(GlobalContext); + + const onSubmit = async (data) => { + let sdk = new MkdSDK(); + try { + setSubmitLoading(true) + const result = await sdk.forgot(data.email, member); + + if (!result.error) { + showToast(dispatch, "Reset Code Sent"); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setSubmitLoading(false) + } catch (error) { + setSubmitLoading(false) + console.log("Error", error); + setError("email", { + type: "manual", + message: error?.response?.data?.message?error?.response?.data?.message:error?.message, + }); + tokenExpireError(dispatch, error?.response?.data?.message?error?.response?.data?.message:error?.message); + } + } + + return ( + <> +
    +
    +
    + + +

    + {errors && errors.email?.message} +

    +
    + +
    + + Forgot Password + + + Login? + +
    +
    +

    + © {new Date().getFullYear()} manaknightdigital inc. All rights + reserved. +

    +
    + + ); +} + + export default MemberForgotPage; diff --git a/day20/src/pages/Member/Auth/MemberResetPage.jsx b/day20/src/pages/Member/Auth/MemberResetPage.jsx new file mode 100644 index 0000000..abd5144 --- /dev/null +++ b/day20/src/pages/Member/Auth/MemberResetPage.jsx @@ -0,0 +1,168 @@ + + import React, {useState} from "react"; + import { useForm } from "react-hook-form"; + import { yupResolver } from "@hookform/resolvers/yup"; + import * as yup from "yup"; + import MkdSDK from "Utils/MkdSDK"; + import { Link } from "react-router-dom"; + import { useNavigate } from "react-router-dom"; + import { AuthContext, tokenExpireError } from "Context/Auth"; + import { showToast } from "Context/Global"; + import { InteractiveButton } from "Components/InteractiveButton"; + + const MemberResetPage = () => { + const { dispatch } = React.useContext(AuthContext); + const [submitLoading, setSubmitLoading] = useState(false); + const search = window.location.search; + const params = new URLSearchParams(search); + const token = params.get("token"); + + const schema = yup + .object({ + code: yup.string().required(), + password: yup.string().required(), + confirmPassword: yup + .string() + .oneOf([yup.ref("password"), null], "Passwords must match"), + }) + .required(); + + const navigate = useNavigate(); + + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = async (data) => { + let sdk = new MkdSDK(); + try { + setSubmitLoading(true) + const result = await sdk.reset(token, data.code, data.password); + if (!result.error) { + showToast(dispatch, "Password Reset"); + setTimeout(() => { + navigate(`/member/login`); + }, 2000); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + setSubmitLoading(false) + } catch (error) { + setSubmitLoading(false) + console.log("Error", error); + setError("code", { + type: "manual", + message: error?.response?.data?.message?error?.response?.data?.message:error?.message, + }); + tokenExpireError(dispatch, error?.response?.data?.message?error?.response?.data?.message:error?.message); + } + }; + + return ( + <> +
    +
    +
    + + +

    + {errors.code?.message} +

    +
    +
    + + +

    + {errors.password?.message} +

    +
    +
    + + +

    + {errors.confirmPassword?.message} +

    +
    +
    + + Reset Password + + + Login? + +
    +
    +

    + © {new Date().getFullYear()} manaknightdigital inc. All rights + reserved. +

    +
    + + ); + }; + + export default MemberResetPage; + + + \ No newline at end of file diff --git a/day20/src/pages/Member/View/CustomMemberProfilePage.jsx b/day20/src/pages/Member/View/CustomMemberProfilePage.jsx new file mode 100644 index 0000000..c9ae972 --- /dev/null +++ b/day20/src/pages/Member/View/CustomMemberProfilePage.jsx @@ -0,0 +1,696 @@ + +import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { GlobalContext, showToast } from "Context/Global"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import { InteractiveButton } from "Components/InteractiveButton"; +import ModalPrompt from "Components/Modal/ModalPrompt"; +import { SkeletonLoader } from "Components/Skeleton"; +import { FaCloudUploadAlt, FaEye, FaEyeSlash } from "react-icons/fa"; + +let sdk = new MkdSDK(); +const MemberProfilePage = () => { + const schema = yup + .object({ + email: yup.string().email().required(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const [oldEmail, setOldEmail] = useState(""); + const [fileObj, setFileObj] = React.useState({}); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [oldPhoto, setOldPhoto] = useState(""); + const [isUploadedPhoto, setIsUploadedPhoto] = useState(false); + const [submitLoading, setSubmitLoading] = useState(false); + const [activeTab, setActiveTab] = useState("Profile"); + const [defaultValues, setDefaultValues] = useState({}); + const [loading, setLoading] = useState(true); + const [showPassword, setShowPassword] = useState(false); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const togglePasswordVisibility = () => { + setShowPassword(!showPassword); + }; + + const previewImage = (field, target, multiple = false) => { + setIsUploadedPhoto(true); + let tempFileObj = fileObj; + if (multiple) { + if (tempFileObj[field]) { + tempFileObj[field] = [ + ...tempFileObj[field], + { + file: target.files[0], + tempFile: { + url: URL.createObjectURL(target.files[0]), + name: target.files[0].name, + type: target.files[0].type, + }, + }, + ]; + } else { + tempFileObj[field] = [ + { + file: target.files[0], + tempFile: { + url: URL.createObjectURL(target.files[0]), + name: target.files[0].name, + type: target.files[0].type, + }, + }, + ]; + } + } else { + tempFileObj[field] = { + file: target.files[0], + tempURL: URL.createObjectURL(target.files[0]), + }; + } + setFileObj({ ...tempFileObj }); + }; + + async function fetchData() { + setLoading(true); + try { + const result = await sdk.getProfile(); + if (!result.error) { + setDefaultValues(result); + setValue("email", result?.email); + setValue("first_name", result?.first_name); + setValue("last_name", result?.last_name); + setOldEmail(result?.email); + setOldPhoto(result?.photo); + dispatch({ + type: "UPDATE_PROFILE", + payload: result, + }); + setLoading(false); + } + } catch (error) { + tokenExpireError( + dispatch, + error.response.data.message + ? error.response.data.message + : error.message + ); + } + } + + const onSubmit = async (data) => { + if ( + defaultValues.email === data.email && + defaultValues.first_name === data.first_name && + defaultValues.last_name === data.last_name && + !isUploadedPhoto && + !data.password + ) { + closeModal(); + return showToast(globalDispatch, "No Changes Available", 1000); + } + + setDefaultValues(data); + try { + setSubmitLoading(true); + if (fileObj && fileObj["photo"] && fileObj["photo"]?.file) { + let formData = new FormData(); + formData.append("file", fileObj["photo"]?.file); + let uploadResult = await sdk.uploadImage(formData); + data["photo"] = uploadResult.url; + showToast(globalDispatch, "Profile Photo Updated", 1000); + } + + const result = await sdk.updateProfile({ + first_name: data.first_name || defaultValues?.first_name, + last_name: data.last_name || defaultValues?.last_name, + photo: data.photo || oldPhoto, + }); + + if (!result.error) { + showToast(globalDispatch, "Profile Updated", 4000); + closeModal(); + fetchData(); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + closeModal(); + } + if (oldEmail !== data.email) { + const emailresult = await sdk.updateEmail(data.email); + if (!emailresult.error) { + showToast(globalDispatch, "Email Updated", 1000); + } else { + if (emailresult.validation) { + const keys = Object.keys(emailresult.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: emailresult.validation[field], + }); + } + } + } + closeModal(); + } + + if (data.password?.length > 0) { + const passwordresult = await sdk.updatePassword(data.password); + if (!passwordresult.error) { + showToast(globalDispatch, "Password Updated", 2000); + } else { + if (passwordresult.validation) { + const keys = Object.keys(passwordresult.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: passwordresult.validation[field], + }); + } + } + } + } + data.photo = ""; + await fetchData(); + setSubmitLoading(false); + } catch (error) { + setSubmitLoading(false); + setError("email", { + type: "manual", + message: error.response.data.message + ? error.response.data.message + : error.message, + }); + tokenExpireError( + dispatch, + error.response.data.message + ? error.response.data.message + : error.message + ); + } + }; + + const onDeleteProfile = async () => { + setFileObj({}); + setOldPhoto(""); + setIsUploadedPhoto(true); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "profile", + }, + }); + + fetchData(); + }, []); + + const openModalEdit = () => { + setIsEditModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + setIsEditModalOpen(false); + setOldPhoto(defaultValues?.photo); + setFileObj({}); + setIsUploadedPhoto(false); + }; + + return ( +
    +
    +
    +
    setActiveTab("Profile")} + > + Profile +
    +
    setActiveTab("Security")} + > + Security +
    +
    +
    +
    + {/* Profile Tab */} + {activeTab === "Profile" && ( +
    +
    +

    + {errors.photo?.message} +

    +
    +
    +

    + Personal Details +

    + {!loading ? ( +

    + Edit +

    + ) : ( +
    + +
    + )} +
    + +
    +
    +

    + Profile Picture +

    + + {loading ? ( +
    +
    + +
    +
    + ) : defaultValues?.photo ? ( +
    + +
    + ) : ( +
    +
    + No Image +
    +
    + )} +
    +
    + +
    +
    +

    + First Name +

    +

    + {defaultValues?.first_name} +

    +
    +
    + +
    +
    +

    + Last Name +

    +

    + {defaultValues?.last_name} +

    +
    +
    +
    +
    +

    Email

    +

    + {oldEmail} +

    +
    +
    +
    +
    +
    + )} + + {/* Security tab */} + {activeTab === "Security" && ( +
    +
    +
    +
    + +
    + +
    + {showPassword ? () : ()} +
    +
    +

    + {errors.password?.message} +

    +
    +
    + + Update + +
    +
    +
    +
    + )} + + {isModalOpen && ( + + )} + + {isEditModalOpen && ( + + )} +
    +
    + ); +}; + +export const EditInfoModal = (props) => { + const { + title, + isOpen, + onClose, + handleSubmit, + onSubmit, + register, + submitLoading, + errors, + oldPhoto, + fileObj, + onDeleteProfile, + previewImage, + oldEmail, + } = props; + const [emailConfirm, setEmailConfirm] = useState(false); + const [values, setValues] = useState({ + email: "", + }); + + React.useEffect(() => { + setValues({ ...values, email: oldEmail }); + setEmailConfirm(false); + }, []); + + const handleChange = (prop) => (event) => { + if (prop === "email") { + setValues({ ...values, [prop]: event.target.value }); + } + }; + + return ( +
    +
    +
    +
    +
    +
    + +
    +
    +
    + {title} +
    + +
    +
    +
    + + {oldPhoto ? ( +
    +
    + + +
    + + X + +
    +
    +
    + ) : ( +
    +
    + {fileObj["photo"]?.tempURL ? ( + + ) : ( + + )} +
    +
    + )} +
    + + +

    + {errors?.id?.message} +

    +
    +
    + + +

    + {errors?.id?.message} +

    +
    + {emailConfirm && oldEmail !== values.email ? ( +
    +
    + + + +
    +
    +

    + We've send an email to: {values?.email} +

    +

    +

    + In order to complete the email update click the + confirmation link. +

    +

    + (the link expires in 24 hours) +

    +
    +
    + ) : ( +
    + + +

    + {errors?.id?.message} +

    +
    + )} +
    + +
    + + + setEmailConfirm(true)} + > + Save + +
    +
    +
    +
    +
    +
    + ); +}; + +export default MemberProfilePage; + diff --git a/day20/src/pages/Member/View/MemberDashboardPage.jsx b/day20/src/pages/Member/View/MemberDashboardPage.jsx new file mode 100644 index 0000000..f77e2a6 --- /dev/null +++ b/day20/src/pages/Member/View/MemberDashboardPage.jsx @@ -0,0 +1,28 @@ + + import React from "react"; + import { AuthContext } from "Context/Auth"; + import { GlobalContext } from "Context/Global"; + + const MemberDashboardPage = () => { + const { dispatch } = React.useContext(AuthContext); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "member", + }, + }); + }, []); + return ( + <> +
    + Dashboard +
    + + ); + }; + + export default MemberDashboardPage; + \ No newline at end of file diff --git a/day20/src/pages/Public/Auth/CustomPublicLoginPage.jsx b/day20/src/pages/Public/Auth/CustomPublicLoginPage.jsx new file mode 100644 index 0000000..4623b37 --- /dev/null +++ b/day20/src/pages/Public/Auth/CustomPublicLoginPage.jsx @@ -0,0 +1,184 @@ + + import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { Link, useLocation } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; +import { InteractiveButton } from "Components/InteractiveButton"; +import { AuthContext } from "Context/Auth"; +import { GlobalContext, showToast } from "Context/Global"; +import { LoginBgNew } from "Assets/images"; + +let sdk = new MkdSDK(); + +const PublicLoginPage = () => { + const schema = yup + .object({ + email: yup.string().email().required(), + password: yup.string().required(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const { dispatch: GlobalDispatch } = React.useContext(GlobalContext); + + const [submitLoading, setSubmitLoading] = useState(false); + const [showPassword, setShowPassword] = useState(false); + const location = useLocation(); + const searchParams = new URLSearchParams(location.search); + const redirect_uri = searchParams.get("redirect_uri"); + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = async (data) => { + try { + setSubmitLoading(true); + const result = await sdk.login(data.email, data.password, "public"); + if (!result.error) { + + dispatch({ + type: "LOGIN", + payload: result, + }); + showToast(GlobalDispatch, "Succesfully Logged In", 4000, "success"); + navigate(redirect_uri ?? "/public/dashboard"); + + } else { + setSubmitLoading(false); + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + setSubmitLoading(false); + showToast( + GlobalDispatch, + error?.response?.data?.message + ? error?.response?.data?.message + : error?.message, + 4000, + "error" + ); + console.log("Error", error); + setError("email", { + type: "manual", + message: error?.response?.data?.message + ? error?.response?.data?.message + : error?.message, + }); + } + }; + + + + return ( +
    + +
    + +
    + + + + +
    Welcome Back
    +
    Don’t have account? Sign up here
    + +
    + + + + +
    + + + +
    +
    + + +

    + {errors?.email?.message} +

    +
    +
    + +
    + + setShowPassword(!showPassword)}> + { + showPassword ? ( + + + + ) : ( + + + + + ) + } + +
    +

    + {errors?.password?.message} +

    +
    +
    +
    + + Remember me +
    + Forgot password +
    + + Sign in + +
    + + + +
    + +
    +
    + ) +}; + +export default PublicLoginPage; + + \ No newline at end of file diff --git a/day20/src/pages/Public/Auth/CustomPublicSignUpPage.jsx b/day20/src/pages/Public/Auth/CustomPublicSignUpPage.jsx new file mode 100644 index 0000000..ac84751 --- /dev/null +++ b/day20/src/pages/Public/Auth/CustomPublicSignUpPage.jsx @@ -0,0 +1,156 @@ + +import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { Link, useLocation } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; +import { InteractiveButton } from "Components/InteractiveButton"; +import { AuthContext } from "Context/Auth"; +import { GlobalContext, showToast } from "Context/Global"; +import { LoginBgNew } from "Assets/images"; + +let sdk = new MkdSDK(); + +const PublicSignUpPage = () => { + const schema = yup + .object({ + email: yup.string().email().required(), + password: yup.string().required(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const { dispatch: GlobalDispatch } = React.useContext(GlobalContext); + + const [submitLoading, setSubmitLoading] = useState(false) + const location = useLocation() + const searchParams = new URLSearchParams(location.search) + const redirect_uri = searchParams.get('redirect_uri') + + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = async (data) => { + try { + setSubmitLoading(true) + const result = await sdk.register(data.email, data.password, "public"); + if (!result.error) { + dispatch({ + type: "LOGIN", + payload: result, + }); + showToast(GlobalDispatch, "Succesfully Registered", 4000, "success") + navigate(redirect_uri ?? "/public/dashboard"); + + } else { + setSubmitLoading(false) + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + } + } catch (error) { + setSubmitLoading(false) + console.log("Error", error); + showToast(GlobalDispatch, error?.message, 4000, "error") + setError("email", { + type: "manual", + message: error?.response?.data?.message ? error?.response?.data?.message : error?.message, + }); + } + }; + + return ( +
    +
    +
    +
    +

    Register

    +
    + + + +

    {errors.email?.message}

    +
    + +
    + + + +

    {errors.password?.message}

    +
    + + {/* Forgot Password */} + + + Register + +
    + + {/*
    OR
    */} +
    +
    +

    Already have an account? Login

    +
    +
    + {/*
    */} +

    © {new Date().getFullYear()} manaknightdigital inc. All rights reserved.

    +
    +
    +
    +
    + + +
    + + ); +}; + +export default PublicSignUpPage; diff --git a/day20/src/pages/Public/View/CustomPublicProfilePage.jsx b/day20/src/pages/Public/View/CustomPublicProfilePage.jsx new file mode 100644 index 0000000..e66a0a3 --- /dev/null +++ b/day20/src/pages/Public/View/CustomPublicProfilePage.jsx @@ -0,0 +1,696 @@ + +import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { GlobalContext, showToast } from "Context/Global"; +import { AuthContext, tokenExpireError } from "Context/Auth"; +import { InteractiveButton } from "Components/InteractiveButton"; +import ModalPrompt from "Components/Modal/ModalPrompt"; +import { SkeletonLoader } from "Components/Skeleton"; +import { FaCloudUploadAlt, FaEye, FaEyeSlash } from "react-icons/fa"; + +let sdk = new MkdSDK(); +const PublicProfilePage = () => { + const schema = yup + .object({ + email: yup.string().email().required(), + }) + .required(); + + const { dispatch } = React.useContext(AuthContext); + const [oldEmail, setOldEmail] = useState(""); + const [fileObj, setFileObj] = React.useState({}); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [oldPhoto, setOldPhoto] = useState(""); + const [isUploadedPhoto, setIsUploadedPhoto] = useState(false); + const [submitLoading, setSubmitLoading] = useState(false); + const [activeTab, setActiveTab] = useState("Profile"); + const [defaultValues, setDefaultValues] = useState({}); + const [loading, setLoading] = useState(true); + const [showPassword, setShowPassword] = useState(false); + const { dispatch: globalDispatch } = React.useContext(GlobalContext); + const { + register, + handleSubmit, + setError, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const togglePasswordVisibility = () => { + setShowPassword(!showPassword); + }; + + const previewImage = (field, target, multiple = false) => { + setIsUploadedPhoto(true); + let tempFileObj = fileObj; + if (multiple) { + if (tempFileObj[field]) { + tempFileObj[field] = [ + ...tempFileObj[field], + { + file: target.files[0], + tempFile: { + url: URL.createObjectURL(target.files[0]), + name: target.files[0].name, + type: target.files[0].type, + }, + }, + ]; + } else { + tempFileObj[field] = [ + { + file: target.files[0], + tempFile: { + url: URL.createObjectURL(target.files[0]), + name: target.files[0].name, + type: target.files[0].type, + }, + }, + ]; + } + } else { + tempFileObj[field] = { + file: target.files[0], + tempURL: URL.createObjectURL(target.files[0]), + }; + } + setFileObj({ ...tempFileObj }); + }; + + async function fetchData() { + setLoading(true); + try { + const result = await sdk.getProfile(); + if (!result.error) { + setDefaultValues(result); + setValue("email", result?.email); + setValue("first_name", result?.first_name); + setValue("last_name", result?.last_name); + setOldEmail(result?.email); + setOldPhoto(result?.photo); + dispatch({ + type: "UPDATE_PROFILE", + payload: result, + }); + setLoading(false); + } + } catch (error) { + tokenExpireError( + dispatch, + error.response.data.message + ? error.response.data.message + : error.message + ); + } + } + + const onSubmit = async (data) => { + if ( + defaultValues.email === data.email && + defaultValues.first_name === data.first_name && + defaultValues.last_name === data.last_name && + !isUploadedPhoto && + !data.password + ) { + closeModal(); + return showToast(globalDispatch, "No Changes Available", 1000); + } + + setDefaultValues(data); + try { + setSubmitLoading(true); + if (fileObj && fileObj["photo"] && fileObj["photo"]?.file) { + let formData = new FormData(); + formData.append("file", fileObj["photo"]?.file); + let uploadResult = await sdk.uploadImage(formData); + data["photo"] = uploadResult.url; + showToast(globalDispatch, "Profile Photo Updated", 1000); + } + + const result = await sdk.updateProfile({ + first_name: data.first_name || defaultValues?.first_name, + last_name: data.last_name || defaultValues?.last_name, + photo: data.photo || oldPhoto, + }); + + if (!result.error) { + showToast(globalDispatch, "Profile Updated", 4000); + closeModal(); + fetchData(); + } else { + if (result.validation) { + const keys = Object.keys(result.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: result.validation[field], + }); + } + } + closeModal(); + } + if (oldEmail !== data.email) { + const emailresult = await sdk.updateEmail(data.email); + if (!emailresult.error) { + showToast(globalDispatch, "Email Updated", 1000); + } else { + if (emailresult.validation) { + const keys = Object.keys(emailresult.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: emailresult.validation[field], + }); + } + } + } + closeModal(); + } + + if (data.password?.length > 0) { + const passwordresult = await sdk.updatePassword(data.password); + if (!passwordresult.error) { + showToast(globalDispatch, "Password Updated", 2000); + } else { + if (passwordresult.validation) { + const keys = Object.keys(passwordresult.validation); + for (let i = 0; i < keys.length; i++) { + const field = keys[i]; + setError(field, { + type: "manual", + message: passwordresult.validation[field], + }); + } + } + } + } + data.photo = ""; + await fetchData(); + setSubmitLoading(false); + } catch (error) { + setSubmitLoading(false); + setError("email", { + type: "manual", + message: error.response.data.message + ? error.response.data.message + : error.message, + }); + tokenExpireError( + dispatch, + error.response.data.message + ? error.response.data.message + : error.message + ); + } + }; + + const onDeleteProfile = async () => { + setFileObj({}); + setOldPhoto(""); + setIsUploadedPhoto(true); + }; + + React.useEffect(() => { + globalDispatch({ + type: "SETPATH", + payload: { + path: "profile", + }, + }); + + fetchData(); + }, []); + + const openModalEdit = () => { + setIsEditModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + setIsEditModalOpen(false); + setOldPhoto(defaultValues?.photo); + setFileObj({}); + setIsUploadedPhoto(false); + }; + + return ( +
    +
    +
    +
    setActiveTab("Profile")} + > + Profile +
    +
    setActiveTab("Security")} + > + Security +
    +
    +
    +
    + {/* Profile Tab */} + {activeTab === "Profile" && ( +
    +
    +

    + {errors.photo?.message} +

    +
    +
    +

    + Personal Details +

    + {!loading ? ( +

    + Edit +

    + ) : ( +
    + +
    + )} +
    + +
    +
    +

    + Profile Picture +

    + + {loading ? ( +
    +
    + +
    +
    + ) : defaultValues?.photo ? ( +
    + +
    + ) : ( +
    +
    + No Image +
    +
    + )} +
    +
    + +
    +
    +

    + First Name +

    +

    + {defaultValues?.first_name} +

    +
    +
    + +
    +
    +

    + Last Name +

    +

    + {defaultValues?.last_name} +

    +
    +
    +
    +
    +

    Email

    +

    + {oldEmail} +

    +
    +
    +
    +
    +
    + )} + + {/* Security tab */} + {activeTab === "Security" && ( +
    +
    +
    +
    + +
    + +
    + {showPassword ? () : ()} +
    +
    +

    + {errors.password?.message} +

    +
    +
    + + Update + +
    +
    +
    +
    + )} + + {isModalOpen && ( + + )} + + {isEditModalOpen && ( + + )} +
    +
    + ); +}; + +export const EditInfoModal = (props) => { + const { + title, + isOpen, + onClose, + handleSubmit, + onSubmit, + register, + submitLoading, + errors, + oldPhoto, + fileObj, + onDeleteProfile, + previewImage, + oldEmail, + } = props; + const [emailConfirm, setEmailConfirm] = useState(false); + const [values, setValues] = useState({ + email: "", + }); + + React.useEffect(() => { + setValues({ ...values, email: oldEmail }); + setEmailConfirm(false); + }, []); + + const handleChange = (prop) => (event) => { + if (prop === "email") { + setValues({ ...values, [prop]: event.target.value }); + } + }; + + return ( +
    +
    +
    +
    +
    +
    + +
    +
    +
    + {title} +
    + +
    +
    +
    + + {oldPhoto ? ( +
    +
    + + +
    + + X + +
    +
    +
    + ) : ( +
    +
    + {fileObj["photo"]?.tempURL ? ( + + ) : ( + + )} +
    +
    + )} +
    + + +

    + {errors?.id?.message} +

    +
    +
    + + +

    + {errors?.id?.message} +

    +
    + {emailConfirm && oldEmail !== values.email ? ( +
    +
    + + + +
    +
    +

    + We've send an email to: {values?.email} +

    +

    +

    + In order to complete the email update click the + confirmation link. +

    +

    + (the link expires in 24 hours) +

    +
    +
    + ) : ( +
    + + +

    + {errors?.id?.message} +

    +
    + )} +
    + +
    + + + setEmailConfirm(true)} + > + Save + +
    +
    +
    +
    +
    +
    + ); +}; + +export default PublicProfilePage; + diff --git a/day20/src/routes/AdminRoutes.jsx b/day20/src/routes/AdminRoutes.jsx new file mode 100644 index 0000000..bd00ec3 --- /dev/null +++ b/day20/src/routes/AdminRoutes.jsx @@ -0,0 +1,32 @@ + +import React, { memo, useContext } from "react"; +import { Navigate } from "react-router"; +import { AuthContext } from "Context/Auth"; +import metadataJSON from "Utils/metadata.json"; + +const AdminRoute = ({ path, children }) => { + const Auth = useContext(AuthContext); + + const { isAuthenticated, role } = Auth?.state; + React.useEffect(() => { + const metadata = metadataJSON[path ?? "/"]; + if (metadata !== undefined) { + document.title = metadata?.title?metadata?.title:"inventorylynx"; + } else { + document.title = "inventorylynx"; + } + }, [path]); + + return ( + <> + {isAuthenticated ? ( + <>{children} + ) : ( + + )} + + ); +}; + + +export default memo(AdminRoute); diff --git a/day20/src/routes/LazyLoad.js b/day20/src/routes/LazyLoad.js new file mode 100644 index 0000000..5eb6cb9 --- /dev/null +++ b/day20/src/routes/LazyLoad.js @@ -0,0 +1,382 @@ + + import {lazy} from 'react' + + + export const AddAdminCmsPage = lazy(() => { + const __import = import("../pages/Admin/Add/AddAdminCmsPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AddAdminEmailPage = lazy(() => { + const __import = import("../pages/Admin/Add/AddAdminEmailPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AddAdminPhotoPage = lazy(() => { + const __import = import("../pages/Admin/Add/AddAdminPhotoPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminCmsListPage = lazy(() => { + const __import = import("../pages/Admin/List/AdminCmsListPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminEmailListPage = lazy(() => { + const __import = import("../pages/Admin/List/AdminEmailListPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminStripePricesListPage = lazy(() => { + const __import = import("../pages/Admin/List/AdminStripePricesListPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminStripeSubscriptionsListPage = lazy(() => { + const __import = import("../pages/Admin/List/AdminStripeSubscriptionsListPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminStripeInvoicesListPageV2 = lazy(() => { + const __import = import("../pages/Admin/List/AdminStripeInvoicesListPageV2"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminPhotoListPage = lazy(() => { + const __import = import("../pages/Admin/List/AdminPhotoListPage"); + __import.finally(() => {}); + return __import; + }); + + + export const EditAdminCmsPage = lazy(() => { + const __import = import("../pages/Admin/Edit/EditAdminCmsPage"); + __import.finally(() => {}); + return __import; + }); + + + export const EditAdminEmailPage = lazy(() => { + const __import = import("../pages/Admin/Edit/EditAdminEmailPage"); + __import.finally(() => {}); + return __import; + }); + + + export const UserMagicLoginPage = lazy(() => { + const __import = import("../pages/MagicLogin/UserMagicLoginPage"); + __import.finally(() => {}); + return __import; + }); + + + export const MagicLoginVerifyPage = lazy(() => { + const __import = import("../pages/MagicLogin/MagicLoginVerifyPage"); + __import.finally(() => {}); + return __import; + }); + + + export const CustomAdminLoginPage = lazy(() => { + const __import = import("../pages/Admin/Auth/CustomAdminLoginPage"); + __import.finally(() => {}); + return __import; + }); + + + export const CustomAdminSignUpPage = lazy(() => { + const __import = import("../pages/Admin/Auth/CustomAdminSignUpPage"); + __import.finally(() => {}); + return __import; + }); + + + export const CustomAdminProfilePage = lazy(() => { + const __import = import("../pages/Admin/View/CustomAdminProfilePage"); + __import.finally(() => {}); + return __import; + }); + + + export const CustomPublicLoginPage = lazy(() => { + const __import = import("../pages/Public/Auth/CustomPublicLoginPage"); + __import.finally(() => {}); + return __import; + }); + + + export const CustomPublicSignUpPage = lazy(() => { + const __import = import("../pages/Public/Auth/CustomPublicSignUpPage"); + __import.finally(() => {}); + return __import; + }); + + + export const CustomPublicProfilePage = lazy(() => { + const __import = import("../pages/Public/View/CustomPublicProfilePage"); + __import.finally(() => {}); + return __import; + }); + + + export const CustomMemberLoginPage = lazy(() => { + const __import = import("../pages/Member/Auth/CustomMemberLoginPage"); + __import.finally(() => {}); + return __import; + }); + + + export const CustomMemberSignUpPage = lazy(() => { + const __import = import("../pages/Member/Auth/CustomMemberSignUpPage"); + __import.finally(() => {}); + return __import; + }); + + + export const CustomMemberProfilePage = lazy(() => { + const __import = import("../pages/Member/View/CustomMemberProfilePage"); + __import.finally(() => {}); + return __import; + }); + + + export const CustomAdminAdminDashboardPage = lazy(() => { + const __import = import("../pages/Admin/Custom/CustomAdminAdminDashboardPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminListDepartmentTablePage = lazy(() => { + const __import = import("../pages/Admin/List/AdminListDepartmentTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminAddDepartmentTablePage = lazy(() => { + const __import = import("../pages/Admin/Add/AdminAddDepartmentTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminEditDepartmentTablePage = lazy(() => { + const __import = import("../pages/Admin/Edit/AdminEditDepartmentTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminViewDepartmentTablePage = lazy(() => { + const __import = import("../pages/Admin/View/AdminViewDepartmentTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminListLocationTablePage = lazy(() => { + const __import = import("../pages/Admin/List/AdminListLocationTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminAddLocationTablePage = lazy(() => { + const __import = import("../pages/Admin/Add/AdminAddLocationTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminEditLocationTablePage = lazy(() => { + const __import = import("../pages/Admin/Edit/AdminEditLocationTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminViewLocationTablePage = lazy(() => { + const __import = import("../pages/Admin/View/AdminViewLocationTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminListItemsTablePage = lazy(() => { + const __import = import("../pages/Admin/List/AdminListItemsTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminAddItemsTablePage = lazy(() => { + const __import = import("../pages/Admin/Add/AdminAddItemsTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminEditItemsTablePage = lazy(() => { + const __import = import("../pages/Admin/Edit/AdminEditItemsTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminViewItemsTablePage = lazy(() => { + const __import = import("../pages/Admin/View/AdminViewItemsTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminListItemMovementHistoryTablePage = lazy(() => { + const __import = import("../pages/Admin/List/AdminListItemMovementHistoryTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminAddItemMovementHistoryTablePage = lazy(() => { + const __import = import("../pages/Admin/Add/AdminAddItemMovementHistoryTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminEditItemMovementHistoryTablePage = lazy(() => { + const __import = import("../pages/Admin/Edit/AdminEditItemMovementHistoryTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminViewItemMovementHistoryTablePage = lazy(() => { + const __import = import("../pages/Admin/View/AdminViewItemMovementHistoryTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminListUserTablePage = lazy(() => { + const __import = import("../pages/Admin/List/AdminListUserTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminAddUserTablePage = lazy(() => { + const __import = import("../pages/Admin/Add/AdminAddUserTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminEditUserTablePage = lazy(() => { + const __import = import("../pages/Admin/Edit/AdminEditUserTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminViewUserTablePage = lazy(() => { + const __import = import("../pages/Admin/View/AdminViewUserTablePage"); + __import.finally(() => {}); + return __import; + }); + + + export const MemberForgotPage = lazy(() => { + const __import = import("../pages/Member/Auth/MemberForgotPage"); + __import.finally(() => {}); + return __import; + }); + + + export const MemberResetPage = lazy(() => { + const __import = import("../pages/Member/Auth/MemberResetPage"); + __import.finally(() => {}); + return __import; + }); + + + export const MemberDashboardPage = lazy(() => { + const __import = import("../pages/Member/View/MemberDashboardPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminForgotPage = lazy(() => { + const __import = import("../pages/Admin/Auth/AdminForgotPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminResetPage = lazy(() => { + const __import = import("../pages/Admin/Auth/AdminResetPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminDashboardPage = lazy(() => { + const __import = import("../pages/Admin/View/AdminDashboardPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AdminUserListPage = lazy(() => { + const __import = import("../pages/Admin/List/AdminUserListPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AddAdminUserPage = lazy(() => { + const __import = import("../pages/Admin/Add/AddAdminUserPage"); + __import.finally(() => {}); + return __import; + }); + + + export const AddAdminStripePricePage = lazy(() => { + const __import = import("../pages/Admin/Add/AddAdminStripePricePage"); + __import.finally(() => {}); + return __import; + }); + + + export const EditAdminUserPage = lazy(() => { + const __import = import("../pages/Admin/Edit/EditAdminUserPage"); + __import.finally(() => {}); + return __import; + }); + + + export const EditAdminStripePricePage = lazy(() => { + const __import = import("../pages/Admin/Edit/EditAdminStripePricePage"); + __import.finally(() => {}); + return __import; + }); + + \ No newline at end of file diff --git a/day20/src/routes/MemberRoutes.jsx b/day20/src/routes/MemberRoutes.jsx new file mode 100644 index 0000000..3e7d587 --- /dev/null +++ b/day20/src/routes/MemberRoutes.jsx @@ -0,0 +1,32 @@ + +import React, { memo, useContext } from "react"; +import { Navigate } from "react-router"; +import { AuthContext } from "Context/Auth"; +import metadataJSON from "Utils/metadata.json"; + +const MemberRoute = ({ path, children }) => { + const Auth = useContext(AuthContext); + + const { isAuthenticated, role } = Auth?.state; + React.useEffect(() => { + const metadata = metadataJSON[path ?? "/"]; + if (metadata !== undefined) { + document.title = metadata?.title?metadata?.title:"inventorylynx"; + } else { + document.title = "inventorylynx"; + } + }, [path]); + + return ( + <> + {isAuthenticated ? ( + <>{children} + ) : ( + + )} + + ); +}; + + +export default memo(MemberRoute); diff --git a/day20/src/routes/PrivateRoutes.jsx b/day20/src/routes/PrivateRoutes.jsx new file mode 100644 index 0000000..708930d --- /dev/null +++ b/day20/src/routes/PrivateRoutes.jsx @@ -0,0 +1,30 @@ + +import React, { memo, useContext } from "react"; +import { Navigate } from "react-router-dom"; +import { AuthContext } from "Context/Auth"; +import { NotFound } from "./Routes"; +import PublicRoute from "./PublicRoutes"; +import MemberRoute from "./MemberRoutes"; +import AdminRoute from "./AdminRoutes"; + + +const PrivateRoute = ({ path, element, access }) => { + const Auth = useContext(AuthContext); + + if (Auth?.state?.isAuthenticated) { + switch (true) { + case Auth?.state?.role === "member" && access === "member": + return {element} +case Auth?.state?.role === "admin" && access === "admin": + return {element} + + default: + return } />; + } + } + if (!Auth?.state?.isAuthenticated) { + return } />; + } +}; + +export default memo(PrivateRoute); diff --git a/day20/src/routes/PublicRoutes.jsx b/day20/src/routes/PublicRoutes.jsx new file mode 100644 index 0000000..3659c41 --- /dev/null +++ b/day20/src/routes/PublicRoutes.jsx @@ -0,0 +1,22 @@ + + import React, { memo } from "react"; + import metadataJSON from "Utils/metadata.json"; + +function PublicRoute({ element, path }) { + React.useEffect(() => { + const metadata = metadataJSON[path ?? "/"]; + if (!(metadata == null)) { + document.title = metadata.title; + } else { + document.title = "inventorylynx"; + } + }, [path]); + + return ( + <> + {element} + + ); +} + +export default memo(PublicRoute); diff --git a/day20/src/routes/Routes.jsx b/day20/src/routes/Routes.jsx new file mode 100644 index 0000000..b36060e --- /dev/null +++ b/day20/src/routes/Routes.jsx @@ -0,0 +1,967 @@ + +import React, { useEffect, useContext, useState } from "react"; +import { Routes, Route } from "react-router-dom"; +import { AuthContext } from "Context/Auth"; +import { GlobalContext } from "Context/Global"; + +import PrivateRoute from "./PrivateRoutes"; +import PublicRoute from "./PublicRoutes"; +import {PublicWrapper} from "Components/PublicWrapper"; +import { NotFoundPage } from "Pages/404"; +import {SnackBar} from "Components/SnackBar"; +import { SessionExpiredModal } from "Components/SessionExpiredModal"; + +// generatePagesRoutes +import { MemberWrapper } from "Components/MemberWrapper"; +import { AdminWrapper } from "Components/AdminWrapper"; + + +import { +AddAdminCmsPage, +AddAdminEmailPage, +AddAdminPhotoPage, +AdminCmsListPage, +AdminEmailListPage, +AdminStripePricesListPage, +AdminStripeSubscriptionsListPage, +AdminStripeInvoicesListPageV2, +AdminPhotoListPage, +EditAdminCmsPage, +EditAdminEmailPage, +UserMagicLoginPage, +MagicLoginVerifyPage, +CustomAdminLoginPage, +CustomAdminSignUpPage, +CustomAdminProfilePage, +CustomPublicLoginPage, +CustomPublicSignUpPage, +CustomPublicProfilePage, +CustomMemberLoginPage, +CustomMemberSignUpPage, +CustomMemberProfilePage, +CustomAdminAdminDashboardPage, +AdminListDepartmentTablePage, +AdminAddDepartmentTablePage, +AdminEditDepartmentTablePage, +AdminViewDepartmentTablePage, +AdminListLocationTablePage, +AdminAddLocationTablePage, +AdminEditLocationTablePage, +AdminViewLocationTablePage, +AdminListItemsTablePage, +AdminAddItemsTablePage, +AdminEditItemsTablePage, +AdminViewItemsTablePage, +AdminListItemMovementHistoryTablePage, +AdminAddItemMovementHistoryTablePage, +AdminEditItemMovementHistoryTablePage, +AdminViewItemMovementHistoryTablePage, +AdminListUserTablePage, +AdminAddUserTablePage, +AdminEditUserTablePage, +AdminViewUserTablePage, +MemberForgotPage, +MemberResetPage, +MemberDashboardPage, +AdminForgotPage, +AdminResetPage, +AdminDashboardPage, +AdminUserListPage, +AddAdminUserPage, +AddAdminStripePricePage, +EditAdminUserPage, +EditAdminStripePricePage +} from "./LazyLoad"; + +export const DynamicWrapper = ({ isAuthenticated, role, children }) => { + if (!isAuthenticated) { + return {children}; + } + if (isAuthenticated) { + +if (role === "member") { + return {children}; + } + +if (role === "admin") { + return {children}; + } + + } +}; + +export const NotFound = ({ isAuthenticated, role }) => { + if (!isAuthenticated) { + return ( + + + + ); + } + if (isAuthenticated) { + +if (role === "member") { + return ( + + + + ); +} + +if (role === "admin") { + return ( + + + + ); +} + + } +}; + + +export default () => { + const { state } = useContext(AuthContext); + const { + state: { isOpen }, + dispatch, + } = useContext(GlobalContext); + const [screenSize, setScreenSize] = useState(window.innerWidth); + + function setDimension(e) { + if (e.currentTarget.innerWidth >= 1024) { + toggleSideBar(true); + } else toggleSideBar(false); + setScreenSize(e.currentTarget.innerWidth); + } + + // const toTop = () => { + // containerRef.current.scrollTo(0, 0); + // }; + + const toggleSideBar = (open) => { + if (isOpen && screenSize < 1024) { + dispatch({ + type: "OPEN_SIDEBAR", + payload: { isOpen: open }, + }); + } else if (!isOpen && screenSize >= 1024) { + dispatch({ + type: "OPEN_SIDEBAR", + payload: { isOpen: open }, + }); + } + }; + + useEffect(() => { + window.addEventListener("resize", setDimension); + + return () => { + window.removeEventListener("resize", setDimension); + }; + }, [screenSize]); + + return ( +
    { + isOpen ? toggleSideBar(false) : null; + }} + className={`h-screen overflow-x-hidden bg-gradient-to-br from-[#FCF3F9] to-[#EAF8FB]`} + > + + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + + } + /> + + + + + + } + /> + + } + /> + + + + + + } + /> + + } + /> + + + + + + } + /> + + } + /> + + + + + + } + /> + } + /> + + + + + } + /> + + } + /> + + + + + + } + /> + + } + /> + + + + + + } + /> + + } + /> + + + + + + } + /> + + } + /> + + + + + + } + /> + + } + /> + + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + + } + /> + + + + + + } + /> + + } + /> + + + + + + } + /> + } + /> + + + + + } + /> + + } + /> + + + + + + } + /> + + } + /> + + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + } />} + /> + + + +
    + ); +}; diff --git a/day20/src/utils/EcomSDK.jsx b/day20/src/utils/EcomSDK.jsx new file mode 100644 index 0000000..f339c03 --- /dev/null +++ b/day20/src/utils/EcomSDK.jsx @@ -0,0 +1,529 @@ + + +export default function EcomSDK() { + + this._baseurl = "https://inventorylynx.mkdlabs.com"; + this._project_id = "inventorylynx"; + this._secret = "1qrhpud67j4jpda5zrdm7oi5ugjo8h3c9"; + this._table = ""; + + const raw = this._project_id + ":" + this._secret; + let base64Encode = btoa(raw); + + this.getHeader = function () { + return { + Authorization: "Bearer " + localStorage.getItem("token"), + "x-project": base64Encode, + "Content-Type": "application/json", + }; + }; + + this.baseUrl = function () { + return this._baseurl; + }; + + this.generateUniqueNumber = function () { //length 11 + const array = new Uint32Array(1); + crypto.getRandomValues(array); + return array[0] % 1000000000; + } + + this.routes = { + store: "/products" + } + + this.fetch = async function (resource, options = {}) { + // fire event + return new Request(resource, options) + } + + + + this.getProducts = async function (payload) { + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/product", { + method: "post", + headers: this.getHeader(), + body: JSON.stringify(payload), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.getFilteredProducts = async function (payload = {}) { + let url = `type = product` + payload.hasOwnProperty('price') ? url = `${ url }& price=${ payload.price } ` : null; + payload.hasOwnProperty('category') ? url = `${ url }& category_id=${ payload.category } ` : null; + payload.hasOwnProperty('is_featured') ? url = `${ url }& is_featured=${ payload.is_featured } ` : null; + payload.hasOwnProperty('name') ? url = `${ url }& name=${ payload.name } ` : null; + payload.hasOwnProperty('page') ? url = `${ url }& page=${ payload.page } ` : null; + payload.hasOwnProperty('limit') ? url = `${ url }& limit=${ payload.limit } ` : `${ url }& limit=${ 16 } `; + payload.hasOwnProperty('direction') ? url = `${ url }& direction=${ payload.direction } ` : `${ url }& direction=ASC`; + payload.hasOwnProperty('order') ? url = `${ url }& orderBy=${ payload.order } ` : `${ url }& orderBy=id`; + + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/filters/?" + url, { + method: "get", + headers: this.getHeader(), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.getFilteredProductsByCategory = async function (category_id) { + const result = await fetch(this._baseurl + `/ v2 / api / lambda / ecom / filters /? type = category & id=${ category_id } `, { + method: "get", + headers: this.getHeader(), + // body: JSON.stringify(payload), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.getSingleProduct = async function (id) { + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/product/" + id, { + method: "get", + headers: this.getHeader(), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.getCountries = async function () { + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/country", { + method: "get", + headers: this.getHeader(), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.getStates = async function (country = 0) { + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/state/" + country, { + method: "get", + headers: this.getHeader(), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.getCities = async function (state = 0) { + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/city/" + state, { + method: "get", + headers: this.getHeader(), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.getTax = async function (country = 0) { + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/tax/", { + method: "post", + headers: this.getHeader(), + body: JSON.stringify({ countryId: country }) + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.getCategories = async function () { + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/category", { + method: "get", + headers: this.getHeader(), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.getReviews = async function (payload) { + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/product/review", { + method: "post", + headers: this.getHeader(), + body: JSON.stringify(payload), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + this.getUserReviews = async function (userId) { + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/product/user-review/" + userId, { + method: "get", + headers: this.getHeader(), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.addReview = async function (payload) { + // review, user_id, status, product_id + payload.user_id = localStorage.getItem("user"); + payload.status = 1; + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/product/review/add", { + method: "post", + headers: this.getHeader(), + body: JSON.stringify(payload), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.getCart = async function () { + let params = localStorage.hasOwnProperty('user') ? `user_id = ${ localStorage.getItem('user') } ` + : `session_id = ${ localStorage.getItem('cart-session') }`; + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/cart?" + params, { + method: "get", + headers: this.getHeader(), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + + return json; + } + + this.addCartItem = async function (productId, quantity) { + let payload = { + status: 0 + }; + if (localStorage.hasOwnProperty('user')) { + payload.user_id = localStorage.getItem('user'); + // payload.session_id = false + } else { + // payload.user_id = false; + if (localStorage.hasOwnProperty('cart-session')) { + payload.session_id = Number(localStorage.getItem('cart-session')); + } else { + let session_id = this.generateUniqueNumber(); + localStorage.setItem('cart-session', session_id); + payload.session_id = Number(session_id); + } + } + + // payload.data = [ + // {productId: productId, quantity: quantity} + // ] + payload.productId = productId; + payload.quantity = Number(quantity); + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/cart/item", { + method: "post", + headers: this.getHeader(), + body: JSON.stringify(payload), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.updateCart = async function (data) { + let payload = { + status: 0 + }; + if (localStorage.hasOwnProperty('user')) { + payload.user_id = localStorage.getItem('user'); + payload.session_id = false + } else { + payload.user_id = false; + if (localStorage.hasOwnProperty('cart-session')) { + payload.session_id = localStorage.getItem('cart-session'); + } else { + let session_id = this.generateUniqueNumber(); + localStorage.setItem('cart-session', session_id); + payload.session_id = session_id; + } + } + + payload.data = data; + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/cart/update", { + method: "post", + headers: this.getHeader(), + body: JSON.stringify(payload), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.createCheckout = async function (data) { + + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/create-checkout", { + method: "post", + headers: this.getHeader(), + body: JSON.stringify(data), + }); + const json = await result.json(); + + if (json.status === 401) { + throw new Error(json.message); + } + + if (json.status === 403) { + throw new Error(json.message); + } + return json; + } + + this.getShippingRates = async function () { + + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/shipping-rates", { + method: "get", + headers: this.getHeader(), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + + return json; + } + + + + this.applyCoupon = async function (coupon) { + + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/product/coupon/apply", { + method: "post", + headers: this.getHeader(), + body: JSON.stringify({ coupon_code: coupon }) + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + + return json; + } + + this.getOrderItems = async function (checkoutId) { + + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/order-item/" + checkoutId, { + method: "get", + headers: this.getHeader(), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + + return json; + } + + this.generatePaymentIntent = async function (payload) { + + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/checkout", { + method: "post", + headers: this.getHeader(), + body: JSON.stringify(payload) + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + + return json; + } + + this.verifyPayment = async function (paymentIntent) { + + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/checkout-success", { + method: "post", + headers: this.getHeader(), + body: JSON.stringify({ + paymentIntentId: paymentIntent + }) + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + + return json; + } + + this.getOrders = async function (payload) { + payload.direction = 'desc'; + payload.sortId = 'id'; + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/order-history/user", { + method: "post", + headers: this.getHeader(), + body: JSON.stringify(payload) + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + + return json; + } + + this.addProduct = async function (payload) { + const result = await fetch(this._baseurl + "/v2/api/lambda/ecom/product/add", { + method: "post", + headers: this.getHeader(), + body: JSON.stringify(payload) + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + + return json; + } + + + // this.notes + // this.getcity + // this.getsettings + // this.search + // this.getAbandonCart + +} + + \ No newline at end of file diff --git a/day20/src/utils/MkdSDK.jsx b/day20/src/utils/MkdSDK.jsx new file mode 100644 index 0000000..f625468 --- /dev/null +++ b/day20/src/utils/MkdSDK.jsx @@ -0,0 +1,2810 @@ +import TreeSDK from "./TreeSDK"; +export default function MkdSDK() { + // this._baseurl = "https://inventorylynx.mkdlabs.com"; + this._baseurl = "https://localhost:3048"; + this._project_id = "inventorylynx"; + this._secret = "1qrhpud67j4jpda5zrdm7oi5ugjo8h3c9"; + this._table = ""; + this._GOOGLE_CAPTCHA_SITEKEY = "6LfmBc8jAAAAAKfz4zIiX1HoAwuH-9kcx68-7hhd"; + + const raw = this._project_id + ":" + this._secret; + let base64Encode = btoa(raw); + + this.login = async function (email, password, role) { + const result = await fetch(this._baseurl + "/v2/api/lambda/login", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + body: JSON.stringify({ + email, + password, + role, + }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + this.oauthLoginApi = async function (type, role) { + const socialLogin = await fetch( + `${this._baseurl}/v2/api/lambda/${type}/login?role=${role}`, + { + headers: { + "x-project": base64Encode, + }, + } + ); + const socialLink = await socialLogin.text(); + console.log(socialLink); + + if (socialLogin.status === 401) { + throw new Error(socialLink.message); + } + + if (socialLogin.status === 403) { + throw new Error(socialLink.message); + } + return socialLink; + }; + + this.getHeader = function () { + return { + Authorization: "Bearer " + localStorage.getItem("token"), + "x-project": base64Encode, + }; + }; + + this.baseUrl = function () { + return this._baseurl; + }; + this.uploadUrl = function () { + return this._baseurl + "/v2/api/lambda/upload"; + }; + + this.updateProfile = async function (payload) { + const result = await fetch(this._baseurl + "/v2/api/lambda/profile", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ + ...payload, + }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.upload = async function (file) { + let formData = new FormData(); + formData.append("file", file); + + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + + const uploadResult = await fetch(this._baseurl + `/v2/api/lambda/upload`, { + method: "post", + headers: header, + body: formData, + }).then((res) => res.json()); + return uploadResult; + }; + + this.getProfile = async function () { + const result = await fetch(this._baseurl + "/v2/api/lambda/profile", { + method: "get", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.check = async function (role) { + const result = await fetch(this._baseurl + "/v2/api/lambda/check", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ + role, + }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + return json; + }; + + this.getSession = async function () { + const result = await fetch( + this._baseurl + "/v2/api/lambda/user-sessions/data", + { + method: "get", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.sessionPost = async function (reqObj) { + const { + user_id, + session_id, + status, + events, + screen_width, + screen_height, + screen_size, + start_time, + end_time, + html_copy, + } = reqObj; + + const result = await fetch( + this._baseurl + "/v2/api/lambda/analytics/user-sessions/", + { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + body: JSON.stringify({ + user_id, + session_id, + status, + events, + screen_width, + screen_height, + screen_size, + start_time, + end_time, + html_copy, + }), + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.enable2FA = async function (reqObj = {}) { + const result = await fetch(this._baseurl + "/v2/api/lambda/2fa/enable", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ ...reqObj }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.disable2FA = async function (reqObj = {}) { + const result = await fetch(this._baseurl + "/v2/api/lambda/2fa/disable", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ ...reqObj }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.verify2FA = async function (access_token, token) { + const result = await fetch(this._baseurl + "/v2/api/lambda/2fa/verify", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + access_token, + }, + body: JSON.stringify({ + access_token: access_token, + token: token, + }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.authorize2FA = async function (access_token) { + const result = await fetch(this._baseurl + "/v2/api/lambda/2fa/authorize", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + access_token, + }, + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.captchaValidation = async function (captchaToken) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/google-captcha", + { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + body: JSON.stringify({ captchaToken }), + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.magicLoginAttempt = async function (email, role) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/magic-login/generate", + { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + body: JSON.stringify({ + email, + role, + url: this._baseurl + "/magic-login/verify", + }), + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.magicLoginVerify = async function (token = "") { + const result = await fetch(this._baseurl + "/v2/api/lambda/magic-login", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + body: JSON.stringify({ + token, + }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.exportCSV = async function () { + const header = { + "content-type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const getResult = await fetch( + this._baseurl + `/rest/${this._table}/EXPORT`, + { + method: "post", + headers: header, + } + ); + const res = await getResult.text(); + let hiddenElement = document.createElement("a"); + hiddenElement.href = "data:text/csv;charset=utf-8," + encodeURI(res); + hiddenElement.target = "_blank"; + + hiddenElement.download = this._table + ".csv"; + hiddenElement.click(); + + if (getResult.status === 401) { + throw new Error(res.message); + } + + if (getResult.status === 403) { + throw new Error(res.message); + } + }; + + this.analyticsPost = async function (uri, payload, method) { + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + }; + + const result = await fetch(uri, { + method: method, + headers: header, + body: JSON.stringify(payload), + }); + + const jsonResult = await result.json(); + + if (result.status === 401) { + throw new Error(jsonResult.message); + } + + if (result.status === 403) { + throw new Error(jsonResult.message); + } + return jsonResult; + }; + + this.getProfilePreference = async function () { + const result = await fetch(this._baseurl + "/v2/api/lambda/preference", { + method: "get", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + // update email + this.updateEmail = async function (email) { + const result = await fetch(this._baseurl + "/v2/api/lambda/update/email", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ + email, + }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + // update password + this.updatePassword = async function (password) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/update/password", + { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ + password, + }), + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + // update email + this.updateEmailByAdmin = async function (email, id) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/admin/update/email", + { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ + email, + id, + }), + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + // update password + this.updatePasswordByAdmin = async function (password, id) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/admin/update/password", + { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ + password, + id, + }), + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.sendEmailVerification = function () {}; + this.updateEmailVerification = function () {}; + + this.setTable = function (table) { + this._table = table; + }; + + this.getProjectId = function () { + return this._project_id; + }; + + this.logout = function () { + window.localStorage.clear(); + }; + + this.register = async function (email, password, role) { + const result = await fetch(this._baseurl + "/v2/api/lambda/register", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + body: JSON.stringify({ + email, + password, + role, + }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.verifyUser = async function (user_id) { + const result = await fetch(this._baseurl + "/v2/api/lambda/verify/user", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ + user_id, + }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.createUser = async function (email, password, role) { + const result = await fetch(this._baseurl + "/v2/api/lambda/register", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ + email, + password, + role, + }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.forgot = async function (email, role) { + const result = await fetch(this._baseurl + "/v2/api/lambda/forgot", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + body: JSON.stringify({ + email, + role, + }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.reset = async function (token, code, password) { + const result = await fetch(this._baseurl + "/v2/api/lambda/reset", { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + body: JSON.stringify({ + token, + code, + password, + }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.callRestAPI = async function (payload, method) { + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const tdk = new TreeSDK(); + switch (method) { + case "GET": + let getOptions = {}; + + //TreeSDK compatibility fields + + getOptions.join = payload.join; + return await tdk.getOne(this._table, payload.id, getOptions); + + case "POST": + return await tdk.create(this._table, payload); + + case "PUT": + return tdk.update(this._table, payload.id, payload); + + // Part: Update Table Without Using ID + case "PUTWHERE": + const updateWhereRes = await fetch( + this._baseurl + `/v1/api/rest/${this._table}/${method}`, + { + method: "post", + headers: header, + body: JSON.stringify(payload), // Note: payload: {set: {[string]: any}, where: {[string]: any}} + } + ); + const jsonUpdateWhereRes = await updateWhereRes.json(); + + if (updateWhereRes.status === 401) { + throw new Error(jsonUpdateWhereRes.message); + } + + return jsonUpdateWhereRes; + + case "DELETE": + return tdk.delete(this._table, payload.id); + + case "DELETEALL": + const deleteAllResult = await fetch( + this._baseurl + `/v1/api/rest/${this._table}/${method}`, + { + method: "post", + headers: header, + body: JSON.stringify(payload), + } + ); + const jsonDeleteAll = await deleteAllResult.json(); + + if (deleteAllResult.status === 401) { + throw new Error(jsonDeleteAll.message); + } + + if (deleteAllResult.status === 403) { + throw new Error(jsonDeleteAll.message); + } + return jsonDeleteAll; + case "GETALL": + payload.order = payload.orderBy; + return await tdk.getList(this._table, payload); + case "PAGINATE": + let options = {}; + if (!payload.page) { + payload.page = 1; + } + if (!payload.limit) { + payload.limit = 10; + } + //TreeSDK compatibility fields + options.size = payload.limit; + options.order = payload.sortId; + options.direction = payload.direction; + options.page = payload.page; + options.join = payload.join; + + let filters = payload.payload; + options.filter = Object.keys(filters).map((col) => { + if (typeof filters[col] !== "undefined" && col === "id") + return `${col},eq,${filters[col]}`; + if (typeof filters[col] !== "undefined") + return `${col},cs,${filters[col]}`; + }); + return await tdk.getPaginate(this._table, options); + + case "AUTOCOMPLETE": + const autocompleteResult = await fetch( + this._baseurl + `/v1/api/rest/${this._table}/${method}`, + { + method: "post", + headers: header, + body: JSON.stringify(payload), + } + ); + const jsonAutocomplete = await autocompleteResult.json(); + + if (autocompleteResult.status === 401) { + throw new Error(jsonAutocomplete.message); + } + + if (autocompleteResult.status === 403) { + throw new Error(jsonAutocomplete.message); + } + return jsonAutocomplete; + default: + break; + } + }; + + this.callRawAPI = async function (uri, payload, method) { + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + + let result; + + if (method === "GET") { + result = await fetch(this._baseurl + uri, { + method: method, + headers: header, + }); + } else { + result = await fetch(this._baseurl + uri, { + method: method, + headers: header, + body: JSON.stringify(payload), + }); + } + + const jsonResult = await result.json(); + + if (result.status === 401) { + throw new Error(jsonResult.message); + } + + if (result.status === 403) { + throw new Error(jsonResult.message); + } + return jsonResult; + }; + + // Part: Get All Data by Joining Two Columns + this.callJoinRestAPI = async function ( + table1, + table2, + join_id_1, + join_id_2, + select, + where, + method, + page, + limit + ) { + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + + switch (method) { + case "GETALL": + const result = await fetch( + this._baseurl + `/v1/api/join/${table1}/${table2}/${method}`, + { + method: "post", + headers: header, + body: JSON.stringify({ + join_id_1, // "tableName1" + join_id_2, // "tableName2" + select, // "tableName1.field1, tableName2.field2" + where: where || "", // where: ["status=2424", "id=1"] + }), + } + ); + const jsonResult = await result.json(); + + if (result.status === 401) { + throw new Error(jsonResult.message); + } + + if (result.status === 403) { + throw new Error(jsonResult.message); + } + return jsonResult; + + case "PAGINATE": + if (!page) { + page = 1; + } + if (!limit) { + limit = 10; + } + const paginateResult = await fetch( + this._baseurl + `/v1/api/join/${table1}/${table2}/${method}`, + { + method: "post", + headers: header, + body: JSON.stringify({ + join_id_1, // "tableName1" + join_id_2, // "tableName2" + select, // "tableName1.field1, tableName2.field2" + where: where || "", // where: ["status=2424", "id=1"] + page, + limit, + }), + } + ); + const jsonPaginate = await paginateResult.json(); + + if (paginateResult.status === 401) { + throw new Error(jsonPaginate.message); + } + + if (paginateResult.status === 403) { + throw new Error(jsonPaginate.message); + } + return jsonPaginate; + + default: + break; + } + }; + + // Part: Get Data by Joining Multiple Columns with Pagination + this.callMultiJoinRestAPI = async function ( + tables, + joinIds, + selectStr, + where, + page, + limit, + method + ) { + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + + if (!page) { + page = 1; + } + if (!limit) { + limit = 10; + } + const paginateResult = await fetch( + this._baseurl + `/v1/api/multi-join/${method}`, + { + method: "post", + headers: header, + body: JSON.stringify({ + tables, // ["tableName1", "tableName2"] + joinIds, // ["tableName1.id", "tableName2.id"] + selectStr, // "tableName1.field1, tableName2.field2" + where, // ["status=2424", "id=1"] + page, + limit, + }), + } + ); + const jsonPaginate = await paginateResult.json(); + + if (paginateResult.status === 401) { + throw new Error(jsonPaginate.message); + } + + if (paginateResult.status === 403) { + throw new Error(jsonPaginate.message); + } + return jsonPaginate; + }; + + this.subscribe = function (payload) {}; + this.subscribe = function (payload) {}; + + // subscribe to a channel if it exists, or create if it doesn't + this.subscribeChannel = async function (channel) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/subscription/channel/room", + { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ + room: channel, + }), + } + ); + + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + // Listen on a channel and wait for response + this.subscribeListen = async function (channel) { + const result = await fetch( + this._baseurl + + "/v2/api/lambda/subscription/channel/poll?room=" + + channel, + { + method: "GET", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + this.unSubscribeChannel = async function (channel) { + const result = await fetch( + this._baseurl + + "/v2/api/lambda/subscription/channel/unsubscribe?room=" + + channel, + { + method: "GET", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + // check if channel exist + this.channelOnline = async function (channel) { + const result = await fetch( + this._baseurl + + "/v2/api/lambda/subscription/channel/online?room=" + + channel, + { + method: "GET", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + // Publish to the specified channel + this.broadcast = async function (payload, channel) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/subscription/channel/send", + { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ + payload: payload, + room: channel, + }), + } + ); + + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.cmsAdd = async function (page, key, type, value) { + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + + const insertResult = await fetch(this._baseurl + `/v2/api/lambda/cms`, { + method: "post", + headers: header, + body: JSON.stringify({ + page, + key, + type, + value, + }), + }); + const jsonInsert = await insertResult.json(); + + if (insertResult.status === 401) { + throw new Error(jsonInsert.message); + } + + if (insertResult.status === 403) { + throw new Error(jsonInsert.message); + } + return jsonInsert; + }; + + this.cmsEdit = async function (id, page, key, type, value) { + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + + const updateResult = await fetch( + this._baseurl + `/v2/api/lambda/cms/` + id, + { + method: "put", + headers: header, + body: JSON.stringify({ + page, + key, + type, + value, + }), + } + ); + const jsonInsert = await updateResult.json(); + + if (updateResult.status === 401) { + throw new Error(jsonInsert.message); + } + + if (updateResult.status === 403) { + throw new Error(jsonInsert.message); + } + return jsonInsert; + }; + + this.getToken = function () { + return window.localStorage.getItem("token"); + }; + + // get chat room + this.getMyRoom = async function () { + const result = await fetch( + this._baseurl + "/v3/api/lambda/realtime/room/my", + { + method: "GET", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + // get chat id + this.getChatId = async function (room_id) { + const result = await fetch( + this._baseurl + `/v2/api/lambda/room?room_id=${room_id}`, + { + method: "get", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + // post chat + this.getChats = async function (room_id, chat_id, date) { + const result = await fetch(this._baseurl + `/v3/api/lambda/realtime/chat`, { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ + room_id, + chat_id, + date, + }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.restoreChat = async function (room_id) { + await fetch( + this._baseurl + `/v2/api/lambda/v2/api/lambda/room/poll?room=${room_id}`, + { + method: "get", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + }; + + // post a new message + this.postMessage = async function (messageDetails) { + const result = await fetch(this._baseurl + `/v3/api/lambda/realtime/send`, { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(messageDetails), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.uploadImage = async function (file) { + const result = await fetch(this._baseurl + `/v2/api/lambda/s3/upload`, { + method: "post", + headers: { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: file, + }); + + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + this.editorUploadImage = async function (file) { + let formData = new FormData(); + formData.append("file", file); + const result = await fetch(this._baseurl + `/v2/api/lambda/s3/upload`, { + method: "post", + headers: { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: formData, + }); + + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.createRoom = async function (roomDetails) { + const result = await fetch(this._baseurl + `/v3/api/lambda/realtime/room`, { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(roomDetails), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.getAllUsers = async function () { + const result = await fetch(this._baseurl + `/v1/api/rest/user/GETALL`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + // start pooling + this.startPooling = async function (user_id, signal) { + const result = await fetch( + this._baseurl + `/v3/api/lambda/realtime/room/poll?user_id=${user_id}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + signal, + } + ); + + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + /** + * start stripe functions + */ + + this.addStripeProduct = async function (data) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/stripe/product", + { + method: "post", + headers: { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(data), + } + ); + + const json = await result.json(); + if ([401, 403, 500].includes(result.status)) { + throw new Error(json.message); + } + return json; + }; + + this.getStripeProducts = async function (paginationParams, filterParams) { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const paginationQuery = new URLSearchParams(paginationParams); + // const filterQuery = new URLSearchParams(filterParams); + const getResult = await fetch( + this._baseurl + + `/v2/api/lambda/stripe/products?${paginationQuery}&${filterParams}`, + { + method: "get", + headers: header, + } + ); + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.getStripeProduct = async function (id) { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const getResult = await fetch( + this._baseurl + `/v2/api/lambda/stripe/product/${id}`, + { + method: "get", + headers: header, + } + ); + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.updateStripeProduct = async function (id, payload) { + const header = { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const getResult = await fetch( + this._baseurl + `/v2/api/lambda/stripe/product/${id}`, + { + method: "put", + headers: header, + body: JSON.stringify(payload), + } + ); + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.addStripePrice = async function (data) { + const result = await fetch(this._baseurl + "/v2/api/lambda/stripe/price", { + method: "post", + headers: { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(data), + }); + + const json = await result.json(); + if ([401, 403, 500].includes(result.status)) { + throw new Error(json.message); + } + return json; + }; + + this.getStripePrices = async function (paginationParams, filterParams) { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const paginationQuery = new URLSearchParams(paginationParams); + // const filterQuery = new URLSearchParams(filterParams); + const getResult = await fetch( + this._baseurl + + `/v2/api/lambda/stripe/prices?${paginationQuery}&${filterParams}`, + { + method: "get", + headers: header, + } + ); + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.getStripePaymentLinks = async function () { + const header = { + "x-project": base64Encode, + // Authorization: "Bearer " + localStorage.getItem("token") + }; + + const getResult = await fetch( + this._baseurl + `/v2/api/lambda/stripe/paymentlinks`, + { + method: "get", + headers: header, + } + ); + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.getStripePrice = async function (id) { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const getResult = await fetch( + this._baseurl + `/v2/api/lambda/stripe/price/${id}`, + { + method: "get", + headers: header, + } + ); + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.updateStripePrice = async function (id, payload) { + const header = { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const getResult = await fetch( + this._baseurl + `/v2/api/lambda/stripe/price/${id}`, + { + method: "put", + headers: header, + body: JSON.stringify(payload), + } + ); + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.getStripeSubscriptions = async function ( + paginationParams, + filterParams + ) { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const paginationQuery = new URLSearchParams(paginationParams); + // const filterQuery = new URLSearchParams(filterParams); + const getResult = await fetch( + this._baseurl + + `/v2/api/lambda/stripe/subscriptions?${paginationQuery}&${filterParams}`, + { + method: "get", + headers: header, + } + ); + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.adminCancelStripeSubscription = async function (subId, data) { + const result = await fetch( + this._baseurl + `/v2/api/lambda/stripe/subscription/${subId}`, + { + method: "delete", + headers: { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(data), + } + ); + + const json = await result.json(); + if ([401, 403, 500].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + + this.adminCreateUsageCharge = async function (subId, quantity) { + const result = await fetch( + this._baseurl + `/v2/api/lambda/stripe/subscription/usage-charge`, + { + method: "post", + headers: { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ + subId, + quantity, + }), + } + ); + + const json = await result.json(); + if ([401, 403, 500].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + + this.getStripeInvoices = async function (paginationParams, filterParams) { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const paginationQuery = new URLSearchParams(paginationParams); + const filterQuery = new URLSearchParams(filterParams); + const getResult = await fetch( + this._baseurl + `/v2/api/lambda/stripe/invoices?${paginationQuery}`, + { + method: "get", + headers: header, + } + ); + + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + this.getStripeInvoicesV2 = async function (paginationParams, filterParams) { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const paginationQuery = new URLSearchParams(paginationParams); + // const filterQuery = new URLSearchParams(filterParams); + const getResult = await fetch( + this._baseurl + + `/v2/api/lambda/stripe/invoices-v2?${paginationQuery}&${filterParams}`, + { + method: "get", + headers: header, + } + ); + + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.getStripeOrders = async function (paginationParams, filterParams) { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const paginationQuery = new URLSearchParams(paginationParams); + const filterQuery = new URLSearchParams(filterParams); + const getResult = await fetch( + this._baseurl + + `/v2/api/lambda/stripe/orders?${paginationQuery}&${filterQuery}`, + { + method: "get", + headers: header, + } + ); + + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + /** + * ------------------------------------------------------- + */ + + this.initCheckoutSession = async function (data) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/stripe/checkout", + { + method: "post", + headers: { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(data), + } + ); + + const json = await result.json(); + if ([401, 403, 500].includes(result.status)) { + throw new Error(json.message); + } + return json; + }; + + this.registerAndSubscribe = async function (data) { + /** + * + * @param {object} data {email, password, cardToken, planId} + * @returns + */ + const result = await fetch( + this._baseurl + "/v2/api/lambda/stripe/customer/register-subscribe", + { + method: "post", + headers: { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(data), + } + ); + + const json = await result.json(); + if ([401, 403, 500].includes(result.status)) { + throw new Error(json.message); + } + return json; + }; + + this.createStripeCustomer = async function (payload) { + const header = { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + + const getResult = await fetch( + this._baseurl + `/v2/api/lambda/stripe/customer`, + { + method: "post", + headers: header, + body: JSON.stringify(payload), + } + ); + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.createCustomerStripeCard = async function (payload) { + const header = { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + + const getResult = await fetch( + this._baseurl + `/v2/api/lambda/stripe/customer/card`, + { + method: "post", + headers: header, + body: JSON.stringify(payload), + } + ); + + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.createStripeSubscription = async function (data) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/stripe/customer/subscription", + { + method: "post", + headers: { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(data), + } + ); + + const json = await result.json(); + if ([401, 403, 500].includes(result.status)) { + throw new Error(json.message); + } + return json; + }; + + this.getCustomerStripeSubscription = async function (userId) { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + + const getResult = await fetch( + this._baseurl + `/v2/api/lambda/stripe/customer/subscription`, + { + method: "get", + headers: header, + } + ); + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.getCustomerStripeSubscriptions = async function ( + paginationParams, + filterParams + ) { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const paginationQuery = new URLSearchParams(paginationParams); + const filterQuery = new URLSearchParams(filterParams); + const getResult = await fetch( + this._baseurl + + `/v2/api/lambda/stripe/customer/subscriptions?${paginationQuery}&${filterQuery}`, + { + method: "get", + headers: header, + } + ); + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.changeStripeSubscription = async function (data) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/stripe/customer/subscription", + { + method: "put", + headers: { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(data), + } + ); + + const json = await result.json(); + if ([401, 403, 500].includes(result.status)) { + throw new Error(json.message); + } + return json; + }; + + this.cancelStripeSubscription = async function (subId, data) { + const result = await fetch( + this._baseurl + `/v2/api/lambda/stripe/customer/subscription/${subId}`, + { + method: "delete", + headers: { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(data), + } + ); + + const json = await result.json(); + if ([401, 403, 500].includes(result.status)) { + throw new Error(json.message); + } + return json; + }; + + this.getCustomerStripeDetails = async function () { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + + const getResult = await fetch( + this._baseurl + `/v2/api/lambda/stripe/customer`, + { + method: "get", + headers: header, + } + ); + + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.getCustomerStripeCards = async function (paginationParams) { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const paginationQuery = new URLSearchParams(paginationParams); + const getResult = await fetch( + this._baseurl + `/v2/api/lambda/stripe/customer/cards?${paginationQuery}`, + { + method: "get", + headers: header, + } + ); + + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.getCustomerStripeInvoices = async function (paginationParams) { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const paginationQuery = new URLSearchParams(paginationParams); + const getResult = await fetch( + this._baseurl + + `/v2/api/lambda/stripe/customer/invoices?${paginationQuery}`, + { + method: "get", + headers: header, + } + ); + + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.getCustomerStripeCharges = async function (paginationParams) { + const header = { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const paginationQuery = new URLSearchParams(paginationParams); + const getResult = await fetch( + this._baseurl + + `/v2/api/lambda/stripe/customer/charges?${paginationQuery}`, + { + method: "get", + headers: header, + } + ); + + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.getCustomerStripeOrders = async function (paginationParams) { + const header = { + Authorization: "Bearer " + localStorage.getItem("token"), + "x-project": base64Encode, + }; + const paginationQuery = new URLSearchParams(paginationParams); + const getResult = await fetch( + this._baseurl + + `/v2/api/lambda/stripe/customer/orders?${paginationQuery}`, + { + method: "get", + headers: header, + } + ); + + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.setStripeCustomerDefaultCard = async function (cardId) { + const header = { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const getResult = await fetch( + this._baseurl + + `/v2/api/lambda/stripe/customer/card/${cardId}/set-default`, + { + method: "put", + headers: header, + } + ); + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + this.deleteCustomerStripeCard = async function (cardId) { + const header = { + "content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const getResult = await fetch( + this._baseurl + `/v2/api/lambda/stripe/customer/card/${cardId}`, + { + method: "delete", + headers: header, + } + ); + const jsonGet = await getResult.json(); + + if ([401, 403, 500].includes(getResult.status)) { + throw new Error(jsonGet.message); + } + + return jsonGet; + }; + + /** end stripe functions */ + + // Chapter: Scheduling + // Part: Get Unavailable Scheduling Dates + this.getUnavailableDates = async function (userId, date) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/scheduling/dates", + { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + body: JSON.stringify({ + userId, + date, + }), + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.getSchedules = async function (userId, date) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/scheduling/schedules", + { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + body: JSON.stringify({ + userId, + date, + }), + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.getSchedulesWithTZ = async function (rangeStart, rangeEnd, userId, tz) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/scheduling/tz-month-schedules", + { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + body: JSON.stringify({ + rangeStart, + rangeEnd, + userId, + tz, + }), + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.createScheduling = async function ( + user_id, + meeting_start, + meeting_end, + meeting_details + ) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/scheduling/POST", + { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-project": + "bWFuYWtuaWdodDo1ZmNoeG41bThoYm82amN4aXEzeGRkb2ZvZG9hY3NreWUx", // Note: manaknight + }, + body: JSON.stringify({ + user_id, + meeting_start, + meeting_end, + meeting_details, + }), + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.addEventToGC = async function ( + access_token, + expires_in, + refresh_token, + scope, + token_type, + summary, + location, + description, + startTime, + endTime, + attendees + ) { + const result = await fetch( + this._baseurl + "/v2/api/lambda/scheduling/google-calendar-event", + { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-project": + "bWFuYWtuaWdodDo1ZmNoeG41bThoYm82amN4aXEzeGRkb2ZvZG9hY3NreWUx", // Note: manaknight + }, + body: JSON.stringify({ + access_token, + expires_in, + refresh_token, + scope, + token_type, + summary, + location, + description, + startTime, + endTime, + attendees, + }), + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + /** + * + * @param {number[]|undefined} tags + * @param {number[]|undefined} categories + * @param {"or"| 'and' | undefined} rule + * @returns + */ + this.getFilteredBlogs = async function ( + tags = [], + categories = [], + rule = undefined + ) { + let endpoint = "/v2/api/lambda/blog/filter?"; + if (tags && tags.length) { + if (endpoint.endsWith("?")) { + endpoint += `tags=${tags}`; + } else { + endpoint += `&tags=${tags}`; + } + } + if (categories && categories.length) { + if (endpoint.endsWith("?")) { + endpoint += `categories=${categories}`; + } else { + endpoint += `&categories=${categories}`; + } + // endpoint += `tags=[1]&categories=[4]&rule=or` + } + if (rule) { + if (endpoint.endsWith("?")) { + endpoint += `rule=${rule}`; + } else { + endpoint += `&rule=${rule}`; + } + // endpoint += `tags=[1]&categories=[4]&rule=or` + } + + const result = await fetch(this._baseurl + `${endpoint}`, { + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + }); + const json = await result.json(); + + if ([401, 402, 403, 404, 500, 502, 501].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + + this.getAllBlogs = async function () { + const result = await fetch(this._baseurl + `/v2/api/lambda/blog/all `, { + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + }, + }); + const json = await result.json(); + + if ([401, 402, 403, 404, 500, 502, 501].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + + this.createBlog = async function (payload) { + const result = await fetch(this._baseurl + `/v2/api/lambda/blog/create`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(payload), + }); + const json = await result.json(); + + if ([401, 402, 403, 404, 500, 502, 501].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + this.getSingleBlog = async function (id) { + const result = await fetch( + this._baseurl + `/v2/api/lambda/blog/single/${id}`, + { + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + // Authorization: "Bearer " + localStorage.getItem( "token" ) + }, + // body: JSON.stringify( payload ) + } + ); + const json = await result.json(); + + if ([401, 402, 403, 404, 500, 502, 501].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + this.editBlog = async function (id, payload) { + const result = await fetch( + this._baseurl + `/v2/api/lambda/blog/edit/${id}`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(payload), + } + ); + const json = await result.json(); + + if ([401, 402, 403, 404, 500, 502, 501].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + this.deleteBlog = async function (id) { + const result = await fetch( + this._baseurl + `/v2/api/lambda/blog/delete/${id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + const json = await result.json(); + + if ([401, 402, 403, 404, 500, 502, 501].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + + // BLOG CATEFORY + this.getallBlogCategories = async function () { + const result = await fetch(this._baseurl + `/v2/api/lambda/blog/category`, { + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + // Authorization: "Bearer " + localStorage.getItem( "token" ) + }, + }); + const json = await result.json(); + + if ([401, 402, 403, 404, 500, 502, 501].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + this.createBlogCategory = async function (payload) { + const result = await fetch(this._baseurl + `/v2/api/lambda/blog/category`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(payload), + }); + const json = await result.json(); + + if ([401, 402, 403, 404, 500, 502, 501].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + this.deleteBlogCategory = async function (id) { + const result = await fetch( + this._baseurl + `/v2/api/lambda/blog/category/${id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + const json = await result.json(); + + if ([401, 402, 403, 404, 500, 502, 501].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + + // BLOG TAG + this.getallBlogTags = async function () { + const result = await fetch(this._baseurl + `/v2/api/lambda/blog/tags`, { + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + // Authorization: "Bearer " + localStorage.getItem( "token" ) + }, + }); + const json = await result.json(); + + if ([401, 402, 403, 404, 500, 502, 501].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + this.createBlogTag = async function (payload) { + const result = await fetch(this._baseurl + `/v2/api/lambda/blog/tags`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(payload), + }); + const json = await result.json(); + + if ([401, 402, 403, 404, 500, 502, 501].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + this.deleteBlogTag = async function (id) { + const result = await fetch(this._baseurl + `/v2/api/lambda/blog/tags`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + }); + const json = await result.json(); + + if ([401, 402, 403, 404, 500, 502, 501].includes(result.status)) { + throw new Error(json.message); + } + + return json; + }; + + // Trello Requests + + this.getAllWorkspaces = async function () { + const result = await fetch( + this._baseurl + `/v2/api/lambda/pm/workspaces?limit=99999`, + { + method: "get", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.getWorkspace = async function (id) { + const result = await fetch( + this._baseurl + `/v2/api/lambda/pm/workspaces/${id}`, + { + method: "get", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.createWorkspace = async function (obj) { + const result = await fetch(this._baseurl + `/v2/api/lambda/pm/workspaces`, { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify(obj), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.createUserBoards = async function (workspaceId, reqObj) { + const result = await fetch( + this._baseurl + `/v2/api/lambda/pm/workspaces/${workspaceId}/boards`, + { + method: "post", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: JSON.stringify({ ...reqObj, workspace_id: workspaceId }), + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.getUserBoards = async function ( + workspaceId, + page = 1, + limit = 10, + id = "id", + direction = "desc" + ) { + const result = await fetch( + this._baseurl + + `/v2/api/lambda/pm/workspaces/${workspaceId}/boards?page=${page}&limit=${limit}&sortId=${id}&direction=${direction}`, + { + method: "get", + headers: { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.chatGPT = async function (message) { + const result = await fetch(this._baseurl + "/v2/api/qna/query", { + method: "post", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + localStorage.getItem("token"), + "x-project": base64Encode, + }, + body: JSON.stringify({ + user_prompt: message, + }), + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.gptTranscribe = async function (file) { + const result = await fetch( + this._baseurl + `/v5/api/deployments/sow/gpt-transcribe`, + { + method: "post", + headers: { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: file, + } + ); + + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.gptTranslate = async function (file) { + const result = await fetch( + this._baseurl + `/v5/api/deployments/sow/gpt-translate`, + { + method: "post", + headers: { + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }, + body: file, + } + ); + + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + this.runPodStatus = async function () { + const result = await fetch(this._baseurl + "v2/api/qna/server/status", { + method: "get", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + localStorage.getItem("token"), + "x-project": base64Encode, + }, + }); + const json = await result.json(); + + if (result.status === 401) { + throw new Error(json.message); + } + + if (result.status === 403) { + throw new Error(json.message); + } + return json; + }; + + // CUSTOM GENERATED API + + return this; +} diff --git a/day20/src/utils/TreeSDK.jsx b/day20/src/utils/TreeSDK.jsx new file mode 100644 index 0000000..8cc0b29 --- /dev/null +++ b/day20/src/utils/TreeSDK.jsx @@ -0,0 +1,419 @@ + + import { empty } from "./utils"; + +export default function TreeSDK() { + this._baseurl = "https://inventorylynx.mkdlabs.com"; + this._project_id = "inventorylynx"; + this._secret = "1qrhpud67j4jpda5zrdm7oi5ugjo8h3c9"; + this._table = ""; + + const raw = this._project_id + ":" + this._secret; + let base64Encode = btoa(raw); + + this.updateXProject = function (projectId, secret) { + console.log(projectId, secret); + base64Encode = btoa(projectId + ":" + secret); + }; + this.resetXPRoject = function () { + base64Encode = btoa(raw); + }; + + this.getHeader = function () { + return { + Authorization: "Bearer " + localStorage.getItem("token"), + "x-project": base64Encode, + }; + }; + + this.baseUrl = function () { + return this._baseurl; + }; + + this.getProjectId = function () { + return this._project_id; + }; + + this.treeBaseUrl = function () { + return this._baseurl + "/v4/api/records"; + }; + + function getJoins(options = {}) { + let hasJoin = options.hasOwnProperty("join"); + let joins = options.join; + if (hasJoin && typeof joins === "string") { + joins = joins.split(","); + } else { + joins = []; + } + + let joinQuery = ""; + joins.forEach((join) => { + joinQuery += `join=${join}&`; + }); + + return [hasJoin, joins, joinQuery]; + } + + function getOrdering(options) { + let order = options.order ? options.order : "id"; + let direction = options.direction ? options.direction : "desc"; + + return `order=${order},${direction}&`; + } + + function getFilters(options) { + let hasFilter = options.hasOwnProperty("filter"); + let filters = options.filter; + + let filterQuery = ""; + if (hasFilter && Array.isArray(filters)) { + filters.forEach((filter) => { + filterQuery += `filter=${filter}&`; + }); + } + + return [hasFilter, filters, filterQuery]; + } + + /* + Returns one entry + @params table : string - name of table to fetch + @params id : number - id to fetch + @params options : object - optional parameters + options.join - Array or comma separated list of tables to join + + let res = await (new TreeSDK).getOne('author', 1 { + join: ['book'], + }); + + */ + + this.getOne = async function (table, id, options = {}) { + if (empty(table) || empty(id)) throw new Error("table and id is required."); + + let [hasJoin, joins, joinQuery] = getJoins(options); + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + + const getResult = await fetch( + this.treeBaseUrl() + `/ ${ table } /${id}?${joinQuery}`, + { + method: "get", + headers: header, + } + ); +const json = await getResult.json(); + +if (getResult.status === 401) { + throw new Error(json.message); +} + +if (getResult.status === 403) { + throw new Error(json.message); +} +return json; + }; + +/* + Returns one or more entries + @params table : string - name of table to fetch + @params ids : Array|string|number - array, comma separated list of ids or just a single id to fetch + @params options : object - optional parameters + options.join - Array or comma separated list of tables to join + + let res = await (new TreeSDK).getMany('author', [1,2] { + join: ['book'], + }); + +*/ +this.getMany = async function (table, ids, options = {}) { + if (empty(table) || empty(ids)) + throw new Error("table and id is required."); + + let [hasJoin, joins, joinQuery] = getJoins(options); + let id = Array.isArray(ids) ? ids.join(",") : ids; + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + + const getResult = await fetch( + this.treeBaseUrl() + `/${table}/${id}?${joinQuery}`, + { + method: "get", + headers: header, + } + ); + const json = await getResult.json(); + + if (getResult.status === 401) { + throw new Error(json.message); + } + + if (getResult.status === 403) { + throw new Error(json.message); + } + return json; +}; + +/* + Returns one or more entries with ordering and filters + @params table : string - name of table to fetch + @params options : object - optional parameters + options.join - Array or comma separated list of tables to join + options.filter - + options.order - field used to sort the result + options.direction - direction of result asc|desc + options.size - max number of entries + + let res = await (new TreeSDK).getList('author', { + filter: ['id,gt,2'], + join: ['book'] + }); + +*/ +this.getList = async function (table, options = {}) { + if (empty(table)) throw new Error("table and id is required."); + let [hasJoin, joins, joinQuery] = getJoins(options); + let [hasFilter, filters, filterQuery] = getFilters(options); + let orderQuery = getOrdering(options); + let sizeQuery = options.hasOwnProperty("size") + ? `size=${options.size}&` + : ""; + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + + const getResult = await fetch( + this.treeBaseUrl() + + `/${table}?${joinQuery}${orderQuery}${sizeQuery}${filterQuery}`, + { + method: "get", + headers: header, + } + ); + const json = await getResult.json(); + + if (getResult.status === 401) { + throw new Error(json.message); + } + + if (getResult.status === 403) { + throw new Error(json.message); + } + return json; +}; + +/* + Returns a paginated list of entries + @params table : string - name of table to fetch + @params options : object - optional parameters + options.join - Array or comma separated list of tables to join + options.filter - + options.order - field used to sort the result + options.direction - direction of result asc|desc + options.page - page number + options.size - max number of entries + + let res = await (new TreeSDK).getPaginate('author', { + filter: ['id,gt,2'], + join: ['book'] + }); + +*/ +this.getPaginate = async function (table, options = {}) { + if (empty(table)) throw new Error("table and id is required."); + + let [hasJoin, joins, joinQuery] = getJoins(options); + let [hasFilter, filters, filterQuery] = getFilters(options); + let orderQuery = getOrdering(options); + console.table({ orderQuery, joinQuery, filterQuery }); + let size = options.size ?? 20; + let pageQuery = options.hasOwnProperty("page") + ? `page=${options.page},${size}&` + : `page=1&`; + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const getResult = await fetch( + this.treeBaseUrl() + + `/${table}?${joinQuery}${orderQuery}${pageQuery}${filterQuery}`, + { + method: "get", + headers: header, + } + ); + const json = await getResult.json(); + + if (getResult.status === 401) { + throw new Error(json.message); + } + + if (getResult.status === 403) { + throw new Error(json.message); + } + return json; +}; + +/* + Returns Creates a new entry + @params table : string - name of table to fetch + @params options : object - optional parameters + + + let res = await (new TreeSDK).create('author', { + name: 'authro name', + age: 23 + }); + + +*/ +this.create = async function (table, payload, options = {}) { + if (empty(table)) throw new Error("table and id is required."); + + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const getResult = await fetch(this.treeBaseUrl() + `/${table}`, { + method: "post", + headers: header, + body: JSON.stringify(payload), + }); + const json = await getResult.json(); + + if (getResult.status === 401) { + throw new Error(json.message); + } + + if (getResult.status === 403) { + throw new Error(json.message); + } + return json; +}; + +/* + Returns Updates an entry + @params table : string - name of table to update + @params id : number - id of table entry to update + @params payload : object - key value pair for values to update + + let res = await (new TreeSDK).update('author', 2 { + name: 'updated author name', + }); + +*/ +this.update = async function (table, id, payload) { + if (empty(table) || empty(id)) throw new Error("table and id is required."); + + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const getResult = await fetch(this.treeBaseUrl() + `/${table}/${id}`, { + method: "put", + headers: header, + body: JSON.stringify(payload), + }); + const json = await getResult.json(); + + if (getResult.status === 401) { + throw new Error(json.message); + } + + if (getResult.status === 403) { + throw new Error(json.message); + } + return json; +}; + +this.updateWhere = async function (table, where, payload) { + if (empty(table) || empty(id)) throw new Error("table and id is required."); + if (Object.keys(where).length === 0) + throw new Error("condition is required."); + + payload["updateCondition"] = where; + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const getResult = await fetch(this.treeBaseUrl() + `/${table}`, { + method: "put", + headers: header, + body: JSON.stringify(payload), + }); + const json = await getResult.json(); + + if (getResult.status === 401) { + throw new Error(json.message); + } + + if (getResult.status === 403) { + throw new Error(json.message); + } + return json; +}; + +/* + Returns Delete an entry + @params table : string - name of table to delete from + @params id : number - id of table entry to delete + + let res = await (new TreeSDK).delete('author', 2); +*/ +this.delete = async function (table, id, payload) { + if (empty(table) || empty(id)) throw new Error("table and id is required."); + + const header = { + "Content-Type": "application/json", + "x-project": base64Encode, + Authorization: "Bearer " + localStorage.getItem("token"), + }; + const getResult = await fetch(this.treeBaseUrl() + `/${table}/${id}`, { + method: "delete", + headers: header, + body: JSON.stringify(payload), + }); + const json = await getResult.json(); + + if (getResult.status === 401) { + throw new Error(json.message); + } + + if (getResult.status === 403) { + throw new Error(json.message); + } + return json; +}; + +return this; +} +/* + +cs contains string +sw starts with +ew ends with +eq + equal +Default when no operator is provided +lt less than +le less or equal +ge greater or equal +gt greater than +bt between +in in list +is is null + +*/ + \ No newline at end of file diff --git a/day20/src/utils/config.jsx b/day20/src/utils/config.jsx new file mode 100644 index 0000000..efbcc67 --- /dev/null +++ b/day20/src/utils/config.jsx @@ -0,0 +1,1449 @@ + + const editorFunctions = [ + `// FUNCTION FOR SET CONTENT // + function handleContentChange(value) { + setContent(value); + if (value !== "") { + if (errors.content.message !== "") { + setError(prev => ({ ...prev, content: { message: "" } })); + } + } + // console.log( value ) + } `, +]; +const infiniteScrollFunctions = [ + ` const next = async (initialized, cursor, where) => { + + try { + console.log(initialized, where) + sdk.setTable("user") + const result = await sdk.callRestAPI({ limit: pageSize, cursor: cursor }, "CURSORPAGINATE") + console.log(result) + if (!result.error) { + if (!initialized) { + setCursorPaginateData(() => [...result?.list]) + // setInitialized( true ) + console.log("Not initialized") + } else { + setCursorPaginateData((prev) => [...prev, ...result?.list]) + console.log(" initialized") + } + setCurrentCursor(result?.cursor) + // setPageSize( result?.limit ) + setNextCursor(result?.nextCursor) + } + } catch (error) { + // Do somthing with your error + } + }`, +]; + +export const componentList = [ + { + id: "2b5b729e-4a2f-69e2-e575-cb37cb4ebecd", + name: "AddButton", + tag: "done", + group: "", + props: { + role: "", + model: "", + text: "", + has: { + props: true, + state: false, + function: false, + context: { global: false, auth: false }, + }, + }, + }, + + // { + // id: "1a13851b-9f6e-aa02-0076-1f84d507181c", + // name: "AddTags", + // props: { + // has: { + // props: true,// + // state: true, + // function: true, + // context: { global: false, auth: false }, + // }, + // }, + // }, + { + id: "0d63aea5-9106-6598-8b94-3bc51cb47a16", + name: "Editor", + tag: "done", + props: { + has: { + state: true, + props: true, + function: true, + customStateErrors: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "8e3a25df-1a4c-1955-2de9-0922c7419287", + name: "Chat", + tag: "done", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "4154cfe4-07ad-6ffe-71e6-042b331970af", + name: "CollapsibleMenu", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + + { + id: "65a7123f-b096-1bf9-f742-974222fb3ad1", + name: "IconCards", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "d8ce6c1c-70d0-5acf-22e5-28c32579f55d", + name: "DashboardUI", + tag: "done", + props: { + has: { + props: true, + state: false, + function: false, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "36ab7743-5e0c-6fc3-265b-6c30be87bb99", + name: "LineChart", + tag: "done", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "1ed02e1a-2b2d-a95d-6466-ff6ad87f0440", + name: "NumberLabelCard", + tag: "done", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "af8a289e-fa11-70f3-4dd4-496be2429dd3", + name: "PieChart", + tag: "done", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "3cb3b742-ae1a-27cb-46f9-810ebe4780d0", + name: "Stats", + tag: "done", + props: { + has: { + props: false, + state: false, + function: false, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "51818f2a-a1ff-b94f-64c6-67af89d45949", + name: "DateRange", + tag: "done", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "a34f7ae0-8cfe-a6f1-2a91-c10666a46a5c", + name: "DynamicContentType", + tag: "pending", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "ace4d180-4869-bea2-44c8-0e4123d9c5eb", + name: "CategoryFilter", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "36a799ad-59fe-f75e-1a2b-9224849c375f", + name: "EcomHeader", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "6e8bb184-39d0-c873-178e-b609af5071ae", + name: "FeaturedProducts", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "f592ab71-f08b-f4fd-83ba-3e0dfedccd81", + name: "MiniCart", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "654297d4-3fa3-d9a1-1f5d-be6b4e4d6740", + name: "PaymentElementWrapper", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "face801f-fb30-55ba-fc8d-2f29b0328cf7", + name: "ProductCardFlat", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "b2073c5e-3faf-d1c5-77b7-c8dc8592db2e", + name: "ProductCard", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "e0e2c809-d10e-2c3d-26ec-0926aa055d3a", + name: "ProductList", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "1b72e8a8-bbd8-251a-ad67-8553f06edbf1", + name: "ProductQuickView", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "8959db9a-87b1-d1d8-7cca-2bed679fb7fb", + name: "ProductReview", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "37c7ec07-0136-2370-2980-a3c08fa69755", + name: "ProductSearch", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "a0d12209-0ced-a90c-85c4-9825eac3bf2a", + name: "ExportButton", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + // { id: "8ebebc6f-abc7-66bc-cb93-bcac98308112", name: "Header", props: { + // has: { + // props: true, + // state: true, + // function: true, + // context: { global: false, auth: false }, + // } + // } }, + // { + // id: "e9c8a242-7252-588c-96b5-106471ea15be", + // name: "HorizontalNavbar", + // props: {} }, + { + id: "004f486c-a3ce-b869-8033-b6cb90a935c4", + name: "ImagePreviewModal", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "edc22694-df9f-7d8d-bfac-b12b9053d71c", + name: "InfiniteScroll", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "63cb2af1-cfac-8e4d-046c-79b21865ae09", + name: "InteractiveButton", + tag: "done", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "8fe3c80e-4746-9873-51cc-edc0d25c74af", + name: "Loader", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "d5234b27-d5eb-709a-25b4-d3bbf4b7429b", + name: "LoadingIndicator", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + // { id: "6cc4b97a-6024-2dbe-6911-92579a375631", name: "map", props: { + // has: { + // props: true,// + // state: true, + // function: true, + // context: { global: false, auth: false }, + // } + // } }, + { + id: "5133d3f9-1327-d897-3a7b-27c540bbb3e7", + name: "MKDForm", + props: { + has: { + props: true, + state: false, + function: false, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "788f4de0-b921-fd44-45df-2a8f9cf98083", + name: "MkdInput", + tag: "done", + props: { + input_type: "text", + page: "", //Add|Edit|View + name: "", + label: "", + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "4dafc6ce-8ba9-d65b-d405-ffdb3bcc016d", + name: "MkdListTable", + props: { + table: "", //Model + tableRole: "", + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "f3a75064-ac32-3e3a-a494-15b1998bad96", + name: "Modal", + tag: "done", + container: "true", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "51f8130e-f7e4-1e14-4f5d-0791cc6ab3b2", + name: "MultiSelect", + tag: "done", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "98347b3f-6af6-4139-fb72-abc4fce529d2", + name: "PaginationBar", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "3887ed2f-9566-6769-b53f-9a35b0a6af2e", + name: "PortalWrapper", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "995e98e9-8209-a7fa-8a42-6f6508693247", + name: "PublicHeader", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "272bf426-72cf-17ba-94c6-3c5363a1889b", + name: "PublicWrapper", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "b3663fe5-521b-a9cb-b568-09f848ec9a24", + name: "scheduling", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "1e843c7c-33f0-1feb-1ae3-31e34fb72615", + name: "SessionExpiredModal", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "6a0040ff-b922-bca3-45f5-f79e8b5c22fe", + name: "ShedulePostCard", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "e827cdd8-eb0b-73da-feb2-e1ada6bda41e", + name: "SnackBar", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "stripe-plan-cards", + name: "StripePlansComponent", + props: { + title: "Select Plan", + columns: 3, + has: { + props: true, + state: true, + function: true, + context: { + global: false, + auth: false, + }, + }, + }, + }, + { + id: "87407211-9b17-64d4-8337-863c9d318e0f", + name: "StripeCardComponent", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "caf7453f-26d3-c033-b863-df629883de8d", + name: "StripeChargesComponent", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "97743f4e-3a77-a48c-4d4f-8206c3d0998e", + name: "StripeInvoicesComponent", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "40466bcf-95d8-a8e3-829a-8a327eef4b7f", + name: "StripeOnetimeProductsComponent", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "8e7edc69-c3de-5425-e481-44403af762cb", + name: "StripeOrdersComponent", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "4cd2b9c1-cfa4-3dee-d147-6fb3d023f2ac", + name: "StripePaginationBar", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "e3c6cba6-1ae9-ae7f-b530-cf24b63c83c5", + name: "StripePlansComponent", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "dc37f7ee-aedf-022b-db2c-07749748a250", + name: "StripeRegisterSubscribeComponent", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "061bcb01-f04a-a12e-e1c8-059dbe8086ad", + name: "StripeSubscriptionComponent", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "ded04c46-4b36-f18a-4875-80a9ee184ae0", + name: "TopHeader", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "6a4c8b9b-1418-e0f5-a585-3065fa931e60", + name: "Enable2FA", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "76c30259-37e3-7a80-5e8a-da119808669c", + name: "Modal", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "81203bcd-deb7-edb5-4852-41d873dfd9af", + name: "TwoFactorAuthenticate", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { + id: "d96716c2-bccf-4d92-2595-2bfa009d9dd9", + name: "Video", + tag: "done", + props: { + has: { + props: true, + state: true, + function: true, + context: { global: false, auth: false }, + }, + }, + }, + { id: "audio", name: "audio", tag: "", group: "", props: {} }, + { id: "Image", name: "Image", tag: "", group: "", props: {} }, + { id: "video", name: "NativeVideo", tag: "", group: "", props: {} }, + { id: "heading", name: "heading", tag: "", group: "", props: {} }, + { id: "hr", name: "hr", tag: "", group: "", props: {} }, + { id: "br", name: "br", tag: "", group: "", props: {} }, + { id: "link", name: "link", tag: "", group: "", props: {} }, + { id: "p", name: "p", tag: "", group: "", props: {} }, + { id: "span", name: "span", tag: "", group: "", props: {} }, +]; + +export const componentMap = { + "2b5b729e-4a2f-69e2-e575-cb37cb4ebecd": { + name: "AddButton", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "1a13851b-9f6e-aa02-0076-1f84d507181c": { + name: "AddTags", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "0d63aea5-9106-6598-8b94-3bc51cb47a16": { + name: "Editor", + props: { + props: ["content", "handleContentChange", "errors"], + states: [{ name: "content", default: '""' }], + customStateErrors: ["content"], + context: { auth: [], global: [] }, + functions: [...editorFunctions], + }, + }, + "8e3a25df-1a4c-1955-2de9-0922c7419287": { + name: "Chat", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "4154cfe4-07ad-6ffe-71e6-042b331970af": { + name: "CollapsibleMenu", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "1e1cfc67-65ed-ee96-9280-683d7b75ca3b": { + name: "CreateNewRoomModal", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "65a7123f-b096-1bf9-f742-974222fb3ad1": { + name: "IconCards", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "d8ce6c1c-70d0-5acf-22e5-28c32579f55d": { + name: "DashboardUI", + props: { + props: ["navigation", "user", "aside"], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "36ab7743-5e0c-6fc3-265b-6c30be87bb99": { + name: "LineChart", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "1ed02e1a-2b2d-a95d-6466-ff6ad87f0440": { + name: "NumberLabelCard", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "af8a289e-fa11-70f3-4dd4-496be2429dd3": { + name: "PieChart", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "3cb3b742-ae1a-27cb-46f9-810ebe4780d0": { + name: "Stats", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "51818f2a-a1ff-b94f-64c6-67af89d45949": { + name: "DateRange", + props: { + props: ["selectedDayRange", "setSelectedDayRange"], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "a34f7ae0-8cfe-a6f1-2a91-c10666a46a5c": { + name: "DynamicContentType", + props: { + props: ["contentType", "contentValue", "setContentValue"], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "ace4d180-4869-bea2-44c8-0e4123d9c5eb": { + name: "CategoryFilter", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "36a799ad-59fe-f75e-1a2b-9224849c375f": { + name: "EcomHeader", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "6e8bb184-39d0-c873-178e-b609af5071ae": { + name: "FeaturedProducts", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "f592ab71-f08b-f4fd-83ba-3e0dfedccd81": { + name: "MiniCart", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "654297d4-3fa3-d9a1-1f5d-be6b4e4d6740": { + name: "PaymentElementWrapper", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "face801f-fb30-55ba-fc8d-2f29b0328cf7": { + name: "ProductCardFlat", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "b2073c5e-3faf-d1c5-77b7-c8dc8592db2e": { + name: "ProductCard", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "e0e2c809-d10e-2c3d-26ec-0926aa055d3a": { + name: "ProductList", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "1b72e8a8-bbd8-251a-ad67-8553f06edbf1": { + name: "ProductQuickView", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "8959db9a-87b1-d1d8-7cca-2bed679fb7fb": { + name: "ProductReview", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "37c7ec07-0136-2370-2980-a3c08fa69755": { + name: "ProductSearch", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "a0d12209-0ced-a90c-85c4-9825eac3bf2a": { + name: "ExportButton", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "8ebebc6f-abc7-66bc-cb93-bcac98308112": { + name: "Header", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "d2550c78-406e-94e5-55a3-428d1cb05eef": { + name: "HeatMap", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "e9c8a242-7252-588c-96b5-106471ea15be": { + name: "HorizontalNavbar", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "004f486c-a3ce-b869-8033-b6cb90a935c4": { + name: "ImagePreviewModal", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "edc22694-df9f-7d8d-bfac-b12b9053d71c": { + name: "InfiniteScroll", + props: { + props: [ + "data", + "children", + "height", + "next", + "pageSize", + "nextCursor", + "className", + "setPageSize", + "setNextCursor", + "currentCursor", + "setData", + ], + states: [ + { name: "selected", default: "[]" }, + { name: "cursorPaginateData", default: "[]" }, + { name: "nextCursor", default: "null" }, + { name: "currentcursor", default: 0 }, + { name: "pageSize", default: 5 }, + ], + context: { auth: [], global: [] }, + functions: [...infiniteScrollFunctions], + }, + }, + "63cb2af1-cfac-8e4d-046c-79b21865ae09": { + name: "InteractiveButton", + props: { + props: [ + "loading:interactiveButtonLoading", + "disabled", + "type", + "className", + "onClick", + ], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "8fe3c80e-4746-9873-51cc-edc0d25c74af": { + name: "Loader", + props: { + props: ['style:{"":""}'], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "d5234b27-d5eb-709a-25b4-d3bbf4b7429b": { + name: "LoadingIndicator", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "6cc4b97a-6024-2dbe-6911-92579a375631": { + name: "map", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "5133d3f9-1327-d897-3a7b-27c540bbb3e7": { + name: "MKDForm", + props: { + props: ["onSubmit:handleSubmit(onSubmit)", 'className:"w-full max-w-lg"'], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "788f4de0-b921-fd44-45df-2a8f9cf98083": { + name: "MkdInput", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "4dafc6ce-8ba9-d65b-d405-ffdb3bcc016d": { + name: "MkdListTable", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "f3a75064-ac32-3e3a-a494-15b1998bad96": { + name: "Modal", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "51f8130e-f7e4-1e14-4f5d-0791cc6ab3b2": { + name: "MultiSelect", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "98347b3f-6af6-4139-fb72-abc4fce529d2": { + name: "PaginationBar", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "3887ed2f-9566-6769-b53f-9a35b0a6af2e": { + name: "PortalWrapper", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "995e98e9-8209-a7fa-8a42-6f6508693247": { + name: "PublicHeader", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "272bf426-72cf-17ba-94c6-3c5363a1889b": { + name: "PublicWrapper", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "b3663fe5-521b-a9cb-b568-09f848ec9a24": { + name: "scheduling", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "1e843c7c-33f0-1feb-1ae3-31e34fb72615": { + name: "SessionExpiredModal", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "6a0040ff-b922-bca3-45f5-f79e8b5c22fe": { + name: "ShedulePostCard", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "e827cdd8-eb0b-73da-feb2-e1ada6bda41e": { + name: "SnackBar", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "stripe-plan-cards": { name: "StripePlanCards" }, + "87407211-9b17-64d4-8337-863c9d318e0f": { + name: "StripeCardComponent", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "caf7453f-26d3-c033-b863-df629883de8d": { + name: "StripeChargesComponent", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "97743f4e-3a77-a48c-4d4f-8206c3d0998e": { + name: "StripeInvoicesComponent", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "40466bcf-95d8-a8e3-829a-8a327eef4b7f": { + name: "StripeOnetimeProductsComponent", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "8e7edc69-c3de-5425-e481-44403af762cb": { + name: "StripeOrdersComponent", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "4cd2b9c1-cfa4-3dee-d147-6fb3d023f2ac": { + name: "StripePaginationBar", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "e3c6cba6-1ae9-ae7f-b530-cf24b63c83c5": { + name: "StripePlansComponent", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "dc37f7ee-aedf-022b-db2c-07749748a250": { + name: "StripeRegisterSubscribeComponent", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "061bcb01-f04a-a12e-e1c8-059dbe8086ad": { + name: "StripeSubscriptionComponent", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "ded04c46-4b36-f18a-4875-80a9ee184ae0": { + name: "TopHeader", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "6a4c8b9b-1418-e0f5-a585-3065fa931e60": { + name: "Enable2FA", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "76c30259-37e3-7a80-5e8a-da119808669c": { + name: "Enanble2FAModal", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "81203bcd-deb7-edb5-4852-41d873dfd9af": { + name: "TwoFactorAuthenticate", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "893b81b9-5502-4785-1ce4-4ede3a71dae7": { + name: "VideoItem", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "d96716c2-bccf-4d92-2595-2bfa009d9dd9": { + name: "Video", + props: { + props: [], + states: [], + context: { auth: [], global: [] }, + functions: [], + }, + }, + "MTIzLUZpbGVUYWJsZS00NTY=": { name: "MkdFileTable" }, + MTIzLUZpbGVVcGxvYWQtNDU2: { name: "MkdFileUpload" }, + "MTIzLVFyQ29kZUdlbmVyYXRvci00NTY=": { name: "QrCodeGenerator" }, + "MTIzLVFyQ29kZVJlYWRlci00NTY=": { name: "QrCodeReader" }, + "MTIzLU1rZFRhYkNvbnRhaW5lci00NTY=": { name: "MkdTabContainer" }, + "MTIzLU1rZFNpbXBsZVRhYmxlLTQ1Ng==": { name: "MkdSimpleTable" }, + MTIzLU1rZERlYm91bmNlSW5wdXQtNDU2: { name: "MkdDebounceInput" }, + "MTIzLUNhbWVyYVRvVXBsb2FkLTQ1Ng==": { name: "CameraToUpload" }, + "MTIzLU1rZEpzb25RdWl6LTQ1Ng==": { name: "MkdJsonQuiz" }, + "MTIzLU1rZEdyaWRWaWV3LTQ1Ng==": { name: "MkdGridView" }, + MTIzLU1rZFRyZWxsb0NvbHVtbnMtNDU2: { name: "MkdTrelloColumns" }, + "MTIzLU1rZFdpemFyZENvbnRhaW5lci00NTY=": { name: "MkdWizardContainer" }, + MTIzLUJhY2tCdXR0b24tNDU2: { name: "BackButton" }, + "MTIzLUljb25DYXJkcy00NTY=": { name: "IconCards" }, + "MTIzLURhc2hib2FyZFVJLTQ1Ng==": { name: "DashboardUI" }, + "MTIzLUxpbmVDaGFydC00NTY=": { name: "LineChart" }, + "MTIzLU51bWJlckxhYmVsQ2FyZC00NTY=": { name: "NumberLabelCard" }, + "MTIzLVBpZUNoYXJ0LTQ1Ng==": { name: "PieChart" }, + MTIzLUNoYXRCb3QtNDU2: { name: "ChatBot" }, + "MTIzLU11bHRpcGxlQW5zd2VyLTQ1Ng==": { name: "MultipleAnswer" }, + MTIzLVJhdGluZ1N0YXItNDU2: { name: "RatingStar" }, + + audio: { name: "audio" }, + img: { name: "img" }, + video: { name: "NativeVideo" }, + heading: { name: "heading" }, + br: { name: "br" }, + link: { name: "link" }, + p: { name: "p" }, + span: { name: "span" }, +}; + +export const formType = { + login: "login", + signup: "signup", + add: "add", + edit: "edit", + search: "search", + custom: "custom", +}; + +export const colors = { + primary: "#0ea5e9", + signup: "signup", + add: "add", + edit: "edit", + search: "search", + custom: "custom", + secondary: "#F594C9", + lightInfo: "#29282980", +}; + +export default { colors, formType, componentMap, componentList, infiniteScrollFunctions, editorFunctions } diff --git a/day20/src/utils/metadata.json b/day20/src/utils/metadata.json new file mode 100644 index 0000000..177eae1 --- /dev/null +++ b/day20/src/utils/metadata.json @@ -0,0 +1,71 @@ + + { + "/": {"title": "Something else", "description": "Here are a set of free tools to use for you company", "twitter_image": ""}, + "/login": {"title": "Login Title", "description": ""}, + "/account": {"title": "Account Title", "description": ""}, + "/explore": {"title": "Explore Title", "description": ""}, + "/favorites": {"title": "Your Favorites", "description": ""}, + "/faq": {"title": "FAQ", "description": ""}, + "/contact-us": {"title": "Contact Us", "description": ""}, + "/account/my-bookings": {"title": "Bookings", "description": ""}, + "/account/my-spaces": {"title": "My Spaces", "description": ""}, + "/account/profile": {"title": "Profile", "description": ""}, + "/account/payments": {"title": "Payments", "description": ""}, + "/account/billings": {"title": "Billings", "description": ""}, + "/account/reviews": {"title": "Reviews", "description": ""}, + "/account/my-spaces/:id": {"title": "Dynamic", "description": ""}, + "/admin/add-cms": {"title": " admin add-cms", "description": ""}, +"/admin/add-email": {"title": " admin add-email", "description": ""}, +"/admin/add-photo": {"title": " admin add-photo", "description": ""}, +"/admin/cms": {"title": " admin cms", "description": ""}, +"/admin/email": {"title": " admin email", "description": ""}, +"/admin/photo": {"title": " admin photo", "description": ""}, +"/admin/edit-cms/:id": {"title": " admin edit-cms :id", "description": ""}, +"/admin/edit-email/:id": {"title": " admin edit-email :id", "description": ""}, +"/magic-login/:role": {"title": " magic-login :role", "description": ""}, +"/magic-login/verify": {"title": " magic-login verify", "description": ""}, +"/admin/login": {"title": " admin login", "description": ""}, +"/admin/signup": {"title": " admin signup", "description": ""}, +"/admin/profile": {"title": " admin profile", "description": ""}, +"/public/login": {"title": " public login", "description": ""}, +"/public/signup": {"title": " public signup", "description": ""}, +"/public/profile": {"title": " public profile", "description": ""}, +"/member/login": {"title": " member login", "description": ""}, +"/member/signup": {"title": " member signup", "description": ""}, +"/member/profile": {"title": " member profile", "description": ""}, +"/admin/dashboard": {"title": " admin dashboard", "description": ""}, +"/admin/department": {"title": "admin department", "description": ""}, +"/admin/add-department": {"title": "admin add department", "description": ""}, +"/admin/edit-department": {"title": "admin edit department", "description": ""}, +"/admin/view-department": {"title": "admin view department", "description": ""}, +"/admin/location": {"title": "admin location", "description": ""}, +"/admin/add-location": {"title": "admin add location", "description": ""}, +"/admin/edit-location": {"title": "admin edit location", "description": ""}, +"/admin/view-location": {"title": "admin view location", "description": ""}, +"/admin/items": {"title": "admin items", "description": ""}, +"/admin/add-items": {"title": "admin add items", "description": ""}, +"/admin/edit-items": {"title": "admin edit items", "description": ""}, +"/admin/view-items": {"title": "admin view items", "description": ""}, +"/admin/item_movement_history": {"title": "admin item_movement_history", "description": ""}, +"/admin/add-item_movement_history": {"title": "admin add item_movement_history", "description": ""}, +"/admin/edit-item_movement_history": {"title": "admin edit item_movement_history", "description": ""}, +"/admin/view-item_movement_history": {"title": "admin view item_movement_history", "description": ""}, +"/admin/user": {"title": "admin user", "description": ""}, +"/admin/add-user": {"title": "admin add user", "description": ""}, +"/admin/edit-user": {"title": "admin edit user", "description": ""}, +"/admin/view-user": {"title": "admin view user", "description": ""}, +"/member/forgot": {"title": " member forgot", "description": ""}, +"/member/reset": {"title": " member reset", "description": ""}, +"/member/dashboard": {"title": " member dashboard", "description": ""}, +"/admin/forgot": {"title": " admin forgot", "description": ""}, +"/admin/reset": {"title": " admin reset", "description": ""}, +"/admin/dashboard": {"title": " admin dashboard", "description": ""}, +"/admin/users": {"title": "admin users", "description": ""}, +"/admin/add-user": {"title": "admin add user", "description": ""}, +"/admin/edit-user/:id": {"title": " admin edit-user :id", "description": ""}, + "": { + "title": "404 Page not found", + "description": "Oops. Looks like this page doesn't exists" + } + +} diff --git a/day20/src/utils/utils.jsx b/day20/src/utils/utils.jsx new file mode 100644 index 0000000..320de34 --- /dev/null +++ b/day20/src/utils/utils.jsx @@ -0,0 +1,463 @@ + + import prettier from "prettier/standalone"; +import parserBabel from "prettier/parser-babel"; + +export function classNames(...classes) { + return classes.filter(Boolean).join(" "); +} + +export const getNonNullValue = (value) => { + if (value != "") { + return value; + } else { + return undefined; + } +}; + +export function filterEmptyFields(object) { + Object.keys(object).forEach((key) => { + if (empty(object[key])) { + delete object[key]; + } + }); + return object; +} + +export function empty(value) { + return ( + value === "" || + value === null || + value === undefined || + value === "undefined" + ); +} + +export const isImage = (file) => { + const validImageTypes = ["image/gif", "image/jpeg", "image/jpg", "image/png"]; + if (validImageTypes.includes(file.file.type)) return true; + return false; +}; + +export const isVideo = (file) => { + const validVideoTypes = ["video/webm", "video/mp4"]; + if (validVideoTypes.includes(file.file.type)) return true; + return false; +}; + +export const isPdf = (file) => { + const validVideoTypes = ["application/pdf"]; + if (validVideoTypes.includes(file.file.type)) return true; + return false; +}; + +export const randomString = (length) => { + let result = ""; + let characters = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +}; + +export const generateUUID = () => { + const s4 = () => { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + }; + + return ( + s4() + + s4() + + "-" + + s4() + + "-" + + s4() + + "-" + + s4() + + "-" + + s4() + + s4() + + s4() + ); +}; + +export const capitalize = (string) => { + const removedSpecialCharacters = string.replace(/[^a-zA-Z0-9]/g, " "); + + const splitWords = removedSpecialCharacters.split(" ").filter(Boolean); + + const capitalized = splitWords.map( + (dt) => `${ dt[0].toUpperCase() }${ dt.substring(1) } ` + ); + + return capitalized.join(" "); +}; + +export const dateHandle = (date) => { + const newDate = date + ? new Date(date).toISOString().split("T")[0] + : new Date().toISOString().split("T")[0]; + return newDate; +}; + +export const ghrapDate = (date) => { + const newDate = new Date(date); + var mS = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "June", + "July", + "Aug", + "Sept", + "Oct", + "Nov", + "Dec", + ]; + console.log(newDate.getDate(), mS[newDate.getMonth()]); + + return `${ newDate.getDate() } ${ mS[newDate.getMonth()] } `; +}; + +export const formatCode = function (code) { + return prettier.format(code, { + parser: "babel", + plugins: [parserBabel], + singleQuote: true, + trailingComma: "es5", + jsxSingleQuote: true, + printWidth: 80, + tabWidth: 2, + }); +}; + +export const slugify = (str) => + str + .toLowerCase() + .trim() + .replace(/[^ws-]/g, "") + .replace(/[s_-]+/g, "-") + .replace(/^-+|-+$/g, ""); + +/** + * @typedef {Object} StringCaserOptions + * @property {"space" | String} separator - define what separates each word, undefined returns no separation - passing "space" separates the words by a space + * @property {"uppercase" | "lowercase" | "capitalize" | "camelCase" | "PascalCase"} casetype - text case type, uppercase, lowercase of capitalized | default is lowercase + */ +/** + * + * @param {String} string - text to convert + * @param {StringCaserOptions} options - options + * @returns + */ +export const StringCaser = (string, options) => { + if (!string) return null; + if (typeof string !== "string") return null; + const removedSpecialCharacters = string.replace(/[^a-zA-Z0-9]/g, " "); + let casedText = []; + const splitWords = removedSpecialCharacters.split(" ").filter(Boolean); + + if (options?.casetype === "capitalize") { + casedText = splitWords.map( + (/** @type {string} */ dt) => `${ dt[0].toUpperCase() }${ dt.substring(1) } ` + ); + } + if (options?.casetype === "uppercase") { + casedText = splitWords.map((/** @type {string} */ dt) => dt.toUpperCase()); + } + if (options?.casetype === "lowercase") { + casedText = splitWords.map((/** @type {string} */ dt) => dt.toLowerCase()); + } + if (options?.casetype === "camelCase") { + casedText = splitWords.map((/** @type {string} */ dt, index) => + index === 0 + ? dt.toLowerCase() + : `${ dt[0].toUpperCase() }${ dt.substring(1) } ` + ); + } + if (options?.casetype === "PascalCase") { + casedText = splitWords.map( + (/** @type {string} */ dt) => `${ dt[0].toUpperCase() }${ dt.substring(1) }` + ); + } + + if (options?.separator) { + if (options?.separator === "space") { + return casedText.join(" "); + } else { + return casedText.join(options?.separator); + } + } else { + return casedText.join(""); + } +}; + +export const testColumns = [ + { + header: "Action", + show: true, + accessor: "", + }, + + { + header: "Id", + accessor: "id", + show: false, + isSorted: false, + isSortedDesc: false, + mappingExist: false, + mappings: {}, + }, + + { + header: "User Id", + accessor: "user_id", + show: false, + isSorted: false, + isSortedDesc: false, + mappingExist: false, + mappings: {}, + }, + + { + header: "First Name", + accessor: "first_name", + show: true, + isSorted: false, + isSortedDesc: false, + mappingExist: false, + mappings: {}, + }, + + { + header: "Last Name", + accessor: "last_name", + show: true, + isSorted: false, + isSortedDesc: false, + mappingExist: false, + mappings: {}, + }, + { + header: "Email", + accessor: "email", + show: true, + isSorted: false, + isSortedDesc: false, + mappingExist: false, + mappings: {}, + }, + { + header: "Role", + accessor: "role", + show: true, + isSorted: false, + isSortedDesc: false, + mappingExist: true, + mappings: { + admin: "Admin", + employee: "Employee", + }, + }, + { + header: "Photo", + accessor: "photo", + show: true, + isSorted: false, + isSortedDesc: false, + mappingExist: false, + mappings: {}, + }, + { + header: "Phone", + accessor: "phone", + show: true, + isSorted: false, + isSortedDesc: false, + mappingExist: false, + mappings: {}, + }, + { + header: "Status", + accessor: "status", + show: false, + isSorted: false, + isSortedDesc: false, + mappingExist: true, + mappings: { 0: "pending", 1: "approved" }, + }, + { + header: "Type", + accessor: "type", + show: false, + isSorted: false, + isSortedDesc: false, + mappingExist: true, + mappings: { + 0: "normal", + 1: "facebook", + 2: "google", + }, + }, + { + header: "Verify", + accessor: "verify", + show: false, + isSorted: false, + isSortedDesc: false, + mappingExist: true, + mappings: { + 0: "not verified", + 1: "verified", + }, + }, + + { + header: "Create At", + accessor: "create_at", + show: false, + isSorted: false, + isSortedDesc: false, + mappingExist: false, + mappings: {}, + }, + + { + header: "Update At", + accessor: "update_at", + show: false, + isSorted: false, + isSortedDesc: false, + mappingExist: false, + mappings: {}, + }, +]; + + +export const exceptionalHtmlElements = [ + "audio", + "image", + "img", + "heading", + "video", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "hr", + "br", + "a", + "p", + "link", + "span", +]; +export const excludedHtmlElements = [ + "abbr", + "address", + "area", + "article", + "aside", + "b", + "base", + "bdi", + "bdo", + "blockquote", + "body", + "button", + "canvas", + "caption", + "cite", + "code", + "col", + "colgroup", + "data", + "datalist", + "dd", + "del", + "details", + "dfn", + "dialog", + "div", + "dl", + "dt", + "em", + "embed", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "head", + "header", + "hgroup", + "html", + "i", + "iframe", + "input", + "ins", + "kbd", + "label", + "legend", + "li", + // "link", + "main", + "map", + "mark", + "meta", + "meter", + "nav", + "noscript", + "object", + "ol", + "optgroup", + "option", + "output", + // "p", + "param", + "picture", + "pre", + "progress", + "q", + "rb", + "rp", + "rt", + "rtc", + "ruby", + "s", + "samp", + "script", + "section", + "select", + "slot", + "small", + "source", + "strong", + "style", + "sub", + "summary", + "sup", + "table", + "tbody", + "td", + "template", + "textarea", + "tfoot", + "th", + "thead", + "time", + "title", + "tr", + "track", + "u", + "ul", + "var", + "wbr", +] + \ No newline at end of file diff --git a/day20/tailwind.config.js b/day20/tailwind.config.js new file mode 100644 index 0000000..49bd233 --- /dev/null +++ b/day20/tailwind.config.js @@ -0,0 +1,28 @@ + + + /** @type {import('tailwindcss').Config} */ + module.exports = { + content: [ + "./src/**/*.{js,jsx,ts,tsx}", + "./node_modules/tw-elements/dist/js/**/*.js", + ], + theme: { + extend: { + colors: { + primaryBlue: "#4F46E5", + "primary-light": "#4F46E550", + }, + + keyframes: { + wiggle: { + "0%, 100%": { transform: "rotate(-3deg)" }, + "50%": { transform: "rotate(3deg)" }, + }, + }, + animation: { + wiggle: "wiggle 200ms ease-in-out", + }, + }, + }, + plugins: [require("tw-elements/dist/plugin"), require("@tailwindcss/forms")], + }; diff --git a/day20/vite.config.js b/day20/vite.config.js new file mode 100644 index 0000000..2f8d5da --- /dev/null +++ b/day20/vite.config.js @@ -0,0 +1,72 @@ + + import fs from "fs"; + import path from "path"; + import { fileURLToPath } from "url"; + import { defineConfig } from "vite"; + import react from "@vitejs/plugin-react"; + + import viteCompression from "vite-plugin-compression"; + + const dirname = path.dirname(fileURLToPath(import.meta.url)); + + const { dependencies } = JSON.parse( + fs.readFileSync(path.join(dirname, "package.json")) + ); + + const vendorPackages = [ + "react", + "react-router-dom", + "react-router", + "react-dom", + ]; + + function renderChunks(deps) { + let chunks = {}; + Object.keys(deps).forEach(key => { + if (vendorPackages.includes(key)) return; + chunks[key] = [key]; + }); + return chunks; + } + + export const OUTPUT_DIRECTORY = "dist"; + + // https://vitejs.dev/config/ + export default defineConfig({ + plugins: [ + react(), + + viteCompression({ + algorithm: "brotliCompress", + filter: /.(js|mjs|json|css|html|svg)$/i, + }), + ], + build: { + outDir: OUTPUT_DIRECTORY, + sourcemap: false, + rollupOptions: { + external: ["fsevents"], + output: { + manualChunks: { + vendor: vendorPackages, + ...renderChunks(dependencies), + }, + }, + }, + }, + resolve: { + alias: { + Components: path.resolve(dirname, "./src/components"), + Pages: path.resolve(dirname, "./src/pages"), + Utils: path.resolve(dirname, "./src/utils"), + Assets: path.resolve(dirname, "./src/assets"), + Context: path.resolve(dirname, "./src/context"), + Routes: path.resolve(dirname, "./src/routes"), + Hooks: path.resolve(dirname, "./src/hooks"), + Src: path.resolve(dirname, "./src"), + }, + }, + server: { + port: 3000, + }, + });