bsign-customization-full 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +2 -0
- package/components.json +21 -0
- package/dist/colors/anthracite-gray.webp +0 -0
- package/dist/colors/anthracite-gray_50x50.png +0 -0
- package/dist/colors/dark-wenge.webp +0 -0
- package/dist/colors/dark-wenge_50x50.png +0 -0
- package/dist/colors/indian-rosewood.webp +0 -0
- package/dist/colors/indian-rosewood_50x50.png +0 -0
- package/dist/colors/natural-wood.webp +0 -0
- package/dist/colors/natural-wood_50x50.png +0 -0
- package/dist/colors/redwood.webp +0 -0
- package/dist/colors/redwood_50x50.png +0 -0
- package/dist/colors/walnut.webp +0 -0
- package/dist/colors/walnut_50x50.png +0 -0
- package/dist/html2canvas.esm-BJ_egzt0.js +4802 -0
- package/dist/index-Dw5Zc1iD.js +33162 -0
- package/dist/index.es-Co1KNpGS.js +6681 -0
- package/dist/logo.png +0 -0
- package/dist/purify.es-CKpD2xIC.js +552 -0
- package/dist/sign-constructor.es.js +4 -0
- package/dist/sign-constructor.iife.js +171 -0
- package/dist/size-guide.webp +0 -0
- package/dist/size.webp +0 -0
- package/dist/templates/assets/modern/rectangle-black.webp +0 -0
- package/dist/templates/assets/modern/rectangle-white.webp +0 -0
- package/dist/templates/assets/modern/square-black.webp +0 -0
- package/dist/templates/assets/modern/square-white.webp +0 -0
- package/dist/templates/assets/wave.webp +0 -0
- package/dist/templates/jure.webp +0 -0
- package/dist/templates/modern.webp +0 -0
- package/dist/templates/sherwood.webp +0 -0
- package/dist/templates/wave.webp +0 -0
- package/eslint.config.js +23 -0
- package/index.html +13 -0
- package/modern-debug.svg +39 -0
- package/package.json +62 -0
- package/public/colors/anthracite-gray.webp +0 -0
- package/public/colors/anthracite-gray_50x50.png +0 -0
- package/public/colors/dark-wenge.webp +0 -0
- package/public/colors/dark-wenge_50x50.png +0 -0
- package/public/colors/indian-rosewood.webp +0 -0
- package/public/colors/indian-rosewood_50x50.png +0 -0
- package/public/colors/natural-wood.webp +0 -0
- package/public/colors/natural-wood_50x50.png +0 -0
- package/public/colors/redwood.webp +0 -0
- package/public/colors/redwood_50x50.png +0 -0
- package/public/colors/walnut.webp +0 -0
- package/public/colors/walnut_50x50.png +0 -0
- package/public/logo.png +0 -0
- package/public/size-guide.webp +0 -0
- package/public/size.webp +0 -0
- package/public/templates/assets/modern/rectangle-black.webp +0 -0
- package/public/templates/assets/modern/rectangle-white.webp +0 -0
- package/public/templates/assets/modern/square-black.webp +0 -0
- package/public/templates/assets/modern/square-white.webp +0 -0
- package/public/templates/assets/wave.webp +0 -0
- package/public/templates/jure.webp +0 -0
- package/public/templates/modern.webp +0 -0
- package/public/templates/sherwood.webp +0 -0
- package/public/templates/wave.webp +0 -0
- package/src/App.css +43 -0
- package/src/AppDemo2.tsx +257 -0
- package/src/components/cart-panel.tsx +170 -0
- package/src/components/cart-preview.tsx +356 -0
- package/src/components/cart-view.tsx +113 -0
- package/src/components/constructure-menu.tsx +37 -0
- package/src/components/header.tsx +214 -0
- package/src/components/heading.tsx +28 -0
- package/src/components/icons.tsx +54 -0
- package/src/components/import-file-modal.tsx +252 -0
- package/src/components/layers/grid-view.tsx +29 -0
- package/src/components/layers/image-layer.tsx +128 -0
- package/src/components/layers/layer-forms/material-form.tsx +53 -0
- package/src/components/layers/layer-forms/size-form.tsx +69 -0
- package/src/components/layers/layer-forms/template-form.tsx +39 -0
- package/src/components/layers/layer-forms/text-form.tsx +477 -0
- package/src/components/layers/layers-container.tsx +259 -0
- package/src/components/layers/text-layer.tsx +128 -0
- package/src/components/movable-item.tsx +228 -0
- package/src/components/preview.tsx +258 -0
- package/src/components/resize-button.tsx +83 -0
- package/src/components/size-guide-modal.tsx +47 -0
- package/src/components/size-guide.tsx +98 -0
- package/src/components/ui/button-group.tsx +83 -0
- package/src/components/ui/button.tsx +60 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/dialog.tsx +151 -0
- package/src/components/ui/input-group.tsx +168 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +22 -0
- package/src/components/ui/popover.tsx +54 -0
- package/src/components/ui/progress.tsx +28 -0
- package/src/components/ui/radio-group.tsx +43 -0
- package/src/components/ui/scroll-area.tsx +56 -0
- package/src/components/ui/select.tsx +191 -0
- package/src/components/ui/separator.tsx +25 -0
- package/src/components/ui/sheet.tsx +141 -0
- package/src/components/ui/slider.tsx +61 -0
- package/src/components/ui/spinner.tsx +15 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/toggle-group.tsx +73 -0
- package/src/components/ui/toggle.tsx +45 -0
- package/src/components/ui/tooltip.tsx +67 -0
- package/src/fonts/BEBASNEUE-REGULAR.TTF +0 -0
- package/src/fonts/Braille-Regular.ttf +0 -0
- package/src/fonts/GOTHICB.TTF +0 -0
- package/src/hooks/use-mobile.ts +23 -0
- package/src/hooks/use-resize-constraints.ts +62 -0
- package/src/index.css +238 -0
- package/src/index.tsx +141 -0
- package/src/lib/cart-proposal-pdf.ts +350 -0
- package/src/lib/config-font.tsx +109 -0
- package/src/lib/config.ts +730 -0
- package/src/lib/pricing.ts +61 -0
- package/src/lib/type-checks.ts +47 -0
- package/src/lib/utils.ts +146 -0
- package/src/lib/widget-context.tsx +9 -0
- package/src/main.tsx +11 -0
- package/src/store/cart-store.ts +78 -0
- package/src/store/layers-store.ts +337 -0
- package/src/vite-env.d.ts +1 -0
- package/test/preview.html +37 -0
- package/tsconfig.app.json +33 -0
- package/tsconfig.json +13 -0
- package/tsconfig.node.json +25 -0
- package/vite.config.ts +38 -0
|
Binary file
|
package/dist/size.webp
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
import { globalIgnores } from 'eslint/config'
|
|
7
|
+
|
|
8
|
+
export default tseslint.config([
|
|
9
|
+
globalIgnores(['dist']),
|
|
10
|
+
{
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
extends: [
|
|
13
|
+
js.configs.recommended,
|
|
14
|
+
tseslint.configs.recommended,
|
|
15
|
+
reactHooks.configs['recommended-latest'],
|
|
16
|
+
reactRefresh.configs.vite,
|
|
17
|
+
],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
ecmaVersion: 2020,
|
|
20
|
+
globals: globals.browser,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
])
|
package/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Vite + React + TS</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/modern-debug.svg
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2439 1200" preserveAspectRatio="none">
|
|
3
|
+
<defs>
|
|
4
|
+
<linearGradient id="steelBase" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
5
|
+
<stop offset="0%" stop-color="#a6a7a8" />
|
|
6
|
+
<stop offset="28%" stop-color="#d8d8d9" />
|
|
7
|
+
<stop offset="50%" stop-color="#8d8f92" />
|
|
8
|
+
<stop offset="72%" stop-color="#d6d6d7" />
|
|
9
|
+
<stop offset="100%" stop-color="#efeff0" />
|
|
10
|
+
</linearGradient>
|
|
11
|
+
<linearGradient id="steelShade" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
12
|
+
<stop offset="0%" stop-color="rgba(255,255,255,0.34)" />
|
|
13
|
+
<stop offset="45%" stop-color="rgba(255,255,255,0.06)" />
|
|
14
|
+
<stop offset="100%" stop-color="rgba(0,0,0,0.2)" />
|
|
15
|
+
</linearGradient>
|
|
16
|
+
<linearGradient id="centerBand" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
17
|
+
<stop offset="0%" stop-color="rgba(0,0,0,0)" />
|
|
18
|
+
<stop offset="45%" stop-color="rgba(0,0,0,0)" />
|
|
19
|
+
<stop offset="50%" stop-color="rgba(22,24,26,0.33)" />
|
|
20
|
+
<stop offset="55%" stop-color="rgba(0,0,0,0)" />
|
|
21
|
+
<stop offset="100%" stop-color="rgba(0,0,0,0)" />
|
|
22
|
+
</linearGradient>
|
|
23
|
+
<pattern id="brushLines" width="10" height="4" patternUnits="userSpaceOnUse">
|
|
24
|
+
<rect width="10" height="1" fill="rgba(255,255,255,0.08)" />
|
|
25
|
+
<rect y="2" width="10" height="1" fill="rgba(0,0,0,0.07)" />
|
|
26
|
+
</pattern>
|
|
27
|
+
<filter id="panelShadow" x="-20%" y="-20%" width="140%" height="160%">
|
|
28
|
+
<feDropShadow dx="0" dy="10" stdDeviation="8" flood-color="rgba(0,0,0,0.45)" />
|
|
29
|
+
</filter>
|
|
30
|
+
</defs>
|
|
31
|
+
|
|
32
|
+
<rect width="2439" height="1200" fill="url(#steelBase)" />
|
|
33
|
+
<rect width="2439" height="1200" fill="url(#steelShade)" />
|
|
34
|
+
<rect width="2439" height="1200" fill="url(#centerBand)" />
|
|
35
|
+
<rect width="2439" height="1200" fill="url(#brushLines)" opacity="0.72" />
|
|
36
|
+
|
|
37
|
+
<rect x="132" y="132" width="2175" height="936" rx="0" fill="#020202" filter="url(#panelShadow)" />
|
|
38
|
+
<rect x="132" y="132" width="2175" height="936" rx="0" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="2" />
|
|
39
|
+
</svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bsign-customization-full",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite",
|
|
7
|
+
"build": "tsc -b && vite build",
|
|
8
|
+
"lint": "eslint .",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"patch": "npm run build && npm version patch && npm publish"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@radix-ui/react-checkbox": "^1.3.2",
|
|
14
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
15
|
+
"@radix-ui/react-label": "^2.1.7",
|
|
16
|
+
"@radix-ui/react-popover": "^1.1.15",
|
|
17
|
+
"@radix-ui/react-progress": "^1.1.7",
|
|
18
|
+
"@radix-ui/react-radio-group": "^1.3.7",
|
|
19
|
+
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
20
|
+
"@radix-ui/react-select": "^2.2.5",
|
|
21
|
+
"@radix-ui/react-separator": "^1.1.7",
|
|
22
|
+
"@radix-ui/react-slider": "^1.3.5",
|
|
23
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
24
|
+
"@radix-ui/react-toggle": "^1.1.10",
|
|
25
|
+
"@radix-ui/react-toggle-group": "^1.1.11",
|
|
26
|
+
"@radix-ui/react-tooltip": "^1.2.8",
|
|
27
|
+
"@tailwindcss/vite": "^4.1.11",
|
|
28
|
+
"axios": "^1.12.2",
|
|
29
|
+
"class-variance-authority": "^0.7.1",
|
|
30
|
+
"clsx": "^2.1.1",
|
|
31
|
+
"file-saver": "^2.0.5",
|
|
32
|
+
"html-to-image": "^1.11.13",
|
|
33
|
+
"jspdf": "^4.2.1",
|
|
34
|
+
"lucide-react": "^0.525.0",
|
|
35
|
+
"papaparse": "^5.5.3",
|
|
36
|
+
"radix-ui": "^1.4.3",
|
|
37
|
+
"react": "^19.1.0",
|
|
38
|
+
"react-dom": "^19.1.0",
|
|
39
|
+
"react-resizable": "^3.0.5",
|
|
40
|
+
"tailwind-merge": "^3.3.1",
|
|
41
|
+
"tailwindcss": "^4.1.11",
|
|
42
|
+
"zustand": "^5.0.6"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@eslint/js": "^9.30.1",
|
|
46
|
+
"@types/file-saver": "^2.0.7",
|
|
47
|
+
"@types/node": "^24.1.0",
|
|
48
|
+
"@types/papaparse": "^5.3.16",
|
|
49
|
+
"@types/react": "^19.1.8",
|
|
50
|
+
"@types/react-dom": "^19.1.6",
|
|
51
|
+
"@types/react-resizable": "^3.0.8",
|
|
52
|
+
"@vitejs/plugin-react-swc": "^3.10.2",
|
|
53
|
+
"eslint": "^9.30.1",
|
|
54
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
55
|
+
"eslint-plugin-react-refresh": "^0.4.20",
|
|
56
|
+
"globals": "^16.3.0",
|
|
57
|
+
"tw-animate-css": "^1.3.6",
|
|
58
|
+
"typescript": "~5.8.3",
|
|
59
|
+
"typescript-eslint": "^8.35.1",
|
|
60
|
+
"vite": "^7.0.4"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/public/logo.png
ADDED
|
Binary file
|
|
Binary file
|
package/public/size.webp
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/App.css
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#root {
|
|
2
|
+
max-width: 1280px;
|
|
3
|
+
margin: 0 auto;
|
|
4
|
+
padding: 2rem;
|
|
5
|
+
text-align: center;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.logo {
|
|
9
|
+
height: 6em;
|
|
10
|
+
padding: 1.5em;
|
|
11
|
+
will-change: filter;
|
|
12
|
+
transition: filter 300ms;
|
|
13
|
+
}
|
|
14
|
+
.logo:hover {
|
|
15
|
+
filter: drop-shadow(0 0 2em #646cffaa);
|
|
16
|
+
}
|
|
17
|
+
.logo.react:hover {
|
|
18
|
+
filter: drop-shadow(0 0 2em #61dafbaa);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@keyframes logo-spin {
|
|
22
|
+
from {
|
|
23
|
+
transform: rotate(0deg);
|
|
24
|
+
}
|
|
25
|
+
to {
|
|
26
|
+
transform: rotate(360deg);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
31
|
+
a:nth-of-type(2) .logo {
|
|
32
|
+
animation: logo-spin infinite 20s linear;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.card {
|
|
37
|
+
padding: 2em;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.read-the-docs {
|
|
41
|
+
color: #888;
|
|
42
|
+
}
|
|
43
|
+
|
package/src/AppDemo2.tsx
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useMemo,
|
|
4
|
+
useState,
|
|
5
|
+
type ButtonHTMLAttributes,
|
|
6
|
+
type CSSProperties,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from "react"
|
|
9
|
+
import { TooltipProvider } from "./components/ui/tooltip"
|
|
10
|
+
import Preview from "./components/preview"
|
|
11
|
+
import { useLayersStore } from "./store/layers-store"
|
|
12
|
+
|
|
13
|
+
import TextLayerForm from "./components/layers/layer-forms/text-form"
|
|
14
|
+
import SizeLayerForm from "./components/layers/layer-forms/size-form"
|
|
15
|
+
import MaterialLayerForm from "./components/layers/layer-forms/material-form"
|
|
16
|
+
import TemplateLayerForm from "./components/layers/layer-forms/template-form"
|
|
17
|
+
import { useCartStore, type TCartItem } from "./store/cart-store"
|
|
18
|
+
import { Button } from "./components/ui/button"
|
|
19
|
+
import { Input } from "./components/ui/input"
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
Sheet,
|
|
23
|
+
SheetContent,
|
|
24
|
+
SheetTrigger,
|
|
25
|
+
} from "./components/ui/sheet"
|
|
26
|
+
import ConstructureSidebar from "./components/constructure-menu"
|
|
27
|
+
import { ColorSvgIcon, SizeSvgIcon, TemplateSvgIcon, TextSvgIcon } from "./components/icons"
|
|
28
|
+
import { PlusSquareIcon, ShoppingCart } from "lucide-react"
|
|
29
|
+
import SizeGuide from "./components/size-guide"
|
|
30
|
+
import { ButtonGroup } from "./components/ui/button-group"
|
|
31
|
+
import CartPreview from "./components/cart-preview"
|
|
32
|
+
import Header from "./components/header"
|
|
33
|
+
import CartPanel from "./components/cart-panel"
|
|
34
|
+
import { CURRENCY_SYMBOL, formatPrice, formatPriceAmount, getCartSummary, getUnitPrice, QUANTITY_DISCOUNT_TIERS } from "./lib/pricing"
|
|
35
|
+
import { createTemplateLayers, getDefaultTemplateOptions } from "./lib/config"
|
|
36
|
+
|
|
37
|
+
const normalizeQty = (qty: number): number => {
|
|
38
|
+
return Number.isFinite(qty) && qty > 0 ? Math.floor(qty) : 1
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ConstructorOpenButtonProps {
|
|
42
|
+
label?: ReactNode
|
|
43
|
+
className?: string
|
|
44
|
+
style?: CSSProperties
|
|
45
|
+
buttonProps?: Omit<
|
|
46
|
+
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
47
|
+
"children" | "className" | "style"
|
|
48
|
+
>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SignCustomizerProps {
|
|
52
|
+
openButton?: ConstructorOpenButtonProps
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default function SignCustomizer({ openButton }: SignCustomizerProps = {}) {
|
|
56
|
+
const [constructureSelectedItem, setConstructureSelectedItem] = useState("template")
|
|
57
|
+
const [cartItem, setCartItem] = useState<TCartItem>()
|
|
58
|
+
const [draftQty, setDraftQty] = useState(1)
|
|
59
|
+
|
|
60
|
+
const { init: layersGlobalStateInit, options, preview, layers: layersState, cartId, setCartId } = useLayersStore()
|
|
61
|
+
const { add, cart, update: cartUpdate } = useCartStore()
|
|
62
|
+
|
|
63
|
+
const handleAddToCart = () => {
|
|
64
|
+
const item = add(layersState, preview ?? "", options, currentQty)
|
|
65
|
+
if (item) {
|
|
66
|
+
setCartId(item.id)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (layersState.length === 0) {
|
|
72
|
+
const defaults = getDefaultTemplateOptions()
|
|
73
|
+
layersGlobalStateInit(createTemplateLayers({
|
|
74
|
+
templateId: defaults.selectedTemplateId,
|
|
75
|
+
shapeId: defaults.selectedShapeId,
|
|
76
|
+
materialId: defaults.selectedMaterialId,
|
|
77
|
+
preferredSizeId: defaults.selectedSize.id,
|
|
78
|
+
}))
|
|
79
|
+
}
|
|
80
|
+
}, [layersState.length, layersGlobalStateInit])
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const cartItem = cart.find(item => item.id === cartId)
|
|
84
|
+
setCartItem(cartItem)
|
|
85
|
+
}, [cartId, cart])
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (cartItem) {
|
|
89
|
+
setDraftQty(normalizeQty(cartItem.qty))
|
|
90
|
+
}
|
|
91
|
+
}, [cartItem?.id, cartItem?.qty])
|
|
92
|
+
|
|
93
|
+
const currentQty = cartItem ? normalizeQty(cartItem.qty) : draftQty
|
|
94
|
+
|
|
95
|
+
const handleQtyChange = (nextQty: number) => {
|
|
96
|
+
const normalizedQty = normalizeQty(nextQty)
|
|
97
|
+
|
|
98
|
+
if (cartItem) {
|
|
99
|
+
cartUpdate(cartItem.id, { qty: normalizedQty })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setDraftQty(normalizedQty)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const currentUnitPrice = useMemo(() => getUnitPrice(options.selectedSize, currentQty), [options.selectedSize, currentQty])
|
|
106
|
+
|
|
107
|
+
const guideBySelectedSize = useMemo(() => {
|
|
108
|
+
return QUANTITY_DISCOUNT_TIERS.map((tier) => ({
|
|
109
|
+
qty: tier.qty,
|
|
110
|
+
price: formatPriceAmount(getUnitPrice(options.selectedSize, tier.qty)),
|
|
111
|
+
discount: tier.discount || undefined,
|
|
112
|
+
badgeColor: tier.discount >= 15 ? "#E34D0D" : tier.discount >= 10 ? "#E58211" : undefined,
|
|
113
|
+
}))
|
|
114
|
+
}, [options.selectedSize])
|
|
115
|
+
|
|
116
|
+
const { totalQty, totalAmount } = useMemo(() => getCartSummary(cart), [cart])
|
|
117
|
+
const openButtonLabel = openButton?.label ?? "Open"
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<TooltipProvider>
|
|
121
|
+
<Sheet>
|
|
122
|
+
<SheetTrigger asChild>
|
|
123
|
+
<button
|
|
124
|
+
type="button"
|
|
125
|
+
className={openButton?.className}
|
|
126
|
+
style={openButton?.style}
|
|
127
|
+
{...openButton?.buttonProps}
|
|
128
|
+
>
|
|
129
|
+
{openButtonLabel}
|
|
130
|
+
</button>
|
|
131
|
+
</SheetTrigger>
|
|
132
|
+
<SheetContent className="overflow-hidden p-0">
|
|
133
|
+
<div className="flex min-h-0 flex-1 flex-col overflow-x-hidden overflow-y-auto overscroll-y-contain md:flex-row md:overflow-visible">
|
|
134
|
+
<div className="flex h-auto w-full md:h-full md:w-auto">
|
|
135
|
+
<ConstructureSidebar
|
|
136
|
+
items={[
|
|
137
|
+
{
|
|
138
|
+
label: "template",
|
|
139
|
+
icon: TemplateSvgIcon,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
label: "size",
|
|
143
|
+
icon: SizeSvgIcon,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
label: "material",
|
|
147
|
+
icon: ColorSvgIcon,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
label: "text",
|
|
151
|
+
icon: TextSvgIcon,
|
|
152
|
+
},
|
|
153
|
+
]}
|
|
154
|
+
selectedItem={constructureSelectedItem}
|
|
155
|
+
onSelectItem={setConstructureSelectedItem}
|
|
156
|
+
>
|
|
157
|
+
{constructureSelectedItem === "template" && <TemplateLayerForm />}
|
|
158
|
+
{constructureSelectedItem === "text" && <TextLayerForm />}
|
|
159
|
+
{constructureSelectedItem === "material" && <MaterialLayerForm />}
|
|
160
|
+
{constructureSelectedItem === "size" && <SizeLayerForm />}
|
|
161
|
+
</ConstructureSidebar>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<div className="flex min-h-0 w-full flex-1 flex-col">
|
|
165
|
+
<Header />
|
|
166
|
+
|
|
167
|
+
<div className="flex min-h-0 flex-1 flex-col md:flex-row md:justify-between">
|
|
168
|
+
{layersState.length > 0 && (
|
|
169
|
+
<div className="flex w-full justify-center p-2 pb-4 md:h-full md:items-center md:p-0">
|
|
170
|
+
<div className="hidden xl:block xl:w-[700px]" />
|
|
171
|
+
<div className="z-0 transition-all duration-500 ease-in-out xl:fixed xl:top-1/2 xl:-translate-y-1/2 xl:z-0">
|
|
172
|
+
<Preview />
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
|
|
177
|
+
<CartPanel setConstructureSelectedItem={setConstructureSelectedItem} />
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div className="z-100 w-full border-t border-[#D6D6D6] bg-white px-3 pt-4 pb-[calc(1rem+env(safe-area-inset-bottom))] md:rounded-b-3xl md:p-6">
|
|
183
|
+
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
|
184
|
+
<div className="flex items-center w-full justify-between">
|
|
185
|
+
<div className="flex items-center gap-6">
|
|
186
|
+
<div>
|
|
187
|
+
<p className="text-lg font-medium">{formatPrice(currentUnitPrice)}</p>
|
|
188
|
+
<p className="text-sm text-[#8F8F8F] md:text-[14px]">Price per item</p>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<SizeGuide guide={guideBySelectedSize} currencySymbol={CURRENCY_SYMBOL} />
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<div className="flex items-center gap-6">
|
|
195
|
+
<div className="flex items-center gap-2">
|
|
196
|
+
<p className="text-[20px] text-[#8F8F8F]">Total quantity: </p>
|
|
197
|
+
<p className="text-lg font-medium">{totalQty}</p>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div className="flex items-center gap-2">
|
|
201
|
+
<p className="text-[20px] text-[#8F8F8F]">Total amount: </p>
|
|
202
|
+
<p className="text-[20px] font-medium">{formatPrice(totalAmount)}</p>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<ButtonGroup className="w-full sm:w-auto">
|
|
206
|
+
<Button
|
|
207
|
+
className="h-10 w-10 font-semibold md:h-[56px] md:w-[56px]"
|
|
208
|
+
variant={"ghost"}
|
|
209
|
+
size={"icon"}
|
|
210
|
+
onClick={() => handleQtyChange(currentQty - 1)}
|
|
211
|
+
>
|
|
212
|
+
-
|
|
213
|
+
</Button>
|
|
214
|
+
|
|
215
|
+
<Input
|
|
216
|
+
value={currentQty}
|
|
217
|
+
type="number"
|
|
218
|
+
name={`quantity_${cartItem?.id ?? "draft"}`}
|
|
219
|
+
id={`quantity_${cartItem?.id ?? "draft"}`}
|
|
220
|
+
min={1}
|
|
221
|
+
onChange={e => handleQtyChange(Number(e.target.value))}
|
|
222
|
+
className="h-10 max-w-14 border-0 bg-[#F2F2F2] text-center font-semibold shadow-none md:h-[56px] md:max-w-16"
|
|
223
|
+
/>
|
|
224
|
+
<Button
|
|
225
|
+
className="h-10 w-10 font-semibold md:h-[56px] md:w-[56px]"
|
|
226
|
+
variant={"ghost"}
|
|
227
|
+
size={"icon"}
|
|
228
|
+
onClick={() => handleQtyChange(currentQty + 1)}
|
|
229
|
+
>
|
|
230
|
+
+
|
|
231
|
+
</Button>
|
|
232
|
+
</ButtonGroup>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center md:justify-end md:gap-4">
|
|
237
|
+
<div className="flex gap-3">
|
|
238
|
+
<Button className="w-1/2" variant={"outline"} size="lg" onClick={() => handleAddToCart()}>
|
|
239
|
+
<PlusSquareIcon className="size-4.5" />
|
|
240
|
+
Add to cart
|
|
241
|
+
</Button>
|
|
242
|
+
|
|
243
|
+
<CartPreview>
|
|
244
|
+
<Button className="w-full" size={"lg"}>
|
|
245
|
+
<ShoppingCart fill="white" className="size-4.5" />
|
|
246
|
+
View Cart
|
|
247
|
+
</Button>
|
|
248
|
+
</CartPreview>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
</SheetContent>
|
|
254
|
+
</Sheet>
|
|
255
|
+
</TooltipProvider>
|
|
256
|
+
)
|
|
257
|
+
}
|