Update | Project Ready

This commit is contained in:
Possible
2025-04-03 17:05:59 +01:00
commit c5aee9e2ec
176 changed files with 34486 additions and 0 deletions
Vendored
BIN
View File
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
node_modules
View File
+130
View File
@@ -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.
+52
View File
@@ -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
+21
View File
@@ -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>
+23
View File
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"jsx": "react",
"baseUrl": ".",
"paths": {
"Components/*": [
"src/components/*"
],
"Pages/*": [
"src/pages/*"
],
"Utils/*": [
"src/utils/*"
],
"Assets/*": [
"src/assets/*"
],
"Src/*": [
"src/*"
],
}
}
}
+5235
View File
File diff suppressed because it is too large Load Diff
+52
View File
@@ -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"
}
}
+281
View File
@@ -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
}
}
};
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
BIN
View File
Binary file not shown.
View File
+41
View File
@@ -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;
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+4
View File
@@ -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'
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+9
View File
@@ -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>
)
}
+9
View File
@@ -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>
)
}
+9
View File
@@ -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>
)
}
+9
View File
@@ -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>
)
}
+9
View File
@@ -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>
)
}
+3
View File
@@ -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

+9
View File
@@ -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>
)
}
+9
View File
@@ -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>
)
}
+9
View File
@@ -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>
)
}
+16
View File
@@ -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",
}
+9
View File
@@ -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>
)
}
+15
View File
@@ -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>
+9
View File
@@ -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>
)
}
+9
View File
@@ -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>
)
}
+9
View File
@@ -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>
)
}
+9
View File
@@ -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>
)
}
+3
View File
@@ -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

+9
View File
@@ -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>
)
}
+9
View File
@@ -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>
)
}
+19
View File
@@ -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'
+104
View File
@@ -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>
)
}
+1
View File
@@ -0,0 +1 @@
export { ActionButtons } from './ActionButtons'
+16
View File
@@ -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;
+205
View File
@@ -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'
+758
View File
@@ -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> */
}
+1
View File
@@ -0,0 +1 @@
export { Builder } from './Builder'
+80
View File
@@ -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 };
+1
View File
@@ -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'
+823
View File
@@ -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>
);
};
+1
View File
@@ -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>
)
}
+1
View File
@@ -0,0 +1 @@
export { DockSidebar } from './DockSidebar'
+464
View File
@@ -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 };
+1
View File
@@ -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 };
+1
View File
@@ -0,0 +1 @@
export { LakeSurroundings } from './LakeSurroundings'
+22
View File
@@ -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;
+58
View File
@@ -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>
);
}
+38
View File
@@ -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 };
+1
View File
@@ -0,0 +1 @@
export { Modal } from './Modal'
+41
View File
@@ -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 `}>
&#x02190;
</button>{" "}
<button onClick={nextPage} disabled={!canNextPage} className={`font-bold h-10 w-10 `}>
&#x02192;
</button>{" "}
</div>
</div>
</>
);
};
export default PaginationBar;
+15
View File
@@ -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;
+18
View File
@@ -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>
)
}
+3
View File
@@ -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> */
}
+1
View File
@@ -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>
)
}
+1
View File
@@ -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>
)
}
+1
View File
@@ -0,0 +1 @@
export { SidebarLogo } from './SidebarLogo'
+32
View File
@@ -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;
+10
View File
@@ -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>
)
}
+1
View File
@@ -0,0 +1 @@
export { Tab } from './Tab'
+95
View File
@@ -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>
)
}
+1
View File
@@ -0,0 +1 @@
export { Table } from './Table'
+25
View File
@@ -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>
)
}
+1
View File
@@ -0,0 +1 @@
export { Tabs } from './Tabs'
+52
View File
@@ -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'
+6
View File
@@ -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'
+1963
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -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

+103
View File
@@ -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;
+143
View File
@@ -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;
}
}
+14
View File
@@ -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>
);
+7
View File
@@ -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

+310
View File
@@ -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;
+5097
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -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;

Some files were not shown because too many files have changed in this diff Show More