Update | Project Ready
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
@@ -0,0 +1,130 @@
|
||||
# PARADISE DOCK BUILDER
|
||||
|
||||
## SETUP Process
|
||||
|
||||
- You are expected to clone this repository and set up a new repo remotely on ur github with the code (Preferably name the repo with your name. E.g john_doe_paradise_dock_builder_solution)
|
||||
To set up locally
|
||||
|
||||
- **a.** clone the repo to your local machine
|
||||
- **a.** update remote origin
|
||||
- **b.** install dependencies (using -- "npm install")
|
||||
- **b.** run locally on your computer using this command - "npm run dev"
|
||||
|
||||
- Make sure to commit each fix with a valid message entailing what issue was fixed in the commit. This will help when grading your solution
|
||||
|
||||
- When you are done, cross check all fixes and then submit the repo that has your solutions.
|
||||
|
||||
- Any commit after your submmission will be discarded and won't be graded.
|
||||
|
||||
- PLEASE make sure to deploy the app to a live server, to allow full test by the Development team and the Quality Assurance Team (preferably netlify or vercel)
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Dock Category](#dock-category)
|
||||
2. [Dock Data](#dock-data)
|
||||
3. [Issues](#issues)
|
||||
|
||||
## Dock Category
|
||||
|
||||
- **Dock Category** is a category of the docks.
|
||||
- **Dock Category** can be one of the following:
|
||||
- **ROLL-IN**
|
||||
- **FLOATING**
|
||||
- **SECTIONAL**
|
||||
- **WEDGES**
|
||||
- **RAMPS**
|
||||
- **BOAT LIFE LIFTS (2 CYLINDER)**
|
||||
- **BOAT LIFE LIFTS (4 CYLINDER)**
|
||||
- **ACCESSORIES**
|
||||
|
||||
## Dock Data
|
||||
|
||||
- **Dock Data** is the collection of all the docks information
|
||||
- docks is as follows: {
|
||||
itemName: activeDockCategory (ROLL-IN, FLOATING, SECTIONAL, WEDGES, RAMPS, BOAT LIFTS (2 CYLINDER), BOAT LIFTS (4 CYLINDER), ACCESSORIES)
|
||||
image: dock?.image,
|
||||
category: category,
|
||||
length: dock?.length,
|
||||
materials: materials,
|
||||
top_view: dock?.top_view,
|
||||
width: dock?.width,
|
||||
lift_range: dock?.lift_range,
|
||||
model: dock?.model,
|
||||
no_of_cylinders: dock?.no_of_cylinders,
|
||||
name: dock?.name,
|
||||
thumbnail: dock?.thumbnail,
|
||||
weight_capacity: dock?.weight_capacity
|
||||
};
|
||||
- this information is to be attached to every dock that is added into the canvas editor as `dockData`
|
||||
|
||||
#### The Issues Are Listed Below
|
||||
|
||||
1. On the sidebar, each dock is listed as tablets with names and dimensions or size, and when you click on a dock, it will is added to the canvas editor
|
||||
|
||||
Now the issue here is that is doesn't work
|
||||
|
||||
Please fix this issue. (Tip - An onDockSelect handles this in Builder.jsx file)
|
||||
|
||||
2. CopySelection Functionality is not working
|
||||
Expected Behavior: When you click on the copy button, the selected object in the canvas editor should be copied to the clipboard
|
||||
Actual Behavior: The selected object is not copied to the clipboard
|
||||
|
||||
Please fix this issue.
|
||||
|
||||
3. PasteSelection Functionality is not working
|
||||
Expected Behavior: When you click on the paste button, the selected object in the clipboard should be pasted to the canvas editor
|
||||
Actual Behavior: The selected object is not pasted to the canvas editor
|
||||
|
||||
Please fix this issue.
|
||||
|
||||
4. onRedoClick Functionality is not working
|
||||
Expected Behavior: When you click on the redo button, the last undone action should be redone
|
||||
Actual Behavior: The last undone action is not redone
|
||||
|
||||
Please fix this issue.
|
||||
|
||||
5. onUndoClick Functionality is not working
|
||||
Expected Behavior: When you click on the undo button, the last action should be undone
|
||||
Actual Behavior: The last action is not undone
|
||||
|
||||
Please fix this issue.
|
||||
|
||||
6. onPrintScreen Functionality is not working
|
||||
Expected Behavior: When you click on the print screen button, the canvas editor should be printed
|
||||
Actual Behavior: The canvas editor is not printed
|
||||
|
||||
Please fix this issue.
|
||||
|
||||
7. onDeleteSelection Functionality is not working
|
||||
Expected Behavior: When you click on the delete button, the selected object in the canvas editor should be deleted
|
||||
Actual Behavior: The selected object is not deleted
|
||||
|
||||
Please fix this issue.
|
||||
|
||||
8. on the sidebar the downloadImage function is not working
|
||||
|
||||
Expected Behavior: When you click on the download button, the selected dock should be downloaded as a png file and also trigger the download of the dockData you extracted in excel format
|
||||
Actual Behavior: The selected dock is not downloaded as a png file
|
||||
|
||||
Please fix this issue.
|
||||
|
||||
9. on the sidebar onDownloadFile triggers toJSON function
|
||||
|
||||
Expected Behavior: When you click on the download button, the canvas editor content is downloaded as a json file
|
||||
Actual Behavior: Nothing happens when you click on the download button
|
||||
|
||||
Please fix this issue.
|
||||
|
||||
10. on the sidebar, the onUploadFile triigers uploadFile function
|
||||
|
||||
Expected Behavior: When you click on the upload button, the json file we had previously downloaded should be uploaded, its data extracted and loaded into the editor
|
||||
Actual Behavior: Nothing happens when you click on the upload button
|
||||
|
||||
Please fix this issue.
|
||||
|
||||
11. Finally, objects in the canvas editor of the category DockPanelCategories.Accessories, DockPanelCategories.BoatLift2, DockPanelCategories.BoatLift4, when being moved, should snap to the nearest snap point of other objects of any the above listed categories
|
||||
|
||||
Expected Behavior: When you move an object in the canvas editor of the category DockPanelCategories.Accessories, DockPanelCategories.BoatLift2, DockPanelCategories.BoatLift4, it should snap to the nearest snap point of other objects of any the above listed categories
|
||||
Actual Behavior: Objects in the canvas editor of the category DockPanelCategories.Accessories, DockPanelCategories.BoatLift2, DockPanelCategories.BoatLift4 are not snapping to the nearest snap point of other objects of any the above listed categories
|
||||
|
||||
Please fix this issue.
|
||||
@@ -0,0 +1,52 @@
|
||||
-- Adminer 4.6.2 MySQL dump
|
||||
|
||||
SET NAMES utf8;
|
||||
SET time_zone = '+00:00';
|
||||
SET foreign_key_checks = 0;
|
||||
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
|
||||
|
||||
DROP TABLE IF EXISTS `paralift_boat_lifts`;
|
||||
CREATE TABLE `paralift_boat_lifts` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`create_at` date NOT NULL,
|
||||
`update_at` datetime NOT NULL,
|
||||
`model` varchar(255) NOT NULL,
|
||||
`weight_capacity` float NOT NULL,
|
||||
`lift_range` int(11) NOT NULL,
|
||||
`no_of_cylinders` int(11) NOT NULL,
|
||||
`length` float DEFAULT NULL,
|
||||
`width` float DEFAULT NULL,
|
||||
`image` longtext NOT NULL,
|
||||
`top_view` longtext NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
INSERT INTO `paralift_boat_lifts` (`id`, `create_at`, `update_at`, `model`, `weight_capacity`, `lift_range`, `no_of_cylinders`, `length`, `width`, `image`, `top_view`) VALUES
|
||||
(8, '2022-11-17', '2022-11-17 18:25:37', 'PH-2K-4', 2000, 4, 2, 14.84, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/2%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-6%2C8K-4%2C5%2C6.png'),
|
||||
(9, '2022-11-17', '2022-11-17 18:28:51', 'PH-2K-5', 2000, 5, 2, 14.84, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/2%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-6%2C8K-4%2C5%2C6.png'),
|
||||
(10, '2022-11-17', '2022-11-17 18:27:02', 'PH-2K-6', 2000, 6, 2, 14.84, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/2%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-6%2C8K-4%2C5%2C6.png'),
|
||||
(11, '2022-11-17', '2022-11-17 18:27:29', 'PH-6.5K-4', 6500, 4, 2, 14.84, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/2%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-6%2C8K-4%2C5%2C6.png'),
|
||||
(12, '2022-11-17', '2022-11-17 18:27:57', 'PH-6K-5', 6000, 5, 2, 14.84, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/2%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-6%2C8K-4%2C5%2C6.png'),
|
||||
(13, '2022-11-17', '2022-11-17 18:28:32', 'PH-6K-6', 6000, 6, 2, 14.84, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/2%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-6%2C8K-4%2C5%2C6.png'),
|
||||
(14, '2022-11-17', '2022-11-17 18:29:25', 'PH-8.5K-4', 8500, 4, 2, 14.84, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/2%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-6%2C8K-4%2C5%2C6.png'),
|
||||
(15, '2022-11-17', '2022-11-17 18:29:53', 'PH-8K-5', 8000, 5, 2, 14.84, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/2%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-6%2C8K-4%2C5%2C6.png'),
|
||||
(16, '2022-11-17', '2022-11-17 18:33:12', 'PH-8K-6', 8000, 6, 4, 14.84, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-6%2C8K-4%2C5%2C6.png'),
|
||||
(17, '2022-11-17', '2022-11-17 18:37:36', 'PH-11K-4S', 1100, 4, 4, 16.85, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-10%2C12K-4%2C5%2C6.png'),
|
||||
(20, '2022-11-17', '2022-11-17 18:55:35', 'PH-11K-4', 11000, 4, 4, 16.85, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-10%2C12K-4%2C5%2C6.png'),
|
||||
(21, '2022-11-17', '2022-11-17 18:56:18', 'PH-10K-5', 10000, 5, 4, 16.85, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-10%2C12K-4%2C5%2C6.png'),
|
||||
(22, '2022-11-17', '2022-11-17 18:57:25', 'PH-10K-6', 10000, 6, 4, 16.85, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-10%2C12K-4%2C5%2C6.png'),
|
||||
(23, '2022-11-17', '2022-11-17 18:59:12', 'PH-13K-4', 13000, 4, 4, 16.85, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-10%2C12K-4%2C5%2C6.png'),
|
||||
(24, '2022-11-17', '2022-11-17 18:59:39', 'PH-12K-5', 12000, 5, 4, 16.85, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-10%2C12K-4%2C5%2C6.png'),
|
||||
(25, '2022-11-17', '2022-11-17 19:00:36', 'PH-12K-6', 12000, 6, 4, 16.85, 11.3, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-10%2C12K-4%2C5%2C6.png'),
|
||||
(26, '2022-11-17', '2022-11-17 19:05:57', 'PH-15K-4', 15000, 4, 4, 20.95, 12.41, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-15K-4%2C5%2C6.png'),
|
||||
(27, '2022-11-17', '2022-11-17 19:06:47', 'PH-15K-5', 15000, 5, 4, 20.95, 12.41, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-15K-4%2C5%2C6.png'),
|
||||
(28, '2022-11-17', '2022-11-17 19:07:28', 'PH-15K-6', 15000, 6, 4, 20.95, 12.41, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-15K-4%2C5%2C6.png'),
|
||||
(29, '2022-11-17', '2022-11-17 19:08:30', 'PH-20K-4', 20000, 4, 4, 22.86, 12.41, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-20K-4%2C5%2C6.png'),
|
||||
(30, '2022-11-17', '2022-11-17 19:09:09', 'PH-20K-5', 20000, 5, 4, 22.86, 12.41, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-20K-4%2C5%2C6.png'),
|
||||
(31, '2022-11-17', '2022-11-17 19:10:21', 'PH-20K-6', 20000, 6, 4, 22.86, 12.41, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-20K-4%2C5%2C6.png'),
|
||||
(32, '2022-11-17', '2022-11-17 19:11:09', 'PH-24K-4', 24000, 4, 4, 25.03, 12.41, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-24K-4%2C5%2C6.png'),
|
||||
(33, '2022-11-17', '2022-11-17 19:12:07', 'PH-24K-5', 24000, 5, 4, 25.03, 12.41, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-24K-4%2C5%2C6.png'),
|
||||
(34, '2022-11-17', '2022-11-17 19:12:37', 'PH-24K-6', 24000, 6, 4, 25.03, 12.41, 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/4%20Cylinder%20Boat%20Lift-min.png', 'https://s3.us-west-1.amazonaws.com/com.mkdlab.images/PH-24K-4%2C5%2C6.png')
|
||||
ON DUPLICATE KEY UPDATE `id` = VALUES(`id`), `create_at` = VALUES(`create_at`), `update_at` = VALUES(`update_at`), `model` = VALUES(`model`), `weight_capacity` = VALUES(`weight_capacity`), `lift_range` = VALUES(`lift_range`), `no_of_cylinders` = VALUES(`no_of_cylinders`), `length` = VALUES(`length`), `width` = VALUES(`width`), `image` = VALUES(`image`), `top_view` = VALUES(`top_view`);
|
||||
|
||||
-- 2025-04-02 18:13:38
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||
<meta name="viewport"
|
||||
content="width=desktop-width, initial-scale=.5, user-scalable=no maximum-scale=1.0, user-scalable=no shrink-to-fit=no" />
|
||||
|
||||
<title>Paradise Dock</title>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.jsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"Components/*": [
|
||||
"src/components/*"
|
||||
],
|
||||
"Pages/*": [
|
||||
"src/pages/*"
|
||||
],
|
||||
"Utils/*": [
|
||||
"src/utils/*"
|
||||
],
|
||||
"Assets/*": [
|
||||
"src/assets/*"
|
||||
],
|
||||
"Src/*": [
|
||||
"src/*"
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"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",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/rajdhani": "^4.5.10",
|
||||
"@heroicons/react": "^2.0.12",
|
||||
"@hookform/resolvers": "^2.8.10",
|
||||
"@stripe/react-stripe-js": "^1.9.0",
|
||||
"@stripe/stripe-js": "^1.32.0",
|
||||
"@uppy/aws-s3": "^2.1.0",
|
||||
"@uppy/core": "^2.2.0",
|
||||
"@uppy/dashboard": "^2.1.4",
|
||||
"@uppy/drag-drop": "^2.1.0",
|
||||
"@uppy/dropbox": "^2.0.5",
|
||||
"@uppy/google-drive": "^2.0.5",
|
||||
"@uppy/onedrive": "^2.0.6",
|
||||
"@uppy/react": "^2.2.0",
|
||||
"@uppy/tus": "^2.3.0",
|
||||
"@uppy/xhr-upload": "^2.1.0",
|
||||
"bootstrap": "^5.2.2",
|
||||
"fabric": "5.2.4",
|
||||
"fabricjs-react": "1.0.8",
|
||||
"framer-motion": "^10.12.4",
|
||||
"moment": "^2.29.3",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-hook-form": "^7.34.2",
|
||||
"react-loading-skeleton": "^3.1.0",
|
||||
"react-router": "^6.2.2",
|
||||
"react-router-dom": "^6.2.2",
|
||||
"tw-elements": "^1.0.0-alpha12",
|
||||
"uppy": "^2.9.1",
|
||||
"xlsx": "^0.18.5",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@vitejs/plugin-react": "^1.3.0",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"postcss": "^8.4.14",
|
||||
"tailwindcss": "^3.0.24",
|
||||
"vite": "^2.9.9"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
const a = {
|
||||
authentication: {
|
||||
login: true,
|
||||
register: false,
|
||||
forgot: true,
|
||||
reset: true,
|
||||
lambdaLogin: true,
|
||||
lambdaRegister: true,
|
||||
lambdatwofa: true,
|
||||
lambdaFacebook: true,
|
||||
lambdaGoogle: true,
|
||||
lambdaPreference: true,
|
||||
lambdaProfile: true,
|
||||
lambdaRealtime: true,
|
||||
lambdaReset: true,
|
||||
lambdaSend_Mail: true,
|
||||
lambdaUpdate_Email: false,
|
||||
lambdaUpdate_Password: true,
|
||||
lambdaUpload: true,
|
||||
lambdaForgot: true,
|
||||
lambdaEmail: true,
|
||||
"lambdaVerify-email": true,
|
||||
lambdaVerify_user: true,
|
||||
lambdaApple: true,
|
||||
lambdaCaptcha: true
|
||||
},
|
||||
models: {
|
||||
user: {
|
||||
POST: true,
|
||||
PUT: true,
|
||||
GET: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
AUTOCOMPLETE: true,
|
||||
IMPORT: false,
|
||||
GETALL: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
token: {
|
||||
POST: false,
|
||||
PUT: false,
|
||||
GET: false,
|
||||
DELETE: false,
|
||||
EXPORT: false,
|
||||
AUTOCOMPLETE: false,
|
||||
IMPORT: false,
|
||||
GETALL: false
|
||||
},
|
||||
photo: {
|
||||
POST: true,
|
||||
PUT: true,
|
||||
GET: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
AUTOCOMPLETE: true,
|
||||
IMPORT: true,
|
||||
GETALL: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
email: {
|
||||
POST: true,
|
||||
PUT: true,
|
||||
GET: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
AUTOCOMPLETE: true,
|
||||
IMPORT: true,
|
||||
GETALL: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
profile: {
|
||||
POST: true,
|
||||
PUT: true,
|
||||
GET: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
AUTOCOMPLETE: true,
|
||||
IMPORT: true,
|
||||
GETALL: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
permission: {
|
||||
POST: true,
|
||||
PUT: true,
|
||||
GET: true,
|
||||
DELETE: true,
|
||||
EXPORT: false,
|
||||
AUTOCOMPLETE: false,
|
||||
IMPORT: false,
|
||||
GETALL: true
|
||||
},
|
||||
room: {
|
||||
POST: true,
|
||||
PUT: true,
|
||||
GET: true,
|
||||
DELETE: false,
|
||||
EXPORT: false,
|
||||
AUTOCOMPLETE: false,
|
||||
IMPORT: false,
|
||||
GETALL: true
|
||||
},
|
||||
chat: {
|
||||
POST: true,
|
||||
PUT: true,
|
||||
GET: true,
|
||||
DELETE: false,
|
||||
EXPORT: false,
|
||||
AUTOCOMPLETE: false,
|
||||
IMPORT: false,
|
||||
GETALL: true
|
||||
},
|
||||
accessories: {
|
||||
AUTOCOMPLETE: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
GET: true,
|
||||
GETALL: true,
|
||||
IMPORT: true,
|
||||
POST: true,
|
||||
PUT: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
trigger_rules: {
|
||||
AUTOCOMPLETE: false,
|
||||
DELETE: false,
|
||||
EXPORT: false,
|
||||
GET: false,
|
||||
GETALL: false,
|
||||
IMPORT: false,
|
||||
POST: false,
|
||||
PUT: false,
|
||||
PUTWHERE: false
|
||||
},
|
||||
boat_lifts: {
|
||||
AUTOCOMPLETE: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
GET: true,
|
||||
GETALL: true,
|
||||
IMPORT: true,
|
||||
POST: true,
|
||||
PUT: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
dealers: {
|
||||
AUTOCOMPLETE: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
GET: true,
|
||||
GETALL: true,
|
||||
IMPORT: true,
|
||||
POST: true,
|
||||
PUT: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
docks: {
|
||||
AUTOCOMPLETE: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
GET: true,
|
||||
GETALL: true,
|
||||
IMPORT: true,
|
||||
POST: true,
|
||||
PUT: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
analytic_log: {
|
||||
AUTOCOMPLETE: false,
|
||||
DELETE: false,
|
||||
EXPORT: false,
|
||||
GET: false,
|
||||
GETALL: false,
|
||||
IMPORT: false,
|
||||
POST: false,
|
||||
PUT: false,
|
||||
PUTWHERE: false
|
||||
},
|
||||
cms: {
|
||||
AUTOCOMPLETE: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
GET: true,
|
||||
GETALL: true,
|
||||
IMPORT: true,
|
||||
POST: true,
|
||||
PUT: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
trigger_type: {
|
||||
AUTOCOMPLETE: false,
|
||||
DELETE: false,
|
||||
EXPORT: false,
|
||||
GET: false,
|
||||
GETALL: false,
|
||||
IMPORT: false,
|
||||
POST: false,
|
||||
PUT: false,
|
||||
PUTWHERE: false
|
||||
},
|
||||
instructions: {
|
||||
AUTOCOMPLETE: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
GET: true,
|
||||
GETALL: true,
|
||||
IMPORT: true,
|
||||
POST: true,
|
||||
PUT: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
quotes: {
|
||||
AUTOCOMPLETE: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
GET: true,
|
||||
GETALL: true,
|
||||
IMPORT: true,
|
||||
POST: true,
|
||||
PUT: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
wedges: {
|
||||
AUTOCOMPLETE: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
GET: true,
|
||||
GETALL: true,
|
||||
IMPORT: true,
|
||||
POST: true,
|
||||
PUT: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
ramps: {
|
||||
AUTOCOMPLETE: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
GET: true,
|
||||
GETALL: true,
|
||||
IMPORT: true,
|
||||
POST: true,
|
||||
PUT: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
quotes_mail_recipients: {
|
||||
AUTOCOMPLETE: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
GET: true,
|
||||
GETALL: true,
|
||||
IMPORT: true,
|
||||
POST: true,
|
||||
PUT: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
},
|
||||
reference_items: {
|
||||
AUTOCOMPLETE: true,
|
||||
DELETE: true,
|
||||
EXPORT: true,
|
||||
GET: true,
|
||||
GETALL: true,
|
||||
IMPORT: true,
|
||||
POST: true,
|
||||
PUT: true,
|
||||
PUTWHERE: true,
|
||||
PAGINATE: true
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import React, { useEffect } from "react";
|
||||
import AuthProvider from "./authContext";
|
||||
import GlobalProvider from "./globalContext";
|
||||
import Main from "./main";
|
||||
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 () {
|
||||
|
||||
useEffect( () => {
|
||||
const handleTabReload = ( e ) => {
|
||||
e.preventDefault()
|
||||
localStorage.removeItem( "token" )
|
||||
}
|
||||
|
||||
window.addEventListener( 'beforeunload', handleTabReload )
|
||||
|
||||
return () => {
|
||||
window.removeEventListener( 'beforeunload', handleTabReload )
|
||||
}
|
||||
}, [] )
|
||||
return (
|
||||
<AuthProvider>
|
||||
<GlobalProvider>
|
||||
<Router>
|
||||
<Elements stripe={ stripePromise }>
|
||||
<Main />
|
||||
</Elements>
|
||||
</Router>
|
||||
</GlobalProvider>
|
||||
</AuthProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,4 @@
|
||||
export { default as BrandLogo } from './BrandLogo.png'
|
||||
export { default as GrayMaterial } from './gray.png'
|
||||
export { default as WoodgrainMaterial } from './woodgrain.png'
|
||||
export { default as PerforatedMaterial } from './perforated.png'
|
||||
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const ArrowLeftIcon = () => {
|
||||
return (
|
||||
<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.25 6.5H0.75M0.75 6.5L6 11.75M0.75 6.5L6 1.25" stroke="#4A5578" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const ArrowRrightIcon = () => {
|
||||
return (
|
||||
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.66675 7H13.3334M13.3334 7L7.50008 1.16667M13.3334 7L7.50008 12.8333" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const Chevron = ( { active } ) => {
|
||||
return (
|
||||
<svg className={ `${ active ? 'rotate-90' : '' }` } width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.93734 10.5208C1.65956 10.7014 1.37817 10.7117 1.09317 10.5517C0.808726 10.3922 0.666504 10.1458 0.666504 9.81248V1.18749C0.666504 0.854152 0.808726 0.607485 1.09317 0.447485C1.37817 0.288041 1.65956 0.298597 1.93734 0.479152L8.729 4.79165C8.979 4.95832 9.104 5.19443 9.104 5.49999C9.104 5.80554 8.979 6.04165 8.729 6.20832L1.93734 10.5208Z" fill="black" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const CloseIcon = () => {
|
||||
return (
|
||||
<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 1.5L1 13.5M1 1.5L13 13.5" stroke="#5D6B98" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const CopyIcon = () => {
|
||||
return (
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 15V17.8C7 18.9201 7 19.4802 7.21799 19.908C7.40973 20.2843 7.71569 20.5903 8.09202 20.782C8.51984 21 9.07989 21 10.2 21H17.8C18.9201 21 19.4802 21 19.908 20.782C20.2843 20.5903 20.5903 20.2843 20.782 19.908C21 19.4802 21 18.9201 21 17.8V10.2C21 9.0799 21 8.51984 20.782 8.09202C20.5903 7.71569 20.2843 7.40973 19.908 7.21799C19.4802 7 18.9201 7 17.8 7H15M4.2 15H11.8C12.9201 15 13.4802 15 13.908 14.782C14.2843 14.5903 14.5903 14.2843 14.782 13.908C15 13.4802 15 12.9201 15 11.8V4.2C15 3.0799 15 2.51984 14.782 2.09202C14.5903 1.71569 14.2843 1.40973 13.908 1.21799C13.4802 1 12.9201 1 11.8 1H4.2C3.0799 1 2.51984 1 2.09202 1.21799C1.71569 1.40973 1.40973 1.71569 1.21799 2.09202C1 2.51984 1 3.07989 1 4.2V11.8C1 12.9201 1 13.4802 1.21799 13.908C1.40973 14.2843 1.71569 14.5903 2.09202 14.782C2.51984 15 3.07989 15 4.2 15Z" stroke="#B9C0D4" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.4999 9.50004L9.49992 16.5M9.49992 9.50004L16.4999 16.5M24.6666 13C24.6666 19.4434 19.4432 24.6667 12.9999 24.6667C6.5566 24.6667 1.33325 19.4434 1.33325 13C1.33325 6.55672 6.5566 1.33337 12.9999 1.33337C19.4432 1.33337 24.6666 6.55672 24.6666 13Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 445 B |
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const DownloadIcon = () => {
|
||||
return (
|
||||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 13.5V17.5C19 18.0304 18.7893 18.5391 18.4142 18.9142C18.0391 19.2893 17.5304 19.5 17 19.5H3C2.46957 19.5 1.96086 19.2893 1.58579 18.9142C1.21071 18.5391 1 18.0304 1 17.5V13.5M5 8.5L10 13.5M10 13.5L15 8.5M10 13.5V1.5" stroke="#4A5578" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const GreenTickIcon = () => {
|
||||
return (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.2793 14.6541L7.421 11.7958C7.246 11.6208 7.03211 11.5333 6.77933 11.5333C6.52655 11.5333 6.31266 11.6208 6.13766 11.7958C5.94322 11.9903 5.846 12.2187 5.846 12.4812C5.846 12.7437 5.9335 12.9625 6.1085 13.1375L9.66683 16.6958C9.82239 16.8514 10.0266 16.9291 10.2793 16.9291C10.5321 16.9291 10.7363 16.8514 10.8918 16.6958L17.8918 9.69581C18.0668 9.52081 18.1543 9.30692 18.1543 9.05415C18.1543 8.80137 18.0571 8.57776 17.8627 8.38331C17.6877 8.20831 17.4689 8.12081 17.2064 8.12081C16.9439 8.12081 16.7154 8.21804 16.521 8.41248L10.2793 14.6541ZM12.0002 23.6666C10.3474 23.6666 8.81127 23.3701 7.39183 22.7771C5.97239 22.184 4.73766 21.3625 3.68766 20.3125C2.63766 19.2625 1.81614 18.0278 1.22308 16.6083C0.630024 15.1889 0.333496 13.6528 0.333496 12C0.333496 10.3666 0.630024 8.84026 1.22308 7.42081C1.81614 6.00137 2.63766 4.76665 3.68766 3.71665C4.73766 2.66665 5.97239 1.84026 7.39183 1.23748C8.81127 0.634702 10.3474 0.333313 12.0002 0.333313C13.6335 0.333313 15.1599 0.634702 16.5793 1.23748C17.9988 1.84026 19.2335 2.66665 20.2835 3.71665C21.3335 4.76665 22.1599 6.00137 22.7627 7.42081C23.3654 8.84026 23.6668 10.3666 23.6668 12C23.6668 13.6528 23.3654 15.1889 22.7627 16.6083C22.1599 18.0278 21.3335 19.2625 20.2835 20.3125C19.2335 21.3625 17.9988 22.184 16.5793 22.7771C15.1599 23.3701 13.6335 23.6666 12.0002 23.6666Z" fill="#039855" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const HandIcon = () => {
|
||||
return (
|
||||
<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.9 10.4444V13.2222M4.9 10.4444V3.77778C4.9 2.8573 5.66112 2.11111 6.6 2.11111C7.53888 2.11111 8.3 2.8573 8.3 3.77778M4.9 10.4444C4.9 9.52397 4.13888 8.77778 3.2 8.77778C2.26112 8.77778 1.5 9.52397 1.5 10.4444V12.6667C1.5 17.269 5.30558 21 10 21C14.6944 21 18.5 17.269 18.5 12.6667V7.11111C18.5 6.19064 17.7389 5.44444 16.8 5.44444C15.8611 5.44444 15.1 6.19064 15.1 7.11111M8.3 3.77778V9.88889M8.3 3.77778V2.66667C8.3 1.74619 9.06112 1 10 1C10.9389 1 11.7 1.74619 11.7 2.66667V3.77778M11.7 3.77778V9.88889M11.7 3.77778C11.7 2.8573 12.4611 2.11111 13.4 2.11111C14.3389 2.11111 15.1 2.8573 15.1 3.77778V7.11111M15.1 7.11111V9.88889" stroke="#B9C0D4" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
|
||||
export const Loader = ( { fill, stroke, className } ) => {
|
||||
return (
|
||||
<svg className={ `animate-spin -ml-1 mr-3 text-white inline ${ className }` } xmlns="http://www.w3.org/2000/svg" fill={ fill } viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke={ stroke } strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
Loader.defaultProps = {
|
||||
className: "h-5 w-5",
|
||||
fill: "none",
|
||||
stroke: "currentColor",
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const MinusSquareIcon = () => {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 10H14M5.8 19H14.2C15.8802 19 16.7202 19 17.362 18.673C17.9265 18.3854 18.3854 17.9265 18.673 17.362C19 16.7202 19 15.8802 19 14.2V5.8C19 4.11984 19 3.27976 18.673 2.63803C18.3854 2.07354 17.9265 1.6146 17.362 1.32698C16.7202 1 15.8802 1 14.2 1H5.8C4.11984 1 3.27976 1 2.63803 1.32698C2.07354 1.6146 1.6146 2.07354 1.32698 2.63803C1 3.27976 1 4.11984 1 5.8V14.2C1 15.8802 1 16.7202 1.32698 17.362C1.6146 17.9265 2.07354 18.3854 2.63803 18.673C3.27976 19 4.11984 19 5.8 19Z" stroke="#B9C0D4" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
import React from 'react'
|
||||
|
||||
export const PasteIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="2" stroke="#B9C0D4" className="w-6 h-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
// <path d="M5.5 1H12.6C14.8402 1 15.9603 1 16.816 1.43597C17.5686 1.81947 18.1805 2.43139 18.564 3.18404C19 4.03969 19 5.15979 19 7.4V14.5M4.2 19H12.3C13.4201 19 13.9802 19 14.408 18.782C14.7843 18.5903 15.0903 18.2843 15.282 17.908C15.5 17.4802 15.5 16.9201 15.5 15.8V7.7C15.5 6.57989 15.5 6.01984 15.282 5.59202C15.0903 5.21569 14.7843 4.90973 14.408 4.71799C13.9802 4.5 13.4201 4.5 12.3 4.5H4.2C3.0799 4.5 2.51984 4.5 2.09202 4.71799C1.71569 4.90973 1.40973 5.21569 1.21799 5.59202C1 6.01984 1 6.57989 1 7.7V15.8C1 16.9201 1 17.4802 1.21799 17.908C1.40973 18.2843 1.71569 18.5903 2.09202 18.782C2.51984 19 3.0799 19 4.2 19Z" stroke="#B9C0D4" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
// </svg>
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const PlusSquareIcon = () => {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 6V14M6 10H14M5.8 19H14.2C15.8802 19 16.7202 19 17.362 18.673C17.9265 18.3854 18.3854 17.9265 18.673 17.362C19 16.7202 19 15.8802 19 14.2V5.8C19 4.11984 19 3.27976 18.673 2.63803C18.3854 2.07354 17.9265 1.6146 17.362 1.32698C16.7202 1 15.8802 1 14.2 1H5.8C4.11984 1 3.27976 1 2.63803 1.32698C2.07354 1.6146 1.6146 2.07354 1.32698 2.63803C1 3.27976 1 4.11984 1 5.8V14.2C1 15.8802 1 16.7202 1.32698 17.362C1.6146 17.9265 2.07354 18.3854 2.63803 18.673C3.27976 19 4.11984 19 5.8 19Z" stroke="#B9C0D4" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const PrinterIcon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={ 2 } stroke="#B9C0D4" className="w-6 h-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6.72 13.829c-.24.03-.48.062-.72.096m.72-.096a42.415 42.415 0 0110.56 0m-10.56 0L6.34 18m10.94-4.171c.24.03.48.062.72.096m-.72-.096L17.66 18m0 0l.229 2.523a1.125 1.125 0 01-1.12 1.227H7.231c-.662 0-1.18-.568-1.12-1.227L6.34 18m11.318 0h1.091A2.25 2.25 0 0021 15.75V9.456c0-1.081-.768-2.015-1.837-2.175a48.055 48.055 0 00-1.913-.247M6.34 18H5.25A2.25 2.25 0 013 15.75V9.456c0-1.081.768-2.015 1.837-2.175a48.041 48.041 0 011.913-.247m10.5 0a48.536 48.536 0 00-10.5 0m10.5 0V3.375c0-.621-.504-1.125-1.125-1.125h-8.25c-.621 0-1.125.504-1.125 1.125v3.659M18 10.5h.008v.008H18V10.5zm-3 0h.008v.008H15V10.5z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const ReverseLeftIcon = () => {
|
||||
return (
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 5H11C14.3137 5 17 7.68629 17 11C17 14.3137 14.3137 17 11 17H1M1 5L5 1M1 5L5 9" stroke="#B9C0D4" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const ReverseRightIcon = () => {
|
||||
return (
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17 5H7C3.68629 5 1 7.68629 1 11C1 14.3137 3.68629 17 7 17H17M17 5L13 1M17 5L13 9" stroke="#B9C0D4" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="30" viewBox="0 0 22 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 6.92297C1 6.92297 2.08016 6.34404 9.09964 7.41561C16.1191 8.48719 20.9408 15.0463 19.8693 22.0658C19.4896 24.5528 18.4211 26.7639 16.8873 28.5348M1 6.92297L7.90615 1.84612M1 6.92297L6.07686 13.8291" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 395 B |
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const TrashIcon = () => {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 1H13M1 4H19M17 4L16.2987 14.5193C16.1935 16.0975 16.1409 16.8867 15.8 17.485C15.4999 18.0118 15.0472 18.4353 14.5017 18.6997C13.882 19 13.0911 19 11.5093 19H8.49065C6.90891 19 6.11803 19 5.49834 18.6997C4.95276 18.4353 4.50009 18.0118 4.19998 17.485C3.85911 16.8867 3.8065 16.0975 3.70129 14.5193L3 4" stroke="#B9C0D4" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export const UploadIcon = () => {
|
||||
return (
|
||||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 13.5V17.5C19 18.0304 18.7893 18.5391 18.4142 18.9142C18.0391 19.2893 17.5304 19.5 17 19.5H3C2.46957 19.5 1.96086 19.2893 1.58579 18.9142C1.21071 18.5391 1 18.0304 1 17.5V13.5M15 6.5L10 1.5M10 1.5L5 6.5M10 1.5V13.5" stroke="#4A5578" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export { UploadIcon } from './UploadIcon'
|
||||
export { DownloadIcon } from './DownloadIcon'
|
||||
export { Chevron } from './Chevron'
|
||||
export { HandIcon } from './HandIcon'
|
||||
export { PlusSquareIcon } from './PlusSquareIcon'
|
||||
export { MinusSquareIcon } from './MinusSquareIcon'
|
||||
export { ReverseLeftIcon } from './ReverseLeftIcon'
|
||||
export { ReverseRightIcon } from './ReverseRightIcon'
|
||||
export { CopyIcon } from './CopyIcon'
|
||||
export { TrashIcon } from './TrashIcon'
|
||||
export { PasteIcon } from './PasteIcon'
|
||||
export { Loader } from './Loader'
|
||||
export { CloseIcon } from './CloseIcon'
|
||||
export { ArrowRrightIcon } from './ArrowRrightIcon'
|
||||
export { ArrowLeftIcon } from './ArrowLeftIcon'
|
||||
export { GreenTickIcon } from './GreenTickIcon'
|
||||
export { PrinterIcon } from './PrinterIcon'
|
||||
export { default as DeleteIcon } from './DeleteIcon.svg'
|
||||
export { default as RotateIcon } from './RotateIcon.svg'
|
||||
@@ -0,0 +1,104 @@
|
||||
import React, { useReducer } from "react";
|
||||
import MkdSDK from "./utils/MkdSDK";
|
||||
|
||||
export const AuthContext = React.createContext();
|
||||
|
||||
const initialState = {
|
||||
isAuthenticated: false,
|
||||
user: null,
|
||||
token: null,
|
||||
role: null,
|
||||
};
|
||||
|
||||
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"),
|
||||
};
|
||||
case "LOGOUT":
|
||||
localStorage.removeItem("user");
|
||||
localStorage.removeItem("token");
|
||||
return {
|
||||
...state,
|
||||
isAuthenticated: false,
|
||||
user: null,
|
||||
};
|
||||
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: "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 (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
state,
|
||||
dispatch,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthProvider;
|
||||
@@ -0,0 +1,85 @@
|
||||
|
||||
import React, { useCallback, useState } from 'react'
|
||||
// import { GrayMaterial, PerforatedMaterial, WoodgrainMaterial } from 'Assets/images'
|
||||
// import { MaterialType } from 'Utils/constants'
|
||||
import { CopyIcon, HandIcon, PasteIcon, PrinterIcon, ReverseLeftIcon, ReverseRightIcon, TrashIcon } from 'Assets/svgs'
|
||||
import { CanvasModes } from 'Utils/constants'
|
||||
|
||||
|
||||
|
||||
export const ActionButtons = ( { className, onRedoClick, onUndoClick, onCopy, onPaste, onDeleteSelection, onPrintScreen } ) => {
|
||||
const [ mode, setMode ] = useState( CanvasModes.Still )
|
||||
// const [ activeMaterial, setActiveMaterial ] = useState( MaterialType.Gray )
|
||||
|
||||
// const changeMode = useCallback( () => {
|
||||
// if ( mode === CanvasModes.Pan ) {
|
||||
// setMode( CanvasModes.Still )
|
||||
// onCanvasModeChange( CanvasModes.Still )
|
||||
// } else if ( mode === CanvasModes.Still ) {
|
||||
// setMode( CanvasModes.Pan )
|
||||
// onCanvasModeChange( CanvasModes.Pan )
|
||||
// }
|
||||
// }, [ mode ] )
|
||||
|
||||
return (
|
||||
<div className={ `flex gap-x-[8px] bg-transparent border-none ${ className }` }>
|
||||
|
||||
<button onClick={ () => onPrintScreen() }>
|
||||
<div
|
||||
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
|
||||
>
|
||||
<PrinterIcon />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* <button onClick={ changeMode }>
|
||||
<div
|
||||
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
|
||||
>
|
||||
<HandIcon />
|
||||
</div>
|
||||
</button> */}
|
||||
<button onClick={ onPaste }>
|
||||
<div
|
||||
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
|
||||
>
|
||||
<PasteIcon />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button onClick={ onCopy }>
|
||||
<div
|
||||
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
|
||||
>
|
||||
<CopyIcon />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button onClick={ onUndoClick }>
|
||||
<div
|
||||
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
|
||||
>
|
||||
<ReverseLeftIcon />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button onClick={ onRedoClick }>
|
||||
<div
|
||||
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
|
||||
>
|
||||
<ReverseRightIcon />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button onClick={ onDeleteSelection }>
|
||||
<div
|
||||
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
|
||||
>
|
||||
<TrashIcon />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { ActionButtons } from './ActionButtons'
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
const AddButton = ({ link }) => {
|
||||
return (
|
||||
<>
|
||||
<NavLink to={link} className="w-7 h-7 text-center center-svg text-white font-bold bg-blue-500 hover:bg-blue-700 flex">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-plus" viewBox="0 0 16 16">
|
||||
{" "}
|
||||
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />{" "}
|
||||
</svg>
|
||||
</NavLink>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddButton;
|
||||
@@ -0,0 +1,205 @@
|
||||
import React from "react";
|
||||
import { AuthContext } from "../authContext";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { GlobalContext } from "../globalContext";
|
||||
export const AdminHeader = () => {
|
||||
const { dispatch } = React.useContext(AuthContext);
|
||||
const { state } = React.useContext(GlobalContext);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`sidebar-holder ${!state.isOpen ? "open-nav" : ""}`}>
|
||||
<div className="sticky top-0 h-fit">
|
||||
<div className="w-full p-4 bg-sky-500">
|
||||
<div className="text-white font-bold text-center text-2xl">
|
||||
Admin
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full sidebar-list">
|
||||
<ul className="flex flex-wrap">
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/dashboard"
|
||||
className={`${
|
||||
state.path == "admin" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
Dashboard
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/accessories"
|
||||
className={`${
|
||||
state.path == "accessories" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
Accessories
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/email"
|
||||
className={`${
|
||||
state.path == "email" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
Email
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/photo"
|
||||
className={`${
|
||||
state.path == "photo" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
Photo
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/boat_lifts"
|
||||
className={`${
|
||||
state.path == "boat_lifts" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
Boat Lifts
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/dealers"
|
||||
className={`${
|
||||
state.path == "dealers" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
Dealers
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/docks"
|
||||
className={`${
|
||||
state.path == "docks" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
Docks
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/cms"
|
||||
className={`${
|
||||
state.path == "cms" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
Cms
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/instructions"
|
||||
className={`${
|
||||
state.path == "instructions" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
Instructions
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/user"
|
||||
className={`${
|
||||
state.path == "user" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
User
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/quotes"
|
||||
className={`${
|
||||
state.path == "quotes" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
Quotes
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/wedges"
|
||||
className={`${
|
||||
state.path == "wedges" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
Wedges
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/ramps"
|
||||
className={`${
|
||||
state.path == "ramps" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
Ramps
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/quotes_mail_recipients"
|
||||
className={`${
|
||||
state.path == "quotes_mail_recipients"
|
||||
? "text-black bg-gray-200"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
Quotes Mail Recipients
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/profile"
|
||||
className={`${
|
||||
state.path == "profile" ? "text-black bg-gray-200" : ""
|
||||
}`}
|
||||
>
|
||||
Profile
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="list-none block w-full">
|
||||
<NavLink
|
||||
to="/admin/login"
|
||||
onClick={() =>
|
||||
dispatch({
|
||||
type: "LOGOUT",
|
||||
})
|
||||
}
|
||||
>
|
||||
Logout
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminHeader;
|
||||
@@ -0,0 +1,82 @@
|
||||
import React, { memo, useState, useCallback } from 'react';
|
||||
import { Modal } from 'Components/Modal';
|
||||
import { InteractiveButton } from 'Components/InteractiveButton';
|
||||
// import { ArrowLeftIcon, ArrowRrightIcon, GreenTickIcon } from 'Assets/svgs';
|
||||
// import { dock_image, EstimateSteps, Tables, Truthy } from 'Utils/constants';
|
||||
// import { ContactInformation } from 'Components/ContactInformation';
|
||||
// import { LakeSurroundings } from 'Components/LakeSurroundings';
|
||||
// import { Comments } from 'Components/Comments';
|
||||
import MkdSDK from 'Utils/MkdSDK';
|
||||
|
||||
const sdk = new MkdSDK()
|
||||
const classes = {
|
||||
modal: 'string',
|
||||
modalDialog: 'relative bg-white w-[500px]',
|
||||
modalContent: 'string',
|
||||
modalHeader: 'string',
|
||||
modalTitle: 'string',
|
||||
modalBody: 'string',
|
||||
modalFooter: 'string',
|
||||
closeButtonClass: 'string',
|
||||
saveButtonClass: 'string',
|
||||
}
|
||||
|
||||
const BuildCanvasFromLocalModal = ( {
|
||||
showBuildCanvasFromLocalModal,
|
||||
modalCloseClick,
|
||||
editor
|
||||
} ) => {
|
||||
// const [ step, setStep ] = useState( EstimateSteps.ContactInformation )
|
||||
// const [ submitLoading, setSubmitLoading ] = useState( false )
|
||||
// const [ errorMessage, setErrorMessage ] = useState( null )
|
||||
// const [ hasDealer, setHasDealer ] = useState( Truthy.False )
|
||||
|
||||
|
||||
const onConfirm = useCallback( () => {
|
||||
const json = JSON.parse( localStorage.getItem( "dock" ) )
|
||||
editor.loadFromJSON( json, () => {
|
||||
editor.renderAll()
|
||||
}, ( o ) => {
|
||||
// console.log( o )
|
||||
} )
|
||||
modalCloseClick()
|
||||
}, [ editor ] )
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="CONTINUE FROM LAST SAVE"
|
||||
isOpen={ showBuildCanvasFromLocalModal }
|
||||
classes={ classes }
|
||||
modalHeader
|
||||
modalCloseClick={ modalCloseClick }
|
||||
>
|
||||
<div>
|
||||
<div className={ `my-3` }>
|
||||
Would You Like to Continue Building Dock From Last Save?
|
||||
</div>
|
||||
|
||||
<div className={ `w-full flex gap-x-2 justify-between items-center` }>
|
||||
<button
|
||||
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center bg-[#0F75BC] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px]` }
|
||||
onClick={ modalCloseClick }
|
||||
>No
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center bg-[#0F75BC] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px]` }
|
||||
onClick={ onConfirm }
|
||||
>Yes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const BuildCanvasFromLocalModalMemo = memo( BuildCanvasFromLocalModal );
|
||||
|
||||
export { BuildCanvasFromLocalModalMemo as BuildCanvasFromLocalModal };
|
||||
@@ -0,0 +1 @@
|
||||
export { BuildCanvasFromLocalModal } from './BuildCanvasFromLocalModal'
|
||||
@@ -0,0 +1,758 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
// import { Tabs } from 'Components/Tabs'
|
||||
// import { TabNames } from 'Utils'
|
||||
import { fabric } from "fabric";
|
||||
import {
|
||||
GrayMaterial,
|
||||
PerforatedMaterial,
|
||||
WoodgrainMaterial
|
||||
} from "Assets/images";
|
||||
import {
|
||||
DockPanelCategories,
|
||||
MaterialType,
|
||||
DockPanelCategoryMap,
|
||||
CylinderType
|
||||
} from "Utils/constants";
|
||||
import { Chevron } from "Assets/svgs";
|
||||
import MkdSDK from "Utils/MkdSDK";
|
||||
import { oneFeet, scaleFactor, Tables } from "Utils/constants";
|
||||
import {
|
||||
getCategory,
|
||||
getMaterial,
|
||||
getRampsCategory,
|
||||
getWedgesAndRampsMaterial,
|
||||
getWedgesCategory
|
||||
} from "Utils/utils";
|
||||
|
||||
const sdk = new MkdSDK();
|
||||
export const Builder = ({ editor }) => {
|
||||
// let scaleFactor = 0.2;
|
||||
const [activeMaterial, setActiveMaterial] = useState(MaterialType.Gray);
|
||||
const [activeDockCategory, setActiveDockCategory] = useState(
|
||||
DockPanelCategories.RollIn
|
||||
);
|
||||
const rampsInitialState = {
|
||||
data: []
|
||||
};
|
||||
// const [ramps, setRamps] = useState(null);
|
||||
const [activeLiftRange, setActiveLiftRange] = useState(null);
|
||||
const [dock, setDock] = useState([]);
|
||||
const [docks, setDocks] = useState([]);
|
||||
const [accessories, setAccessories] = useState([]);
|
||||
const [boatlifts, setBoatlifts] = useState([]);
|
||||
const [wedgesAndRamps, setWedgesAndRamps] = useState({
|
||||
wedges: [],
|
||||
ramps: [],
|
||||
selectedRamps: [],
|
||||
selectedWedges: []
|
||||
});
|
||||
const [boatLift, setBoatLift] = useState([]);
|
||||
const [liftRanges, setLiftRanges] = useState([]);
|
||||
const [left, setLeft] = useState(300);
|
||||
// const [ rollinDock, setRollinDock ] = useState( null )
|
||||
// const [ floatingDock, setFloatingDock ] = useState( null )
|
||||
// const [ sectionalDock, setSectionalDock ] = useState( null )
|
||||
|
||||
const onMaterialClick = useCallback(
|
||||
(material) => {
|
||||
setActiveMaterial(material);
|
||||
},
|
||||
[activeMaterial]
|
||||
);
|
||||
|
||||
const onDockSelect = useCallback((dock) => {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
const editorHeight = editor.getHeight();
|
||||
const division = editorHeight / oneFeet - 4;
|
||||
|
||||
let imageTopViewURL;
|
||||
let materials;
|
||||
let category;
|
||||
if (["wedges", "ramps"].includes(dock?.type)) {
|
||||
imageTopViewURL = (dock?.top_view).replace("%20", "+");
|
||||
|
||||
materials = getWedgesAndRampsMaterial(dock?.material);
|
||||
} else {
|
||||
imageTopViewURL = dock?.top_view;
|
||||
materials = getMaterial(dock?.materials);
|
||||
}
|
||||
|
||||
if (["ramps"].includes(dock?.type)) {
|
||||
category = getRampsCategory(dock?.category);
|
||||
} else if (["wedges"].includes(dock?.type)) {
|
||||
category = getWedgesCategory(dock?.category);
|
||||
} else {
|
||||
category = getCategory(dock?.category);
|
||||
}
|
||||
|
||||
const dockData = {
|
||||
itemName: activeDockCategory,
|
||||
image: dock?.image,
|
||||
category: category,
|
||||
length: dock?.length,
|
||||
materials: materials,
|
||||
top_view: dock?.top_view,
|
||||
width: dock?.width,
|
||||
lift_range: dock?.lift_range,
|
||||
model: dock?.model,
|
||||
no_of_cylinders: dock?.no_of_cylinders,
|
||||
name: dock?.name,
|
||||
thumbnail: dock?.thumbnail,
|
||||
weight_capacity: dock?.weight_capacity
|
||||
};
|
||||
|
||||
// TODO: Add dock to editor
|
||||
// TODO: object which is the image should have the dockData, snapAngle of 45, snapThreshold of 5
|
||||
// TODO: image should be scaled down by scaleFactor
|
||||
// TODO: image should be positioned at the top left of the editor
|
||||
// TODO: image should be added to the editor
|
||||
// TODO: render the editor
|
||||
}, []);
|
||||
|
||||
const getItems = useCallback((table) => {
|
||||
// console.log( category, materials );
|
||||
(async () => {
|
||||
try {
|
||||
// sdk.setTable( table );
|
||||
const result = await sdk.getItems(table);
|
||||
// console.log( result )
|
||||
switch (table) {
|
||||
case Tables.Docks:
|
||||
return setDocks(() =>
|
||||
result?.model
|
||||
? [...result?.model.map((item) => ({ ...item, type: "docks" }))]
|
||||
: []
|
||||
);
|
||||
|
||||
case Tables.Boat_lifts:
|
||||
// return console.log( result )
|
||||
const liftRanges = result?.model
|
||||
.filter((boatLift, index, self) => {
|
||||
return (
|
||||
index ===
|
||||
self.findIndex(
|
||||
(selfItem) => selfItem.lift_range === boatLift.lift_range
|
||||
)
|
||||
);
|
||||
})
|
||||
.map((item) => item.lift_range);
|
||||
setLiftRanges(liftRanges.sort());
|
||||
setActiveLiftRange(liftRanges.sort()[0]);
|
||||
return setBoatlifts(() =>
|
||||
result?.model
|
||||
? [
|
||||
...result?.model.map((item) => ({
|
||||
...item,
|
||||
type: "boatlifts"
|
||||
}))
|
||||
]
|
||||
: []
|
||||
);
|
||||
|
||||
case Tables.Accessories:
|
||||
// return console.log( result )
|
||||
return setAccessories(() =>
|
||||
result?.model
|
||||
? [
|
||||
...result?.model.map((item) => ({
|
||||
...item,
|
||||
type: "accessories"
|
||||
}))
|
||||
]
|
||||
: []
|
||||
);
|
||||
case Tables.Wedges:
|
||||
// return console.log( result )
|
||||
return setWedgesAndRamps((prev) => ({
|
||||
...prev,
|
||||
wedges: result?.model
|
||||
? [
|
||||
...result?.model.map((item) => ({
|
||||
...item,
|
||||
type: "wedges"
|
||||
}))
|
||||
]
|
||||
: []
|
||||
}));
|
||||
case Tables.Ramps:
|
||||
// return console.log( result )
|
||||
return setWedgesAndRamps((prev) => ({
|
||||
...prev,
|
||||
ramps: result?.model
|
||||
? [...result?.model.map((item) => ({ ...item, type: "ramps" }))]
|
||||
: []
|
||||
}));
|
||||
// return setRamps(() => [...result?.model]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error.message);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const onDockPanelClick = useCallback(
|
||||
(dockCategory) => {
|
||||
if (dockCategory !== activeDockCategory) {
|
||||
setActiveDockCategory(dockCategory);
|
||||
} else {
|
||||
setActiveDockCategory("");
|
||||
}
|
||||
},
|
||||
[activeDockCategory]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!docks.length) {
|
||||
// console.log( "I ran Docks" )
|
||||
getItems(Tables.Docks);
|
||||
}
|
||||
if (!accessories.length) {
|
||||
// console.log( "I ran Accessories" )
|
||||
getItems(Tables.Accessories);
|
||||
}
|
||||
if (!wedgesAndRamps?.wedges?.length) {
|
||||
// console.log( "I ran Wedges" )
|
||||
getItems(Tables.Wedges);
|
||||
}
|
||||
if (!wedgesAndRamps?.ramps?.length) {
|
||||
// console.log( "I ran Ramps" )
|
||||
getItems(Tables.Ramps);
|
||||
}
|
||||
if (!boatlifts.length) {
|
||||
// console.log( "I ran Boat_lifts" )
|
||||
getItems(Tables.Boat_lifts);
|
||||
}
|
||||
}, [docks, accessories, boatlifts]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
[
|
||||
DockPanelCategories.RollIn,
|
||||
DockPanelCategories.Floating,
|
||||
DockPanelCategories.Sectional
|
||||
].includes(activeDockCategory)
|
||||
) {
|
||||
// console.log( activeDockCategory, activeMaterial )
|
||||
const category =
|
||||
activeDockCategory === DockPanelCategories.RollIn
|
||||
? DockPanelCategoryMap.RollIn
|
||||
: activeDockCategory === DockPanelCategories.Floating
|
||||
? DockPanelCategoryMap.Floating
|
||||
: activeDockCategory === DockPanelCategories.Sectional
|
||||
? DockPanelCategoryMap.Sectional
|
||||
: null;
|
||||
|
||||
const dockObj = docks
|
||||
.map((dock) => {
|
||||
if (dock.category === category && dock.materials === activeMaterial) {
|
||||
return dock;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
// console.log( dockObj )
|
||||
setDock(() => [...dockObj]);
|
||||
}
|
||||
if (DockPanelCategories.Wedges === activeDockCategory) {
|
||||
}
|
||||
}, [activeMaterial, activeDockCategory, docks, wedgesAndRamps]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
[DockPanelCategories.BoatLift2, DockPanelCategories.BoatLift4].includes(
|
||||
activeDockCategory
|
||||
)
|
||||
) {
|
||||
// console.log( activeDockCategory, activeLiftRange )
|
||||
const cylinder = CylinderType[activeDockCategory];
|
||||
|
||||
const boatliftsObj = boatlifts
|
||||
.map((boatlift) => {
|
||||
if (boatlift.no_of_cylinders === cylinder) {
|
||||
return boatlift;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
// console.log( boatliftsObj )
|
||||
setBoatLift(() => [...boatliftsObj]);
|
||||
}
|
||||
}, [activeLiftRange, boatlifts, activeDockCategory]);
|
||||
// useEffect(() => {
|
||||
// (async () => {
|
||||
// await sdk.projectHealth();
|
||||
// })();
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col gap-x-[8px] border-t-2 w-full`}>
|
||||
<div className={`w-full flex h-[54px] bg-gray-200`}>
|
||||
<div
|
||||
onClick={() => onMaterialClick(MaterialType.Gray)}
|
||||
className={`grow h-full px-[12px] py-[5px] flex justify-center items-center cursor-pointer
|
||||
${
|
||||
activeMaterial === MaterialType.Gray
|
||||
? " bg-white border-transparent"
|
||||
: "bg-gray-200"
|
||||
}`}
|
||||
>
|
||||
<img className={`rounded-md`} src={GrayMaterial} alt="GreyMaterial" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={() => onMaterialClick(MaterialType.Perforated)}
|
||||
className={`grow h-full px-[12px] py-[5px] flex justify-center items-center cursor-pointer
|
||||
${
|
||||
activeMaterial === MaterialType.Perforated
|
||||
? " bg-white border-transparent"
|
||||
: "bg-gray-200"
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
className={`rounded-md`}
|
||||
src={PerforatedMaterial}
|
||||
alt="PerforatedMaterial"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={() => onMaterialClick(MaterialType.Woodgrain)}
|
||||
className={`grow h-full px-[12px] py-[5px] flex justify-center items-center cursor-pointer
|
||||
${
|
||||
activeMaterial === MaterialType.Woodgrain
|
||||
? " bg-white border-transparent"
|
||||
: "bg-gray-200"
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
className={`rounded-md`}
|
||||
src={WoodgrainMaterial}
|
||||
alt="WoodgrainMaterial"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`h-fit `}>
|
||||
<div className={`text-black w-full h-fit overflow-y-auto`}>
|
||||
<div
|
||||
onClick={() => onDockPanelClick(DockPanelCategories.RollIn)}
|
||||
className={`w-full h-[67px] z-30 flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
|
||||
>
|
||||
<Chevron
|
||||
active={
|
||||
activeDockCategory === DockPanelCategories.RollIn ? true : false
|
||||
}
|
||||
/>
|
||||
{DockPanelCategories.RollIn}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
activeDockCategory === DockPanelCategories.RollIn
|
||||
? "block"
|
||||
: "close-dock-panel hidden"
|
||||
} px-[12px]`}
|
||||
>
|
||||
{dock.length ? (
|
||||
<>
|
||||
<img src={dock[0].image} alt="" className={`rounded-md my-2`} />
|
||||
<div className={`grid grid-cols-2`}>
|
||||
{dock?.map((dockItem, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => onDockSelect(dockItem)}
|
||||
className={`w-[100px] h-[47px] rounded py-2 mx-1 mt-1 border border-[#B9C0D4]`}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-center gap-x-1`}
|
||||
>
|
||||
<span>{dockItem.width}'</span>
|
||||
<span>x</span>
|
||||
<span>{dockItem.length}'</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={() => onDockPanelClick(DockPanelCategories.Floating)}
|
||||
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
|
||||
>
|
||||
<Chevron
|
||||
active={
|
||||
activeDockCategory === DockPanelCategories.Floating
|
||||
? true
|
||||
: false
|
||||
}
|
||||
/>
|
||||
{DockPanelCategories.Floating}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
activeDockCategory === DockPanelCategories.Floating
|
||||
? "block"
|
||||
: "close-dock-panel hidden"
|
||||
} px-[12px]`}
|
||||
>
|
||||
{dock.length ? (
|
||||
<>
|
||||
<img src={dock[0].image} alt="" className={`rounded-md my-2`} />
|
||||
<div className={`grid grid-cols-2`}>
|
||||
{dock?.map((dockItem, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => onDockSelect(dockItem)}
|
||||
className={`w-[100px] h-[47px] rounded py-2 mx-1 mt-1 border border-[#B9C0D4]`}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-center gap-x-1`}
|
||||
>
|
||||
<span>{dockItem.width}'</span>
|
||||
<span>x</span>
|
||||
<span>{dockItem.length}'</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={() => onDockPanelClick(DockPanelCategories.Sectional)}
|
||||
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
|
||||
>
|
||||
<Chevron
|
||||
active={
|
||||
activeDockCategory === DockPanelCategories.Sectional
|
||||
? true
|
||||
: false
|
||||
}
|
||||
/>
|
||||
{DockPanelCategories.Sectional}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
activeDockCategory === DockPanelCategories.Sectional
|
||||
? "block"
|
||||
: "close-dock-panel hidden"
|
||||
} px-[12px]`}
|
||||
>
|
||||
{dock.length ? (
|
||||
<>
|
||||
<img src={dock[0].image} alt="" className={`rounded-md my-2`} />
|
||||
<div className={`grid grid-cols-2`}>
|
||||
{dock?.map((dockItem, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => onDockSelect(dockItem)}
|
||||
className={`w-[100px] h-[47px] rounded py-2 mx-1 mt-1 border border-[#B9C0D4]`}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-center gap-x-1`}
|
||||
>
|
||||
<span>{dockItem.width}'</span>
|
||||
<span>x</span>
|
||||
<span>{dockItem.length}'</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div
|
||||
onClick={() => onDockPanelClick(DockPanelCategories.Wedges)}
|
||||
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
|
||||
>
|
||||
<Chevron
|
||||
active={
|
||||
activeDockCategory === DockPanelCategories.Wedges ? true : false
|
||||
}
|
||||
/>
|
||||
{DockPanelCategories.Wedges}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
activeDockCategory === DockPanelCategories.Wedges
|
||||
? "block"
|
||||
: "close-dock-panel hidden"
|
||||
} px-[12px]`}
|
||||
>
|
||||
{wedgesAndRamps?.wedges.length ? (
|
||||
<>
|
||||
<img
|
||||
src={wedgesAndRamps?.wedges[0].image}
|
||||
alt=""
|
||||
className={`rounded-md my-2`}
|
||||
/>
|
||||
<div className={`grid grid-cols-2`}>
|
||||
{wedgesAndRamps?.wedges?.map((dockItem, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => onDockSelect(dockItem)}
|
||||
className={`w-[100px] flex flex-col items-center flex-1 h-[47px] rounded py-1 mx-1 mt-1 border border-[#B9C0D4]`}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-center gap-x-1`}
|
||||
>
|
||||
<span>{dockItem.width}'</span>
|
||||
<span>x</span>
|
||||
<span>{dockItem.length}'</span>
|
||||
</div>
|
||||
<span className={`text-xs`}>
|
||||
{getWedgesCategory(dockItem.category)}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div
|
||||
onClick={() => onDockPanelClick(DockPanelCategories.Ramps)}
|
||||
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
|
||||
>
|
||||
<Chevron
|
||||
active={
|
||||
activeDockCategory === DockPanelCategories.Ramps ? true : false
|
||||
}
|
||||
/>
|
||||
{DockPanelCategories.Ramps}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
activeDockCategory === DockPanelCategories.Ramps
|
||||
? "block"
|
||||
: "close-dock-panel hidden"
|
||||
} px-[12px]`}
|
||||
>
|
||||
{wedgesAndRamps.ramps.length ? (
|
||||
<>
|
||||
<img
|
||||
src={wedgesAndRamps.ramps[0].image}
|
||||
alt=""
|
||||
className={`rounded-md my-2`}
|
||||
/>
|
||||
<div className={`grid grid-cols-2`}>
|
||||
{wedgesAndRamps.ramps?.map((dockItem, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => onDockSelect(dockItem)}
|
||||
className={`w-[100px] flex flex-col items-center flex-1 h-[47px] rounded py-1 mx-1 mt-1 border border-[#B9C0D4]`}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-center gap-x-1`}
|
||||
>
|
||||
<span>{dockItem.width}'</span>
|
||||
<span>x</span>
|
||||
<span>{dockItem.length}'</span>
|
||||
</div>
|
||||
<span className={`text-xs`}>
|
||||
{getRampsCategory(dockItem?.category)}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div
|
||||
onClick={() => onDockPanelClick(DockPanelCategories.BoatLift2)}
|
||||
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
|
||||
>
|
||||
<Chevron
|
||||
active={
|
||||
activeDockCategory === DockPanelCategories.BoatLift2
|
||||
? true
|
||||
: false
|
||||
}
|
||||
/>
|
||||
{DockPanelCategories.BoatLift2}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
activeDockCategory === DockPanelCategories.BoatLift2
|
||||
? "block"
|
||||
: "close-dock-panel hidden"
|
||||
} px-[12px]`}
|
||||
>
|
||||
{boatLift.length ? (
|
||||
<>
|
||||
<div
|
||||
className={`flex flex-col w-full justify-center items-center `}
|
||||
>
|
||||
<div
|
||||
className={`w-full h-5 text-center text-[#4A5578] font-normal text-[14px] leading-[20px]`}
|
||||
>
|
||||
Lift Range
|
||||
</div>
|
||||
<div className={`flex w-full justify-between items-center`}>
|
||||
{liftRanges.map((liftRange) => (
|
||||
<button
|
||||
onClick={() => setActiveLiftRange(liftRange)}
|
||||
className={`grow h-[43px] text-[#4A5578] font-normal text-[18px] leading-[150%] tracking-wide bg-white p-2 ${
|
||||
activeLiftRange === liftRange
|
||||
? "border-b-2 border-b-[#0F75BC]"
|
||||
: ""
|
||||
} `}
|
||||
key={liftRange}
|
||||
>
|
||||
{liftRange}ft
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src={boatLift[0].image}
|
||||
alt=""
|
||||
className={`rounded-md my-2`}
|
||||
/>
|
||||
<div className={`grid grid-cols-2`}>
|
||||
{boatLift?.map((dockItem, index) => {
|
||||
if (dockItem?.lift_range === activeLiftRange) {
|
||||
return (
|
||||
<div key={index}>
|
||||
<button
|
||||
onClick={() => onDockSelect(dockItem)}
|
||||
className={`w-[100px] h-[47px] rounded py-2 mx-1 mt-1 border border-[#B9C0D4] `}
|
||||
>
|
||||
{dockItem.weight_capacity}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={() => onDockPanelClick(DockPanelCategories.BoatLift4)}
|
||||
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
|
||||
>
|
||||
<Chevron
|
||||
active={
|
||||
activeDockCategory === DockPanelCategories.BoatLift4
|
||||
? true
|
||||
: false
|
||||
}
|
||||
/>
|
||||
{DockPanelCategories.BoatLift4}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
activeDockCategory === DockPanelCategories.BoatLift4
|
||||
? "block"
|
||||
: "close-dock-panel hidden"
|
||||
} px-[12px]`}
|
||||
>
|
||||
{boatLift.length ? (
|
||||
<>
|
||||
<div
|
||||
className={`flex flex-col w-full justify-center items-center `}
|
||||
>
|
||||
<div
|
||||
className={`w-full h-5 text-center text-[#4A5578] font-normal text-[14px] leading-[20px]`}
|
||||
>
|
||||
Lift Range
|
||||
</div>
|
||||
<div className={`flex w-full justify-between items-center`}>
|
||||
{liftRanges.map((liftRange) => (
|
||||
<button
|
||||
onClick={() => setActiveLiftRange(liftRange)}
|
||||
className={`grow h-[43px] text-[#4A5578] font-normal text-[18px] leading-[150%] tracking-wide bg-white p-2 ${
|
||||
activeLiftRange === liftRange
|
||||
? "border-b-2 border-b-[#0F75BC]"
|
||||
: ""
|
||||
}`}
|
||||
key={liftRange}
|
||||
>
|
||||
{liftRange}ft
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src={boatLift[0].image}
|
||||
alt=""
|
||||
className={`rounded-md my-2`}
|
||||
/>
|
||||
<div className={`grid grid-cols-2`}>
|
||||
{boatLift?.map((dockItem, index) => {
|
||||
if (dockItem?.lift_range === activeLiftRange) {
|
||||
return (
|
||||
<div key={index}>
|
||||
<button
|
||||
onClick={() => onDockSelect(dockItem)}
|
||||
className={`w-[100px] h-[47px] rounded py-2 mx-1 mt-1 border border-[#B9C0D4] `}
|
||||
>
|
||||
{dockItem.weight_capacity}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={() => onDockPanelClick(DockPanelCategories.Accessories)}
|
||||
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
|
||||
>
|
||||
<Chevron
|
||||
active={
|
||||
activeDockCategory === DockPanelCategories.Accessories
|
||||
? true
|
||||
: false
|
||||
}
|
||||
/>
|
||||
{DockPanelCategories.Accessories}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
activeDockCategory === DockPanelCategories.Accessories
|
||||
? "block"
|
||||
: "close-dock-panel hidden"
|
||||
} px-[12px]`}
|
||||
>
|
||||
{accessories.length ? (
|
||||
<>
|
||||
<div className={`grid grid-cols-2`}>
|
||||
{accessories?.map((dockItem, index) => (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => onDockSelect(dockItem)}
|
||||
className={`h-fit rounded mx-1 mt-1 border border-[#B9C0D4] cursor-pointer`}
|
||||
>
|
||||
{/* <div className={ `flex items-center justify-center gap-x-1` }>
|
||||
<span>{ dockItem.width }'</span>
|
||||
<span>x</span>
|
||||
<span>{ dockItem.length }'</span> */}
|
||||
<img
|
||||
src={dockItem.thumbnail}
|
||||
alt=""
|
||||
className={`rounded-md `}
|
||||
/>
|
||||
{/* </div> */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
{
|
||||
/* <div className={ `grow flex flex-col justify-center items-start bg-transparent` }>
|
||||
|
||||
</div> */
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { Builder } from './Builder'
|
||||
@@ -0,0 +1,80 @@
|
||||
import React, { memo } from 'react';
|
||||
import { ArrowLeftIcon, ArrowRrightIcon, } from 'Assets/svgs';
|
||||
import { EstimateSteps, } from 'Utils/constants';
|
||||
|
||||
const Comments = ( { step, onStepChange, onUpdateComment, onSubmit, errorMessage, submitLoading, comments } ) => {
|
||||
|
||||
{/* Comments */ }
|
||||
|
||||
return (
|
||||
|
||||
<div className={ `${ step === EstimateSteps.Comments ? '' : 'hidden' }` }>
|
||||
|
||||
<div>
|
||||
<button
|
||||
disabled={ submitLoading }
|
||||
className={ `text-[#4A5578] leading-[150%] tracking-tight text-[14px] font-medium flex items-center` }
|
||||
onClick={ () => onStepChange( EstimateSteps.LakeSurrounding ) }
|
||||
>
|
||||
<ArrowLeftIcon /> Back
|
||||
</button>
|
||||
<div className={ `flex items-center gap-x-2` }>
|
||||
<p className={ ` uppercase text-[#0F75BC] leading-[150%] tracking-wide text-[18px] font-bold` }>Comments</p>
|
||||
<span className={ `text-[#4A5578] leading-[150%] tracking-tight text-[14px] font-medium` }>(3 of 3)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<fieldset className="cus-input">
|
||||
<label
|
||||
htmlFor="comments"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>
|
||||
<span className={ `text-red-600 mr-2` }>*</span>Comments
|
||||
</label>
|
||||
<div className="relative mb-6">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
{/* <img
|
||||
className="w-5 h-5 object-contain"
|
||||
src={ `` }
|
||||
/> */}
|
||||
</div>
|
||||
<textarea
|
||||
name="comments" cols="30" rows="10"
|
||||
type="text"
|
||||
id="comments"
|
||||
disabled={ submitLoading }
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
placeholder="Comments"
|
||||
onKeyUp={ ( e ) => onUpdateComment( e.target.value ) }
|
||||
>
|
||||
</textarea>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div className={ `flex w-full justify-center bg-transparent ` }>
|
||||
<button
|
||||
disabled={ submitLoading || !comments }
|
||||
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center bg-[#0F75BC] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px] ${ submitLoading || !comments ? "cursor-not-allowed" : "" }` }
|
||||
onClick={ onSubmit }
|
||||
>
|
||||
{ submitLoading ?
|
||||
<>
|
||||
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg> <>Submit Estimate Inquiry <ArrowRrightIcon /></>
|
||||
</>
|
||||
: <>Submit Estimate Inquiry <ArrowRrightIcon /></>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-red-500 text-xs italic capitalize">{ errorMessage }</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const CommentsMemo = memo( Comments );
|
||||
|
||||
export { CommentsMemo as Comments };
|
||||
@@ -0,0 +1 @@
|
||||
export { Comments } from './Comments'
|
||||
@@ -0,0 +1,324 @@
|
||||
import React, { memo, useState, useCallback, useEffect } from 'react';
|
||||
import { ArrowRrightIcon } from 'Assets/svgs';
|
||||
import { EstimateSteps, Tables, Truthy } from 'Utils/constants';
|
||||
import MkdSDK from 'Utils/MkdSDK';
|
||||
|
||||
const sdk = new MkdSDK()
|
||||
const ContactInformation = ( {
|
||||
step,
|
||||
onStepChange,
|
||||
hasDealer,
|
||||
onHasDealerChange,
|
||||
onDealerChange,
|
||||
setFirstName,
|
||||
setLastName,
|
||||
setEmail,
|
||||
setPhone,
|
||||
setLake,
|
||||
setCity,
|
||||
setCountry,
|
||||
} ) => {
|
||||
|
||||
const [ TC, setTC ] = useState( false )
|
||||
const [ dealers, setDealers ] = useState( [] )
|
||||
|
||||
const getDealers = useCallback( ( table ) => {
|
||||
( async () => {
|
||||
try {
|
||||
const result = await sdk.getItems( table )
|
||||
setDealers( result?.model )
|
||||
} catch ( error ) {
|
||||
console.log( error.message )
|
||||
}
|
||||
} )()
|
||||
}, [ dealers ] )
|
||||
|
||||
useEffect( () => {
|
||||
if ( !dealers.length ) {
|
||||
getDealers( Tables.Dealers )
|
||||
}
|
||||
}, [ dealers ] )
|
||||
|
||||
{/* ContactInformation */ }
|
||||
return (
|
||||
<div className={ `h-[500px] max-h-[500px] overflow-auto ${ step === EstimateSteps.ContactInformation ? '' : 'hidden' }` }>
|
||||
|
||||
<div>
|
||||
<div className={ `flex items-center gap-x-2` }>
|
||||
<p className={ ` uppercase text-[#0F75BC] leading-[150%] tracking-wide text-[18px] font-bold` }>Contact information</p>
|
||||
<span className={ `text-[#4A5578] leading-[150%] tracking-tight text-[14px] font-medium` }>(1 of 3)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={ `` }>
|
||||
<div className={ `flex w-full px-0 gap-x-2 justify-between items-center ` }>
|
||||
<fieldset className="cus-input w-1/2">
|
||||
<label
|
||||
htmlFor="firstName"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>
|
||||
<span className={ `text-red-600 mr-2` }>*</span>First Name
|
||||
</label>
|
||||
<div className="relative mb-6">
|
||||
{/* <div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
<img
|
||||
className="w-5 h-5 object-contain"
|
||||
src={ `` }
|
||||
/>
|
||||
</div> */}
|
||||
<input
|
||||
type="text"
|
||||
id="firstName"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
placeholder="John"
|
||||
onChange={ ( e ) => setFirstName( e.target.value ) }
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset className="cus-input w-1/2">
|
||||
<label
|
||||
htmlFor="lastName"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>
|
||||
<span className={ `text-red-600 mr-2` }>*</span>Last Name
|
||||
</label>
|
||||
<div className="relative mb-6">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
{/* <img
|
||||
className="w-5 h-5 object-contain"
|
||||
src={ `` }
|
||||
/> */}
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
id="lastName"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
placeholder="Doe"
|
||||
onChange={ ( e ) => setLastName( e.target.value ) }
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<fieldset className="cus-input">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>
|
||||
<span className={ `text-red-600 mr-2` }>*</span>Email
|
||||
</label>
|
||||
<div className="relative mb-6">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
{/* <img
|
||||
className="w-5 h-5 object-contain"
|
||||
src={ `` }
|
||||
/> */}
|
||||
</div>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
placeholder="JohnDoe@example.com"
|
||||
onChange={ ( e ) => setEmail( e.target.value ) }
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset className="cus-input">
|
||||
<label
|
||||
htmlFor="phoneNumber"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>
|
||||
<span className={ `text-red-600 mr-2` }>*</span>Telephone
|
||||
</label>
|
||||
<div className="relative mb-6">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
{/* <img
|
||||
className="w-5 h-5 object-contain"
|
||||
src={ `` }
|
||||
/> */}
|
||||
</div>
|
||||
<input
|
||||
type="tel"
|
||||
id="phoneNumber"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
// placeholder="James"
|
||||
onChange={ ( e ) => setPhone( e.target.value ) }
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div className={ `flex w-full px-0 gap-x-2 justify-between items-center ` }>
|
||||
<fieldset className="cus-input w-1/3">
|
||||
<label
|
||||
htmlFor="lake"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>
|
||||
<span className={ `text-red-600 mr-2` }>*</span>Location
|
||||
</label>
|
||||
<div className="relative mb-6">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
{/* <img
|
||||
className="w-5 h-5 object-contain"
|
||||
src={ `` }
|
||||
/> */}
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
id="lake"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
placeholder="Lake"
|
||||
onChange={ ( e ) => setLake( e.target.value ) }
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset className="cus-input w-1/3">
|
||||
<label
|
||||
htmlFor="city"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>
|
||||
<span className={ `text-red-600 mr-2` }>*</span>City
|
||||
</label>
|
||||
<div className="relative mb-6">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
{/* <img
|
||||
className="w-5 h-5 object-contain"
|
||||
src={ `` }
|
||||
/> */}
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
id="city"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
placeholder="City"
|
||||
onChange={ ( e ) => setCity( e.target.value ) }
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset className="cus-input w-1/3">
|
||||
<label
|
||||
htmlFor="country"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>
|
||||
<span className={ `text-red-600 mr-2` }>*</span>Country
|
||||
</label>
|
||||
<div className="relative mb-6">
|
||||
{/* <div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
<img
|
||||
className="w-5 h-5 object-contain"
|
||||
src={ `` }
|
||||
/>
|
||||
</div> */}
|
||||
<select
|
||||
type="text"
|
||||
id="country"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
placeholder="Country"
|
||||
onChange={ ( e ) => setCountry( e.target.value ) }
|
||||
>
|
||||
<option></option>
|
||||
<option value={ `Canada` }>Canada</option>
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<fieldset className="cus-input">
|
||||
<label
|
||||
// htmlFor="city"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>
|
||||
<span className={ `text-red-600 mr-2` }>*</span>Are you currently working with a Dealer in your area?
|
||||
</label>
|
||||
<div className="relative flex items-center justify-start mb-6">
|
||||
<input
|
||||
type="radio"
|
||||
name="dealer"
|
||||
id="yesDealer"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-[44px] pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
onChange={ () => onHasDealerChange( Truthy.True ) }
|
||||
/>
|
||||
<label
|
||||
htmlFor="yesDealer"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>Yes
|
||||
</label>
|
||||
<input
|
||||
type="radio"
|
||||
name="dealer"
|
||||
id="noDealer"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-[44px] pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
onChange={ () => onHasDealerChange( Truthy.False ) }
|
||||
/>
|
||||
<label
|
||||
htmlFor="noDealer"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>No
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
{ hasDealer ?
|
||||
<fieldset className="cus-input">
|
||||
<label
|
||||
htmlFor="dealers"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>
|
||||
<span className={ `text-red-600 mr-2` }>*</span>Dealers
|
||||
</label>
|
||||
<div className="relative mb-6">
|
||||
{/* <div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
<img
|
||||
className="w-5 h-5 object-contain"
|
||||
src={ `` }
|
||||
/>
|
||||
</div> */}
|
||||
<select
|
||||
id="dealers"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
placeholder="Delaers"
|
||||
onChange={ ( e ) => onDealerChange( e.target.value ) }
|
||||
>
|
||||
<>
|
||||
<option></option>
|
||||
{ dealers?.length ? dealers.map( ( dealer ) => (
|
||||
<option key={ dealer?.id } value={ dealer?.id }>{ dealer?.name }</option>
|
||||
) )
|
||||
: null }
|
||||
</>
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
: null
|
||||
}
|
||||
<fieldset className="cus-input">
|
||||
<div className="relative flex items-center justify-start mb-6">
|
||||
|
||||
<input
|
||||
// ref={tcRef}
|
||||
type="checkbox"
|
||||
name="desclaimer"
|
||||
id="desclaimer"
|
||||
checked={ TC }
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-[44px] pl-0 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none self-start mt-1"
|
||||
onChange={ ( e ) => setTC( e.target.checked ) }
|
||||
/>
|
||||
<label
|
||||
htmlFor="desclaimer"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>Terms And Conditions
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div className={ `flex w-full justify-center bg-transparent ` }>
|
||||
<button
|
||||
disabled={ !TC }
|
||||
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center bg-[#0F75BC] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px]` }
|
||||
onClick={ () => onStepChange( EstimateSteps.LakeSurrounding ) }
|
||||
>
|
||||
Continue <ArrowRrightIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const ContactInformationMemo = memo( ContactInformation );
|
||||
|
||||
export { ContactInformationMemo as ContactInformation };
|
||||
@@ -0,0 +1 @@
|
||||
export { ContactInformation } from './ContactInformation'
|
||||
@@ -0,0 +1,823 @@
|
||||
import React, {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useCallback,
|
||||
useRef,
|
||||
useState,
|
||||
useContext
|
||||
} from "react";
|
||||
import { FabricJSCanvas, useFabricJSEditor } from "fabricjs-react";
|
||||
import { fabric } from "fabric";
|
||||
import { DockSidebar } from "Components/DockSidebar";
|
||||
import { ActionButtons } from "Components/ActionButtons";
|
||||
import MkdSDK from "Utils/MkdSDK";
|
||||
import { Stack } from "Utils/utils";
|
||||
import { EstimateModal } from "Components/EstimateModal";
|
||||
import * as XLSX from "xlsx";
|
||||
import { BuildCanvasFromLocalModal } from "Components/BuildCanvasFromLocalModal";
|
||||
import { GlobalContext } from "../../globalContext";
|
||||
import { clearClone, clone } from "Utils/DockBuilderUtils/clone";
|
||||
import {
|
||||
edgeDetection,
|
||||
handleEdgeDetection,
|
||||
handleIntersection
|
||||
} from "Utils/DockBuilderUtils";
|
||||
import {
|
||||
CanvasModes,
|
||||
deleteIcon,
|
||||
DockPanelCategories,
|
||||
edgeSnapThreshold,
|
||||
nintyDeg,
|
||||
oneEightyDeg,
|
||||
oneFeet,
|
||||
rotateIcon,
|
||||
scaleFactor,
|
||||
twoSeventyDeg
|
||||
} from "Utils/constants";
|
||||
import {
|
||||
reScaleXY,
|
||||
resolveHeight,
|
||||
resolveWidth
|
||||
} from "Utils/DockBuilderUtils/edgeDetection";
|
||||
import { DeleteIcon, RotateIcon } from "Assets/svgs";
|
||||
import { capitalize } from "Utils/helper";
|
||||
|
||||
const sdk = new MkdSDK();
|
||||
const stack = new Stack();
|
||||
|
||||
export const DockBuilder = () => {
|
||||
let clipboard = null;
|
||||
let mouseDownPoint = null;
|
||||
let shiftKeyDown = false;
|
||||
window.counter = 0;
|
||||
let prevPointer = null;
|
||||
let isDragging = false;
|
||||
let isZoomed = false;
|
||||
let startX, startY;
|
||||
|
||||
const { dispatch } = useContext(GlobalContext);
|
||||
const { editor, onReady } = useFabricJSEditor({ selection: true });
|
||||
|
||||
const canvasModeRef = useRef(null);
|
||||
const fileRef = useRef();
|
||||
const imageRef = useRef();
|
||||
|
||||
const editorMemo = useMemo(() => editor?.canvas, [editor?.canvas]);
|
||||
// const { dispatch } = React.useContext( AuthContext );
|
||||
const [objHovered, setObjHovered] = useState(false);
|
||||
const [selectedItems, setSelectedItems] = useState([]);
|
||||
const [estimateLoading, setEstimateLoading] = useState(false);
|
||||
const [showEstimateModal, setShowEstimateModal] = useState(false);
|
||||
const [showBuildCanvasFromLocalModal, setShowBuildCanvasFromLocalModal] =
|
||||
useState(false);
|
||||
const [initialLoad, setInitialLoad] = useState(true);
|
||||
const [linesAdded, setLinesAdded] = useState(false);
|
||||
const [dockImage, setDockImage] = useState(null);
|
||||
const [editorReady, setEditorReady] = useState(false);
|
||||
const [dockLabel, setDockLabel] = useState(null);
|
||||
const [dockDimensions, setDockDimensions] = useState(null);
|
||||
|
||||
const deleteImg = document.createElement("img");
|
||||
deleteImg.src = deleteIcon;
|
||||
const rotateImg = document.createElement("img");
|
||||
rotateImg.src = rotateIcon;
|
||||
|
||||
const onEstimateModalClose = useCallback(() => {
|
||||
setShowEstimateModal(false);
|
||||
}, [showEstimateModal]);
|
||||
|
||||
const onEstimateModalOpen = useCallback(() => {
|
||||
const ext = "png";
|
||||
const base64 = editorMemo.toDataURL({
|
||||
format: ext,
|
||||
enableRetinaScaling: true
|
||||
});
|
||||
setDockImage(base64);
|
||||
setShowEstimateModal(true);
|
||||
}, [showEstimateModal, editorMemo, dockImage]);
|
||||
|
||||
// const onAddRect = useCallback( () => {
|
||||
// editor?.canvas?.add( rect );
|
||||
// window.counter++;
|
||||
// // newleft += 200;
|
||||
// }, [ editor ] );
|
||||
|
||||
const toJSON = () => {
|
||||
// TODO: download the json file
|
||||
// TODO: get json of editor content
|
||||
// TODO: Ensure dockData is included in the json
|
||||
// TODO: save the json to the local storage as dock
|
||||
// TODO: name the file as paradise_dock_<timestamp here>.dock
|
||||
};
|
||||
|
||||
const uploadFile = (e) => {
|
||||
// TODO: Our own upload the file we must have downloaded previously
|
||||
// TODO: extract the json from the file
|
||||
// TODO: load the json to the editor
|
||||
// TODO: render all
|
||||
};
|
||||
|
||||
const downloadImage = useCallback(() => {
|
||||
// // console.log( 'Download' )
|
||||
const ext = "png";
|
||||
|
||||
// TODO: download the image
|
||||
// TODO: get the json of the editor content
|
||||
// TODO: extract the dockData from the json
|
||||
// TODO: filter the dockData to ensure it does not contain snapClone
|
||||
// TODO: generate the base64 image
|
||||
// TODO: download the image and name it as paradise_dock_snapshot_<timestamp here>.${ext}
|
||||
// TODO: download the excel file of the dockData you extracted
|
||||
|
||||
// TODO: check if dockData is empty
|
||||
// TODO: generate the base64 image
|
||||
|
||||
// TODO: trigger download of the dockData you extracted in excel format
|
||||
}, [editorMemo]);
|
||||
|
||||
const renderBg = useCallback(() => {
|
||||
const imageURL =
|
||||
"https://s3.us-east-2.amazonaws.com/com.nds.images/download.png";
|
||||
|
||||
new fabric.Image.fromURL(
|
||||
imageURL,
|
||||
function (img) {
|
||||
img.set({
|
||||
scaleX: editorMemo.width / img.width,
|
||||
scaleY: editorMemo.height / img.height
|
||||
});
|
||||
editorMemo.setBackgroundImage(img);
|
||||
editorMemo.renderAll();
|
||||
// updateModifications( true, )
|
||||
},
|
||||
{
|
||||
crossOrigin: "anonymous"
|
||||
}
|
||||
);
|
||||
}, [editorMemo]);
|
||||
|
||||
const downloadExcel = useCallback((data) => {
|
||||
const worksheet = XLSX.utils.json_to_sheet(data);
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, "Selected Items");
|
||||
XLSX.writeFile(workbook, "Paradise_dock_selected_items.xlsx");
|
||||
}, []);
|
||||
|
||||
function updateZoom(opt) {
|
||||
let delta = opt.e.deltaY;
|
||||
let pointer = editorMemo.getPointer(opt.e);
|
||||
let zoom = editorMemo.getZoom();
|
||||
|
||||
if (delta > 0) {
|
||||
zoom *= 1.1;
|
||||
} else {
|
||||
zoom /= 1.1;
|
||||
}
|
||||
if (zoom > 5) zoom = 5;
|
||||
if (zoom < 1) zoom = 1;
|
||||
// console.log( zoom )
|
||||
// editorMemo.setBackgroundImageStretch=false
|
||||
var vpt = editorMemo.viewportTransform;
|
||||
if (zoom === 1) {
|
||||
vpt[4] = 0;
|
||||
vpt[5] = 0;
|
||||
editorMemo.selection = true;
|
||||
this.isZoomed = false;
|
||||
}
|
||||
if (zoom > 1) {
|
||||
this.isZoomed = true;
|
||||
}
|
||||
|
||||
fabric.util.animate({
|
||||
startValue: editorMemo.getZoom(),
|
||||
endValue: zoom,
|
||||
// duration: 0,
|
||||
onChange: function (value) {
|
||||
editorMemo.zoomToPoint({ x: pointer.x, y: pointer.y }, value);
|
||||
},
|
||||
fps: 1080
|
||||
});
|
||||
opt.e.preventDefault();
|
||||
opt.e.stopPropagation();
|
||||
}
|
||||
|
||||
function handleZoomMouseDown(options) {
|
||||
var pointer = editorMemo.getPointer(options.e, true);
|
||||
mouseDownPoint = new fabric.Point(pointer.x, pointer.y);
|
||||
if (options.target === null && this.isZoomed) {
|
||||
editorMemo.upperCanvasEl.style.cursor = "grabbing";
|
||||
editorMemo.selection = false;
|
||||
// this.lastPosX = options.e.movementX;
|
||||
// this.lastPosY = options.e.movementY;
|
||||
this.lastPosX = options.e.clientX;
|
||||
this.lastPosY = options.e.clientY;
|
||||
|
||||
//
|
||||
}
|
||||
// else if ( options.target !== null && this.isZoomed ) {
|
||||
// var activeObject = editorMemo.getActiveObject();
|
||||
// if ( activeObject ) {
|
||||
// activeObject.lockMovementX = true;
|
||||
// activeObject.lockMovementY = true;
|
||||
// activeObject.lockScalingX = true;
|
||||
// activeObject.lockScalingY = true;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
const handleZoomMouseUp = useCallback(() => {
|
||||
editorMemo.defaultCursor = "default";
|
||||
mouseDownPoint = null;
|
||||
editorMemo.selection = true;
|
||||
}, [editor]);
|
||||
|
||||
function handleZoomMouseMove(options) {
|
||||
if (options.target === null && mouseDownPoint && this.isZoomed) {
|
||||
editorMemo.selection = false;
|
||||
editorMemo.upperCanvasEl.style.cursor = "grabbing";
|
||||
|
||||
var delta = new fabric.Point(options.e.movementX, options.e.movementY);
|
||||
editorMemo.relativePan(delta);
|
||||
editorMemo.viewportTransform[4] = Math.max(
|
||||
Math.min(editorMemo.viewportTransform[4], 0),
|
||||
editorMemo.getWidth() - editorMemo.getWidth() * editorMemo.getZoom()
|
||||
);
|
||||
editorMemo.viewportTransform[5] = Math.max(
|
||||
Math.min(editorMemo.viewportTransform[5], 0),
|
||||
editorMemo.getHeight() - editorMemo.getHeight() * editorMemo.getZoom()
|
||||
);
|
||||
|
||||
// var vpt = this.viewportTransform;
|
||||
// vpt[ 4 ] += options.e.clientX - this.lastPosX;
|
||||
// vpt[ 5 ] += options.e.clientY - this.lastPosY;
|
||||
requestAnimationFrame(function () {
|
||||
editorMemo.renderAll();
|
||||
this.requestRenderAll();
|
||||
});
|
||||
// this.lastPosX = options.e.movementX;
|
||||
// this.lastPosY = options.e.movementY;
|
||||
// this.lastPosX = options.e.clientX;
|
||||
// this.lastPosY = options.e.clientY;
|
||||
}
|
||||
}
|
||||
|
||||
const addEdgeDetectionClone = useCallback(
|
||||
(options) => {
|
||||
objectMoving = true;
|
||||
if (options) {
|
||||
const selectedObj = options.target.setCoords();
|
||||
editorMemo.defaultCursor = "grabbing";
|
||||
if (
|
||||
selectedObj.dockData &&
|
||||
selectedObj.dockData.itemName === DockPanelCategories.Accessories
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
editorMemo.forEachObject((obj) => {
|
||||
const pointer = { x: options.e.clientX, y: options.e.clientY };
|
||||
|
||||
if (obj === selectedObj) return;
|
||||
// canvas.setCursor('grabbing');
|
||||
// // console.log(pointer.x, pointer.y);
|
||||
if (!obj.snapClone && obj.type === "image") {
|
||||
// // console.log( pointer, prevPointer )
|
||||
if (
|
||||
prevPointer &&
|
||||
(prevPointer.x !== pointer.x || prevPointer.y !== pointer.y)
|
||||
) {
|
||||
switch (true) {
|
||||
case edgeDetection(selectedObj, obj, "left"):
|
||||
clone(selectedObj, editorMemo, obj, "left");
|
||||
break;
|
||||
case edgeDetection(selectedObj, obj, "right"):
|
||||
clone(selectedObj, editorMemo, obj, "right");
|
||||
break;
|
||||
case edgeDetection(selectedObj, obj, "top"):
|
||||
clone(selectedObj, editorMemo, obj, "top");
|
||||
break;
|
||||
case edgeDetection(selectedObj, obj, "bottom"):
|
||||
clone(selectedObj, editorMemo, obj, "bottom");
|
||||
break;
|
||||
case edgeDetection(selectedObj, obj, "neutral"):
|
||||
clearClone(editorMemo);
|
||||
break;
|
||||
default:
|
||||
// console.log( "edge detection switch default" )
|
||||
}
|
||||
// prevPointer = null;
|
||||
}
|
||||
prevPointer = pointer;
|
||||
// // console.log( pointer, prevPointer )
|
||||
}
|
||||
// if ( edgeDetection( selectedObj, obj, "left" ) ) {
|
||||
// clone( selectedObj, editorMemo, obj, "left" )
|
||||
// } else {
|
||||
// // clearClone( editorMemo )
|
||||
// }
|
||||
|
||||
// if ( edgeDetection( selectedObj, obj, 'right' ) ) {
|
||||
// clone( selectedObj, editorMemo, obj, "right" )
|
||||
|
||||
// } else {
|
||||
// // clearClone( editorMemo )
|
||||
// }
|
||||
|
||||
// if ( edgeDetection( selectedObj, obj, 'top' ) ) {
|
||||
// clone( selectedObj, editorMemo, obj, "top" )
|
||||
|
||||
// } else {
|
||||
// // clearClone( editorMemo )
|
||||
// }
|
||||
|
||||
// if ( edgeDetection( selectedObj, obj, 'bottom' ) ) {
|
||||
// clone( selectedObj, editorMemo, obj, "bottom" )
|
||||
// } else {
|
||||
// // clearClone( editorMemo )
|
||||
// }
|
||||
});
|
||||
}
|
||||
},
|
||||
[editor, editorMemo]
|
||||
);
|
||||
|
||||
const horizontalIndicatorLines = (newTop, objTop) => {
|
||||
editorMemo.getObjects("line").forEach((obj) => {
|
||||
if (obj.testLine) {
|
||||
editorMemo.remove(obj);
|
||||
}
|
||||
});
|
||||
|
||||
let line = new fabric.Line([0, newTop, editorMemo.getWidth(), newTop], {
|
||||
stroke: "#AAAAAA",
|
||||
testLine: true
|
||||
// strokeDashArray: [ 5 ],
|
||||
});
|
||||
let line2 = new fabric.Line([0, objTop, editorMemo.getWidth(), objTop], {
|
||||
stroke: "#AAAAAA",
|
||||
testLine: true
|
||||
// strokeDashArray: [ 5 ],
|
||||
});
|
||||
editorMemo.add(line);
|
||||
editorMemo.add(line2);
|
||||
editorMemo.renderAll();
|
||||
};
|
||||
const verticleIndicatorLines = (newLeft, objLeft) => {
|
||||
editorMemo.getObjects("line").forEach((obj) => {
|
||||
if (obj.testLine) {
|
||||
editorMemo.remove(obj);
|
||||
}
|
||||
});
|
||||
|
||||
let line = new fabric.Line([newLeft, 0, newLeft, editorMemo.getHeight()], {
|
||||
stroke: "#AAAAAA",
|
||||
testLine: true
|
||||
// strokeDashArray: [ 5 ],
|
||||
});
|
||||
let line2 = new fabric.Line([objLeft, 0, objLeft, editorMemo.getWidth()], {
|
||||
stroke: "#AAAAAA",
|
||||
testLine: true
|
||||
// strokeDashArray: [ 5 ],
|
||||
});
|
||||
editorMemo.add(line);
|
||||
editorMemo.add(line2);
|
||||
editorMemo.renderAll();
|
||||
};
|
||||
|
||||
function edgeDetectionAndSnap(options) {
|
||||
if (this.isZoomed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Edge detection and snap to object within snap range
|
||||
// TODO: Detect if the object is within the snap range of the selected object
|
||||
// TODO: detect if the selected object contains dockData and dockData contains itemName of DockPanelCategories.Accessories || DockPanelCategories.BoatLift2 || DockPanelCategories.BoatLift4
|
||||
// TODO: handle rotation of 0, 90, 180, 270 degrees
|
||||
|
||||
editorMemo.defaultCursor = "default";
|
||||
prevPointer = null;
|
||||
clearClone(editorMemo);
|
||||
}
|
||||
|
||||
const showHighlight = useCallback(
|
||||
(event) => {
|
||||
if (event.target) {
|
||||
if (event.target.type === "image") {
|
||||
setObjHovered(true);
|
||||
// console.log(event.target.dockData)
|
||||
if (
|
||||
event.target &&
|
||||
event.target.dockData &&
|
||||
event.target.dockData.image
|
||||
) {
|
||||
imageRef.current.src = event.target.dockData.image;
|
||||
setDockLabel(
|
||||
`${
|
||||
event.target.dockData.materials
|
||||
? capitalize(event.target.dockData.itemName)
|
||||
: event.target.dockData.itemName
|
||||
} (${
|
||||
event.target.dockData.materials
|
||||
? event.target.dockData.materials
|
||||
: event.target.dockData.model
|
||||
})`
|
||||
);
|
||||
setDockDimensions(
|
||||
`${event.target.dockData.width}' x ${event.target.dockData.length}'`
|
||||
);
|
||||
} else if (
|
||||
event.target &&
|
||||
event.target.dockData &&
|
||||
event.target.dockData.thumbnail
|
||||
) {
|
||||
setDockLabel(
|
||||
`${capitalize(event.target.dockData.itemName)} (${
|
||||
event.target.dockData.name
|
||||
})`
|
||||
);
|
||||
setDockDimensions(
|
||||
`${event.target.dockData.width}' x ${event.target.dockData.length}'`
|
||||
);
|
||||
imageRef.current.src = event.target.dockData.thumbnail;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!event.target && objHovered) {
|
||||
setObjHovered(false);
|
||||
setDockLabel(null);
|
||||
setDockDimensions(null);
|
||||
setDockDimensions(null);
|
||||
imageRef.current.src = "";
|
||||
}
|
||||
},
|
||||
[objHovered]
|
||||
);
|
||||
|
||||
const hideHighlight = useCallback(() => {
|
||||
if (objHovered) {
|
||||
setObjHovered(false);
|
||||
setDockLabel(null);
|
||||
setDockDimensions(null);
|
||||
imageRef.current.src = "";
|
||||
}
|
||||
}, [objHovered]);
|
||||
|
||||
const updateModifications = useCallback(
|
||||
(savehistory, event) => {
|
||||
if (savehistory === true) {
|
||||
if ((event && event === "update") || event.target.dockData) {
|
||||
const json = editor?.canvas?.toJSON(["dockData", "snapClone"]);
|
||||
const data = JSON.stringify(json);
|
||||
stack.updateStack(data);
|
||||
|
||||
updateLocalstore();
|
||||
}
|
||||
}
|
||||
},
|
||||
[selectedItems, editor]
|
||||
);
|
||||
|
||||
const updateLocalstore = useCallback(() => {
|
||||
const json = editor?.canvas?.toJSON(["dockData", "snapClone"]);
|
||||
const newObjects = json.objects
|
||||
.filter((object) => !object.snapClone)
|
||||
.filter(Boolean);
|
||||
json.objects = newObjects;
|
||||
const items = json.objects
|
||||
.map((object) => {
|
||||
if (object.dockData && !object.snapClone) {
|
||||
return object.dockData;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
setSelectedItems(() => [...items]);
|
||||
|
||||
const data = JSON.stringify(json);
|
||||
localStorage.setItem("dock", data);
|
||||
}, [editor, selectedItems]);
|
||||
|
||||
const onUndoClick = useCallback(() => {
|
||||
// TODO: Undo
|
||||
}, [editorMemo]);
|
||||
|
||||
const onRedoClick = useCallback(() => {
|
||||
// TODO: Redo
|
||||
}, [editorMemo]);
|
||||
|
||||
const onPrintScreen = useCallback(() => {
|
||||
// convert the canvas to a data url and download it.
|
||||
// TODO: Print screen
|
||||
// TODO: print screen without background image and background color, the background image should be white
|
||||
// TODO: after printing, the background image and background color should be restored
|
||||
}, [editorMemo]);
|
||||
|
||||
const CopySelection = () => {
|
||||
// TODO: Copy selection
|
||||
};
|
||||
|
||||
const PasteSelection = () => {
|
||||
// TODO: Paste selection
|
||||
};
|
||||
|
||||
const onDeleteSelection = () => {
|
||||
// TODO: Delete selection
|
||||
};
|
||||
|
||||
const addLines = useCallback(() => {
|
||||
// Initiate a line instance
|
||||
const editorHeight = editorMemo.getHeight();
|
||||
const division = editorHeight / oneFeet;
|
||||
|
||||
if (!linesAdded) {
|
||||
let initialFt = 8;
|
||||
let multiplier = 1;
|
||||
for (let i = division - 3; i > 0; i--) {
|
||||
if (multiplier > division - 4) {
|
||||
break;
|
||||
}
|
||||
let line = new fabric.Line(
|
||||
[0, oneFeet * i, editorMemo.getWidth(), oneFeet * i],
|
||||
{
|
||||
stroke: "#AAAAAA",
|
||||
strokeDashArray: [5]
|
||||
}
|
||||
);
|
||||
|
||||
let text = new fabric.Text(`${initialFt * multiplier}ft`, {
|
||||
// stroke: '#000000',
|
||||
fill: "#AAAAAA",
|
||||
fontSize: 18,
|
||||
left: editorMemo.getWidth() - 40,
|
||||
top: oneFeet * i
|
||||
});
|
||||
|
||||
multiplier++;
|
||||
// Render the rectangle in canvas
|
||||
editorMemo.add(line).sendToBack(line);
|
||||
editorMemo.add(text).sendToBack(text);
|
||||
editorMemo.renderAll();
|
||||
}
|
||||
setLinesAdded(true);
|
||||
}
|
||||
}, [editorMemo, linesAdded]);
|
||||
|
||||
const removeLines = useCallback(() => {
|
||||
editorMemo.getObjects("line").forEach((line) => {
|
||||
editorMemo.remove(line);
|
||||
});
|
||||
editorMemo.getObjects("text").forEach((text) => {
|
||||
editorMemo.remove(text);
|
||||
});
|
||||
setLinesAdded(false);
|
||||
}, [editorMemo, linesAdded]);
|
||||
|
||||
const onCloseBuildFromLocalModal = useCallback(() => {
|
||||
setShowBuildCanvasFromLocalModal(false);
|
||||
|
||||
dispatch({
|
||||
type: "DOCK_LOADING",
|
||||
payload: false
|
||||
});
|
||||
setInitialLoad(false);
|
||||
}, [editorMemo, showBuildCanvasFromLocalModal, dispatch, initialLoad]);
|
||||
function renderIcon(icon) {
|
||||
return function renderIcon(ctx, left, top, styleOverride, fabricObject) {
|
||||
var size = this.cornerSize;
|
||||
ctx.save();
|
||||
ctx.translate(left, top);
|
||||
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
|
||||
ctx.drawImage(icon, -size / 2, -size / 2, size, size);
|
||||
ctx.restore();
|
||||
};
|
||||
}
|
||||
function deleteObject(eventData, transform) {
|
||||
var target = transform.target;
|
||||
var canvas = target.canvas;
|
||||
canvas.remove(target);
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (editor) {
|
||||
editorMemo.backgroundColor = "#011e23";
|
||||
editorMemo.enableRetinaScaling = false;
|
||||
|
||||
// Use hardware acceleration
|
||||
// editorMemo.renderOnAddRemove = false;
|
||||
editorMemo.skipOffscreen = true;
|
||||
// Lower render quality setting
|
||||
fabric.devicePixelRatio = 1;
|
||||
fabric.Group.prototype.hasControls = true;
|
||||
fabric.Group.prototype.snapAngle = 45;
|
||||
fabric.Group.prototype.snapThreshold = 5;
|
||||
fabric.Group.prototype.stroke = "#0f75bc";
|
||||
fabric.Object.prototype.snapAngle = 45;
|
||||
// Optimize object rendering
|
||||
fabric.Object.prototype.objectCaching = true;
|
||||
|
||||
fabric.Object.prototype.snapThreshold = 5;
|
||||
fabric.Object.prototype.setControlsVisibility({
|
||||
tl: false, //top-left
|
||||
mt: false, // middle-top
|
||||
tr: true, //top-right
|
||||
ml: false, //middle-left
|
||||
mr: false, //middle-right
|
||||
bl: false, // bottom-left
|
||||
mb: false, //middle-bottom
|
||||
br: false, //bottom-right
|
||||
mtr: false // rotate icon
|
||||
});
|
||||
// fabric.Object.prototype.controls.deleteControl = new fabric.Control( {
|
||||
// x: -0.8,
|
||||
// y: -1,
|
||||
// offsetY: 16,
|
||||
// offsetX: 16,
|
||||
// cursorStyle: 'pointer',
|
||||
// mouseUpHandler: deleteObject,
|
||||
// render: renderIcon( deleteImg ),
|
||||
// cornerPadding: 0,
|
||||
// cornerSize: 24
|
||||
// } );
|
||||
fabric.Object.prototype.controls.tr = new fabric.Control({
|
||||
x: 0.5,
|
||||
y: -0.5,
|
||||
cursorStyle: "rotate",
|
||||
offsetY: -16,
|
||||
offsetX: 16,
|
||||
cornerPadding: 0,
|
||||
actionHandler: fabric.controlsUtils.rotationWithSnapping,
|
||||
cursorStyleHandler: fabric.controlsUtils.rotationStyleHandler,
|
||||
withConnection: true,
|
||||
render: renderIcon(rotateImg),
|
||||
cornerSize: 20,
|
||||
actionName: "rotate",
|
||||
sizeX: 40,
|
||||
sizeY: 40,
|
||||
touchSizeX: 40,
|
||||
touchSizeY: 40
|
||||
});
|
||||
fabric.Object.prototype.hasControls = true;
|
||||
fabric.Object.prototype.lockScalingX = true;
|
||||
fabric.Object.prototype.lockScalingY = true;
|
||||
fabric.Object.prototype.cornerSize = 5;
|
||||
fabric.Line.prototype.hasBorders = false;
|
||||
fabric.Line.prototype.cornerSize = 0;
|
||||
fabric.Line.prototype.borderColor = "transparent";
|
||||
fabric.Line.prototype.hasRotatingPoint = false;
|
||||
fabric.Line.prototype.hasControls = false;
|
||||
fabric.Line.prototype.lockRotation = true;
|
||||
fabric.Line.prototype.hoverCursor = "default";
|
||||
fabric.Line.prototype.selectable = false;
|
||||
fabric.Line.prototype.lockMovementX = true;
|
||||
fabric.Line.prototype.lockMovementY = true;
|
||||
fabric.Text.prototype.hasBorders = false;
|
||||
fabric.Text.prototype.cornerSize = 0;
|
||||
fabric.Text.prototype.borderColor = "transparent";
|
||||
fabric.Text.prototype.hasRotatingPoint = false;
|
||||
fabric.Text.prototype.hoverCursor = "default";
|
||||
fabric.Text.prototype.selectable = false;
|
||||
fabric.Text.prototype.hasControls = false;
|
||||
fabric.Text.prototype.lockRotation = true;
|
||||
fabric.Text.prototype.lockMovementX = true;
|
||||
fabric.Text.prototype.lockMovementY = true;
|
||||
|
||||
// fabric.Text.prototype.viewportCenter = true
|
||||
setEditorReady(true);
|
||||
editorMemo.setHeight(window.innerHeight);
|
||||
editorMemo.setWidth(window.innerWidth);
|
||||
fabric.Object.prototype.cornerColor = "#B9C0D4";
|
||||
|
||||
renderBg();
|
||||
addLines();
|
||||
|
||||
// Intersection
|
||||
// TODO: Edge detection and snap to object within snap range
|
||||
editorMemo.on("object:moving", edgeDetectionAndSnap);
|
||||
|
||||
editorMemo.on("mouse:over", showHighlight);
|
||||
editorMemo.on("mouse:out", hideHighlight);
|
||||
editorMemo.on("object:modified", (e) => updateModifications(true, e));
|
||||
editorMemo.on("object:added", (e) => updateModifications(true, e));
|
||||
|
||||
// Listen for the delete key event
|
||||
document.onkeydown = (event) => {
|
||||
if (event.keyCode === 46) {
|
||||
// 46 is the keyCode for the delete key
|
||||
onDeleteSelection();
|
||||
}
|
||||
};
|
||||
|
||||
const savedDock = localStorage.getItem("dock")
|
||||
? JSON.parse(localStorage.getItem("dock"))
|
||||
: null;
|
||||
if (savedDock) {
|
||||
if (initialLoad) {
|
||||
setShowBuildCanvasFromLocalModal(true);
|
||||
}
|
||||
} else if (initialLoad) {
|
||||
setInitialLoad(false);
|
||||
dispatch({
|
||||
type: "DOCK_LOADING",
|
||||
payload: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editor) {
|
||||
const handleResize = () => {
|
||||
// let canvas = canvasRef.current;
|
||||
editorMemo.setWidth(window.innerWidth);
|
||||
editorMemo.setHeight(window.innerHeight);
|
||||
editorMemo.setBackgroundImage(editorMemo.backgroundImage, (bgImage) => {
|
||||
bgImage.set({
|
||||
scaleX: editorMemo.width / bgImage.width,
|
||||
scaleY: editorMemo.height / bgImage.height
|
||||
});
|
||||
});
|
||||
removeLines();
|
||||
addLines();
|
||||
editorMemo.renderAll();
|
||||
};
|
||||
window.addEventListener("resize", handleResize);
|
||||
handleResize();
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`relative`}>
|
||||
<>
|
||||
<DockSidebar
|
||||
onDownloadImage={downloadImage}
|
||||
onDownloadFile={toJSON}
|
||||
onUploadFile={uploadFile}
|
||||
fileRef={fileRef}
|
||||
editor={editorMemo}
|
||||
updateModifications={updateModifications}
|
||||
selectedItems={selectedItems}
|
||||
onEstimateModalOpen={onEstimateModalOpen}
|
||||
/>
|
||||
|
||||
<FabricJSCanvas className={`floor-canvas`} onReady={onReady} />
|
||||
|
||||
<ActionButtons
|
||||
className={`absolute w-fit top-4 right-8`}
|
||||
onCopy={CopySelection}
|
||||
onPaste={PasteSelection}
|
||||
onRedoClick={onRedoClick}
|
||||
onUndoClick={onUndoClick}
|
||||
onPrintScreen={onPrintScreen}
|
||||
onDeleteSelection={onDeleteSelection}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={`rounded w-[10.875rem] absolute top-0 inset-x-0 m-auto ${
|
||||
objHovered ? "" : "hidden"
|
||||
}`}
|
||||
>
|
||||
{dockLabel ? (
|
||||
<div
|
||||
className={`w-full shadow text-white text-center font-medium text-[1.125rem] leading-[1.6875rem] tracking-[2%] font-['Rajdhani']`}
|
||||
>
|
||||
{dockLabel}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<img
|
||||
ref={imageRef}
|
||||
src=""
|
||||
alt=""
|
||||
width={174}
|
||||
height={116}
|
||||
className={`rounded relative ${objHovered ? "" : "hidden"}`}
|
||||
/>
|
||||
|
||||
{dockDimensions ? (
|
||||
<div
|
||||
className={`w-full shadow text-white text-center font-medium text-[1.125rem] leading-[1.6875rem] tracking-[2%] font-['Rajdhani']`}
|
||||
>
|
||||
{dockDimensions}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
|
||||
<EstimateModal
|
||||
loading={estimateLoading}
|
||||
modalCloseClick={onEstimateModalClose}
|
||||
showEstimateModal={showEstimateModal}
|
||||
dockImage={dockImage}
|
||||
selectedItems={selectedItems}
|
||||
/>
|
||||
<BuildCanvasFromLocalModal
|
||||
modalCloseClick={onCloseBuildFromLocalModal}
|
||||
showBuildCanvasFromLocalModal={showBuildCanvasFromLocalModal}
|
||||
editor={editorMemo}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { DockBuilder } from './DockBuilder'
|
||||
@@ -0,0 +1,73 @@
|
||||
import React, { useCallback, useContext } from 'react'
|
||||
import { SidebarLogo } from 'Components/SidebarLogo'
|
||||
import { GlobalContext } from '../../globalContext'
|
||||
import { SidebarFoot } from 'Components/SidebarFoot'
|
||||
import { SidebarBuilder } from 'Components/SidebarBuilder'
|
||||
|
||||
export const DockSidebar = ( { onAddItem, onDownloadImage, onDownloadFile, onUploadFile, fileRef, editor, updateModifications, selectedItems, onEstimateModalOpen } ) => {
|
||||
const { state, dispatch } = useContext( GlobalContext )
|
||||
const { dockSideBarOpen } = state
|
||||
let toggleOpen = ( open ) =>
|
||||
dispatch( {
|
||||
type: "TOGGLE_DOCK_SIDEBAR",
|
||||
payload: { dockSideBarOpen: open },
|
||||
} );
|
||||
|
||||
const onWheel = useCallback( ( e ) => {
|
||||
if ( e.ctrlKey ) {
|
||||
return e.preventDefault();//prevent zoom
|
||||
}
|
||||
|
||||
}, [] );
|
||||
|
||||
return (
|
||||
<div style={ { zIndex: 10000000 } } className={ `flex absolute` }>
|
||||
<div className={ `sidebar-holder flex flex-col bg-white max-h-screen ${ !dockSideBarOpen ? "open-nav" : "" }` } onWheel={ onWheel }>
|
||||
<SidebarLogo />
|
||||
|
||||
<SidebarBuilder
|
||||
onAddItem={ onAddItem }
|
||||
editor={ editor }
|
||||
updateModifications={ updateModifications }
|
||||
selectedItems={ selectedItems }
|
||||
/>
|
||||
<SidebarFoot onDownloadImage={ onDownloadImage } onDownloadFile={ onDownloadFile } onUploadFile={ onUploadFile } fileRef={ fileRef } onEstimateModalOpen={ onEstimateModalOpen } />
|
||||
</div>
|
||||
<span className='bg-white h-fit' onClick={ () => toggleOpen( !dockSideBarOpen ) }>
|
||||
{ !dockSideBarOpen ? (
|
||||
<svg
|
||||
className="block h-6 w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
className="block h-6 w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
) }
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { DockSidebar } from './DockSidebar'
|
||||
@@ -0,0 +1,464 @@
|
||||
import React from "react";
|
||||
import MkdSDK from "../utils/MkdSDK";
|
||||
import {empty} from "../utils/utils";
|
||||
|
||||
const sdk = new MkdSDK();
|
||||
const defaultImage = 'https://via.placeholder.com/150?text=%20';
|
||||
const DynamicContentType = ({contentType, contentValue, setContentValue}) => {
|
||||
const [previewUrl, setPreviewUrl] = React.useState(defaultImage);
|
||||
const handleImageChange = async (e) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', e.target.files[0]);
|
||||
try {
|
||||
const result = await sdk.uploadImage(formData);
|
||||
setPreviewUrl(result.url);
|
||||
setContentValue(result.url);
|
||||
}catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
}
|
||||
switch (contentType) {
|
||||
case "text":
|
||||
return (
|
||||
<>
|
||||
<textarea 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`}
|
||||
rows={15}
|
||||
placeholder="Content"
|
||||
onChange={e => setContentValue(e.target.value)}
|
||||
defaultValue={contentValue}
|
||||
></textarea>
|
||||
</>
|
||||
)
|
||||
|
||||
case "image":
|
||||
return (
|
||||
<>
|
||||
<img src={empty(contentValue) ? previewUrl : contentValue } alt="preview" height={150} width={150} />
|
||||
<input
|
||||
type="file"
|
||||
onChange={handleImageChange}
|
||||
className={`shadow appearance-none border block rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
case "number":
|
||||
return (
|
||||
<input
|
||||
type="number"
|
||||
className={`shadow appearance-none border block rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
|
||||
onChange={e => setContentValue(e.target.value)}
|
||||
defaultValue={contentValue}
|
||||
/>
|
||||
)
|
||||
|
||||
case "team-list":
|
||||
return (
|
||||
<TeamList setContentValue={setContentValue} contentValue={contentValue} />
|
||||
)
|
||||
|
||||
case "image-list":
|
||||
return (
|
||||
<ImageList setContentValue={setContentValue} contentValue={contentValue} />
|
||||
)
|
||||
|
||||
case "captioned-image-list":
|
||||
return (
|
||||
<CaptionedImageList setContentValue={setContentValue} contentValue={contentValue} />
|
||||
)
|
||||
|
||||
case "kvp":
|
||||
return (
|
||||
<KeyValuePair setContentValue={setContentValue} contentValue={contentValue} />
|
||||
)
|
||||
|
||||
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 (
|
||||
<div className="block">
|
||||
{items.map( (item, index) => <div key={index*0.23}>
|
||||
<img src={item.value !== null ? item.value : defaultImage} alt="preview" height={150} width={150} />
|
||||
<div className="flex">
|
||||
<input
|
||||
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`}
|
||||
type="text" placeholder="key" listkey={index} onChange={handleKeyChange} defaultValue={item.key} />
|
||||
<input
|
||||
listkey={index}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleImageChange}
|
||||
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`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>)
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
className="bg-blue-400 hover:bg-blue-700 text-white font-bold py-1 px-2 my-4 rounded focus:outline-none focus:shadow-outline"
|
||||
onClick={ e => setItems(old => [...old, {key: '', value_type: 'image', value: null}])}>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="block">
|
||||
{items.map( (item, index) => <div key={index*0.23} >
|
||||
<img src={item.value !== null ? item.value : defaultImage} alt="preview" height={150} width={150} />
|
||||
<div className="flex">
|
||||
<input
|
||||
className={`shadow appearance-none border rounded w-full py-2 px-3 mr-2 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
|
||||
type="text" placeholder="Key" listkey={index} onChange={handleKeyChange} defaultValue={item.key}
|
||||
/>
|
||||
<input
|
||||
listkey={index}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleImageChange}
|
||||
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`}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
className={`shadow block appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
|
||||
type="text"
|
||||
placeholder="Caption" listkey={index} onChange={handleCaptionChange} defaultValue={item.caption}
|
||||
/>
|
||||
|
||||
</div>)
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
className="bg-blue-400 hover:bg-blue-700 text-white font-bold py-1 px-2 my-4 rounded focus:outline-none focus:shadow-outline"
|
||||
onClick={ e => setItems(old => [...old, {key: '', value_type: 'image', value: null, caption: ''}])}>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
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 (
|
||||
<div className="block my-4">
|
||||
{items.map( (item, index) => <div key={index*0.23} className="my-4" >
|
||||
|
||||
{/* <div className="flex"> */}
|
||||
<input
|
||||
className={`shadow block appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
|
||||
type="text"
|
||||
placeholder="Title" listkey={index} onChange={handleTitleChange} defaultValue={item.title}
|
||||
/>
|
||||
<input
|
||||
className={`shadow appearance-none border rounded w-full py-2 px-3 mr-2 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
|
||||
type="text" placeholder="Name" listkey={index} onChange={handleNameChange} defaultValue={item.name}
|
||||
/>
|
||||
<img src={item.image !== null ? item.image : defaultImage} alt="preview" height={150} width={150} />
|
||||
<input
|
||||
listkey={index}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleImageChange}
|
||||
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`}
|
||||
/>
|
||||
{/* </div> */}
|
||||
|
||||
|
||||
</div>)
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
className="bg-blue-400 hover:bg-blue-700 text-white font-bold py-1 px-2 my-4 rounded focus:outline-none focus:shadow-outline"
|
||||
onClick={ e => setItems(old => [...old, {name: '', image: null, title: ''}])}>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
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 (
|
||||
<div className="block">
|
||||
{items.map( (item, index) => <div key={index*0.23} className="my-4" >
|
||||
<input
|
||||
className={`shadow appearance-none border rounded w-full py-2 px-3 mr-2 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
|
||||
type="text" placeholder="Key" listkey={index} onChange={handleKeyChange} defaultValue={item.key}
|
||||
/>
|
||||
<select
|
||||
className={`shadow block border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
|
||||
listkey={index} onChange={handleValueTypeChange} defaultValue={item.value_type}>
|
||||
{valueTypeMap.map( (type, index) => <option key={index*122} value={type.key}>{type.value}</option>)}
|
||||
</select>
|
||||
<input
|
||||
className={`shadow block appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
|
||||
type="text"
|
||||
required
|
||||
placeholder="Value" listkey={index} onChange={handleValueChange} defaultValue={item.value}
|
||||
/>
|
||||
|
||||
|
||||
</div>)
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
className="bg-blue-400 hover:bg-blue-700 text-white font-bold py-1 px-2 my-4 rounded focus:outline-none focus:shadow-outline"
|
||||
onClick={ e => setItems(old => [...old, {key: '', value_type: 'text', value: ''}])}>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
import React, { memo, useState, useCallback } from 'react';
|
||||
import { Modal } from 'Components/Modal';
|
||||
import { InteractiveButton } from 'Components/InteractiveButton';
|
||||
import { ArrowLeftIcon, ArrowRrightIcon, GreenTickIcon } from 'Assets/svgs';
|
||||
import { dock_image, EstimateSteps, Tables, Truthy } from 'Utils/constants';
|
||||
import { ContactInformation } from 'Components/ContactInformation';
|
||||
import { LakeSurroundings } from 'Components/LakeSurroundings';
|
||||
import { Comments } from 'Components/Comments';
|
||||
import MkdSDK from 'Utils/MkdSDK';
|
||||
|
||||
const sdk = new MkdSDK()
|
||||
const classes = {
|
||||
modal: 'string',
|
||||
modalDialog: 'relative bg-white w-[500px]',
|
||||
modalContent: 'string',
|
||||
modalHeader: 'string',
|
||||
modalTitle: 'string',
|
||||
modalBody: 'string',
|
||||
modalFooter: 'string',
|
||||
closeButtonClass: 'string',
|
||||
saveButtonClass: 'string',
|
||||
}
|
||||
|
||||
const EstimateModal = ( {
|
||||
showEstimateModal,
|
||||
modalCloseClick,
|
||||
selectedItems,
|
||||
dockImage,
|
||||
} ) => {
|
||||
const [ step, setStep ] = useState( EstimateSteps.ContactInformation )
|
||||
const [ submitLoading, setSubmitLoading ] = useState( false )
|
||||
const [ errorMessage, setErrorMessage ] = useState( null )
|
||||
const [ hasDealer, setHasDealer ] = useState( Truthy.False )
|
||||
|
||||
// Contact Information
|
||||
const [ dealer, setDealer ] = useState( 0 )
|
||||
const [ firstName, setFirstName ] = useState( '' )
|
||||
const [ lastName, setLastName ] = useState( '' )
|
||||
const [ email, setEmail ] = useState( '' )
|
||||
const [ phone, setPhone ] = useState( '' )
|
||||
const [ lake, setLake ] = useState( '' )
|
||||
const [ city, setCity ] = useState( '' )
|
||||
const [ country, setCountry ] = useState( '' )
|
||||
|
||||
// Lake Surroundings
|
||||
const [ dockConnection, setDockConnection ] = useState( '' )
|
||||
const [ lakeBottom, setLakeBottom ] = useState( '' )
|
||||
const [ willDockBoat, setWillDockBoat ] = useState( Truthy.False )
|
||||
|
||||
// Comments
|
||||
const [ comments, setComments ] = useState( "" )
|
||||
|
||||
const onStepChange = useCallback( ( next ) => {
|
||||
setStep( next )
|
||||
}, [ step ] )
|
||||
|
||||
const onUpdateComment = useCallback( ( comment ) => {
|
||||
setComments( comment )
|
||||
}, [ comments ] )
|
||||
|
||||
const onSubmit = useCallback( () => {
|
||||
( async () => {
|
||||
try {
|
||||
setSubmitLoading( true )
|
||||
const quoteBody = {
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
email: email,
|
||||
phone: phone,
|
||||
lake: lake,
|
||||
city: city,
|
||||
dock_connection: dockConnection,
|
||||
lake_bottom: lakeBottom,
|
||||
country: country,
|
||||
dock_image: dockImage,
|
||||
has_dealer: hasDealer,
|
||||
dealer_id: dealer,
|
||||
will_dock_boat: willDockBoat,
|
||||
comments: comments,
|
||||
selected_items: JSON.stringify( selectedItems )
|
||||
}
|
||||
const result = await sdk.requestQuote( quoteBody )
|
||||
if ( !result?.error ) {
|
||||
setSubmitLoading( false )
|
||||
setStep( EstimateSteps.InquirySubmitted )
|
||||
}
|
||||
} catch ( error ) {
|
||||
setErrorMessage( error.message )
|
||||
setSubmitLoading( false )
|
||||
}
|
||||
} )()
|
||||
}, [ step, submitLoading, errorMessage, comments, hasDealer, dealer, firstName, lastName, email, phone, lake, city, country, dockConnection, lakeBottom, willDockBoat, selectedItems, dockImage ] )
|
||||
|
||||
const onClose = useCallback( () => {
|
||||
setStep( EstimateSteps.ContactInformation )
|
||||
modalCloseClick()
|
||||
}, [ step ] )
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="REQUEST AN ESTIMATE"
|
||||
isOpen={ showEstimateModal }
|
||||
classes={ classes }
|
||||
modalHeader
|
||||
modalCloseClick={ modalCloseClick }
|
||||
>
|
||||
{/* ContactInformation */ }
|
||||
<ContactInformation
|
||||
step={ step }
|
||||
onStepChange={ onStepChange }
|
||||
hasDealer={ hasDealer }
|
||||
onHasDealerChange={ setHasDealer }
|
||||
onDealerChange={ setDealer }
|
||||
setFirstName={ setFirstName }
|
||||
setLastName={ setLastName }
|
||||
setEmail={ setEmail }
|
||||
setPhone={ setPhone }
|
||||
setLake={ setLake }
|
||||
setCity={ setCity }
|
||||
setCountry={ setCountry }
|
||||
/>
|
||||
|
||||
{/* LakeSurroundings */ }
|
||||
<LakeSurroundings
|
||||
step={ step }
|
||||
onStepChange={ onStepChange }
|
||||
setDockConnection={ setDockConnection }
|
||||
setLakeBottom={ setLakeBottom }
|
||||
setWillDockBoat={ setWillDockBoat }
|
||||
/>
|
||||
|
||||
{/* Comments */ }
|
||||
<Comments
|
||||
step={ step }
|
||||
onSubmit={ onSubmit }
|
||||
onUpdateComment={ onUpdateComment }
|
||||
onStepChange={ onStepChange }
|
||||
errorMessage={ errorMessage }
|
||||
submitLoading={ submitLoading }
|
||||
comments={ comments }
|
||||
/>
|
||||
|
||||
{/* InquirySubmitted */ }
|
||||
<div className={ `${ step === EstimateSteps.InquirySubmitted ? '' : 'hidden' }` }>
|
||||
|
||||
<div>
|
||||
<div className={ `flex items-center gap-x-2` }>
|
||||
<GreenTickIcon />
|
||||
<p className={ ` uppercase text-[#0F75BC] leading-[150%] tracking-wide text-[18px] font-bold` }>Inquiry Submitted</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={ `flex w-full justify-center bg-transparent ` }>
|
||||
<button
|
||||
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center bg-[#0F75BC] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px]` }
|
||||
onClick={ onClose }
|
||||
>
|
||||
close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const EstimateModalMemo = memo( EstimateModal );
|
||||
|
||||
export { EstimateModalMemo as EstimateModal };
|
||||
@@ -0,0 +1 @@
|
||||
export { EstimateModal } from './EstimateModal'
|
||||
@@ -0,0 +1,63 @@
|
||||
import React from "react";
|
||||
import { fabric } from "fabric";
|
||||
|
||||
function Rect({ left, top, width, height, fill }) {
|
||||
const rectRef = React.useRef(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const rect = new fabric.Rect({
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height,
|
||||
fill,
|
||||
});
|
||||
|
||||
rectRef.current = rect;
|
||||
}, [left, top, width, height, fill]);
|
||||
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export default Rect;
|
||||
|
||||
// const addTestRect = () => {
|
||||
// if (editor) {
|
||||
// const rect = new fabric.Rect({
|
||||
// left: 200,
|
||||
// top: 50,
|
||||
// width: 500,
|
||||
// height: 50,
|
||||
// fill: "red",
|
||||
// });
|
||||
|
||||
// editorMemo.add(rect);
|
||||
// const rect2 = new fabric.Rect({
|
||||
// left: 250,
|
||||
// top: 100,
|
||||
// width: 200,
|
||||
// height: 50,
|
||||
// fill: "red",
|
||||
// });
|
||||
|
||||
// editorMemo.add(rect2);
|
||||
// const rect3 = new fabric.Rect({
|
||||
// left: 300,
|
||||
// top: 150,
|
||||
// width: 200,
|
||||
// height: 50,
|
||||
// fill: "red",
|
||||
// });
|
||||
|
||||
// editorMemo.add(rect3);
|
||||
// const rect4 = new fabric.Rect({
|
||||
// left: 350,
|
||||
// top: 200,
|
||||
// width: 200,
|
||||
// height: 50,
|
||||
// fill: "red",
|
||||
// });
|
||||
|
||||
// editorMemo.add(rect4);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
|
||||
export const InteractiveButton = ({
|
||||
loading,
|
||||
children,
|
||||
type = "button",
|
||||
className,
|
||||
backgroundColor,
|
||||
onClick,
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
style={{ backgroundColor: backgroundColor && backgroundColor }}
|
||||
onClick={onClick}
|
||||
type={type}
|
||||
disabled={loading}
|
||||
className={`flex justify-center items-center gap-5 bg-[#0F75BC] ${className}`}
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<svg
|
||||
className="animate-spin -ml-1 mr-3 h-5 w-5 text-white inline"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>{" "}
|
||||
{children}
|
||||
</>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { InteractiveButton } from './InteractiveButton'
|
||||
@@ -0,0 +1,141 @@
|
||||
import React, { memo } from 'react';
|
||||
import { ArrowLeftIcon, ArrowRrightIcon } from 'Assets/svgs';
|
||||
import { EstimateSteps, Truthy } from 'Utils/constants';
|
||||
|
||||
const LakeSurroundings = ( { step, onStepChange, setWillDockBoat, setDockConnection, setLakeBottom } ) => {
|
||||
|
||||
const dockConnectionMap = [ 'Resting', 'Bolted', 'Other' ]
|
||||
const lakeBottomMap = [ 'Rocky', 'Silty', 'Sandy' ]
|
||||
// const dock_connection = { 0: 'Resting', 1: 'Bolted', 2: 'Other' }
|
||||
// const lake_bottom = { 0: 'Rocky', 1: 'Silty', 2: 'Sandy' }
|
||||
// const has_dealer = { 0: 'No', 1: 'Yes' }
|
||||
// const will_dock_boat = { 0: 'No', 1: 'Yes' }
|
||||
{/* LakeSurroundings */ }
|
||||
|
||||
return (
|
||||
|
||||
<div className={ `${ step === EstimateSteps.LakeSurrounding ? '' : 'hidden' }` }>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className={ `text-[#4A5578] leading-[150%] tracking-tight text-[14px] font-medium flex items-center` }
|
||||
onClick={ () => onStepChange( EstimateSteps.ContactInformation ) }
|
||||
>
|
||||
<ArrowLeftIcon /> Back
|
||||
</button>
|
||||
<div className={ `flex items-center gap-x-2` }>
|
||||
<p className={ ` uppercase text-[#0F75BC] leading-[150%] tracking-wide text-[18px] font-bold` }>Lake Surroundings</p>
|
||||
<span className={ `text-[#4A5578] leading-[150%] tracking-tight text-[14px] font-medium` }>(2 of 3)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<fieldset className="cus-input">
|
||||
<label
|
||||
htmlFor="dockConnection"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>
|
||||
<span className={ `text-red-600 mr-2` }>*</span> How Will The Dock Be Connected to The Shore?
|
||||
</label>
|
||||
<div className="relative mb-6">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
{/* <img
|
||||
className="w-5 h-5 object-contain"
|
||||
src={ `` }
|
||||
/> */}
|
||||
</div>
|
||||
<select
|
||||
id="dockConnection"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
onChange={ ( e ) => setDockConnection( e.target.value ) }
|
||||
>
|
||||
<>
|
||||
<option></option>
|
||||
{ dockConnectionMap.map( ( dockConnection, index ) => (
|
||||
<option key={ index } value={ index }>{ dockConnection }</option>
|
||||
) ) }
|
||||
</>
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset className="cus-input">
|
||||
<label
|
||||
htmlFor="lakeBottom"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>
|
||||
<span className={ `text-red-600 mr-2 capitalize` }>*</span> What is the Lake Bottom Like?
|
||||
|
||||
</label>
|
||||
<div className="relative mb-6">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
{/* <img
|
||||
className="w-5 h-5 object-contain"
|
||||
src={ `` }
|
||||
/> */}
|
||||
</div>
|
||||
<select
|
||||
id="lakeBottom"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
onChange={ ( e ) => setLakeBottom( e.target.value ) }
|
||||
>
|
||||
<>
|
||||
<option></option>
|
||||
{ lakeBottomMap.map( ( lakeBottom, index ) => (
|
||||
<option key={ index } value={ index }>{ lakeBottom }</option>
|
||||
) ) }
|
||||
</>
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset className="cus-input">
|
||||
<label
|
||||
// htmlFor="willDockBoat"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>
|
||||
<span className={ `text-red-600 mr-2 capitalize` }>*</span>Will you be docking a boat to this system?
|
||||
</label>
|
||||
<div className="relative flex items-center justify-start mb-6">
|
||||
<input
|
||||
type="radio"
|
||||
name="willDockBoat"
|
||||
id="yesDockBoat"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-[44px] p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
onChange={ () => setWillDockBoat( Truthy.True ) }
|
||||
/>
|
||||
<label
|
||||
htmlFor="yesDockBoat"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>Yes
|
||||
</label>
|
||||
<input
|
||||
type="radio"
|
||||
name="willDockBoat"
|
||||
id="noDockBoat"
|
||||
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-[44px] pl-10 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
|
||||
onChange={ () => setWillDockBoat( Truthy.False ) }
|
||||
/>
|
||||
<label
|
||||
htmlFor="noDockBoat"
|
||||
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
|
||||
>No
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
</div>
|
||||
<div className={ `flex w-full justify-center bg-transparent ` }>
|
||||
<button
|
||||
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center bg-[#0F75BC] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px]` }
|
||||
onClick={ () => onStepChange( EstimateSteps.Comments ) }
|
||||
>
|
||||
Continue <ArrowRrightIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const LakeSurroundingsMemo = memo( LakeSurroundings );
|
||||
|
||||
export { LakeSurroundingsMemo as LakeSurroundings };
|
||||
@@ -0,0 +1 @@
|
||||
export { LakeSurroundings } from './LakeSurroundings'
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
import Indicator from "./LoadingIndicator";
|
||||
|
||||
const Loader = ({ style }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
...style
|
||||
}}
|
||||
>
|
||||
<Indicator />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loader;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
const containerVariant = {
|
||||
start: {
|
||||
transition: { staggerChildren: 0.2 }
|
||||
},
|
||||
end: {
|
||||
transition: { staggerChildren: 0.2 }
|
||||
}
|
||||
};
|
||||
|
||||
const dotsVariants = {
|
||||
start: {
|
||||
y: "0%"
|
||||
},
|
||||
end: {
|
||||
y: "100%"
|
||||
}
|
||||
};
|
||||
|
||||
const loadingTransition = {
|
||||
duration: 0.4,
|
||||
yoyo: Infinity,
|
||||
ease: "easeIn"
|
||||
};
|
||||
|
||||
export default function Indicator({ dotsClasses, size, style }) {
|
||||
const dotsStyles = "block w-[9px] h-[9px] bg-slate-900 rounded-md shrink-0 " + dotsClasses;
|
||||
return (
|
||||
<motion.div
|
||||
variants={containerVariant}
|
||||
className={`flex justify-between items-center w-[40px] pb-[10px]`}
|
||||
initial="start"
|
||||
animate="end"
|
||||
style={{ ...style }}
|
||||
>
|
||||
<motion.span
|
||||
className={dotsStyles}
|
||||
variants={dotsVariants}
|
||||
transition={loadingTransition}
|
||||
/>
|
||||
<motion.span
|
||||
className={dotsStyles}
|
||||
variants={dotsVariants}
|
||||
transition={loadingTransition}
|
||||
/>
|
||||
<motion.span
|
||||
className={dotsStyles}
|
||||
variants={dotsVariants}
|
||||
transition={loadingTransition}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import React, { useEffect, useRef, useState, memo } from 'react';
|
||||
import { CloseIcon } from 'Assets/svgs';
|
||||
|
||||
const Modal = ( {
|
||||
children,
|
||||
title,
|
||||
isOpen = false,
|
||||
modalCloseClick,
|
||||
modalHeader,
|
||||
classes
|
||||
} ) => {
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
style={ { zIndex: 100000002 } }
|
||||
className={ `modal-holder bg-[#00000099] items-center justify-center ${ isOpen ? 'flex' : 'hidden' }` }>
|
||||
<div className={ `shadow p-4 bg-white rounded-lg ${ classes?.modalDialog }` }>
|
||||
{ modalHeader &&
|
||||
<div className={ `flex justify-between border-b pb-2` }>
|
||||
<h5 className="font-bold text-center text-lg">{ title }</h5>
|
||||
<div className="modal-close" onClick={ modalCloseClick }>
|
||||
<CloseIcon />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className="mt-4">
|
||||
{ children }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const ModalMemo = memo( Modal );
|
||||
export { ModalMemo as Modal };
|
||||
@@ -0,0 +1 @@
|
||||
export { Modal } from './Modal'
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from "react";
|
||||
const PaginationBar = ({ currentPage, pageCount, pageSize, canPreviousPage, canNextPage, updatePageSize, previousPage, nextPage }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-between ">
|
||||
<div className="mt-2">
|
||||
<span>
|
||||
Page{" "}
|
||||
<strong>
|
||||
{+currentPage} of {pageCount}
|
||||
</strong>{" "}
|
||||
</span>
|
||||
<select
|
||||
className="mt-2"
|
||||
value={pageSize}
|
||||
onChange={(e) => {
|
||||
updatePageSize(Number(e.target.value));
|
||||
}}
|
||||
>
|
||||
{[5, 10, 20, 30, 40, 50].map((pageSize) => (
|
||||
<option key={pageSize} value={pageSize}>
|
||||
Show {pageSize}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* */}
|
||||
<div>
|
||||
<button onClick={previousPage} disabled={!canPreviousPage} className={`font-bold h-10 w-10 `}>
|
||||
←
|
||||
</button>{" "}
|
||||
<button onClick={nextPage} disabled={!canNextPage} className={`font-bold h-10 w-10 `}>
|
||||
→
|
||||
</button>{" "}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaginationBar;
|
||||
@@ -0,0 +1,15 @@
|
||||
import React, { useState } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { AuthContext } from "../authContext";
|
||||
|
||||
export const PublicHeader = () => {
|
||||
const { state, dispatch } = React.useContext(AuthContext);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
return <div>
|
||||
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default PublicHeader;
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
import { capitalize } from 'Utils/helper'
|
||||
|
||||
export const Field = ( { item, property } ) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{ item[ property ] ? (
|
||||
<div className={ `flex items-center justify-between ${ property === "name" ? "text-sm" : "" }` }>
|
||||
<div>{ capitalize( property ) }:</div>
|
||||
<div>{ item[ property ] }{ " " }{ [ "length", "width" ].includes( property ) ? 'ft' : '' }</div>
|
||||
</div> )
|
||||
|
||||
: null
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Chevron } from 'Assets/svgs'
|
||||
import React from 'react'
|
||||
import { Field } from './Field'
|
||||
|
||||
export const SelectedItem = ( { selectedItem } ) => {
|
||||
const itemRef = React.useRef( null )
|
||||
|
||||
const [ open, setOpen ] = React.useState( false )
|
||||
|
||||
const onOpen = React.useCallback( ( isTrue ) => {
|
||||
if ( isTrue ) {
|
||||
itemRef.current.style.maxHeight = null
|
||||
} else {
|
||||
itemRef.current.style.maxHeight = `${ itemRef.current.scrollHeight }px`
|
||||
}
|
||||
setOpen( !isTrue )
|
||||
}, [ open, itemRef ] )
|
||||
|
||||
return (
|
||||
<div className={ ` mb-2` }>
|
||||
|
||||
<button onClick={ () => onOpen( open ) } className={ `flex items-center text-black max-w-full min-w-full mb-2 text-base border-b-2 border-b-state-200 py-2` }>
|
||||
|
||||
<div className={ `w-5` }>
|
||||
<Chevron active={ open } />
|
||||
</div>
|
||||
|
||||
<div className={ `truncate w-full text-left` }>
|
||||
{ selectedItem.itemName } { `(${ selectedItem?.width }' x ${ selectedItem?.length }')` }
|
||||
</div>
|
||||
|
||||
</button>
|
||||
|
||||
<div ref={ itemRef } className={ `max-h-0 overflow-hidden shadow-lg text-black` }>
|
||||
<Field item={ selectedItem } property={ `name` } />
|
||||
<Field item={ selectedItem } property={ `length` } />
|
||||
<Field item={ selectedItem } property={ `width` } />
|
||||
<Field item={ selectedItem } property={ `lift_range` } />
|
||||
<Field item={ selectedItem } property={ `model` } />
|
||||
<Field item={ selectedItem } property={ `no_of_cylinders` } />
|
||||
<Field item={ selectedItem } property={ `weight_capacity` } />
|
||||
<Field item={ selectedItem } property={ `category` } />
|
||||
<Field item={ selectedItem } property={ `materials` } />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import { SelectedItem } from './SelectedItem'
|
||||
|
||||
export const SelectedItems = ( { selectedItems } ) => {
|
||||
|
||||
return (
|
||||
<div className={ `gap-x-[8px] border-t-2 w-full px-3` }>
|
||||
{ selectedItems?.length ? selectedItems?.map( ( item, index ) => (
|
||||
<SelectedItem key={ index } selectedItem={ item } />
|
||||
) )
|
||||
|
||||
: null }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { SelectedItems } from './SelectedItems'
|
||||
export { SelectedItem } from './SelectedItem'
|
||||
export { Field } from './Field'
|
||||
@@ -0,0 +1,51 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { Tabs } from "Components/Tabs";
|
||||
import { Builder } from "Components/Builder";
|
||||
import { SelectedItems } from "Components/SelectedItems";
|
||||
import { TabNames } from "Utils/constants";
|
||||
|
||||
export const SidebarBuilder = ({
|
||||
editor,
|
||||
updateModifications,
|
||||
selectedItems,
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState(TabNames.Builder);
|
||||
// const [ramps, setRamps] = useState([]);
|
||||
|
||||
const onTabClick = useCallback(
|
||||
(tab) => {
|
||||
setActiveTab(tab);
|
||||
},
|
||||
[activeTab]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col max-h-[80%] grow relative overflow-auto gap-x-[8px] border-2 w-full`}
|
||||
>
|
||||
<Tabs
|
||||
className={`flex gap-x-0 justify-center items-center min-h-[50px] max-h-[50px] h-[50px] w-full`}
|
||||
activeTab={activeTab}
|
||||
onTabClick={onTabClick}
|
||||
/>
|
||||
|
||||
{activeTab === TabNames.Builder ? (
|
||||
<Builder
|
||||
editor={editor}
|
||||
updateModifications={updateModifications}
|
||||
// ramps={ramps}
|
||||
// setRamps={setRamps}
|
||||
/>
|
||||
) : null}
|
||||
{activeTab === TabNames.SelectedItems ? (
|
||||
<SelectedItems selectedItems={selectedItems} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
{
|
||||
/* <div className={ `grow flex flex-col justify-center items-start bg-transparent` }>
|
||||
|
||||
</div> */
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { SidebarBuilder } from './SidebarBuilder'
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react'
|
||||
import { DownloadIcon, UploadIcon } from 'Assets/svgs'
|
||||
|
||||
export const SidebarFoot = ( { onDownloadImage, onDownloadFile, fileRef, onUploadFile, onEstimateModalOpen } ) => {
|
||||
|
||||
return (
|
||||
<div className={ `flex flex-col justify-end items-end m-auto h-fit z-50 w-full py-2 px-[20px] bg-white` }>
|
||||
<button
|
||||
onClick={ () => fileRef.current.click() }
|
||||
className={ `text-[#000000] flex justify-start items-center gap-x-5 w-full h-[45px] rounded` }
|
||||
>
|
||||
<UploadIcon /> Upload File
|
||||
<input ref={ fileRef } type="file" hidden onChangeCapture={ ( e ) => onUploadFile( e ) } accept={ `.dock` } />
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={ `text-[#000000] flex justify-start items-center gap-x-5 w-full h-[45px] rounded` }
|
||||
onClick={ onDownloadFile }
|
||||
>
|
||||
<DownloadIcon /> Download File
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={ `text-[#000000] flex justify-start items-center gap-x-5 w-full h-[45px] rounded` }
|
||||
onClick={ onDownloadImage }
|
||||
>
|
||||
<DownloadIcon /> Download Image
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={ `text-white flex justify-center items-center w-full h-[35px] rounded bg-[#0F75BC]` }
|
||||
onClick={ onEstimateModalOpen }
|
||||
>
|
||||
GET ESTIMATE
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { SidebarFoot } from './SidebarFoot'
|
||||
@@ -0,0 +1,15 @@
|
||||
import { BrandLogo } from 'Assets/images'
|
||||
import React from 'react'
|
||||
|
||||
export const SidebarLogo = () =>
|
||||
{
|
||||
return (
|
||||
<div className={ `flex h-[96px] px-[12px] py-[20px] gap-x-[8px]` }>
|
||||
<img src={ BrandLogo } alt="Brand Logo" width={ 43.87 } height={ 56 } className={ `relative h-[56px] w-[43.87px] rounded-[8px]` } />
|
||||
<div className={ `grow flex flex-col justify-center items-start bg-transparent` }>
|
||||
<div className={ `w-full text-[#005C9B] font-bold text-[14px] leading-[150%] tracking-[-0.02em]` }>Paradise Dock & Lift Inc.</div>
|
||||
<div className={ `w-full uppercase text-[#30374F] font-bold text-[14px] leading-[150%] tracking-[-0.02em]` }>Dock builder tool</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { SidebarLogo } from './SidebarLogo'
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
import { GlobalContext } from "../globalContext";
|
||||
const SnackBar = () => {
|
||||
const { state, dispatch } = React.useContext(GlobalContext);
|
||||
const show = state.globalMessage.length > 0;
|
||||
return show ? (
|
||||
<div id="mkd-toast" className="absolute top-5 right-5 flex items-center w-full max-w-xs p-4 text-gray-500 bg-slate-100 rounded-lg shadow dark:text-gray-400" role="alert">
|
||||
<div className="text-sm font-normal">{state.globalMessage}</div>
|
||||
<div className="flex items-center ml-auto space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:hover:bg-gray-700"
|
||||
aria-label="Close"
|
||||
onClick={() => {
|
||||
dispatch({ type: "SNACKBAR", payload: { message: "" } });
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default SnackBar;
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
import React from 'react'
|
||||
|
||||
export const Tab = ( { name, className, active, onTabClick } ) => {
|
||||
return (
|
||||
<button className={ `${ className } ${ active ? "border-b-2 border-b-[#0F75BC]" : "" }` } onClick={ () => onTabClick( name ) }>
|
||||
{ name }
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { Tab } from './Tab'
|
||||
@@ -0,0 +1,95 @@
|
||||
import React from 'react'
|
||||
|
||||
export const Table = ( { className, tableColumns, tableData } ) => {
|
||||
return (
|
||||
<div className={ `shadow overflow-x-auto border-b border-gray-200 ${ className }` }>
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
{ tableColumns.map( ( column, i ) => (
|
||||
<th
|
||||
key={ i }
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
onClick={ () => onSort( i ) }
|
||||
>
|
||||
{ column.header }
|
||||
<span>
|
||||
{ column.isSorted
|
||||
? column.isSortedDesc
|
||||
? " ▼"
|
||||
: " ▲"
|
||||
: "" }
|
||||
</span>
|
||||
</th>
|
||||
) ) }
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{ tableData.map( ( row, i ) => {
|
||||
return (
|
||||
<tr key={ i }>
|
||||
{ tableColumns.map( ( cell, index ) => {
|
||||
if ( cell.accessor == "" ) {
|
||||
return (
|
||||
<td
|
||||
key={ index }
|
||||
className="px-6 py-4 whitespace-nowrap"
|
||||
>
|
||||
{/* <button
|
||||
className="text-xs"
|
||||
onClick={ () => {
|
||||
navigate( "/admin/edit-quotes/" + row.id, {
|
||||
state: row,
|
||||
} );
|
||||
} }
|
||||
>
|
||||
{ " " }
|
||||
Edit
|
||||
</button> */}
|
||||
<button
|
||||
className="text-xs px-1 text-blue-500"
|
||||
onClick={ () => {
|
||||
navigate( "/admin/view-quotes/" + row.id, {
|
||||
state: row,
|
||||
} );
|
||||
} }
|
||||
>
|
||||
{ " " }
|
||||
View
|
||||
</button>
|
||||
<button
|
||||
className="text-xs px-1 text-red-500"
|
||||
onClick={ () => deleteItem( row.id ) }
|
||||
>
|
||||
{ " " }
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
if ( cell.mappingExist ) {
|
||||
return (
|
||||
<td
|
||||
key={ index }
|
||||
className="px-6 py-4 whitespace-nowrap"
|
||||
>
|
||||
{ cell.mappings[ row[ cell.accessor ] ] }
|
||||
</td>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<td key={ index } className="px-6 py-4 whitespace-nowrap">
|
||||
{ [ "image", "top_view", "thumbnail" ].includes( cell.accessor ) ? <img className={ `rounded-md` } src={ row[ cell.accessor ] } /> : row[ cell.accessor ] }
|
||||
</td>
|
||||
);
|
||||
} ) }
|
||||
</tr>
|
||||
);
|
||||
} ) }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { Table } from './Table'
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react'
|
||||
import { Tab } from 'Components/Tab'
|
||||
import { TabNames } from 'Utils/constants'
|
||||
|
||||
|
||||
|
||||
export const Tabs = ( { activeTab, className, onTabClick } ) => {
|
||||
return (
|
||||
<div className={ `${ className }` }>
|
||||
<Tab
|
||||
name={ TabNames.Builder }
|
||||
className={ `w-1/2 text-[#111322] h-full text-[14px] leading-[150%] font-medium tracking-tight` }
|
||||
active={ activeTab === TabNames.Builder ? true : false }
|
||||
onTabClick={ onTabClick }
|
||||
/>
|
||||
|
||||
<Tab
|
||||
name={ TabNames.SelectedItems }
|
||||
className={ `w-1/2 text-[#111322] h-full text-[14px] leading-[150%] font-medium tracking-tight` }
|
||||
active={ activeTab === TabNames.SelectedItems ? true : false }
|
||||
onTabClick={ onTabClick }
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { Tabs } from './Tabs'
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from "react";
|
||||
import { GlobalContext } from "../globalContext";
|
||||
const TopHeader = () => {
|
||||
const { state, dispatch } = React.useContext(GlobalContext);
|
||||
let { isOpen } = state;
|
||||
let toggleOpen = (open) =>
|
||||
dispatch({
|
||||
type: "OPEN_SIDEBAR",
|
||||
payload: { isOpen: open },
|
||||
});
|
||||
return (
|
||||
<div className="page-header shadow">
|
||||
<span onClick={() => toggleOpen(!isOpen)}>
|
||||
{!isOpen ? (
|
||||
<svg
|
||||
className="block h-6 w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
className="block h-6 w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopHeader;
|
||||
@@ -0,0 +1,77 @@
|
||||
import React, { memo, useState, useCallback } from 'react';
|
||||
import { Modal } from 'Components/Modal';
|
||||
import { InteractiveButton } from 'Components/InteractiveButton';
|
||||
// import { ArrowLeftIcon, ArrowRrightIcon, GreenTickIcon } from 'Assets/svgs';
|
||||
// import { dock_image, EstimateSteps, Tables, Truthy } from 'Utils/constants';
|
||||
// import { ContactInformation } from 'Components/ContactInformation';
|
||||
// import { LakeSurroundings } from 'Components/LakeSurroundings';
|
||||
// import { Comments } from 'Components/Comments';
|
||||
import MkdSDK from 'Utils/MkdSDK';
|
||||
|
||||
const sdk = new MkdSDK()
|
||||
const classes = {
|
||||
modal: 'string',
|
||||
modalDialog: 'relative bg-white w-[800px]',
|
||||
modalContent: 'string',
|
||||
modalHeader: 'string',
|
||||
modalTitle: 'string',
|
||||
modalBody: 'string',
|
||||
modalFooter: 'string',
|
||||
closeButtonClass: 'string',
|
||||
saveButtonClass: 'string',
|
||||
}
|
||||
|
||||
const ViewDockImageModal = ( {
|
||||
showViewDockImageModal,
|
||||
modalCloseClick,
|
||||
dockImage
|
||||
} ) => {
|
||||
// const [ step, setStep ] = useState( EstimateSteps.ContactInformation )
|
||||
// const [ submitLoading, setSubmitLoading ] = useState( false )
|
||||
// const [ errorMessage, setErrorMessage ] = useState( null )
|
||||
// const [ hasDealer, setHasDealer ] = useState( Truthy.False )
|
||||
|
||||
|
||||
const onDownloadClick = useCallback( () => {
|
||||
const Anchor = document.createElement( "a" )
|
||||
// console.log( "viewDockImageModal", dockImage )
|
||||
Anchor.href = dockImage;
|
||||
Anchor.download = `dock_image_snapshot.png`;
|
||||
Anchor.click();
|
||||
// modalCloseClick()
|
||||
}, [ dockImage ] )
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="DOCK IMAGE"
|
||||
isOpen={ showViewDockImageModal }
|
||||
classes={ classes }
|
||||
modalHeader
|
||||
modalCloseClick={ modalCloseClick }
|
||||
>
|
||||
<div>
|
||||
<div className={ `w-full h-[400px] rounded-xl my-3` }>
|
||||
<img src={ dockImage } className={ `w-full h-full rounded-xl` } alt="dock_image" />
|
||||
</div>
|
||||
|
||||
<div className={ `w-full flex gap-x-2 justify-between items-center` }>
|
||||
<InteractiveButton
|
||||
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center hover:scale-95 hover:border-[#084c7c] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px]` }
|
||||
backgroundColor={ `#0F75BC` }
|
||||
onClick={ onDownloadClick }
|
||||
>
|
||||
Download
|
||||
</InteractiveButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const ViewDockImageModalMemo = memo( ViewDockImageModal );
|
||||
|
||||
export { ViewDockImageModalMemo as ViewDockImageModal };
|
||||
@@ -0,0 +1 @@
|
||||
export { ViewDockImageModal } from './ViewDockImageModal'
|
||||
@@ -0,0 +1,6 @@
|
||||
export { default as AddButton } from './AddButton'
|
||||
export { default as AdminHeader } from './AdminHeader'
|
||||
export { default as PaginationBar } from './PaginationBar'
|
||||
export { default as PublicHeader } from './PublicHeader'
|
||||
export { default as SnackBar } from './SnackBar'
|
||||
export { default as TopHeader } from './TopHeader'
|
||||
@@ -0,0 +1,15 @@
|
||||
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#41D1FF"/>
|
||||
<stop offset="1" stop-color="#BD34FE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFEA83"/>
|
||||
<stop offset="0.0833333" stop-color="#FFDD35"/>
|
||||
<stop offset="1" stop-color="#FFA800"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,103 @@
|
||||
import React, { useReducer } from "react";
|
||||
export const GlobalContext = React.createContext();
|
||||
|
||||
const initialState = {
|
||||
globalMessage: "",
|
||||
isOpen: true,
|
||||
dockSideBarOpen: true,
|
||||
path: "",
|
||||
dockLoading: true,
|
||||
dockLeft: null,
|
||||
dockTop: null,
|
||||
};
|
||||
|
||||
const reducer = ( state, action ) => {
|
||||
switch ( action.type ) {
|
||||
case "SNACKBAR":
|
||||
return {
|
||||
...state,
|
||||
globalMessage: action.payload.message,
|
||||
};
|
||||
case "SETPATH":
|
||||
return {
|
||||
...state,
|
||||
path: action.payload.path,
|
||||
};
|
||||
case "OPEN_SIDEBAR":
|
||||
return {
|
||||
...state,
|
||||
isOpen: action.payload.isOpen,
|
||||
};
|
||||
case "TOGGLE_DOCK_SIDEBAR":
|
||||
return {
|
||||
...state,
|
||||
dockSideBarOpen: action.payload.dockSideBarOpen,
|
||||
};
|
||||
case "DOCK_LOADING":
|
||||
return {
|
||||
...state,
|
||||
dockLoading: action.payload,
|
||||
};
|
||||
case "UPDATE_DOCK_TOP":
|
||||
return {
|
||||
...state,
|
||||
dockTop: action.payload,
|
||||
};
|
||||
case "UPDATE_DOCK_LEFT":
|
||||
return {
|
||||
...state,
|
||||
dockLeft: action.payload,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const showToast = ( dispatch, message, timeout = 3000 ) => {
|
||||
dispatch( {
|
||||
type: "SNACKBAR",
|
||||
payload: {
|
||||
message,
|
||||
},
|
||||
} );
|
||||
|
||||
setTimeout( () => {
|
||||
dispatch( {
|
||||
type: "SNACKBAR",
|
||||
payload: {
|
||||
message: "",
|
||||
},
|
||||
} );
|
||||
}, timeout );
|
||||
};
|
||||
|
||||
export const toggleDockSideBar = ( dispatch, dockSideBarOpen ) => {
|
||||
dispatch( {
|
||||
type: "TOGGLE_DOCK_SIDEBAR",
|
||||
payload: {
|
||||
dockSideBarOpen
|
||||
},
|
||||
} );
|
||||
};
|
||||
|
||||
const GlobalProvider = ( { children } ) => {
|
||||
const [ state, dispatch ] = useReducer( reducer, initialState );
|
||||
|
||||
// React.useEffect(() => {
|
||||
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<GlobalContext.Provider
|
||||
value={ {
|
||||
state,
|
||||
dispatch,
|
||||
} }
|
||||
>
|
||||
{ children }
|
||||
</GlobalContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalProvider;
|
||||
@@ -0,0 +1,143 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
transition: all .5s ease;
|
||||
}
|
||||
*::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
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;
|
||||
/* color: #ffffff65; */
|
||||
}
|
||||
|
||||
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: 240px;
|
||||
max-width: 240px;
|
||||
position: relative;
|
||||
background: #151515;
|
||||
z-index: 2;
|
||||
transition: all 0.3s;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.open-nav {
|
||||
min-width: 0px !important;
|
||||
max-width: 0px !important;
|
||||
width: 0 !important;
|
||||
transition: 0.2s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.close-dock-panel {
|
||||
min-height: 0px !important;
|
||||
max-height: 0px !important;
|
||||
height: 0 !important;
|
||||
transition: 0.2s;
|
||||
opacity: 0;
|
||||
/* overflow: hidden; */
|
||||
}
|
||||
|
||||
.sidebar-list ul li a {
|
||||
padding: 10px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: 18px;
|
||||
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: 20px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.page-header span {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
width: fit-content;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.center-svg {
|
||||
aspect-ratio: 1/1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1.2em !important;
|
||||
}
|
||||
|
||||
/* Custom BEN */
|
||||
|
||||
.modal-holder {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: rgba(255, 255, 255, 0.782);
|
||||
backdrop-filter: blur(3px);
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
z-index: 99999999;
|
||||
}
|
||||
.cus-m-close {
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
top: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filter-close{
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.uppy-Dashboard-inner {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.sidebar-holder {
|
||||
width: 100%;
|
||||
min-width: 200px;
|
||||
max-width: 200px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.page-header span {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "@fontsource/rajdhani";
|
||||
import "./index.css";
|
||||
import "./output.css";
|
||||
import App from "./App";
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,310 @@
|
||||
import React from "react";
|
||||
import { AuthContext } from "./authContext";
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
|
||||
import { AdminHeader, TopHeader, PublicHeader, SnackBar } from "./components";
|
||||
|
||||
import { NotFoundPage } from "Pages/404";
|
||||
|
||||
import {
|
||||
AddAdminAccessoriesPage,
|
||||
AddAdminBoatLiftsPage,
|
||||
AddAdminCmsPage,
|
||||
AddAdminDealersPage,
|
||||
AddAdminDocksPage,
|
||||
AddAdminEmailPage,
|
||||
AddAdminInstructionsPage,
|
||||
AddAdminPhotoPage,
|
||||
AddAdminQuotesPage,
|
||||
AddAdminUserPage,
|
||||
AddAdminWedgesPage,
|
||||
AddAdminQuotesMailRecipientsPage,
|
||||
AdminQuotesMailRecipientsListPage,
|
||||
AdminAccessoriesListPage,
|
||||
AdminBoatLiftsListPage,
|
||||
AdminCmsListPage,
|
||||
AdminDashboardPage,
|
||||
AdminDealersListPage,
|
||||
AdminDocksListPage,
|
||||
AdminEmailListPage,
|
||||
AdminForgotPage,
|
||||
AdminInstructionsListPage,
|
||||
AdminLoginPage,
|
||||
AdminPhotoListPage,
|
||||
AdminProfilePage,
|
||||
AdminQuotesListPage,
|
||||
AdminResetPage,
|
||||
AdminUserListPage,
|
||||
AdminWedgesListPage,
|
||||
EditAdminAccessoriesPage,
|
||||
EditAdminBoatLiftsPage,
|
||||
EditAdminCmsPage,
|
||||
EditAdminDealersPage,
|
||||
EditAdminDocksPage,
|
||||
EditAdminEmailPage,
|
||||
EditAdminInstructionsPage,
|
||||
EditAdminQuotesPage,
|
||||
EditAdminUserPage,
|
||||
EditAdminWedgesPage,
|
||||
EditAdminQuotesMailRecipientsPage,
|
||||
ViewAdminAccessoriesPage,
|
||||
ViewAdminBoatLiftsPage,
|
||||
ViewAdminDealersPage,
|
||||
ViewAdminDocksPage,
|
||||
ViewAdminInstructionsPage,
|
||||
ViewAdminQuotesPage,
|
||||
ViewAdminWedgesPage,
|
||||
ViewAdminQuotesMailRecipientsPage,
|
||||
AdminRampsListPage,
|
||||
AddAdminRampsPage,
|
||||
ViewAdminRampsPage,
|
||||
EditAdminRampsPage,
|
||||
} from "Pages/admin";
|
||||
|
||||
import { DockBuilderPage } from "Pages/dock";
|
||||
|
||||
function renderHeader(role) {
|
||||
switch (role) {
|
||||
case "admin":
|
||||
return <AdminHeader />;
|
||||
|
||||
default:
|
||||
return <PublicHeader />;
|
||||
}
|
||||
}
|
||||
|
||||
function renderRoutes(role) {
|
||||
switch (role) {
|
||||
case "admin":
|
||||
return (
|
||||
<Routes>
|
||||
<Route exact path="/admin" element={<AdminDashboardPage />}></Route>
|
||||
<Route
|
||||
exact
|
||||
path="/admin/dashboard"
|
||||
element={<AdminDashboardPage />}
|
||||
></Route>
|
||||
<Route
|
||||
exact
|
||||
path="/admin/profile"
|
||||
element={<AdminProfilePage />}
|
||||
></Route>
|
||||
|
||||
<Route
|
||||
path="/admin/accessories"
|
||||
element={<AdminAccessoriesListPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/add-accessories"
|
||||
element={<AddAdminAccessoriesPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/edit-accessories/:id"
|
||||
element={<EditAdminAccessoriesPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/view-accessories/:id"
|
||||
element={<ViewAdminAccessoriesPage />}
|
||||
></Route>
|
||||
|
||||
<Route
|
||||
path="/admin/quotes_mail_recipients"
|
||||
element={<AdminQuotesMailRecipientsListPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/add-quotes_mail_recipients"
|
||||
element={<AddAdminQuotesMailRecipientsPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/edit-quotes_mail_recipients/:id"
|
||||
element={<EditAdminQuotesMailRecipientsPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/view-quotes_mail_recipients/:id"
|
||||
element={<ViewAdminQuotesMailRecipientsPage />}
|
||||
></Route>
|
||||
|
||||
<Route path="/admin/email" element={<AdminEmailListPage />}></Route>
|
||||
<Route
|
||||
path="/admin/add-email"
|
||||
element={<AddAdminEmailPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/edit-email/:id"
|
||||
element={<EditAdminEmailPage />}
|
||||
></Route>
|
||||
|
||||
<Route path="/admin/photo" element={<AdminPhotoListPage />}></Route>
|
||||
<Route
|
||||
path="/admin/add-photo"
|
||||
element={<AddAdminPhotoPage />}
|
||||
></Route>
|
||||
|
||||
<Route
|
||||
path="/admin/boat_lifts"
|
||||
element={<AdminBoatLiftsListPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/add-boat_lifts"
|
||||
element={<AddAdminBoatLiftsPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/edit-boat_lifts/:id"
|
||||
element={<EditAdminBoatLiftsPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/view-boat_lifts/:id"
|
||||
element={<ViewAdminBoatLiftsPage />}
|
||||
></Route>
|
||||
|
||||
<Route
|
||||
path="/admin/dealers"
|
||||
element={<AdminDealersListPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/add-dealers"
|
||||
element={<AddAdminDealersPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/edit-dealers/:id"
|
||||
element={<EditAdminDealersPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/view-dealers/:id"
|
||||
element={<ViewAdminDealersPage />}
|
||||
></Route>
|
||||
|
||||
<Route path="/admin/docks" element={<AdminDocksListPage />}></Route>
|
||||
<Route
|
||||
path="/admin/add-docks"
|
||||
element={<AddAdminDocksPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/edit-docks/:id"
|
||||
element={<EditAdminDocksPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/view-docks/:id"
|
||||
element={<ViewAdminDocksPage />}
|
||||
></Route>
|
||||
|
||||
<Route path="/admin/cms" element={<AdminCmsListPage />}></Route>
|
||||
<Route path="/admin/add-cms" element={<AddAdminCmsPage />}></Route>
|
||||
<Route
|
||||
path="/admin/edit-cms/:id"
|
||||
element={<EditAdminCmsPage />}
|
||||
></Route>
|
||||
|
||||
<Route
|
||||
path="/admin/instructions"
|
||||
element={<AdminInstructionsListPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/add-instructions"
|
||||
element={<AddAdminInstructionsPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/edit-instructions/:id"
|
||||
element={<EditAdminInstructionsPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/view-instructions/:id"
|
||||
element={<ViewAdminInstructionsPage />}
|
||||
></Route>
|
||||
|
||||
<Route path="/admin/user" element={<AdminUserListPage />}></Route>
|
||||
<Route path="/admin/add-user" element={<AddAdminUserPage />}></Route>
|
||||
<Route
|
||||
path="/admin/edit-user/:id"
|
||||
element={<EditAdminUserPage />}
|
||||
></Route>
|
||||
|
||||
<Route path="/admin/quotes" element={<AdminQuotesListPage />}></Route>
|
||||
<Route
|
||||
path="/admin/add-quotes"
|
||||
element={<AddAdminQuotesPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/edit-quotes/:id"
|
||||
element={<EditAdminQuotesPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/view-quotes/:id"
|
||||
element={<ViewAdminQuotesPage />}
|
||||
></Route>
|
||||
|
||||
<Route path="/admin/wedges" element={<AdminWedgesListPage />}></Route>
|
||||
<Route
|
||||
path="/admin/add-wedges"
|
||||
element={<AddAdminWedgesPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/edit-wedges/:id"
|
||||
element={<EditAdminWedgesPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/view-wedges/:id"
|
||||
element={<ViewAdminWedgesPage />}
|
||||
></Route>
|
||||
|
||||
<Route path="/admin/ramps" element={<AdminRampsListPage />}></Route>
|
||||
<Route
|
||||
path="/admin/add-ramps"
|
||||
element={<AddAdminRampsPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/edit-ramps/:id"
|
||||
element={<EditAdminRampsPage />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/admin/view-ramps/:id"
|
||||
element={<ViewAdminRampsPage />}
|
||||
></Route>
|
||||
|
||||
{/* <Route
|
||||
path="*"
|
||||
element={<Navigate to="/admin" /> || <NotFoundPage />}
|
||||
></Route> */}
|
||||
</Routes>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<Routes>
|
||||
<Route exact path="/" element={<DockBuilderPage />}></Route>
|
||||
<Route exact path="/admin/login" element={<AdminLoginPage />}></Route>
|
||||
<Route
|
||||
exact
|
||||
path="/admin/forgot"
|
||||
element={<AdminForgotPage />}
|
||||
></Route>
|
||||
<Route exact path="/admin/reset" element={<AdminResetPage />}></Route>
|
||||
|
||||
<Route path="*" exact element={<NotFoundPage />}></Route>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Main() {
|
||||
const { state } = React.useContext(AuthContext);
|
||||
|
||||
return (
|
||||
<div className="h-full">
|
||||
<div className="flex w-full">
|
||||
{!state.isAuthenticated ? <PublicHeader /> : renderHeader(state?.role)}
|
||||
<div className="w-full">
|
||||
{state.isAuthenticated ? <TopHeader /> : null}
|
||||
<div className="page-wrapper w-full">
|
||||
{!state.isAuthenticated
|
||||
? renderRoutes("none")
|
||||
: renderRoutes(state?.role)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SnackBar />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Main;
|
||||
@@ -0,0 +1,27 @@
|
||||
import Loader from "Components/Loader";
|
||||
import React from "react";
|
||||
|
||||
const NotFoundPage = () => {
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
const interval = setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 5000);
|
||||
// return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<div className="w-full flex justify-center items-center text-7xl h-screen text-gray-700 ">
|
||||
Not Found
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotFoundPage;
|
||||