From 460d13c143e286611443e351d3dc9078cc7d5fe2 Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Thu, 26 Jun 2025 14:52:27 +0300 Subject: [PATCH 1/7] Add TSX form --- ui/package-lock.json | 114 +++++++++++- ui/package.json | 4 +- ui/src/app/form/page.tsx | 392 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 508 insertions(+), 2 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index e7222cf..983eb78 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -17,9 +17,11 @@ "@mui/material-nextjs": "^7.1.1", "@mui/system": "^7.1.1", "axios": "^1.10.0", + "formik": "^2.4.6", "next": "15.3.4", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "yup": "^1.6.1" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -1918,6 +1920,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", + "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", + "license": "MIT", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3210,6 +3222,15 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -4141,6 +4162,31 @@ "node": ">= 6" } }, + "node_modules/formik": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz", + "integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==", + "funding": [ + { + "type": "individual", + "url": "https://opencollective.com/formik" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.1", + "deepmerge": "^2.1.1", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "react-fast-compare": "^2.0.1", + "tiny-warning": "^1.0.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -5319,6 +5365,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5936,6 +5994,12 @@ "react-is": "^16.13.1" } }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -5994,6 +6058,12 @@ "react": "^19.1.0" } }, + "node_modules/react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==", + "license": "MIT" + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6693,6 +6763,18 @@ "node": ">=18" } }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -6751,6 +6833,12 @@ "node": ">=8.0" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -6796,6 +6884,18 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -7114,6 +7214,18 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yup": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.6.1.tgz", + "integrity": "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==", + "license": "MIT", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } } } } diff --git a/ui/package.json b/ui/package.json index bc1a8a5..3cfb0a1 100644 --- a/ui/package.json +++ b/ui/package.json @@ -18,9 +18,11 @@ "@mui/material-nextjs": "^7.1.1", "@mui/system": "^7.1.1", "axios": "^1.10.0", + "formik": "^2.4.6", "next": "15.3.4", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "yup": "^1.6.1" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/ui/src/app/form/page.tsx b/ui/src/app/form/page.tsx index e69de29..312df13 100644 --- a/ui/src/app/form/page.tsx +++ b/ui/src/app/form/page.tsx @@ -0,0 +1,392 @@ +'use client'; + +import React from 'react'; +import { + Box, + Card, + CardContent, + TextField, + Button, + Typography, + Grid, + MenuItem, + FormControl, + InputLabel, + Select, + FormHelperText, + Chip, + Avatar, + Container, + SelectChangeEvent, +} from '@mui/material'; +import { useFormik, FormikHelpers } from 'formik'; +import * as yup from 'yup'; +import PersonIcon from '@mui/icons-material/Person'; +import EmailIcon from '@mui/icons-material/Email'; +import PhoneIcon from '@mui/icons-material/Phone'; +import WorkIcon from '@mui/icons-material/Work'; +import SendIcon from '@mui/icons-material/Send'; + +interface FormValues { + firstName: string; + lastName: string; + email: string; + phone: string; + age: string | number; + department: string; + skills: string[]; + bio: string; +} + +const validationSchema = yup.object({ + firstName: yup + .string() + .min(2, 'First name should be at least 2 characters') + .required('First name is required'), + lastName: yup + .string() + .min(2, 'Last name should be at least 2 characters') + .required('Last name is required'), + email: yup + .string() + .email('Enter a valid email') + .required('Email is required'), + phone: yup + .string() + .matches(/^[\+]?[1-9][\d]{0,15}$/, 'Enter a valid phone number') + .required('Phone number is required'), + age: yup + .number() + .min(18, 'Must be at least 18 years old') + .max(120, 'Must be less than 120 years old') + .required('Age is required'), + department: yup + .string() + .required('Department is required'), + skills: yup + .array() + .of(yup.string()) + .min(1, 'Select at least one skill') + .required('Skills are required'), + bio: yup + .string() + .min(10, 'Bio should be at least 10 characters') + .max(500, 'Bio should not exceed 500 characters') + .required('Bio is required'), +}); + + +const departments: string[] = [ + 'Engineering', + 'Marketing', + 'Sales', + 'HR', + 'Finance', + 'Operations', + 'Design', +]; + +const skillOptions: string[] = [ + 'JavaScript', + 'React', + 'Node.js', + 'Python', + 'UI/UX Design', + 'Project Management', + 'Data Analysis', + 'Marketing', + 'Sales', + 'Communication', +]; + +const FormPage: React.FC = () => { + const formik = useFormik({ + initialValues: { + firstName: '', + lastName: '', + email: '', + phone: '', + age: '', + department: '', + skills: [], + bio: '', + }, + validationSchema: validationSchema, + onSubmit: (values: FormValues, { setSubmitting, resetForm }: FormikHelpers) => { + setTimeout(() => { + console.log('Form submitted:', values); + alert('Form submitted successfully!'); + setSubmitting(false); + resetForm(); + }, 1000); + }, + }); + + const handleSkillChange = (event: SelectChangeEvent): void => { + const value = event.target.value; + formik.setFieldValue('skills', typeof value === 'string' ? value.split(',') : value); + }; + + return ( + + theme.palette.mode === 'dark' + ? 'linear-gradient(135deg, #0f0f23 0%, #1a1a2e 50%, #16213e 100%)' + : 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + py: 4, + }} + > + + + theme.palette.mode === 'dark' + ? 'rgba(26, 26, 46, 0.95)' + : 'rgba(255, 255, 255, 0.95)', + backdropFilter: 'blur(10px)', + border: (theme) => + theme.palette.mode === 'dark' + ? '1px solid rgba(99, 102, 241, 0.2)' + : '1px solid rgba(255, 255, 255, 0.3)', + }} + > + + + + + + + User Registration + + + Please fill out all required fields + + + +
+ + + Personal Information + + + + + + + + + + + + , + }} + /> + + , + }} + /> + + + + + Professional Information + + + + Department + + {formik.touched.department && formik.errors.department && ( + {formik.errors.department} + )} + + + + Skills + + {formik.touched.skills && formik.errors.skills && ( + {formik.errors.skills} + )} + + + + + + +
+
+
+
+
+ ); +}; + +export default FormPage; \ No newline at end of file From 4cb6dd0759b464c7d5a5a0511623b1eba2a5b94b Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Sun, 29 Jun 2025 21:58:37 +0300 Subject: [PATCH 2/7] Fix builder UX --- ui/package-lock.json | 1297 ++++++++++++++++- ui/package.json | 6 + ui/src/app/builder/[id]/page.tsx | 906 ++++-------- .../components/gallery/CategorySidebar.tsx | 179 +++ .../components/gallery/MobileFilterDrawer.tsx | 79 + ui/src/app/components/gallery/ProductCard.tsx | 219 +++ .../app/components/gallery/SearchFilters.tsx | 190 +++ .../orderbuilder/AddAddressDialog.tsx | 81 + .../orderbuilder/StepChooseQuantity.tsx | 124 ++ .../orderbuilder/StepCustomization.tsx | 465 ++++++ .../orderbuilder/StepDeliveryAddress.tsx | 241 +++ .../components/orderbuilder/StepPayment.tsx | 352 +++++ .../orderbuilder/StepProductDetails.tsx | 116 ++ .../orderbuilder/StepReviewOrder.tsx | 160 ++ .../orderbuilder/StepSelectVariant.tsx | 110 ++ .../components/orderbuilder/StepperHeader.tsx | 15 + ui/src/app/form/page.tsx | 392 ----- ui/src/app/gallery/page.tsx | 573 +------- ui/src/app/page.tsx | 21 - ui/src/constants/index.ts | 12 + ui/src/types/index.ts | 137 ++ 21 files changed, 4098 insertions(+), 1577 deletions(-) create mode 100644 ui/src/app/components/gallery/CategorySidebar.tsx create mode 100644 ui/src/app/components/gallery/MobileFilterDrawer.tsx create mode 100644 ui/src/app/components/gallery/ProductCard.tsx create mode 100644 ui/src/app/components/gallery/SearchFilters.tsx create mode 100644 ui/src/app/components/orderbuilder/AddAddressDialog.tsx create mode 100644 ui/src/app/components/orderbuilder/StepChooseQuantity.tsx create mode 100644 ui/src/app/components/orderbuilder/StepCustomization.tsx create mode 100644 ui/src/app/components/orderbuilder/StepDeliveryAddress.tsx create mode 100644 ui/src/app/components/orderbuilder/StepPayment.tsx create mode 100644 ui/src/app/components/orderbuilder/StepProductDetails.tsx create mode 100644 ui/src/app/components/orderbuilder/StepReviewOrder.tsx create mode 100644 ui/src/app/components/orderbuilder/StepSelectVariant.tsx create mode 100644 ui/src/app/components/orderbuilder/StepperHeader.tsx delete mode 100644 ui/src/app/form/page.tsx create mode 100644 ui/src/constants/index.ts create mode 100644 ui/src/types/index.ts diff --git a/ui/package-lock.json b/ui/package-lock.json index 983eb78..31729b5 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -16,11 +16,17 @@ "@mui/material": "^7.1.2", "@mui/material-nextjs": "^7.1.1", "@mui/system": "^7.1.1", + "@stripe/react-stripe-js": "^3.7.0", + "@stripe/stripe-js": "^7.4.0", + "@types/fabric": "^5.3.10", "axios": "^1.10.0", + "fabric": "^6.7.0", "formik": "^2.4.6", + "konva": "^9.3.20", "next": "15.3.4", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-konva": "^19.0.6", "yup": "^1.6.1" }, "devDependencies": { @@ -1093,6 +1099,112 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, "node_modules/@mui/core-downloads-tracker": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.2.tgz", @@ -1611,6 +1723,29 @@ "dev": true, "license": "MIT" }, + "node_modules/@stripe/react-stripe-js": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-3.7.0.tgz", + "integrity": "sha512-PYls/2S9l0FF+2n0wHaEJsEU8x7CmBagiH7zYOsxbBlLIHEsqUIQ4MlIAbV9Zg6xwT8jlYdlRIyBTHmO3yM7kQ==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "@stripe/stripe-js": ">=1.44.1 <8.0.0", + "react": ">=16.8.0 <20.0.0", + "react-dom": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@stripe/stripe-js": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.4.0.tgz", + "integrity": "sha512-lQHQPfXPTBeh0XFjq6PqSBAyR7umwcJbvJhXV77uGCUDD6ymXJU/f2164ydLMLCCceNuPlbV9b+1smx98efwWQ==", + "license": "MIT", + "engines": { + "node": ">=12.16" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -1902,6 +2037,16 @@ "tailwindcss": "4.1.10" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -1920,6 +2065,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/fabric": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/@types/fabric/-/fabric-5.3.10.tgz", + "integrity": "sha512-fsJIuVkU+B2AnmQh+Ml2X0ax3NmRIqLvEXmZ+squX60HaF89TvdIP6tI6Uk5srXaauswTwPOOfWE7k2QboUZCg==", + "license": "MIT" + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", @@ -1985,6 +2136,15 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-reconciler": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.32.0.tgz", + "integrity": "sha512-+WHarFkJevhH1s655qeeSEf/yxFST0dVRsmSqUgxG8mMOKqycgYBv2wVpyubBY7MX8KiX5FQ03rNIwrxfm7Bmw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.12", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", @@ -2550,11 +2710,26 @@ "win32" ] }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2563,6 +2738,17 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -2573,6 +2759,32 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "optional": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2590,6 +2802,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2606,6 +2828,28 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2872,14 +3116,14 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2988,6 +3232,22 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3075,6 +3335,16 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3091,9 +3361,16 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -3131,6 +3408,33 @@ "node": ">= 8" } }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "license": "MIT", + "optional": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "license": "MIT", + "optional": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "license": "MIT", + "optional": true + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -3144,6 +3448,21 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -3215,6 +3534,26 @@ } } }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "license": "MIT", + "optional": true + }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3276,6 +3615,13 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -3318,6 +3664,20 @@ "csstype": "^3.0.2" } }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "license": "MIT", + "optional": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3353,6 +3713,19 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3553,6 +3926,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/eslint": { "version": "9.29.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", @@ -3933,6 +4338,20 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -3963,7 +4382,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -3973,12 +4392,25 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/fabric": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/fabric/-/fabric-6.7.0.tgz", + "integrity": "sha512-+yKumsh1MvJ44Um2eOhb4Q6CyZ6e2XKBV3IfQvzuGKhl2UkRFQtIKPUi6f06m3gd0r5zspgMUl5iwxtT1dmFAQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "canvas": "^2.11.2", + "jsdom": "^20.0.1" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4187,6 +4619,46 @@ "react": ">=16.8.0" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", + "optional": true + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4227,6 +4699,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -4295,6 +4789,28 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4443,6 +4959,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -4464,6 +4987,61 @@ "react-is": "^16.7.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4500,6 +5078,25 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC", + "optional": true + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -4692,6 +5289,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", @@ -4777,6 +5384,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT", + "optional": true + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -4954,6 +5568,27 @@ "node": ">= 0.4" } }, + "node_modules/its-fine": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz", + "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==", + "license": "MIT", + "dependencies": { + "@types/react-reconciler": "^0.28.9" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/its-fine/node_modules/@types/react-reconciler": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", + "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/jiti": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", @@ -4992,6 +5627,52 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -5070,6 +5751,26 @@ "json-buffer": "3.0.1" } }, + "node_modules/konva": { + "version": "9.3.20", + "resolved": "https://registry.npmjs.org/konva/-/konva-9.3.20.tgz", + "integrity": "sha512-7XPD/YtgfzC8b1c7z0hhY5TF1IO/pBYNa29zMTA2PeBaqI0n5YplUeo4JRuRcljeAF8lWtW65jePZZF7064c8w==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/lavrton" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/konva" + }, + { + "type": "github", + "url": "https://github.com/sponsors/lavrton" + } + ], + "license": "MIT" + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -5406,6 +6107,32 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5460,11 +6187,24 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -5528,6 +6268,13 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/nan": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", + "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", + "license": "MIT", + "optional": true + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -5651,6 +6398,89 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT", + "optional": true + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/nwsapi": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "license": "MIT", + "optional": true + }, "node_modules/oauth4webapi": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.5.3.tgz", @@ -5782,6 +6612,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5880,6 +6720,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "optional": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5890,6 +6743,16 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -6006,16 +6869,36 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "optional": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT", + "optional": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -6070,6 +6953,52 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-konva": { + "version": "19.0.6", + "resolved": "https://registry.npmjs.org/react-konva/-/react-konva-19.0.6.tgz", + "integrity": "sha512-6jsjU1QAStwP4AulaE2BqNlO3Oq+YwvC7OfeEfrj4RSr/CiACojYvdAuQ8Fm7XoALwQOGWf9fqeGvDWD4/c2Cg==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/lavrton" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/konva" + }, + { + "type": "github", + "url": "https://github.com/sponsors/lavrton" + } + ], + "license": "MIT", + "dependencies": { + "@types/react-reconciler": "^0.32.0", + "its-fine": "^2.0.0", + "react-reconciler": "0.32.0", + "scheduler": "0.26.0" + }, + "peerDependencies": { + "konva": "^8.0.1 || ^7.2.5 || ^9.0.0", + "react": "^18.3.1 || ^19.0.0", + "react-dom": "^18.3.1 || ^19.0.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.32.0.tgz", + "integrity": "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -6086,6 +7015,21 @@ "react-dom": ">=16.6.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -6130,6 +7074,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT", + "optional": true + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -6180,6 +7131,23 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6224,6 +7192,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -6259,6 +7248,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "optional": true + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "optional": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -6278,6 +7287,13 @@ "node": ">=10" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -6468,6 +7484,46 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -6525,6 +7581,38 @@ "node": ">=10.0.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -6638,6 +7726,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -6728,6 +7829,13 @@ "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT", + "optional": true + }, "node_modules/tailwindcss": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz", @@ -6839,6 +7947,35 @@ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", "license": "MIT" }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -7014,6 +8151,16 @@ "dev": true, "license": "MIT" }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/unrs-resolver": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.2.tgz", @@ -7059,6 +8206,17 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/use-sync-external-store": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", @@ -7068,6 +8226,73 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT", + "optional": true + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "license": "MIT", + "optional": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7173,6 +8398,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -7183,6 +8418,52 @@ "node": ">=0.10.0" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT", + "optional": true + }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", diff --git a/ui/package.json b/ui/package.json index 3cfb0a1..bb9a5aa 100644 --- a/ui/package.json +++ b/ui/package.json @@ -17,11 +17,17 @@ "@mui/material": "^7.1.2", "@mui/material-nextjs": "^7.1.1", "@mui/system": "^7.1.1", + "@stripe/react-stripe-js": "^3.7.0", + "@stripe/stripe-js": "^7.4.0", + "@types/fabric": "^5.3.10", "axios": "^1.10.0", + "fabric": "^6.7.0", "formik": "^2.4.6", + "konva": "^9.3.20", "next": "15.3.4", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-konva": "^19.0.6", "yup": "^1.6.1" }, "devDependencies": { diff --git a/ui/src/app/builder/[id]/page.tsx b/ui/src/app/builder/[id]/page.tsx index 2268fff..f292da2 100644 --- a/ui/src/app/builder/[id]/page.tsx +++ b/ui/src/app/builder/[id]/page.tsx @@ -2,126 +2,44 @@ import React, { useState, useEffect } from 'react'; import { useRouter, useParams } from 'next/navigation'; +import { loadStripe } from '@stripe/stripe-js'; +import { Elements } from '@stripe/react-stripe-js'; import clientApi from '@/lib/clientApi'; import { - Box, - Button, - Card, - CardContent, - CardMedia, - Typography, - Stepper, - Step, - StepLabel, - Grid, - TextField, - IconButton, - Radio, - RadioGroup, - FormControlLabel, - FormControl, - FormLabel, - Dialog, - DialogTitle, - DialogContent, - DialogActions, - Chip, Container, Paper, - Fade, - CircularProgress + Box, + Button, + CircularProgress, } from '@mui/material'; -import { - Add as AddIcon, - Remove as RemoveIcon, - LocationOn as LocationIcon, - AddLocation as AddLocationIcon -} from '@mui/icons-material'; +import { Product, Variant, Address, NewAddress } from '@/types'; +import StepperHeader from '@/app/components/orderbuilder/StepperHeader'; +import AddAddressDialog from '@/app/components/orderbuilder/AddAddressDialog'; +import StepProductDetails from '@/app/components/orderbuilder/StepProductDetails'; +import StepSelectVariant from '@/app/components/orderbuilder/StepSelectVariant'; +import StepCustomization from '@/app/components/orderbuilder/StepCustomization'; +import StepChooseQuantity from '@/app/components/orderbuilder/StepChooseQuantity'; +import StepDeliveryAddress from '@/app/components/orderbuilder/StepDeliveryAddress'; +import StepReviewOrder from '@/app/components/orderbuilder/StepReviewOrder'; +import StepPayment from '@/app/components/orderbuilder/StepPayment'; -interface Category { +interface CustomizationImage { id: string; - name: string; - description: string; - imageUrl: string; - sortOrder: number; - isActive: boolean; - parentCategoryId: string; - createdAt: string; - modifiedAt: string; + url: string; + file: File; } -interface Product { - id: string; - name: string; - description: string; - basePrice: number; - isCustomizable: boolean; - isActive: boolean; - imageUrl: string; - categoryId: string; - category: Category; - createdAt: string; - modifiedAt: string; -} +const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!); -interface Variant { - id: string; - productId: string; - size: string; - color: string; - price: number; - imageUrl: string; - sku: string; - stockQuantity: number; - isActive: boolean; - product: Product; - createdAt: string; - modifiedAt: string; -} - -interface Address { - id: string; - userId: string; - addressType: string; - firstName: string; - lastName: string; - company: string; - addressLine1: string; - addressLine2: string; - apartmentNumber: string; - buildingNumber: string; - floor: string; - city: string; - state: string; - postalCode: string; - country: string; - phoneNumber: string; - instructions: string; - isDefault: boolean; - isActive: boolean; -} - -interface NewAddress { - addressType: string; - firstName: string; - lastName: string; - company: string; - addressLine1: string; - addressLine2: string; - apartmentNumber: string; - buildingNumber: string; - floor: string; - city: string; - state: string; - postalCode: string; - country: string; - phoneNumber: string; - instructions: string; - isDefault: boolean; - isActive: boolean; -} - -const steps = ['Product Details', 'Select Variant', 'Choose Quantity', 'Delivery Address', 'Review & Order']; +const steps = [ + 'Product Details', + 'Select Variant', + 'Customization', + 'Choose Quantity', + 'Delivery Address', + 'Review & Order', + 'Payment' +]; export default function OrderBuilder() { const router = useRouter(); @@ -137,6 +55,11 @@ export default function OrderBuilder() { const [quantity, setQuantity] = useState(1); const [selectedAddress, setSelectedAddress] = useState
(null); const [showAddressDialog, setShowAddressDialog] = useState(false); + const [customizationImages, setCustomizationImages] = useState([]); + const [finalImageUrl, setFinalImageUrl] = useState(''); + const [customizationDescription, setCustomizationDescription] = useState(''); + const [orderId, setOrderId] = useState(''); + const [clientSecret, setClientSecret] = useState(''); const [newAddress, setNewAddress] = useState({ addressType: 'Home', firstName: '', @@ -154,22 +77,18 @@ export default function OrderBuilder() { phoneNumber: '', instructions: '', isDefault: false, - isActive: true + isActive: true, }); useEffect(() => { - if (productId) { - loadProduct(); - } + if (productId) loadProduct(); }, [productId]); const loadProduct = async () => { setLoading(true); try { - const productData = await clientApi.get(`/products/${productId}`); - setProduct(productData.data); - } catch (error) { - console.error('Failed to load product:', error); + const { data } = await clientApi.get(`/products/${productId}`); + setProduct(data); } finally { setLoading(false); } @@ -178,10 +97,8 @@ export default function OrderBuilder() { const loadVariants = async () => { setLoading(true); try { - const variantsData = await clientApi.get(`/products/variants/${productId}`); - setVariants(variantsData.data); - } catch (error) { - console.error('Failed to load variants:', error); + const { data } = await clientApi.get(`/products/variants/${productId}`); + setVariants(data); } finally { setLoading(false); } @@ -190,432 +107,249 @@ export default function OrderBuilder() { const loadAddresses = async () => { setLoading(true); try { - const addressesData = await clientApi.get('/addresses/me'); - setAddresses(addressesData.data); - if (addressesData.data.length > 0) { - const defaultAddress = addressesData.data.find((addr: Address) => addr.isDefault) || addressesData.data[0]; - setSelectedAddress(defaultAddress); + console.log('Loading addresses...'); + const response = await clientApi.get('/addresses/me'); + console.log('API Response:', response); + console.log('API Data:', response.data); + + const data = response.data; + setAddresses(data); + + if (data.length > 0) { + const defaultAddr = data.find((addr: Address) => addr.isDefault) || data[0]; + setSelectedAddress(defaultAddr); + console.log('Selected address:', defaultAddr); + } else { + console.log('No addresses found'); } } catch (error) { - console.error('Failed to load addresses:', error); + console.error('Error loading addresses:', error); } finally { setLoading(false); } }; - const handleNext = () => { - if (activeStep === 0 && product) { - loadVariants(); - } else if (activeStep === 2) { - loadAddresses(); - } - setActiveStep((prevActiveStep) => prevActiveStep + 1); - }; + const handleNext = async () => { + if (activeStep === 0 && product) { + loadVariants(); + } else if (activeStep === 2 && product?.isCustomizable && customizationImages.length > 0 && !finalImageUrl) { + return; + } else if (activeStep === 3) { + await loadAddresses(); + } + setActiveStep((prev) => prev + 1); +}; - const handleBack = () => { - setActiveStep((prevActiveStep) => prevActiveStep - 1); - }; + const handleBack = () => setActiveStep((prev) => prev - 1); const handleQuantityChange = (delta: number) => { - const newQuantity = quantity + delta; - if (newQuantity >= 1) { - setQuantity(newQuantity); - } + setQuantity((prev) => Math.max(1, prev + delta)); }; const handleAddAddress = async () => { - try { - const addedAddress = await clientApi.post('/addresses', newAddress); - setAddresses([...addresses, addedAddress.data]); - setSelectedAddress(addedAddress.data); - setShowAddressDialog(false); - setNewAddress({ - addressType: 'shipping', - firstName: '', - lastName: '', - company: '', - addressLine1: '', - addressLine2: '', - apartmentNumber: '', - buildingNumber: '', - floor: '', - city: '', - state: '', - postalCode: '', - country: '', - phoneNumber: '', - instructions: '', - isDefault: false, - isActive: true - }); - } catch (error) { - console.error('Failed to add address:', error); - } + const added = await clientApi.post('/addresses', newAddress); + setAddresses([...addresses, added.data]); + setSelectedAddress(added.data); + setShowAddressDialog(false); + setNewAddress({ + addressType: 'Home', + firstName: '', + lastName: '', + company: '', + addressLine1: '', + addressLine2: '', + apartmentNumber: '', + buildingNumber: '', + floor: '', + city: '', + state: '', + postalCode: '', + country: '', + phoneNumber: '', + instructions: '', + isDefault: false, + isActive: true, + }); }; const handlePlaceOrder = async () => { if (!selectedVariant || !selectedAddress) return; - + const orderData = { + quantity, productId: product!.id, productVariantId: selectedVariant.id, - quantity: quantity, + originalImageUrls: customizationImages.map(img => img.url), + customizationImageUrl: finalImageUrl, + customizationDescription, addressId: selectedAddress.id, - totalPrice: selectedVariant.price * quantity }; - + + setLoading(true); try { - setLoading(true); - await clientApi.post('/orders', orderData); - router.push('/orders/success'); + const orderResponse = await clientApi.post('/orders', orderData); + const newOrderId = orderResponse.data.id; + setOrderId(newOrderId); + + const paymentResponse = await clientApi.post('/stripe/create-payment-intent', { + orderId: newOrderId + }); + + setClientSecret(paymentResponse.data.clientSecret); + setActiveStep((prev) => prev + 1); } catch (error) { - console.error('Failed to place order:', error); + console.error('Error creating order or payment intent:', error); + alert('Failed to create order. Please try again.'); } finally { setLoading(false); } }; - const getTotalPrice = () => { - if (!selectedVariant) return 0; - return selectedVariant.price * quantity; + const handlePaymentSuccess = () => { + router.push(`/orders/success?orderId=${orderId}`); }; + const getTotalPrice = () => (selectedVariant ? selectedVariant.price * quantity : 0); + const canProceed = () => { switch (activeStep) { case 0: return product !== null; case 1: return selectedVariant !== null; - case 2: return quantity > 0; - case 3: return selectedAddress !== null; + case 2: + if (!product?.isCustomizable) return true; + return customizationImages.length === 0 || finalImageUrl !== ''; + case 3: return quantity > 0; + case 4: return selectedAddress !== null; + case 5: return true; + case 6: return false; default: return true; } }; + const shouldShowCustomization = () => { + return product?.isCustomizable; + }; + const renderStepContent = () => { switch (activeStep) { case 0: - return ( - - - {product && ( - - - - - {product.name} - - - {product.description} - - - ${product.basePrice.toFixed(2)} - - - - {product.isCustomizable && } - - - - )} - - - ); - + return ; case 1: return ( - - - - Select Variant - - - {variants.map((variant) => ( - - setSelectedVariant(variant)} - > - - - - {variant.size} - {variant.color} - - - SKU: {variant.sku} - - - ${variant.price.toFixed(2)} - - 0 ? 'success.main' : 'error.main'}> - {variant.stockQuantity > 0 ? `${variant.stockQuantity} in stock` : 'Out of stock'} - - - - - ))} - - - + ); - case 2: + if (!shouldShowCustomization()) { + setActiveStep(3); + return null; + } return ( - - - - Choose Quantity - - {selectedVariant && ( - - - - - - {selectedVariant.size} - - - {selectedVariant.product.name} - - - {selectedVariant.size} - {selectedVariant.color} - - - ${selectedVariant.price.toFixed(2)} each - - - - - - - handleQuantityChange(-1)} - disabled={quantity <= 1} - > - - - { - const val = parseInt(e.target.value) || 1; - if (val >= 1) setQuantity(val); - }} - inputProps={{ - style: { textAlign: 'center', fontSize: '1.2rem' }, - min: 1 - }} - sx={{ width: 80 }} - /> - handleQuantityChange(1)}> - - - - - - - - Total: ${getTotalPrice().toFixed(2)} - - - - - )} - - + ); - case 3: return ( - - - - Select Delivery Address - - - { - const addr = addresses.find(a => a.id === e.target.value); - setSelectedAddress(addr || null); - }} - > - - {addresses.map((address) => ( - - - - } - label="" - sx={{ position: 'absolute', top: 8, right: 8 }} - /> - - - - {address.firstName} {address.lastName} - - {address.isDefault && } - - {address.company && ( - - {address.company} - - )} - - {address.addressLine1} - - {address.addressLine2 && ( - - {address.addressLine2} - - )} - - {address.city}, {address.state} {address.postalCode} - - - {address.country} - - {address.phoneNumber && ( - - Phone: {address.phoneNumber} - - )} - - - - ))} - - setShowAddressDialog(true)} - > - - - - - Add New Address - - - - - - - - - - + ); - case 4: return ( - - - - Review Your Order - - - - - - - Order Summary - - {selectedVariant && ( - - {selectedVariant.size} - - - {selectedVariant.product.name} - - - {selectedVariant.size} - {selectedVariant.color} - - - Quantity: {quantity} - - - - ${getTotalPrice().toFixed(2)} - - - )} - - - - - - - - Delivery Address - - {selectedAddress && ( - - - {selectedAddress.firstName} {selectedAddress.lastName} - - {selectedAddress.company && ( - - {selectedAddress.company} - - )} - - {selectedAddress.addressLine1} - - {selectedAddress.addressLine2 && ( - - {selectedAddress.addressLine2} - - )} - - {selectedAddress.city}, {selectedAddress.state} {selectedAddress.postalCode} - - - {selectedAddress.country} - - - )} - - - - - - + + ); + case 5: + return ( + + ); + case 6: + if (clientSecret) { + return ( + + + + ); + } + return ( + + + ); - default: return null; } }; + const getStepButtonText = () => { + if (activeStep === steps.length - 2) return 'Place Order'; + if (activeStep === steps.length - 1) return 'Payment'; + return 'Next'; + }; + + const handleStepAction = () => { + if (activeStep === steps.length - 2) { + handlePlaceOrder(); + } else if (activeStep === steps.length - 1) { + return; + } else { + handleNext(); + } + }; + if (loading && !product) { return ( @@ -627,174 +361,36 @@ export default function OrderBuilder() { return ( - - {steps.map((label) => ( - - {label} - - ))} - - - - {renderStepContent()} - - - - - - + + {renderStepContent()} + + {activeStep < steps.length - 1 && ( + + + + + )} - setShowAddressDialog(false)} maxWidth="md" fullWidth> - Add New Address - - - - setNewAddress({...newAddress, firstName: e.target.value})} - /> - - - setNewAddress({...newAddress, lastName: e.target.value})} - /> - - - setNewAddress({...newAddress, company: e.target.value})} - /> - - - setNewAddress({...newAddress, addressLine1: e.target.value})} - /> - - - setNewAddress({...newAddress, addressLine2: e.target.value})} - /> - - - setNewAddress({...newAddress, apartmentNumber: e.target.value})} - /> - - - setNewAddress({...newAddress, buildingNumber: e.target.value})} - /> - - - setNewAddress({...newAddress, floor: e.target.value})} - /> - - - setNewAddress({...newAddress, city: e.target.value})} - /> - - - setNewAddress({...newAddress, state: e.target.value})} - /> - - - setNewAddress({...newAddress, postalCode: e.target.value})} - /> - - - setNewAddress({...newAddress, country: e.target.value})} - /> - - - setNewAddress({...newAddress, phoneNumber: e.target.value})} - /> - - - setNewAddress({...newAddress, instructions: e.target.value})} - /> - - - - - - - - + setShowAddressDialog(false)} + onAdd={handleAddAddress} + newAddress={newAddress} + setNewAddress={setNewAddress} + /> ); } \ No newline at end of file diff --git a/ui/src/app/components/gallery/CategorySidebar.tsx b/ui/src/app/components/gallery/CategorySidebar.tsx new file mode 100644 index 0000000..f3d9b26 --- /dev/null +++ b/ui/src/app/components/gallery/CategorySidebar.tsx @@ -0,0 +1,179 @@ +import { + Box, + Typography, + List, + ListItem, + ListItemButton, + ListItemText, + Collapse, + IconButton, + Slider, + Switch, + FormControlLabel +} from '@mui/material'; +import { ExpandLess, ExpandMore } from '@mui/icons-material'; +import { ChangeEvent } from 'react'; +import { GalleryCategory, Filters } from '@/types'; + +interface CategorySidebarProps { + categories: GalleryCategory[]; + filters: Filters; + expandedCategories: Set; + priceRange: number[]; + onFilterChange: (key: K, value: Filters[K]) => void; + onToggleCategoryExpansion: (categoryId: string) => void; + onPriceRangeChange: (event: Event, newValue: number | number[]) => void; + onPriceRangeCommitted: (event: Event | React.SyntheticEvent, newValue: number | number[]) => void; +} + +export default function CategorySidebar({ + categories, + filters, + expandedCategories, + priceRange, + onFilterChange, + onToggleCategoryExpansion, + onPriceRangeChange, + onPriceRangeCommitted +}: CategorySidebarProps) { + const getChildCategories = (parentId: string): GalleryCategory[] => { + return categories.filter(cat => cat.parentCategoryId === parentId); + }; + + const getParentCategories = (): GalleryCategory[] => { + return categories.filter(cat => !cat.parentCategoryId); + }; + + return ( + + + Categories + + + + + + onFilterChange('categoryId', '')} + sx={{ borderRadius: 1, mb: 0.5 }} + > + + + + + {getParentCategories().map((category) => { + const childCategories = getChildCategories(category.id); + const hasChildren = childCategories.length > 0; + const isExpanded = expandedCategories.has(category.id); + + return ( + + + onFilterChange('categoryId', category.id)} + sx={{ borderRadius: 1, mb: 0.5 }} + > + + {hasChildren && ( + { + e.stopPropagation(); + onToggleCategoryExpansion(category.id); + }} + > + {isExpanded ? : } + + )} + + + + {hasChildren && ( + + + {childCategories.map((childCategory) => ( + + onFilterChange('categoryId', childCategory.id)} + sx={{ borderRadius: 1, mb: 0.5 }} + > + + + + ))} + + + )} + + ); + })} + + + + + + Price Range + + + `$${value}`} + /> + + ${priceRange[0]} + ${priceRange[1]} + + + + ) => + onFilterChange('isCustomizable', e.target.checked ? true : null) + } + /> + } + label="Customizable Only" + sx={{ mb: 0 }} + /> + + + ); +} \ No newline at end of file diff --git a/ui/src/app/components/gallery/MobileFilterDrawer.tsx b/ui/src/app/components/gallery/MobileFilterDrawer.tsx new file mode 100644 index 0000000..af4b3f4 --- /dev/null +++ b/ui/src/app/components/gallery/MobileFilterDrawer.tsx @@ -0,0 +1,79 @@ +import { + Drawer, + Box, + Typography, + IconButton, + CircularProgress +} from '@mui/material'; +import { Close as CloseIcon } from '@mui/icons-material'; +import CategorySidebar from './CategorySidebar'; +import { GalleryCategory, Filters } from '@/types'; + +interface MobileFilterDrawerProps { + open: boolean; + categories: GalleryCategory[]; + categoriesLoading: boolean; + filters: Filters; + expandedCategories: Set; + priceRange: number[]; + onClose: () => void; + onFilterChange: (key: K, value: Filters[K]) => void; + onToggleCategoryExpansion: (categoryId: string) => void; + onPriceRangeChange: (event: Event, newValue: number | number[]) => void; + onPriceRangeCommitted: (event: Event | React.SyntheticEvent, newValue: number | number[]) => void; +} + +export default function MobileFilterDrawer({ + open, + categories, + categoriesLoading, + filters, + expandedCategories, + priceRange, + onClose, + onFilterChange, + onToggleCategoryExpansion, + onPriceRangeChange, + onPriceRangeCommitted +}: MobileFilterDrawerProps) { + return ( + + + + Filters + + + + + + {categoriesLoading ? ( + + + + ) : ( + + )} + + ); +} \ No newline at end of file diff --git a/ui/src/app/components/gallery/ProductCard.tsx b/ui/src/app/components/gallery/ProductCard.tsx new file mode 100644 index 0000000..81f1e4b --- /dev/null +++ b/ui/src/app/components/gallery/ProductCard.tsx @@ -0,0 +1,219 @@ +import { + Card, + CardContent, + CardMedia, + Typography, + Button, + Chip, + Box, + useMediaQuery, + useTheme +} from '@mui/material'; +import { useUser } from '@auth0/nextjs-auth0'; +import { useRouter } from 'next/navigation'; +import { GalleryProduct } from '@/types'; + +interface ProductCardProps { + product: GalleryProduct; +} + +export default function ProductCard({ product }: ProductCardProps) { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const { user, isLoading } = useUser(); + const router = useRouter(); + + const handleViewProduct = () => { + router.push(`/products/${product.id}`); + }; + + const handleLoginRedirect = () => { + router.push('/auth/login'); + }; + + const handleBuild = () => { + router.push(`/builder/${product.id}`); + }; + + return ( + + + + + {product.name} + + + {product.category && ( + + )} + + + {product.description} + + + + + From ${product.basePrice?.toFixed(2)} + + {product.isCustomizable && ( + + )} + + + {!isLoading && ( + + {user ? ( + <> + + + + ) : ( + <> + + + Login to customize + + + )} + + )} + + + ); +} \ No newline at end of file diff --git a/ui/src/app/components/gallery/SearchFilters.tsx b/ui/src/app/components/gallery/SearchFilters.tsx new file mode 100644 index 0000000..764918d --- /dev/null +++ b/ui/src/app/components/gallery/SearchFilters.tsx @@ -0,0 +1,190 @@ +import { + Paper, + Grid, + IconButton, + TextField, + InputAdornment, + FormControl, + Select, + MenuItem, + InputLabel, + Typography, + useMediaQuery, + useTheme, + SelectChangeEvent, + Box +} from '@mui/material'; +import { + Search, + Tune as TuneIcon +} from '@mui/icons-material'; +import { useState, ChangeEvent, KeyboardEvent } from 'react'; +import { Filters } from '@/types'; +import { SORT_OPTIONS, PAGE_SIZE_OPTIONS } from '@/constants'; + +interface SearchFiltersProps { + filters: Filters; + totalCount: number; + productsCount: number; + onFilterChange: (key: K, value: Filters[K]) => void; + onOpenMobileDrawer: () => void; +} + +export default function SearchFilters({ + filters, + totalCount, + productsCount, + onFilterChange, + onOpenMobileDrawer +}: SearchFiltersProps) { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const isSmall = useMediaQuery(theme.breakpoints.down('sm')); + const [searchInput, setSearchInput] = useState(''); + + const handleSearch = (): void => { + onFilterChange('searchTerm', searchInput); + }; + + const handleSortChange = (value: string): void => { + const [sortBy, sortDirection] = value.split('-'); + onFilterChange('sortBy', sortBy); + onFilterChange('sortDirection', sortDirection); + }; + + return ( + + + {isMobile && ( + + + + + + )} + + + ) => setSearchInput(e.target.value)} + onKeyPress={(e: KeyboardEvent) => e.key === 'Enter' && handleSearch()} + size="small" + sx={{ + '& .MuiInputBase-input': { + fontSize: { xs: '0.75rem', sm: '0.85rem' }, + py: { xs: 0.75, sm: 1 } + } + }} + InputProps={{ + startAdornment: ( + + + + ), + endAdornment: searchInput && ( + + + + + + ) + }} + /> + + + + + + Sort + + + + + + + + + {isSmall ? 'Per' : 'Per Page'} + + + + + + + + {isSmall ? ( + `${productsCount}/${totalCount}` + ) : ( + `Showing ${productsCount} of ${totalCount}` + )} + + + + + ); +} \ No newline at end of file diff --git a/ui/src/app/components/orderbuilder/AddAddressDialog.tsx b/ui/src/app/components/orderbuilder/AddAddressDialog.tsx new file mode 100644 index 0000000..3977495 --- /dev/null +++ b/ui/src/app/components/orderbuilder/AddAddressDialog.tsx @@ -0,0 +1,81 @@ +'use client'; + +import { + Dialog, DialogTitle, DialogContent, DialogActions, + Button, Grid, TextField +} from '@mui/material'; + +import { NewAddress } from '@/types'; // Or inline if needed + +export default function AddAddressDialog({ + open, + onClose, + onAdd, + newAddress, + setNewAddress, +}: { + open: boolean, + onClose: () => void, + onAdd: () => void, + newAddress: NewAddress, + setNewAddress: (address: NewAddress) => void, +}) { + return ( + + Add New Address + + + {[ + { label: 'First Name', field: 'firstName' }, + { label: 'Last Name', field: 'lastName' }, + { label: 'Company (Optional)', field: 'company' }, + { label: 'Address Line 1', field: 'addressLine1' }, + { label: 'Address Line 2 (Optional)', field: 'addressLine2' }, + { label: 'Apartment #', field: 'apartmentNumber' }, + { label: 'Building #', field: 'buildingNumber' }, + { label: 'Floor', field: 'floor' }, + { label: 'City', field: 'city' }, + { label: 'State', field: 'state' }, + { label: 'Postal Code', field: 'postalCode' }, + { label: 'Country', field: 'country' }, + { label: 'Phone Number', field: 'phoneNumber' }, + ].map(({ label, field }, index) => ( + + setNewAddress({ ...newAddress, [field]: e.target.value })} + /> + + ))} + + setNewAddress({ ...newAddress, instructions: e.target.value })} + /> + + + + + + + + + ); +} diff --git a/ui/src/app/components/orderbuilder/StepChooseQuantity.tsx b/ui/src/app/components/orderbuilder/StepChooseQuantity.tsx new file mode 100644 index 0000000..6b2fa69 --- /dev/null +++ b/ui/src/app/components/orderbuilder/StepChooseQuantity.tsx @@ -0,0 +1,124 @@ +'use client'; + +import { Box, Typography, IconButton, TextField, Fade, Stack, Divider } from '@mui/material'; +import { Add as AddIcon, Remove as RemoveIcon } from '@mui/icons-material'; +import { Variant } from '@/types'; + +export default function StepChooseQuantity({ + selectedVariant, + quantity, + handleQuantityChange, + getTotalPrice, + setQuantity, +}: { + selectedVariant: Variant | null, + quantity: number, + handleQuantityChange: (delta: number) => void, + getTotalPrice: () => number, + setQuantity: (val: number) => void, +}) { + if (!selectedVariant) return null; + + return ( + + + + Choose Quantity + + + + + + + + + {selectedVariant.product.name} + + + {selectedVariant.size} - {selectedVariant.color} + + + ${selectedVariant.price.toFixed(2)} each + + + + + + + Quantity + + + handleQuantityChange(-1)} + disabled={quantity <= 1} + sx={{ + border: '1px solid', + borderColor: 'grey.300', + '&:hover': { borderColor: 'primary.main' } + }} + > + + + { + const val = parseInt(e.target.value) || 1; + if (val >= 1) setQuantity(val); + }} + inputProps={{ + style: { textAlign: 'center', fontSize: '1.2rem', fontWeight: 600 }, + min: 1 + }} + sx={{ + width: 80, + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'grey.300', + }, + '&:hover fieldset': { + borderColor: 'primary.main', + } + } + }} + /> + handleQuantityChange(1)} + sx={{ + border: '1px solid', + borderColor: 'grey.300', + '&:hover': { borderColor: 'primary.main' } + }} + > + + + + + + + + + + + Total Price: + + + ${getTotalPrice().toFixed(2)} + + + + + + ); +} \ No newline at end of file diff --git a/ui/src/app/components/orderbuilder/StepCustomization.tsx b/ui/src/app/components/orderbuilder/StepCustomization.tsx new file mode 100644 index 0000000..4228f3f --- /dev/null +++ b/ui/src/app/components/orderbuilder/StepCustomization.tsx @@ -0,0 +1,465 @@ +'use client'; + +import React, { useState, useRef, useEffect } from 'react'; +import { + Box, + Typography, + Paper, + IconButton, + Grid, + Button, + CircularProgress, + Fade, + Alert, + Divider +} from '@mui/material'; +import { + CloudUpload as UploadIcon, + Delete as DeleteIcon, + Refresh as RefreshIcon, + Save as SaveIcon +} from '@mui/icons-material'; + +interface CustomizationImage { + id: string; + url: string; + file: File; +} + +declare global { + interface Window { + fabric: any; + } +} + +export default function StepCustomization({ + images, + setImages, + finalImageUrl, + setFinalImageUrl, + customizationDescription, + setCustomizationDescription, + loading, + setLoading +}: { + images: CustomizationImage[]; + setImages: (images: CustomizationImage[]) => void; + finalImageUrl: string; + setFinalImageUrl: (url: string) => void; + customizationDescription: string; + setCustomizationDescription: (desc: string) => void; + loading: boolean; + setLoading: (loading: boolean) => void; +}) { + const [uploading, setUploading] = useState(false); + const [generating, setGenerating] = useState(false); + const [fabricLoaded, setFabricLoaded] = useState(false); + const [canvasInitialized, setCanvasInitialized] = useState(false); + const canvasRef = useRef(null); + const fabricCanvasRef = useRef(null); + const fileInputRef = useRef(null); + + useEffect(() => { + const loadFabric = async () => { + if (typeof window !== 'undefined' && !window.fabric) { + const script = document.createElement('script'); + script.src = 'https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.0/fabric.min.js'; + script.onload = () => { + console.log('Fabric.js loaded'); + setFabricLoaded(true); + }; + script.onerror = () => { + console.error('Failed to load Fabric.js'); + }; + document.head.appendChild(script); + } else if (window.fabric) { + console.log('Fabric.js already available'); + setFabricLoaded(true); + } + }; + + loadFabric(); + + return () => { + if (fabricCanvasRef.current) { + fabricCanvasRef.current.dispose(); + } + }; + }, []); + + // Initialize canvas when Fabric is loaded AND canvas ref is available + useEffect(() => { + if (fabricLoaded && canvasRef.current && !canvasInitialized) { + initCanvas(); + } + }, [fabricLoaded, canvasInitialized]); + + const initCanvas = () => { + if (canvasRef.current && window.fabric && !fabricCanvasRef.current) { + console.log('Initializing canvas'); + try { + fabricCanvasRef.current = new window.fabric.Canvas(canvasRef.current, { + width: 800, + height: 600, + backgroundColor: 'white' + }); + + // Add some visual feedback that canvas is working + fabricCanvasRef.current.renderAll(); + setCanvasInitialized(true); + console.log('Canvas initialized successfully'); + } catch (error) { + console.error('Failed to initialize canvas:', error); + } + } + }; + + const uploadImage = async (file: File): Promise => { + const formData = new FormData(); + formData.append('file', file); + + const response = await fetch('https://impr.ink/upload', { + method: 'POST', + body: formData, + }); + + if (!response.ok) { + throw new Error('Upload failed'); + } + + const data = await response.json(); + return data.url; + }; + + const handleFileSelect = async (files: FileList) => { + if (images.length + files.length > 10) { + alert('Maximum 10 images allowed'); + return; + } + + setUploading(true); + try { + const newImages: CustomizationImage[] = []; + + for (let i = 0; i < files.length; i++) { + const file = files[i]; + if (file.type.startsWith('image/')) { + const url = await uploadImage(file); + newImages.push({ + id: Math.random().toString(36).substr(2, 9), + url, + file + }); + } + } + + setImages([...images, ...newImages]); + } catch (error) { + console.error('Upload failed:', error); + alert('Upload failed. Please try again.'); + } finally { + setUploading(false); + } + }; + + const addImageToCanvas = (imageUrl: string) => { + console.log('Adding image to canvas:', imageUrl); + console.log('Canvas available:', !!fabricCanvasRef.current); + console.log('Fabric available:', !!window.fabric); + + if (!fabricCanvasRef.current || !window.fabric) { + console.error('Canvas or Fabric not available'); + return; + } + + window.fabric.Image.fromURL(imageUrl, (img: any) => { + if (img) { + console.log('Image loaded successfully'); + + // Scale the image to fit reasonably on canvas + const maxWidth = 200; + const maxHeight = 200; + const scaleX = maxWidth / img.width; + const scaleY = maxHeight / img.height; + const scale = Math.min(scaleX, scaleY); + + img.set({ + left: Math.random() * (800 - maxWidth), + top: Math.random() * (600 - maxHeight), + scaleX: scale, + scaleY: scale + }); + + fabricCanvasRef.current.add(img); + fabricCanvasRef.current.setActiveObject(img); + fabricCanvasRef.current.renderAll(); + console.log('Image added to canvas'); + } else { + console.error('Failed to load image'); + } + }, { + crossOrigin: 'anonymous' + }); + }; + + const removeImage = (id: string) => { + setImages(images.filter(img => img.id !== id)); + }; + + const clearCanvas = () => { + if (fabricCanvasRef.current) { + fabricCanvasRef.current.clear(); + fabricCanvasRef.current.backgroundColor = 'white'; + fabricCanvasRef.current.renderAll(); + } + }; + + const generateFinalImage = async () => { + if (!fabricCanvasRef.current) return; + + setGenerating(true); + try { + const dataURL = fabricCanvasRef.current.toDataURL({ + format: 'png', + quality: 0.9 + }); + + const response = await fetch(dataURL); + const blob = await response.blob(); + const file = new File([blob], 'customization.png', { type: 'image/png' }); + const url = await uploadImage(file); + setFinalImageUrl(url); + } catch (error) { + console.error('Failed to generate final image:', error); + alert('Failed to generate final image. Please try again.'); + } finally { + setGenerating(false); + } + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + const files = e.dataTransfer.files; + if (files.length > 0) { + handleFileSelect(files); + } + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + }; + + if (!fabricLoaded) { + return ( + + + Loading canvas editor... + + ); + } + + return ( + + + Customize Your Product + + + + + + Design Canvas + + + + + + + + + {!canvasInitialized && ( + + + Initializing canvas... + + + )} + + + + Drag, resize, and rotate images on the canvas. Use the controls around selected images to manipulate them. + + + + + + + Image Library + + + + {images.length === 0 ? ( + fileInputRef.current?.click()} + > + + + Drop images here or click to browse + + + ) : ( + + {images.map((image, index) => ( + + canvasInitialized && addImageToCanvas(image.url)} + > + {`Image + + Image {index + 1} + + { + e.stopPropagation(); + removeImage(image.id); + }} + > + + + + + ))} + + + + + {canvasInitialized + ? 'Click on any image to add it to the canvas' + : 'Canvas is initializing...' + } + + + )} + + + {finalImageUrl && ( + + Saved Design + Final design + + )} + + + + {uploading && ( + + + + Uploading images... + + + )} + + { + if (e.target.files) { + handleFileSelect(e.target.files); + } + }} + /> + + + ); +} \ No newline at end of file diff --git a/ui/src/app/components/orderbuilder/StepDeliveryAddress.tsx b/ui/src/app/components/orderbuilder/StepDeliveryAddress.tsx new file mode 100644 index 0000000..b469434 --- /dev/null +++ b/ui/src/app/components/orderbuilder/StepDeliveryAddress.tsx @@ -0,0 +1,241 @@ +'use client'; + +import { + Box, Typography, FormControl, RadioGroup, FormControlLabel, + Radio, Grid, Card, CardContent, Chip, Fade, Stack, Divider, Alert, + Avatar, IconButton, Tooltip +} from '@mui/material'; +import { + LocationOn as LocationIcon, + AddLocation as AddLocationIcon, + Person as PersonIcon, + Business as BusinessIcon, + Phone as PhoneIcon +} from '@mui/icons-material'; +import { Address } from '@/types'; + +export default function StepDeliveryAddress({ + addresses, + selectedAddress, + setSelectedAddress, + setShowAddressDialog, +}: { + addresses: Address[], + selectedAddress: Address | null, + setSelectedAddress: (addr: Address | null) => void, + setShowAddressDialog: (open: boolean) => void, +}) { + + const formatAddress = (address: Address) => { + const parts = [ + address.addressLine1, + address.addressLine2, + `${address.city}, ${address.state} ${address.postalCode}`, + address.country + ].filter(Boolean); + return parts.join(' • '); + }; + + const getInitials = (firstName: string, lastName: string) => { + return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase(); + }; + + return ( + + + + Select Delivery Address + + + {addresses.length === 0 && ( + + No addresses found. Please add a delivery address to continue. + + )} + + + { + const addr = addresses.find(a => a.id === e.target.value); + setSelectedAddress(addr || null); + }} + > + + {addresses.map((address) => ( + + setSelectedAddress(address)} + > + + + } + label="" + sx={{ m: 0 }} + /> + + + + {getInitials(address.firstName, address.lastName)} + + + + {address.firstName} {address.lastName} + + {address.company && ( + + {address.company} + + )} + + {address.isDefault && ( + + )} + + + + + + {formatAddress(address)} + + + + + {address.phoneNumber ? ( + + + + {address.phoneNumber} + + + ) : ( + + )} + + + + + ))} + + + setShowAddressDialog(true)} + > + + + + + Add New Address + + + Click to add delivery address + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/ui/src/app/components/orderbuilder/StepPayment.tsx b/ui/src/app/components/orderbuilder/StepPayment.tsx new file mode 100644 index 0000000..5137362 --- /dev/null +++ b/ui/src/app/components/orderbuilder/StepPayment.tsx @@ -0,0 +1,352 @@ +'use client'; + +import React, { useState } from 'react'; +import { lightTheme } from '../theme/lightTheme'; +import { + Box, + Typography, + Paper, + Button, + CircularProgress, + Alert, + Fade, + Stack, + Divider, + Card, + CardContent, + useTheme, + ThemeProvider, + createTheme +} from '@mui/material'; +import { + CheckCircle as CheckCircleIcon, + Payment as PaymentIcon, + Receipt as ReceiptIcon +} from '@mui/icons-material'; +import { + useStripe, + useElements, + PaymentElement, + AddressElement, +} from '@stripe/react-stripe-js'; +import { Variant, Address } from '@/types'; + +interface CustomizationImage { + id: string; + url: string; + file: File; +} + +export default function StepPayment({ + orderId, + selectedVariant, + quantity, + getTotalPrice, + selectedAddress, + customizationImages = [], + finalImageUrl = '', + onSuccess +}: { + orderId: string; + selectedVariant: Variant | null; + quantity: number; + getTotalPrice: () => number; + selectedAddress: Address | null; + customizationImages?: CustomizationImage[]; + finalImageUrl?: string; + onSuccess: () => void; +}) { + const stripe = useStripe(); + const elements = useElements(); + const theme = useTheme(); + const [isLoading, setIsLoading] = useState(false); + const [message, setMessage] = useState(''); + const [isSuccess, setIsSuccess] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!stripe || !elements) { + return; + } + + setIsLoading(true); + setMessage(''); + + const { error } = await stripe.confirmPayment({ + elements, + confirmParams: { + return_url: `${window.location.origin}/payment-success`, + }, + redirect: 'if_required', + }); + + if (error) { + if (error.type === 'card_error' || error.type === 'validation_error') { + setMessage(error.message || 'An error occurred'); + } else { + setMessage('An unexpected error occurred.'); + } + setIsLoading(false); + } else { + setMessage('Payment successful! 🎉'); + setIsSuccess(true); + setTimeout(() => { + onSuccess(); + }, 2000); + } + }; + + // Create Stripe appearance object with light theme forced for payment sections + const stripeAppearance = { + theme: 'stripe' as const, // Always use light theme + variables: { + colorPrimary: '#1976d2', // Use a consistent primary color + colorBackground: '#ffffff', // Force white background + colorText: '#212121', // Force dark text + colorDanger: '#d32f2f', // Red for errors + fontFamily: '"Roboto","Helvetica","Arial",sans-serif', + spacingUnit: '4px', + borderRadius: '4px', + }, + rules: { + '.Input': { + backgroundColor: '#ffffff', + border: '1px solid #e0e0e0', + borderRadius: '4px', + color: '#212121', + fontSize: '14px', + padding: '12px', + }, + '.Input:focus': { + borderColor: '#1976d2', + boxShadow: '0 0 0 1px #1976d2', + }, + '.Label': { + color: '#424242', + fontSize: '12px', + fontWeight: '500', + }, + '.Tab': { + backgroundColor: '#ffffff', + border: '1px solid #e0e0e0', + color: '#757575', + }, + '.Tab:hover': { + backgroundColor: '#f5f5f5', + }, + '.Tab--selected': { + backgroundColor: '#1976d2', + color: '#ffffff', + borderColor: '#1976d2', + }, + } + }; + + const paymentElementOptions = { + layout: 'tabs' as const, + appearance: stripeAppearance, + }; + + const addressElementOptions = { + mode: 'billing' as const, + // Remove allowedCountries to accept all countries + fields: { + phone: 'always' as const, + }, + validation: { + phone: { + required: 'never' as const, + }, + }, + appearance: stripeAppearance, + }; + + if (isSuccess) { + return ( + + + + + + + Payment Successful! + + + + Thank you for your purchase! + + + + + + + + + Order Details + + + + Order ID: {orderId} + + + You will receive a confirmation email shortly with your order details and tracking information. + + + + + + + + ); + } + + if (!selectedVariant || !selectedAddress) return null; + + return ( + + + + Complete Your Payment + + + + + + + + Order Summary + + + + + + + {selectedVariant.product.name} + + + {selectedVariant.size} - {selectedVariant.color} × {quantity} + + + + ${getTotalPrice().toFixed(2)} + + + + + + + + Total Amount: + + + ${getTotalPrice().toFixed(2)} + + + + + + {/* Billing Address - Always Light Theme */} + + + + + + Billing Information + + + + + + + + + + {/* Payment Information - Always Light Theme */} + + + + Payment Information + + + + + + + + + + + + + + + {message && ( + + {message} + + )} + + + + + ); +} \ No newline at end of file diff --git a/ui/src/app/components/orderbuilder/StepProductDetails.tsx b/ui/src/app/components/orderbuilder/StepProductDetails.tsx new file mode 100644 index 0000000..c4afaa7 --- /dev/null +++ b/ui/src/app/components/orderbuilder/StepProductDetails.tsx @@ -0,0 +1,116 @@ +'use client'; + +import { + Box, + Typography, + Chip, + Fade, + Grid, + Stack +} from '@mui/material'; +import { Product } from '@/types'; + +export default function StepProductDetails({ product }: { product: Product | null }) { + if (!product) return null; + + return ( + + + + + + + + + + + + + {product.name} + + + + + {product.isCustomizable && ( + + )} + + + + ${product.basePrice.toFixed(2)} + + + + {product.description} + + + + + + + ); +} \ No newline at end of file diff --git a/ui/src/app/components/orderbuilder/StepReviewOrder.tsx b/ui/src/app/components/orderbuilder/StepReviewOrder.tsx new file mode 100644 index 0000000..2d143a3 --- /dev/null +++ b/ui/src/app/components/orderbuilder/StepReviewOrder.tsx @@ -0,0 +1,160 @@ +'use client'; + +import { Box, Typography, Grid, Card, CardContent, Fade, Stack, Divider } from '@mui/material'; +import { Variant, Address } from '@/types'; + +interface CustomizationImage { + id: string; + url: string; + file: File; +} + +export default function StepReviewOrder({ + selectedVariant, + quantity, + getTotalPrice, + selectedAddress, + customizationImages = [], + finalImageUrl = '', + customizationDescription = '' +}: { + selectedVariant: Variant | null, + quantity: number, + getTotalPrice: () => number, + selectedAddress: Address | null, + customizationImages?: CustomizationImage[], + finalImageUrl?: string, + customizationDescription?: string +}) { + if (!selectedVariant || !selectedAddress) return null; + + return ( + + + + Review Your Order + + + + + + + + Order Summary + + + + + + + {selectedVariant.product.name} + + + {selectedVariant.size} - {selectedVariant.color} + + + Quantity: {quantity} + + + + ${getTotalPrice().toFixed(2)} + + + + {customizationImages.length > 0 && ( + <> + + + + Customization Details + + + {customizationImages.length} original image{customizationImages.length > 1 ? 's' : ''} uploaded + + {customizationDescription && ( + + {customizationDescription} + + )} + + + )} + + + + + + + {finalImageUrl && ( + + + + Preview + + + + + )} + + + + + Delivery Address + + + + {selectedAddress.firstName} {selectedAddress.lastName} + + {selectedAddress.company && ( + + {selectedAddress.company} + + )} + + {selectedAddress.addressLine1} + + {selectedAddress.addressLine2 && ( + + {selectedAddress.addressLine2} + + )} + + {selectedAddress.city}, {selectedAddress.state} {selectedAddress.postalCode} + + + {selectedAddress.country} + + + + + + + + + + ); +} \ No newline at end of file diff --git a/ui/src/app/components/orderbuilder/StepSelectVariant.tsx b/ui/src/app/components/orderbuilder/StepSelectVariant.tsx new file mode 100644 index 0000000..645e8fc --- /dev/null +++ b/ui/src/app/components/orderbuilder/StepSelectVariant.tsx @@ -0,0 +1,110 @@ +'use client'; + +import { Box, Typography, Grid, Card, Fade, Stack, Chip } from '@mui/material'; +import { Variant } from '@/types'; + +export default function StepSelectVariant({ + variants, + selectedVariant, + setSelectedVariant, +}: { + variants: Variant[], + selectedVariant: Variant | null, + setSelectedVariant: (variant: Variant) => void +}) { + return ( + + + + Select Variant + + + {variants.map((variant) => ( + + setSelectedVariant(variant)} + > + + + + + + {variant.size} - {variant.color} + + + + SKU: {variant.sku} + + + + + ${variant.price.toFixed(2)} + + + 0 ? `${variant.stockQuantity} in stock` : 'Out of stock'} + size="small" + color="primary" + variant="filled" + sx={{ + fontSize: '0.75rem', + height: 24 + }} + /> + + + + + + ))} + + + + ); +} \ No newline at end of file diff --git a/ui/src/app/components/orderbuilder/StepperHeader.tsx b/ui/src/app/components/orderbuilder/StepperHeader.tsx new file mode 100644 index 0000000..9f8cf28 --- /dev/null +++ b/ui/src/app/components/orderbuilder/StepperHeader.tsx @@ -0,0 +1,15 @@ +'use client'; + +import { Stepper, Step, StepLabel } from '@mui/material'; + +export default function StepperHeader({ activeStep, steps }: { activeStep: number, steps: string[] }) { + return ( + + {steps.map((label) => ( + + {label} + + ))} + + ); +} diff --git a/ui/src/app/form/page.tsx b/ui/src/app/form/page.tsx deleted file mode 100644 index 312df13..0000000 --- a/ui/src/app/form/page.tsx +++ /dev/null @@ -1,392 +0,0 @@ -'use client'; - -import React from 'react'; -import { - Box, - Card, - CardContent, - TextField, - Button, - Typography, - Grid, - MenuItem, - FormControl, - InputLabel, - Select, - FormHelperText, - Chip, - Avatar, - Container, - SelectChangeEvent, -} from '@mui/material'; -import { useFormik, FormikHelpers } from 'formik'; -import * as yup from 'yup'; -import PersonIcon from '@mui/icons-material/Person'; -import EmailIcon from '@mui/icons-material/Email'; -import PhoneIcon from '@mui/icons-material/Phone'; -import WorkIcon from '@mui/icons-material/Work'; -import SendIcon from '@mui/icons-material/Send'; - -interface FormValues { - firstName: string; - lastName: string; - email: string; - phone: string; - age: string | number; - department: string; - skills: string[]; - bio: string; -} - -const validationSchema = yup.object({ - firstName: yup - .string() - .min(2, 'First name should be at least 2 characters') - .required('First name is required'), - lastName: yup - .string() - .min(2, 'Last name should be at least 2 characters') - .required('Last name is required'), - email: yup - .string() - .email('Enter a valid email') - .required('Email is required'), - phone: yup - .string() - .matches(/^[\+]?[1-9][\d]{0,15}$/, 'Enter a valid phone number') - .required('Phone number is required'), - age: yup - .number() - .min(18, 'Must be at least 18 years old') - .max(120, 'Must be less than 120 years old') - .required('Age is required'), - department: yup - .string() - .required('Department is required'), - skills: yup - .array() - .of(yup.string()) - .min(1, 'Select at least one skill') - .required('Skills are required'), - bio: yup - .string() - .min(10, 'Bio should be at least 10 characters') - .max(500, 'Bio should not exceed 500 characters') - .required('Bio is required'), -}); - - -const departments: string[] = [ - 'Engineering', - 'Marketing', - 'Sales', - 'HR', - 'Finance', - 'Operations', - 'Design', -]; - -const skillOptions: string[] = [ - 'JavaScript', - 'React', - 'Node.js', - 'Python', - 'UI/UX Design', - 'Project Management', - 'Data Analysis', - 'Marketing', - 'Sales', - 'Communication', -]; - -const FormPage: React.FC = () => { - const formik = useFormik({ - initialValues: { - firstName: '', - lastName: '', - email: '', - phone: '', - age: '', - department: '', - skills: [], - bio: '', - }, - validationSchema: validationSchema, - onSubmit: (values: FormValues, { setSubmitting, resetForm }: FormikHelpers) => { - setTimeout(() => { - console.log('Form submitted:', values); - alert('Form submitted successfully!'); - setSubmitting(false); - resetForm(); - }, 1000); - }, - }); - - const handleSkillChange = (event: SelectChangeEvent): void => { - const value = event.target.value; - formik.setFieldValue('skills', typeof value === 'string' ? value.split(',') : value); - }; - - return ( - - theme.palette.mode === 'dark' - ? 'linear-gradient(135deg, #0f0f23 0%, #1a1a2e 50%, #16213e 100%)' - : 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - py: 4, - }} - > - - - theme.palette.mode === 'dark' - ? 'rgba(26, 26, 46, 0.95)' - : 'rgba(255, 255, 255, 0.95)', - backdropFilter: 'blur(10px)', - border: (theme) => - theme.palette.mode === 'dark' - ? '1px solid rgba(99, 102, 241, 0.2)' - : '1px solid rgba(255, 255, 255, 0.3)', - }} - > - - - - - - - User Registration - - - Please fill out all required fields - - - -
- - - Personal Information - - - - - - - - - - - - , - }} - /> - - , - }} - /> - - - - - Professional Information - - - - Department - - {formik.touched.department && formik.errors.department && ( - {formik.errors.department} - )} - - - - Skills - - {formik.touched.skills && formik.errors.skills && ( - {formik.errors.skills} - )} - - - - - - -
-
-
-
-
- ); -}; - -export default FormPage; \ No newline at end of file diff --git a/ui/src/app/gallery/page.tsx b/ui/src/app/gallery/page.tsx index ff70fb1..dc3e68f 100644 --- a/ui/src/app/gallery/page.tsx +++ b/ui/src/app/gallery/page.tsx @@ -4,125 +4,36 @@ import { Box, Container, Typography, - Button, - Card, - CardContent, - CardMedia, - Grid, - Chip, CircularProgress, Alert, - TextField, - InputAdornment, Pagination, - FormControl, - Select, - MenuItem, - InputLabel, - Drawer, - List, - ListItem, - ListItemButton, - ListItemText, - Collapse, - IconButton, useMediaQuery, useTheme, - Paper, - Slider, - Switch, - FormControlLabel, - SelectChangeEvent + Paper } from '@mui/material'; -import {useState, useEffect, useCallback, KeyboardEvent, ChangeEvent, JSX} from 'react'; -import { - Search, - ExpandLess, - ExpandMore, - Close as CloseIcon, - Tune as TuneIcon -} from '@mui/icons-material'; +import {useState, useEffect, useCallback} from 'react'; import clientApi from "@/lib/clientApi"; import useRoles from "@/app/components/hooks/useRoles"; +import ProductCard from '@/app/components/gallery/ProductCard'; +import CategorySidebar from '@/app/components/gallery/CategorySidebar'; +import SearchFilters from '@/app/components/gallery/SearchFilters'; +import MobileFilterDrawer from '@/app/components/gallery/MobileFilterDrawer'; +import { GalleryProduct, GalleryCategory, Filters, ProductsResponse, ApiParams } from '@/types'; -interface SortOption { - value: string; - label: string; -} - -interface Category { - id: string; - name: string; - parentCategoryId?: string; -} - -interface Product { - id: string; - name: string; - description: string; - basePrice: number; - imageUrl?: string; - isCustomizable: boolean; - category?: Category; -} - -interface ProductsResponse { - items: Product[]; - totalPages: number; - totalCount: number; -} - -interface Filters { - pageNumber: number; - pageSize: number; - searchTerm: string; - categoryId: string; - minPrice: number; - maxPrice: number; - isActive: boolean; - isCustomizable: boolean | null; - sortBy: string; - sortDirection: string; -} - -interface ApiParams { - PageNumber: number; - PageSize: number; - IsActive: boolean; - SortBy: string; - SortDirection: string; - SearchTerm?: string; - CategoryId?: string; - MinPrice?: number; - MaxPrice?: number; - IsCustomizable?: boolean; -} - -const SORT_OPTIONS: SortOption[] = [ - { value: 'Name-ASC', label: 'Name (A-Z)' }, - { value: 'Name-DESC', label: 'Name (Z-A)' }, - { value: 'Price-ASC', label: 'Price (Low to High)' }, - { value: 'Price-DESC', label: 'Price (High to Low)' }, - { value: 'CreatedDate-DESC', label: 'Newest First' }, - { value: 'CreatedDate-ASC', label: 'Oldest First' } -]; - -const PAGE_SIZE_OPTIONS: number[] = [12, 24, 48, 96]; - -export default function GalleryPage(): JSX.Element { +export default function GalleryPage() { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const { isAdmin } = useRoles(); const heightOffset = isAdmin ? 192 : 128; - const [products, setProducts] = useState([]); + const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [totalPages, setTotalPages] = useState(0); const [totalCount, setTotalCount] = useState(0); - const [categories, setCategories] = useState([]); + const [categories, setCategories] = useState([]); const [categoriesLoading, setCategoriesLoading] = useState(true); const [expandedCategories, setExpandedCategories] = useState>(new Set()); @@ -141,12 +52,11 @@ export default function GalleryPage(): JSX.Element { const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); const [priceRange, setPriceRange] = useState([0, 1000]); - const [searchInput, setSearchInput] = useState(''); useEffect(() => { const fetchCategories = async (): Promise => { try { - const response = await clientApi.get('/products/categories'); + const response = await clientApi.get('/products/categories'); setCategories(response.data); } catch (err) { console.error('Error fetching categories:', err); @@ -200,10 +110,6 @@ export default function GalleryPage(): JSX.Element { })); }; - const handleSearch = (): void => { - handleFilterChange('searchTerm', searchInput); - }; - const handlePriceRangeChange = (event: Event, newValue: number | number[]): void => { setPriceRange(newValue as number[]); }; @@ -214,12 +120,6 @@ export default function GalleryPage(): JSX.Element { handleFilterChange('maxPrice', range[1]); }; - const handleSortChange = (value: string): void => { - const [sortBy, sortDirection] = value.split('-'); - handleFilterChange('sortBy', sortBy); - handleFilterChange('sortDirection', sortDirection); - }; - const toggleCategoryExpansion = (categoryId: string): void => { const newExpanded = new Set(expandedCategories); if (newExpanded.has(categoryId)) { @@ -230,152 +130,14 @@ export default function GalleryPage(): JSX.Element { setExpandedCategories(newExpanded); }; - const getChildCategories = (parentId: string): Category[] => { - return categories.filter(cat => cat.parentCategoryId === parentId); - }; - - const getParentCategories = (): Category[] => { - return categories.filter(cat => !cat.parentCategoryId); - }; - - const CategorySidebar = (): JSX.Element => ( - - - Categories - - - - - - handleFilterChange('categoryId', '')} - sx={{ borderRadius: 1, mb: 0.5 }} - > - - - - - {getParentCategories().map((category) => { - const childCategories = getChildCategories(category.id); - const hasChildren = childCategories.length > 0; - const isExpanded = expandedCategories.has(category.id); - - return ( - - - handleFilterChange('categoryId', category.id)} - sx={{ borderRadius: 1, mb: 0.5 }} - > - - {hasChildren && ( - { - e.stopPropagation(); - toggleCategoryExpansion(category.id); - }} - > - {isExpanded ? : } - - )} - - - - {hasChildren && ( - - - {childCategories.map((childCategory) => ( - - handleFilterChange('categoryId', childCategory.id)} - sx={{ borderRadius: 1, mb: 0.5 }} - > - - - - ))} - - - )} - - ); - })} - - - - - - Price Range - - - `$${value}`} - /> - - ${priceRange[0]} - ${priceRange[1]} - - - - ) => - handleFilterChange('isCustomizable', e.target.checked ? true : null) - } - /> - } - label="Customizable Only" - sx={{ mb: 0 }} - /> - - - ); - return ( - + {!isMobile && ( @@ -390,11 +152,20 @@ export default function GalleryPage(): JSX.Element { }} > {categoriesLoading ? ( - - + + ) : ( - + )} @@ -411,132 +182,58 @@ export default function GalleryPage(): JSX.Element { flex: 1, overflowY: { xs: 'visible', md: 'auto' }, minHeight: 0, - pr: { xs: 0, md: 1 } + pr: { xs: 0, md: 0.5 } }}> - + Product Gallery Explore our complete collection of customizable products - - - {isMobile && ( - - setMobileDrawerOpen(true)} - sx={{ mr: 1 }} - size="small" - > - - - - )} - - - ) => setSearchInput(e.target.value)} - onKeyPress={(e: KeyboardEvent) => e.key === 'Enter' && handleSearch()} - size={isMobile ? "small" : "medium"} - InputProps={{ - startAdornment: ( - - - - ), - endAdornment: searchInput && ( - - - - - - ) - }} - /> - - - - - Sort By - - - - - - - Per Page - - - - - - - Showing {products.length} of {totalCount} products - - - - + setMobileDrawerOpen(true)} + /> {loading && ( - - + + )} {error && ( - + {error} )} {!loading && !error && products.length === 0 && ( - - + + No products found - + Try adjusting your search criteria or filters @@ -546,121 +243,11 @@ export default function GalleryPage(): JSX.Element { {products.map((product) => ( - - - - - {product.name} - - - {product.category && ( - - )} - - - {product.description} - - - - - From ${product.basePrice?.toFixed(2)} - - {product.isCustomizable && ( - - )} - - - - - + ))} )} @@ -669,14 +256,14 @@ export default function GalleryPage(): JSX.Element { handleFilterChange('pageNumber', page)} color="primary" - size={isMobile ? 'small' : 'large'} + size={isMobile ? 'small' : 'medium'} showFirstButton={!isMobile} showLastButton={!isMobile} /> @@ -686,35 +273,19 @@ export default function GalleryPage(): JSX.Element { - setMobileDrawerOpen(false)} - ModalProps={{ keepMounted: true }} - PaperProps={{ - sx: { - width: 280, - display: 'flex', - flexDirection: 'column' - } - }} - > - - - Filters - - setMobileDrawerOpen(false)}> - - - - {categoriesLoading ? ( - - - - ) : ( - - )} - + onFilterChange={handleFilterChange} + onToggleCategoryExpansion={toggleCategoryExpansion} + onPriceRangeChange={handlePriceRangeChange} + onPriceRangeCommitted={handlePriceRangeCommitted} + /> ); } \ No newline at end of file diff --git a/ui/src/app/page.tsx b/ui/src/app/page.tsx index 4b3a205..e80f9c0 100644 --- a/ui/src/app/page.tsx +++ b/ui/src/app/page.tsx @@ -17,27 +17,6 @@ import {useState, useEffect, JSX} from 'react'; import { ShoppingCart, Palette, ImageOutlined, CreditCard, LocalShipping, CheckCircle } from '@mui/icons-material'; import clientApi from "@/lib/clientApi"; -interface Product { - id: string; - name: string; - description: string; - imageUrl?: string; - basePrice: number; - isCustomizable: boolean; -} - -interface ApiResponse { - items: Product[]; -} - -interface Step { - number: number; - label: string; - description: string; - icon: JSX.Element; - details: string; -} - export default function HomePage(): JSX.Element { const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); diff --git a/ui/src/constants/index.ts b/ui/src/constants/index.ts new file mode 100644 index 0000000..ab7af86 --- /dev/null +++ b/ui/src/constants/index.ts @@ -0,0 +1,12 @@ +import { SortOption } from '@/types'; + +export const SORT_OPTIONS: SortOption[] = [ + { value: 'Name-ASC', label: 'Name (A-Z)' }, + { value: 'Name-DESC', label: 'Name (Z-A)' }, + { value: 'Price-ASC', label: 'Price (Low to High)' }, + { value: 'Price-DESC', label: 'Price (High to Low)' }, + { value: 'CreatedDate-DESC', label: 'Newest First' }, + { value: 'CreatedDate-ASC', label: 'Oldest First' } +]; + +export const PAGE_SIZE_OPTIONS: number[] = [12, 24, 48, 96]; \ No newline at end of file diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts new file mode 100644 index 0000000..1ffea0d --- /dev/null +++ b/ui/src/types/index.ts @@ -0,0 +1,137 @@ +import {JSX} from 'react'; + +export interface Category { + id: string; + name: string; + description: string; + imageUrl: string; + sortOrder: number; + isActive: boolean; + parentCategoryId: string; + createdAt: string; + modifiedAt: string; +} + +export interface Product { + id: string; + name: string; + description: string; + basePrice: number; + isCustomizable: boolean; + isActive: boolean; + imageUrl: string; + categoryId: string; + category: Category; + createdAt: string; + modifiedAt: string; +} + +export interface Variant { + id: string; + productId: string; + size: string; + color: string; + price: number; + imageUrl: string; + sku: string; + stockQuantity: number; + isActive: boolean; + product: Product; + createdAt: string; + modifiedAt: string; +} + +export interface Address { + id: string; + userId: string; + addressType: string; + firstName: string; + lastName: string; + company: string; + addressLine1: string; + addressLine2: string; + apartmentNumber: string; + buildingNumber: string; + floor: string; + city: string; + state: string; + postalCode: string; + country: string; + phoneNumber: string; + instructions: string; + isDefault: boolean; + isActive: boolean; +} + +export interface NewAddress { + addressType: string; + firstName: string; + lastName: string; + company: string; + addressLine1: string; + addressLine2: string; + apartmentNumber: string; + buildingNumber: string; + floor: string; + city: string; + state: string; + postalCode: string; + country: string; + phoneNumber: string; + instructions: string; + isDefault: boolean; + isActive: boolean; +} + +export interface SortOption { + value: string; + label: string; +} + +export interface GalleryCategory { + id: string; + name: string; + parentCategoryId?: string; +} + +export interface GalleryProduct { + id: string; + name: string; + description: string; + basePrice: number; + imageUrl?: string; + isCustomizable: boolean; + category?: Category; +} + +export interface ProductsResponse { + items: Product[]; + totalPages: number; + totalCount: number; +} + +export interface Filters { + pageNumber: number; + pageSize: number; + searchTerm: string; + categoryId: string; + minPrice: number; + maxPrice: number; + isActive: boolean; + isCustomizable: boolean | null; + sortBy: string; + sortDirection: string; +} + +export interface ApiParams { + PageNumber: number; + PageSize: number; + IsActive: boolean; + SortBy: string; + SortDirection: string; + SearchTerm?: string; + CategoryId?: string; + MinPrice?: number; + MaxPrice?: number; + IsCustomizable?: boolean; +} \ No newline at end of file From 430fb4fd09f8cd158979f13b7a378e08f0a57524 Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Sun, 29 Jun 2025 23:46:58 +0300 Subject: [PATCH 3/7] Mobile friendly gallery --- ui/src/app/components/ImprinkAppBar.tsx | 23 +-- .../components/gallery/CategorySidebar.tsx | 9 +- .../components/gallery/MobileFilterDrawer.tsx | 40 +++-- ui/src/app/components/gallery/ProductCard.tsx | 94 ++++------- .../app/components/gallery/SearchFilters.tsx | 151 ++++++++++++++++-- ui/src/app/gallery/page.tsx | 6 +- ui/src/app/page.tsx | 21 +++ 7 files changed, 222 insertions(+), 122 deletions(-) diff --git a/ui/src/app/components/ImprinkAppBar.tsx b/ui/src/app/components/ImprinkAppBar.tsx index 41b6108..da6836a 100644 --- a/ui/src/app/components/ImprinkAppBar.tsx +++ b/ui/src/app/components/ImprinkAppBar.tsx @@ -58,14 +58,13 @@ export default function ImprinkAppBar() { const navigationLinks: NavLink[] = [ { label: 'Home', href: '/', icon: , show: true }, { label: 'Gallery', href: '/gallery', icon: , show: true }, - { label: 'Orders', href: '/orders', icon: , show: true }, + { label: 'Orders', href: '/orders', icon: , show: !!user }, { label: 'Merchant', href: '/merchant', icon: , show: isMerchant }, ]; const adminLinks: NavLink[] = [ { label: 'Dashboard', href: '/dashboard', icon: , show: isMerchant }, { label: 'Admin', href: '/admin', icon: , show: isAdmin }, - { label: 'Swagger', href: '/swagger', icon: , show: isAdmin }, { label: 'SEQ', href: '/seq', icon: , show: isAdmin }, ]; @@ -164,14 +163,6 @@ export default function ImprinkAppBar() { {user.name} - e.stopPropagation()}> - - Theme - - - - - Logout @@ -184,12 +175,6 @@ export default function ImprinkAppBar() { Sign Up - - - Theme - {isDarkMode ? '🌙' : '☀️'} - - )} @@ -230,6 +215,12 @@ export default function ImprinkAppBar() { )} )} + {/* Add theme toggle button to the right side of the toolbar on mobile */} + {isMobile && ( + + + + )} {renderAdminBar()} diff --git a/ui/src/app/components/gallery/CategorySidebar.tsx b/ui/src/app/components/gallery/CategorySidebar.tsx index f3d9b26..eead932 100644 --- a/ui/src/app/components/gallery/CategorySidebar.tsx +++ b/ui/src/app/components/gallery/CategorySidebar.tsx @@ -46,7 +46,6 @@ export default function CategorySidebar({ return ( Categories @@ -90,7 +87,7 @@ export default function CategorySidebar({ onFilterChange('categoryId', category.id)} + onClick={() => onToggleCategoryExpansion(category.id)} sx={{ borderRadius: 1, mb: 0.5 }} > @@ -151,7 +148,7 @@ export default function CategorySidebar({ onChangeCommitted={onPriceRangeCommitted} valueLabelDisplay="auto" min={0} - max={1000} + max={100} step={5} valueLabelFormat={(value) => `$${value}`} /> diff --git a/ui/src/app/components/gallery/MobileFilterDrawer.tsx b/ui/src/app/components/gallery/MobileFilterDrawer.tsx index af4b3f4..b59a049 100644 --- a/ui/src/app/components/gallery/MobileFilterDrawer.tsx +++ b/ui/src/app/components/gallery/MobileFilterDrawer.tsx @@ -44,13 +44,21 @@ export default function MobileFilterDrawer({ ModalProps={{ keepMounted: true }} PaperProps={{ sx: { - width: 280, + width: '100vw', display: 'flex', - flexDirection: 'column' + flexDirection: 'column', + overflow: 'hidden' } }} > - + Filters @@ -63,16 +71,22 @@ export default function MobileFilterDrawer({ ) : ( - + + + )} ); diff --git a/ui/src/app/components/gallery/ProductCard.tsx b/ui/src/app/components/gallery/ProductCard.tsx index 81f1e4b..a9af034 100644 --- a/ui/src/app/components/gallery/ProductCard.tsx +++ b/ui/src/app/components/gallery/ProductCard.tsx @@ -27,10 +27,6 @@ export default function ProductCard({ product }: ProductCardProps) { router.push(`/products/${product.id}`); }; - const handleLoginRedirect = () => { - router.push('/auth/login'); - }; - const handleBuild = () => { router.push(`/builder/${product.id}`); }; @@ -147,69 +143,33 @@ export default function ProductCard({ product }: ProductCardProps) { gap: { xs: 0.5, sm: 0.75 }, flexDirection: { xs: 'column', sm: user ? 'row' : 'column' } }}> - {user ? ( - <> - - - - ) : ( - <> - - - Login to customize - - + + {user && ( + )} )} diff --git a/ui/src/app/components/gallery/SearchFilters.tsx b/ui/src/app/components/gallery/SearchFilters.tsx index 764918d..a71e56f 100644 --- a/ui/src/app/components/gallery/SearchFilters.tsx +++ b/ui/src/app/components/gallery/SearchFilters.tsx @@ -12,7 +12,8 @@ import { useMediaQuery, useTheme, SelectChangeEvent, - Box + Box, + Button } from '@mui/material'; import { Search, @@ -52,28 +53,144 @@ export default function SearchFilters({ onFilterChange('sortDirection', sortDirection); }; + if (isMobile) { + return ( + + + ) => setSearchInput(e.target.value)} + onKeyPress={(e: KeyboardEvent) => e.key === 'Enter' && handleSearch()} + size="small" + sx={{ + '& .MuiInputBase-input': { + fontSize: '0.85rem', + py: 1.25 + } + }} + InputProps={{ + startAdornment: ( + + + + ), + endAdornment: searchInput && ( + + + + + + ) + }} + /> + + + + + + + + Sort + + + + + + + Per + + + + + + + + {productsCount} of {totalCount} products + + + + ); + } + return ( - {isMobile && ( - - - - - - )} - - + - + Product Gallery Explore our complete collection of customizable products diff --git a/ui/src/app/page.tsx b/ui/src/app/page.tsx index e80f9c0..4b3a205 100644 --- a/ui/src/app/page.tsx +++ b/ui/src/app/page.tsx @@ -17,6 +17,27 @@ import {useState, useEffect, JSX} from 'react'; import { ShoppingCart, Palette, ImageOutlined, CreditCard, LocalShipping, CheckCircle } from '@mui/icons-material'; import clientApi from "@/lib/clientApi"; +interface Product { + id: string; + name: string; + description: string; + imageUrl?: string; + basePrice: number; + isCustomizable: boolean; +} + +interface ApiResponse { + items: Product[]; +} + +interface Step { + number: number; + label: string; + description: string; + icon: JSX.Element; + details: string; +} + export default function HomePage(): JSX.Element { const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); From 007270fce471830071c18429cfcd4a01dbc4c122 Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Sun, 29 Jun 2025 23:54:15 +0300 Subject: [PATCH 4/7] Fix desktop cards layout in the gallery --- ui/src/app/components/ImprinkAppBar.tsx | 1 - ui/src/app/components/gallery/ProductCard.tsx | 18 +++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/ui/src/app/components/ImprinkAppBar.tsx b/ui/src/app/components/ImprinkAppBar.tsx index da6836a..a3ce8fc 100644 --- a/ui/src/app/components/ImprinkAppBar.tsx +++ b/ui/src/app/components/ImprinkAppBar.tsx @@ -215,7 +215,6 @@ export default function ImprinkAppBar() { )} )} - {/* Add theme toggle button to the right side of the toolbar on mobile */} {isMobile && ( diff --git a/ui/src/app/components/gallery/ProductCard.tsx b/ui/src/app/components/gallery/ProductCard.tsx index a9af034..c9ca6e7 100644 --- a/ui/src/app/components/gallery/ProductCard.tsx +++ b/ui/src/app/components/gallery/ProductCard.tsx @@ -34,12 +34,24 @@ export default function ProductCard({ product }: ProductCardProps) { return ( Date: Mon, 30 Jun 2025 00:29:40 +0300 Subject: [PATCH 5/7] Full creation -> payment flow --- .../Commands/Orders/GetMyOrders.cs | 38 + .../Commands/Orders/SetOrderPaymentStatus.cs | 6 + .../Commands/Orders/SetOrderShippingStatus.cs | 6 + src/Imprink.Application/Dtos/EnumValueDto.cs | 6 + .../{ => Mappings}/MappingProfile.cs | 0 .../Mappings/OrderStatusMappingProfile.cs | 15 - .../Mappings/ShippingStatusMappingProfile.cs | 15 - src/Imprink.Domain/Entities/OrderStatus.cs | 9 - src/Imprink.Domain/Entities/ShippingStatus.cs | 9 - src/Imprink.Domain/Enums/OrderStatusEnum.cs | 6 + .../Enums/ShippingStatusEnum.cs | 6 + .../Extensions/StatusExtensions.cs | 6 + .../Configuration/OrderStatusConfiguration.cs | 31 - .../ShippingStatusConfiguration.cs | 31 - .../20250625223159_InitialSetup.Designer.cs | 1027 ----------------- .../Migrations/20250625223159_InitialSetup.cs | 676 ----------- .../ApplicationDbContextModelSnapshot.cs | 1024 ---------------- 17 files changed, 74 insertions(+), 2837 deletions(-) create mode 100644 src/Imprink.Application/Commands/Orders/GetMyOrders.cs create mode 100644 src/Imprink.Application/Commands/Orders/SetOrderPaymentStatus.cs create mode 100644 src/Imprink.Application/Commands/Orders/SetOrderShippingStatus.cs create mode 100644 src/Imprink.Application/Dtos/EnumValueDto.cs rename src/Imprink.Application/{ => Mappings}/MappingProfile.cs (100%) delete mode 100644 src/Imprink.Application/Mappings/OrderStatusMappingProfile.cs delete mode 100644 src/Imprink.Application/Mappings/ShippingStatusMappingProfile.cs delete mode 100644 src/Imprink.Domain/Entities/OrderStatus.cs delete mode 100644 src/Imprink.Domain/Entities/ShippingStatus.cs create mode 100644 src/Imprink.Domain/Enums/OrderStatusEnum.cs create mode 100644 src/Imprink.Domain/Enums/ShippingStatusEnum.cs create mode 100644 src/Imprink.Domain/Extensions/StatusExtensions.cs delete mode 100644 src/Imprink.Infrastructure/Configuration/OrderStatusConfiguration.cs delete mode 100644 src/Imprink.Infrastructure/Configuration/ShippingStatusConfiguration.cs delete mode 100644 src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.Designer.cs delete mode 100644 src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.cs delete mode 100644 src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs diff --git a/src/Imprink.Application/Commands/Orders/GetMyOrders.cs b/src/Imprink.Application/Commands/Orders/GetMyOrders.cs new file mode 100644 index 0000000..7889877 --- /dev/null +++ b/src/Imprink.Application/Commands/Orders/GetMyOrders.cs @@ -0,0 +1,38 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Orders; + +public class GetOrdersByUserIdQuery : IRequest> +{ + public string UserId { get; set; } = null!; + public bool IncludeDetails { get; set; } +} + +public class GetOrdersByUserId( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler> +{ + public async Task> Handle( + GetOrdersByUserIdQuery request, + CancellationToken cancellationToken) + { + IEnumerable orders; + + if (request.IncludeDetails) + { + orders = await uw.OrderRepository + .GetByUserIdWithDetailsAsync(request.UserId, cancellationToken); + } + else + { + orders = await uw.OrderRepository + .GetByUserIdAsync(request.UserId, cancellationToken); + } + + return mapper.Map>(orders); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Orders/SetOrderPaymentStatus.cs b/src/Imprink.Application/Commands/Orders/SetOrderPaymentStatus.cs new file mode 100644 index 0000000..c3adb00 --- /dev/null +++ b/src/Imprink.Application/Commands/Orders/SetOrderPaymentStatus.cs @@ -0,0 +1,6 @@ +namespace Imprink.Application.Commands.Orders; + +public class SetOrderStatus +{ + +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Orders/SetOrderShippingStatus.cs b/src/Imprink.Application/Commands/Orders/SetOrderShippingStatus.cs new file mode 100644 index 0000000..01efc38 --- /dev/null +++ b/src/Imprink.Application/Commands/Orders/SetOrderShippingStatus.cs @@ -0,0 +1,6 @@ +namespace Imprink.Application.Commands.Orders; + +public class SetOrderShippingStatus +{ + +} \ No newline at end of file diff --git a/src/Imprink.Application/Dtos/EnumValueDto.cs b/src/Imprink.Application/Dtos/EnumValueDto.cs new file mode 100644 index 0000000..8d433f6 --- /dev/null +++ b/src/Imprink.Application/Dtos/EnumValueDto.cs @@ -0,0 +1,6 @@ +namespace Imprink.Application.Dtos; + +public class EnumValueDto +{ + +} \ No newline at end of file diff --git a/src/Imprink.Application/MappingProfile.cs b/src/Imprink.Application/Mappings/MappingProfile.cs similarity index 100% rename from src/Imprink.Application/MappingProfile.cs rename to src/Imprink.Application/Mappings/MappingProfile.cs diff --git a/src/Imprink.Application/Mappings/OrderStatusMappingProfile.cs b/src/Imprink.Application/Mappings/OrderStatusMappingProfile.cs deleted file mode 100644 index c61c7fe..0000000 --- a/src/Imprink.Application/Mappings/OrderStatusMappingProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; -using Imprink.Application.Dtos; -using Imprink.Domain.Entities; - -namespace Imprink.Application.Mappings; - -public class OrderStatusMappingProfile : Profile -{ - public OrderStatusMappingProfile() - { - CreateMap(); - - CreateMap(); - } -} \ No newline at end of file diff --git a/src/Imprink.Application/Mappings/ShippingStatusMappingProfile.cs b/src/Imprink.Application/Mappings/ShippingStatusMappingProfile.cs deleted file mode 100644 index 16a60a5..0000000 --- a/src/Imprink.Application/Mappings/ShippingStatusMappingProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; -using Imprink.Application.Dtos; -using Imprink.Domain.Entities; - -namespace Imprink.Application.Mappings; - -public class ShippingStatusMappingProfile : Profile -{ - public ShippingStatusMappingProfile() - { - CreateMap(); - - CreateMap(); - } -} \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/OrderStatus.cs b/src/Imprink.Domain/Entities/OrderStatus.cs deleted file mode 100644 index 0408618..0000000 --- a/src/Imprink.Domain/Entities/OrderStatus.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Imprink.Domain.Entities; - -public class OrderStatus -{ - public int Id { get; set; } - public string Name { get; set; } = null!; - - public virtual ICollection Orders { get; set; } = new List(); -} \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/ShippingStatus.cs b/src/Imprink.Domain/Entities/ShippingStatus.cs deleted file mode 100644 index d2384bf..0000000 --- a/src/Imprink.Domain/Entities/ShippingStatus.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Imprink.Domain.Entities; - -public class ShippingStatus -{ - public int Id { get; set; } - public string Name { get; set; } = null!; - - public virtual ICollection Orders { get; set; } = new List(); -} \ No newline at end of file diff --git a/src/Imprink.Domain/Enums/OrderStatusEnum.cs b/src/Imprink.Domain/Enums/OrderStatusEnum.cs new file mode 100644 index 0000000..7a0f670 --- /dev/null +++ b/src/Imprink.Domain/Enums/OrderStatusEnum.cs @@ -0,0 +1,6 @@ +namespace Imprink.Domain.Enums; + +public enum OrderStatusEnum +{ + +} \ No newline at end of file diff --git a/src/Imprink.Domain/Enums/ShippingStatusEnum.cs b/src/Imprink.Domain/Enums/ShippingStatusEnum.cs new file mode 100644 index 0000000..50922af --- /dev/null +++ b/src/Imprink.Domain/Enums/ShippingStatusEnum.cs @@ -0,0 +1,6 @@ +namespace Imprink.Domain.Enums; + +public enum ShippingStatusEnum +{ + +} \ No newline at end of file diff --git a/src/Imprink.Domain/Extensions/StatusExtensions.cs b/src/Imprink.Domain/Extensions/StatusExtensions.cs new file mode 100644 index 0000000..694aeb5 --- /dev/null +++ b/src/Imprink.Domain/Extensions/StatusExtensions.cs @@ -0,0 +1,6 @@ +namespace Imprink.Domain.Extensions; + +public class StatusExtensions +{ + +} \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Configuration/OrderStatusConfiguration.cs b/src/Imprink.Infrastructure/Configuration/OrderStatusConfiguration.cs deleted file mode 100644 index 917d343..0000000 --- a/src/Imprink.Infrastructure/Configuration/OrderStatusConfiguration.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Imprink.Domain.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace Imprink.Infrastructure.Configuration; - -public class OrderStatusConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(os => os.Id); - - builder.Property(os => os.Id) - .ValueGeneratedNever(); - - builder.Property(os => os.Name) - .IsRequired() - .HasMaxLength(50); - - builder.HasIndex(os => os.Name) - .IsUnique() - .HasDatabaseName("IX_OrderStatus_Name"); - - builder.HasData( - new OrderStatus { Id = 0, Name = "Pending" }, - new OrderStatus { Id = 1, Name = "Processing" }, - new OrderStatus { Id = 2, Name = "Completed" }, - new OrderStatus { Id = 3, Name = "Cancelled" } - ); - } -} \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Configuration/ShippingStatusConfiguration.cs b/src/Imprink.Infrastructure/Configuration/ShippingStatusConfiguration.cs deleted file mode 100644 index 1dcf9b5..0000000 --- a/src/Imprink.Infrastructure/Configuration/ShippingStatusConfiguration.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Imprink.Domain.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace Imprink.Infrastructure.Configuration; - -public class ShippingStatusConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ss => ss.Id); - - builder.Property(ss => ss.Id) - .ValueGeneratedNever(); - - builder.Property(ss => ss.Name) - .IsRequired() - .HasMaxLength(50); - - builder.HasIndex(ss => ss.Name) - .IsUnique() - .HasDatabaseName("IX_ShippingStatus_Name"); - - builder.HasData( - new ShippingStatus { Id = 0, Name = "Prepping" }, - new ShippingStatus { Id = 1, Name = "Packaging" }, - new ShippingStatus { Id = 2, Name = "Shipped" }, - new ShippingStatus { Id = 3, Name = "Delivered" } - ); - } -} \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.Designer.cs b/src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.Designer.cs deleted file mode 100644 index 8e73eb7..0000000 --- a/src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.Designer.cs +++ /dev/null @@ -1,1027 +0,0 @@ -// -using System; -using Imprink.Infrastructure.Database; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Imprink.Infrastructure.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20250625223159_InitialSetup")] - partial class InitialSetup - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.5") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Imprink.Domain.Entities.Address", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("AddressLine1") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("AddressLine2") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("AddressType") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("ApartmentNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("BuildingNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Company") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("FirstName") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Floor") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("Instructions") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("IsDefault") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("LastName") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("PhoneNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("State") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Address_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Address_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Address_ModifiedAt"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_Address_UserId"); - - b.HasIndex("UserId", "AddressType") - .HasDatabaseName("IX_Address_User_Type"); - - b.HasIndex("UserId", "IsDefault") - .HasDatabaseName("IX_Address_User_Default"); - - b.ToTable("Addresses"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("ImageUrl") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ParentCategoryId") - .HasColumnType("uniqueidentifier"); - - b.Property("SortOrder") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(0); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Category_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Category_CreatedBy"); - - b.HasIndex("IsActive") - .HasDatabaseName("IX_Category_IsActive"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Category_ModifiedAt"); - - b.HasIndex("Name") - .HasDatabaseName("IX_Category_Name"); - - b.HasIndex("ParentCategoryId") - .HasDatabaseName("IX_Category_ParentCategoryId"); - - b.HasIndex("IsActive", "SortOrder") - .HasDatabaseName("IX_Category_Active_SortOrder"); - - b.HasIndex("ParentCategoryId", "SortOrder") - .HasDatabaseName("IX_Category_Parent_SortOrder"); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = new Guid("11111111-1111-1111-1111-111111111111"), - CreatedAt = new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), - CreatedBy = "system@printbase.com", - Description = "Textile and fabric-based products", - IsActive = true, - ModifiedAt = new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), - ModifiedBy = "system@printbase.com", - Name = "Textile", - SortOrder = 1 - }, - new - { - Id = new Guid("22222222-2222-2222-2222-222222222222"), - CreatedAt = new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), - CreatedBy = "system@printbase.com", - Description = "Products for hard surface printing", - IsActive = true, - ModifiedAt = new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), - ModifiedBy = "system@printbase.com", - Name = "Hard Surfaces", - SortOrder = 2 - }, - new - { - Id = new Guid("33333333-3333-3333-3333-333333333333"), - CreatedAt = new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), - CreatedBy = "system@printbase.com", - Description = "Paper-based printing products", - IsActive = true, - ModifiedAt = new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), - ModifiedBy = "system@printbase.com", - Name = "Paper", - SortOrder = 3 - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("Amount") - .HasColumnType("decimal(18,2)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("CustomizationDescription") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("CustomizationImageUrl") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("MerchantId") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Notes") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("OrderDate") - .HasColumnType("datetime2"); - - b.Property("OrderStatusId") - .HasColumnType("int"); - - b.Property("OriginalImageUrls") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("ProductId") - .HasColumnType("uniqueidentifier"); - - b.Property("ProductVariantId") - .HasColumnType("uniqueidentifier"); - - b.Property("Quantity") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("ShippingStatusId") - .HasColumnType("int"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Order_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Order_CreatedBy"); - - b.HasIndex("MerchantId") - .HasDatabaseName("IX_Order_MerchantId"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Order_ModifiedAt"); - - b.HasIndex("OrderDate") - .HasDatabaseName("IX_Order_OrderDate"); - - b.HasIndex("OrderStatusId") - .HasDatabaseName("IX_Order_OrderStatusId"); - - b.HasIndex("ProductId") - .HasDatabaseName("IX_Order_ProductId"); - - b.HasIndex("ProductVariantId") - .HasDatabaseName("IX_Order_ProductVariantId"); - - b.HasIndex("ShippingStatusId") - .HasDatabaseName("IX_Order_ShippingStatusId"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_Order_UserId"); - - b.HasIndex("MerchantId", "OrderDate") - .HasDatabaseName("IX_Order_Merchant_Date"); - - b.HasIndex("ProductId", "OrderDate") - .HasDatabaseName("IX_Order_Product_Date"); - - b.HasIndex("UserId", "OrderDate") - .HasDatabaseName("IX_Order_User_Date"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.OrderAddress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("AddressLine1") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("AddressLine2") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("AddressType") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("ApartmentNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("BuildingNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Company") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("FirstName") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Floor") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("Instructions") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("LastName") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); - - b.Property("PhoneNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("State") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_OrderAddress_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_OrderAddress_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_OrderAddress_ModifiedAt"); - - b.HasIndex("OrderId") - .IsUnique() - .HasDatabaseName("IX_OrderAddress_OrderId"); - - b.ToTable("OrderAddresses"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.OrderStatus", b => - { - b.Property("Id") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("IX_OrderStatus_Name"); - - b.ToTable("OrderStatuses"); - - b.HasData( - new - { - Id = 0, - Name = "Pending" - }, - new - { - Id = 1, - Name = "Processing" - }, - new - { - Id = 2, - Name = "Completed" - }, - new - { - Id = 3, - Name = "Cancelled" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("BasePrice") - .HasColumnType("decimal(18,2)"); - - b.Property("CategoryId") - .HasColumnType("uniqueidentifier"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("ImageUrl") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("IsCustomizable") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId") - .HasDatabaseName("IX_Product_CategoryId"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Product_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Product_CreatedBy"); - - b.HasIndex("IsActive") - .HasDatabaseName("IX_Product_IsActive"); - - b.HasIndex("IsCustomizable") - .HasDatabaseName("IX_Product_IsCustomizable"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Product_ModifiedAt"); - - b.HasIndex("Name") - .HasDatabaseName("IX_Product_Name"); - - b.HasIndex("CategoryId", "IsActive") - .HasDatabaseName("IX_Product_Category_Active"); - - b.HasIndex("IsActive", "IsCustomizable") - .HasDatabaseName("IX_Product_Active_Customizable"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("ImageUrl") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.Property("ProductId") - .HasColumnType("uniqueidentifier"); - - b.Property("Size") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("Sku") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("StockQuantity") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(0); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_ProductVariant_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_ProductVariant_CreatedBy"); - - b.HasIndex("IsActive") - .HasDatabaseName("IX_ProductVariant_IsActive"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_ProductVariant_ModifiedAt"); - - b.HasIndex("ProductId") - .HasDatabaseName("IX_ProductVariant_ProductId"); - - b.HasIndex("Sku") - .IsUnique() - .HasDatabaseName("IX_ProductVariant_SKU"); - - b.HasIndex("ProductId", "Size", "Color") - .IsUnique() - .HasDatabaseName("IX_ProductVariant_Product_Size_Color") - .HasFilter("[Color] IS NOT NULL"); - - b.ToTable("ProductVariants"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Role", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.Property("RoleName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("RoleName") - .IsUnique() - .HasDatabaseName("IX_Role_RoleName"); - - b.ToTable("Roles"); - - b.HasData( - new - { - Id = new Guid("22222222-2222-2222-2222-222222222222"), - RoleName = "Merchant" - }, - new - { - Id = new Guid("33333333-3333-3333-3333-333333333333"), - RoleName = "Admin" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.ShippingStatus", b => - { - b.Property("Id") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("IX_ShippingStatus_Name"); - - b.ToTable("ShippingStatuses"); - - b.HasData( - new - { - Id = 0, - Name = "Prepping" - }, - new - { - Id = 1, - Name = "Packaging" - }, - new - { - Id = 2, - Name = "Shipped" - }, - new - { - Id = 3, - Name = "Delivered" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.User", b => - { - b.Property("Id") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailVerified") - .HasColumnType("bit"); - - b.Property("FirstName") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("LastName") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Nickname") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("PhoneNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.HasKey("Id"); - - b.HasIndex("Email") - .IsUnique() - .HasDatabaseName("IX_User_Email"); - - b.HasIndex("IsActive") - .HasDatabaseName("IX_User_IsActive"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.UserRole", b => - { - b.Property("UserId") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId") - .HasDatabaseName("IX_UserRole_RoleId"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_UserRole_UserId"); - - b.ToTable("UserRole"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Address", b => - { - b.HasOne("Imprink.Domain.Entities.User", "User") - .WithMany("Addresses") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Category", b => - { - b.HasOne("Imprink.Domain.Entities.Category", "ParentCategory") - .WithMany("SubCategories") - .HasForeignKey("ParentCategoryId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("ParentCategory"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Order", b => - { - b.HasOne("Imprink.Domain.Entities.User", "Merchant") - .WithMany("MerchantOrders") - .HasForeignKey("MerchantId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Imprink.Domain.Entities.OrderStatus", "OrderStatus") - .WithMany("Orders") - .HasForeignKey("OrderStatusId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Product", "Product") - .WithMany("Orders") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.ProductVariant", "ProductVariant") - .WithMany("Orders") - .HasForeignKey("ProductVariantId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("Imprink.Domain.Entities.ShippingStatus", "ShippingStatus") - .WithMany("Orders") - .HasForeignKey("ShippingStatusId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.User", "User") - .WithMany("Orders") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Merchant"); - - b.Navigation("OrderStatus"); - - b.Navigation("Product"); - - b.Navigation("ProductVariant"); - - b.Navigation("ShippingStatus"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.OrderAddress", b => - { - b.HasOne("Imprink.Domain.Entities.Order", "Order") - .WithOne("OrderAddress") - .HasForeignKey("Imprink.Domain.Entities.OrderAddress", "OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Order"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product", b => - { - b.HasOne("Imprink.Domain.Entities.Category", "Category") - .WithMany("Products") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.SetNull); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => - { - b.HasOne("Imprink.Domain.Entities.Product", "Product") - .WithMany("ProductVariants") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Product"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.UserRole", b => - { - b.HasOne("Imprink.Domain.Entities.Role", "Role") - .WithMany("UserRoles") - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.User", "User") - .WithMany("UserRoles") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Role"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Category", b => - { - b.Navigation("Products"); - - b.Navigation("SubCategories"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Order", b => - { - b.Navigation("OrderAddress") - .IsRequired(); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.OrderStatus", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product", b => - { - b.Navigation("Orders"); - - b.Navigation("ProductVariants"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Role", b => - { - b.Navigation("UserRoles"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.ShippingStatus", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.User", b => - { - b.Navigation("Addresses"); - - b.Navigation("MerchantOrders"); - - b.Navigation("Orders"); - - b.Navigation("UserRoles"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.cs b/src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.cs deleted file mode 100644 index 9606a5c..0000000 --- a/src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.cs +++ /dev/null @@ -1,676 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional - -namespace Imprink.Infrastructure.Migrations -{ - /// - public partial class InitialSetup : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Categories", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), - Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - Description = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false), - ImageUrl = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), - SortOrder = table.Column(type: "int", nullable: false, defaultValue: 0), - IsActive = table.Column(type: "bit", nullable: false, defaultValue: true), - ParentCategoryId = table.Column(type: "uniqueidentifier", nullable: true), - CreatedAt = table.Column(type: "datetime2", nullable: true), - ModifiedAt = table.Column(type: "datetime2", nullable: true, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Categories", x => x.Id); - table.ForeignKey( - name: "FK_Categories_Categories_ParentCategoryId", - column: x => x.ParentCategoryId, - principalTable: "Categories", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "OrderStatuses", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderStatuses", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Roles", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - RoleName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Roles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "ShippingStatuses", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ShippingStatuses", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Users", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - Nickname = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - EmailVerified = table.Column(type: "bit", nullable: false), - FirstName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - LastName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - PhoneNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), - IsActive = table.Column(type: "bit", nullable: false, defaultValue: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Users", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Products", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), - Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - Description = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - BasePrice = table.Column(type: "decimal(18,2)", nullable: false), - IsCustomizable = table.Column(type: "bit", nullable: false, defaultValue: false), - IsActive = table.Column(type: "bit", nullable: false, defaultValue: true), - ImageUrl = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), - CategoryId = table.Column(type: "uniqueidentifier", nullable: true), - CreatedAt = table.Column(type: "datetime2", nullable: true), - ModifiedAt = table.Column(type: "datetime2", nullable: true, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Products", x => x.Id); - table.ForeignKey( - name: "FK_Products_Categories_CategoryId", - column: x => x.CategoryId, - principalTable: "Categories", - principalColumn: "Id", - onDelete: ReferentialAction.SetNull); - }); - - migrationBuilder.CreateTable( - name: "Addresses", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), - UserId = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - AddressType = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - FirstName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - LastName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - Company = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - AddressLine1 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - AddressLine2 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - ApartmentNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), - BuildingNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), - Floor = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), - City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - State = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - PostalCode = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), - Country = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - PhoneNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), - Instructions = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), - IsDefault = table.Column(type: "bit", nullable: false, defaultValue: false), - IsActive = table.Column(type: "bit", nullable: false, defaultValue: true), - CreatedAt = table.Column(type: "datetime2", nullable: true), - ModifiedAt = table.Column(type: "datetime2", nullable: true, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Addresses", x => x.Id); - table.ForeignKey( - name: "FK_Addresses_Users_UserId", - column: x => x.UserId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "UserRole", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - RoleId = table.Column(type: "uniqueidentifier", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UserRole", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_UserRole_Roles_RoleId", - column: x => x.RoleId, - principalTable: "Roles", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_UserRole_Users_UserId", - column: x => x.UserId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ProductVariants", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), - ProductId = table.Column(type: "uniqueidentifier", nullable: false), - Size = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Color = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - ImageUrl = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), - Sku = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - StockQuantity = table.Column(type: "int", nullable: false, defaultValue: 0), - IsActive = table.Column(type: "bit", nullable: false, defaultValue: true), - CreatedAt = table.Column(type: "datetime2", nullable: true), - ModifiedAt = table.Column(type: "datetime2", nullable: true, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_ProductVariants", x => x.Id); - table.ForeignKey( - name: "FK_ProductVariants_Products_ProductId", - column: x => x.ProductId, - principalTable: "Products", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), - UserId = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - OrderDate = table.Column(type: "datetime2", nullable: false), - Amount = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false, defaultValue: 1), - ProductId = table.Column(type: "uniqueidentifier", nullable: false), - ProductVariantId = table.Column(type: "uniqueidentifier", nullable: true), - OrderStatusId = table.Column(type: "int", nullable: false), - ShippingStatusId = table.Column(type: "int", nullable: false), - Notes = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - MerchantId = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), - CustomizationImageUrl = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - OriginalImageUrls = table.Column(type: "nvarchar(max)", nullable: false), - CustomizationDescription = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), - CreatedAt = table.Column(type: "datetime2", nullable: true), - ModifiedAt = table.Column(type: "datetime2", nullable: true, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - table.ForeignKey( - name: "FK_Orders_OrderStatuses_OrderStatusId", - column: x => x.OrderStatusId, - principalTable: "OrderStatuses", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Orders_ProductVariants_ProductVariantId", - column: x => x.ProductVariantId, - principalTable: "ProductVariants", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Orders_Products_ProductId", - column: x => x.ProductId, - principalTable: "Products", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Orders_ShippingStatuses_ShippingStatusId", - column: x => x.ShippingStatusId, - principalTable: "ShippingStatuses", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Orders_Users_MerchantId", - column: x => x.MerchantId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.SetNull); - table.ForeignKey( - name: "FK_Orders_Users_UserId", - column: x => x.UserId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "OrderAddresses", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), - OrderId = table.Column(type: "uniqueidentifier", nullable: false), - AddressType = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - FirstName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - LastName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - Company = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - AddressLine1 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), - AddressLine2 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), - ApartmentNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), - BuildingNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), - Floor = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), - City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - State = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - PostalCode = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), - Country = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - PhoneNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), - Instructions = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), - CreatedAt = table.Column(type: "datetime2", nullable: true), - ModifiedAt = table.Column(type: "datetime2", nullable: true, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderAddresses", x => x.Id); - table.ForeignKey( - name: "FK_OrderAddresses_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.InsertData( - table: "Categories", - columns: new[] { "Id", "CreatedAt", "CreatedBy", "Description", "ImageUrl", "IsActive", "ModifiedAt", "ModifiedBy", "Name", "ParentCategoryId", "SortOrder" }, - values: new object[,] - { - { new Guid("11111111-1111-1111-1111-111111111111"), new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), "system@printbase.com", "Textile and fabric-based products", null, true, new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), "system@printbase.com", "Textile", null, 1 }, - { new Guid("22222222-2222-2222-2222-222222222222"), new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), "system@printbase.com", "Products for hard surface printing", null, true, new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), "system@printbase.com", "Hard Surfaces", null, 2 }, - { new Guid("33333333-3333-3333-3333-333333333333"), new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), "system@printbase.com", "Paper-based printing products", null, true, new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), "system@printbase.com", "Paper", null, 3 } - }); - - migrationBuilder.InsertData( - table: "OrderStatuses", - columns: new[] { "Id", "Name" }, - values: new object[,] - { - { 0, "Pending" }, - { 1, "Processing" }, - { 2, "Completed" }, - { 3, "Cancelled" } - }); - - migrationBuilder.InsertData( - table: "Roles", - columns: new[] { "Id", "RoleName" }, - values: new object[,] - { - { new Guid("22222222-2222-2222-2222-222222222222"), "Merchant" }, - { new Guid("33333333-3333-3333-3333-333333333333"), "Admin" } - }); - - migrationBuilder.InsertData( - table: "ShippingStatuses", - columns: new[] { "Id", "Name" }, - values: new object[,] - { - { 0, "Prepping" }, - { 1, "Packaging" }, - { 2, "Shipped" }, - { 3, "Delivered" } - }); - - migrationBuilder.CreateIndex( - name: "IX_Address_CreatedAt", - table: "Addresses", - column: "CreatedAt"); - - migrationBuilder.CreateIndex( - name: "IX_Address_CreatedBy", - table: "Addresses", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_Address_ModifiedAt", - table: "Addresses", - column: "ModifiedAt"); - - migrationBuilder.CreateIndex( - name: "IX_Address_User_Default", - table: "Addresses", - columns: new[] { "UserId", "IsDefault" }); - - migrationBuilder.CreateIndex( - name: "IX_Address_User_Type", - table: "Addresses", - columns: new[] { "UserId", "AddressType" }); - - migrationBuilder.CreateIndex( - name: "IX_Address_UserId", - table: "Addresses", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_Category_Active_SortOrder", - table: "Categories", - columns: new[] { "IsActive", "SortOrder" }); - - migrationBuilder.CreateIndex( - name: "IX_Category_CreatedAt", - table: "Categories", - column: "CreatedAt"); - - migrationBuilder.CreateIndex( - name: "IX_Category_CreatedBy", - table: "Categories", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_Category_IsActive", - table: "Categories", - column: "IsActive"); - - migrationBuilder.CreateIndex( - name: "IX_Category_ModifiedAt", - table: "Categories", - column: "ModifiedAt"); - - migrationBuilder.CreateIndex( - name: "IX_Category_Name", - table: "Categories", - column: "Name"); - - migrationBuilder.CreateIndex( - name: "IX_Category_Parent_SortOrder", - table: "Categories", - columns: new[] { "ParentCategoryId", "SortOrder" }); - - migrationBuilder.CreateIndex( - name: "IX_Category_ParentCategoryId", - table: "Categories", - column: "ParentCategoryId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderAddress_CreatedAt", - table: "OrderAddresses", - column: "CreatedAt"); - - migrationBuilder.CreateIndex( - name: "IX_OrderAddress_CreatedBy", - table: "OrderAddresses", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_OrderAddress_ModifiedAt", - table: "OrderAddresses", - column: "ModifiedAt"); - - migrationBuilder.CreateIndex( - name: "IX_OrderAddress_OrderId", - table: "OrderAddresses", - column: "OrderId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Order_CreatedAt", - table: "Orders", - column: "CreatedAt"); - - migrationBuilder.CreateIndex( - name: "IX_Order_CreatedBy", - table: "Orders", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_Order_Merchant_Date", - table: "Orders", - columns: new[] { "MerchantId", "OrderDate" }); - - migrationBuilder.CreateIndex( - name: "IX_Order_MerchantId", - table: "Orders", - column: "MerchantId"); - - migrationBuilder.CreateIndex( - name: "IX_Order_ModifiedAt", - table: "Orders", - column: "ModifiedAt"); - - migrationBuilder.CreateIndex( - name: "IX_Order_OrderDate", - table: "Orders", - column: "OrderDate"); - - migrationBuilder.CreateIndex( - name: "IX_Order_OrderStatusId", - table: "Orders", - column: "OrderStatusId"); - - migrationBuilder.CreateIndex( - name: "IX_Order_Product_Date", - table: "Orders", - columns: new[] { "ProductId", "OrderDate" }); - - migrationBuilder.CreateIndex( - name: "IX_Order_ProductId", - table: "Orders", - column: "ProductId"); - - migrationBuilder.CreateIndex( - name: "IX_Order_ProductVariantId", - table: "Orders", - column: "ProductVariantId"); - - migrationBuilder.CreateIndex( - name: "IX_Order_ShippingStatusId", - table: "Orders", - column: "ShippingStatusId"); - - migrationBuilder.CreateIndex( - name: "IX_Order_User_Date", - table: "Orders", - columns: new[] { "UserId", "OrderDate" }); - - migrationBuilder.CreateIndex( - name: "IX_Order_UserId", - table: "Orders", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderStatus_Name", - table: "OrderStatuses", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Product_Active_Customizable", - table: "Products", - columns: new[] { "IsActive", "IsCustomizable" }); - - migrationBuilder.CreateIndex( - name: "IX_Product_Category_Active", - table: "Products", - columns: new[] { "CategoryId", "IsActive" }); - - migrationBuilder.CreateIndex( - name: "IX_Product_CategoryId", - table: "Products", - column: "CategoryId"); - - migrationBuilder.CreateIndex( - name: "IX_Product_CreatedAt", - table: "Products", - column: "CreatedAt"); - - migrationBuilder.CreateIndex( - name: "IX_Product_CreatedBy", - table: "Products", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_Product_IsActive", - table: "Products", - column: "IsActive"); - - migrationBuilder.CreateIndex( - name: "IX_Product_IsCustomizable", - table: "Products", - column: "IsCustomizable"); - - migrationBuilder.CreateIndex( - name: "IX_Product_ModifiedAt", - table: "Products", - column: "ModifiedAt"); - - migrationBuilder.CreateIndex( - name: "IX_Product_Name", - table: "Products", - column: "Name"); - - migrationBuilder.CreateIndex( - name: "IX_ProductVariant_CreatedAt", - table: "ProductVariants", - column: "CreatedAt"); - - migrationBuilder.CreateIndex( - name: "IX_ProductVariant_CreatedBy", - table: "ProductVariants", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_ProductVariant_IsActive", - table: "ProductVariants", - column: "IsActive"); - - migrationBuilder.CreateIndex( - name: "IX_ProductVariant_ModifiedAt", - table: "ProductVariants", - column: "ModifiedAt"); - - migrationBuilder.CreateIndex( - name: "IX_ProductVariant_Product_Size_Color", - table: "ProductVariants", - columns: new[] { "ProductId", "Size", "Color" }, - unique: true, - filter: "[Color] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_ProductVariant_ProductId", - table: "ProductVariants", - column: "ProductId"); - - migrationBuilder.CreateIndex( - name: "IX_ProductVariant_SKU", - table: "ProductVariants", - column: "Sku", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Role_RoleName", - table: "Roles", - column: "RoleName", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ShippingStatus_Name", - table: "ShippingStatuses", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_UserRole_RoleId", - table: "UserRole", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "IX_UserRole_UserId", - table: "UserRole", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_User_Email", - table: "Users", - column: "Email", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_User_IsActive", - table: "Users", - column: "IsActive"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Addresses"); - - migrationBuilder.DropTable( - name: "OrderAddresses"); - - migrationBuilder.DropTable( - name: "UserRole"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropTable( - name: "Roles"); - - migrationBuilder.DropTable( - name: "OrderStatuses"); - - migrationBuilder.DropTable( - name: "ProductVariants"); - - migrationBuilder.DropTable( - name: "ShippingStatuses"); - - migrationBuilder.DropTable( - name: "Users"); - - migrationBuilder.DropTable( - name: "Products"); - - migrationBuilder.DropTable( - name: "Categories"); - } - } -} diff --git a/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs deleted file mode 100644 index 9d264b9..0000000 --- a/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs +++ /dev/null @@ -1,1024 +0,0 @@ -// -using System; -using Imprink.Infrastructure.Database; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Imprink.Infrastructure.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - partial class ApplicationDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.5") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Imprink.Domain.Entities.Address", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("AddressLine1") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("AddressLine2") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("AddressType") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("ApartmentNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("BuildingNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Company") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("FirstName") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Floor") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("Instructions") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("IsDefault") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("LastName") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("PhoneNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("State") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Address_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Address_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Address_ModifiedAt"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_Address_UserId"); - - b.HasIndex("UserId", "AddressType") - .HasDatabaseName("IX_Address_User_Type"); - - b.HasIndex("UserId", "IsDefault") - .HasDatabaseName("IX_Address_User_Default"); - - b.ToTable("Addresses"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("ImageUrl") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("ParentCategoryId") - .HasColumnType("uniqueidentifier"); - - b.Property("SortOrder") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(0); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Category_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Category_CreatedBy"); - - b.HasIndex("IsActive") - .HasDatabaseName("IX_Category_IsActive"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Category_ModifiedAt"); - - b.HasIndex("Name") - .HasDatabaseName("IX_Category_Name"); - - b.HasIndex("ParentCategoryId") - .HasDatabaseName("IX_Category_ParentCategoryId"); - - b.HasIndex("IsActive", "SortOrder") - .HasDatabaseName("IX_Category_Active_SortOrder"); - - b.HasIndex("ParentCategoryId", "SortOrder") - .HasDatabaseName("IX_Category_Parent_SortOrder"); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = new Guid("11111111-1111-1111-1111-111111111111"), - CreatedAt = new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), - CreatedBy = "system@printbase.com", - Description = "Textile and fabric-based products", - IsActive = true, - ModifiedAt = new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), - ModifiedBy = "system@printbase.com", - Name = "Textile", - SortOrder = 1 - }, - new - { - Id = new Guid("22222222-2222-2222-2222-222222222222"), - CreatedAt = new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), - CreatedBy = "system@printbase.com", - Description = "Products for hard surface printing", - IsActive = true, - ModifiedAt = new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), - ModifiedBy = "system@printbase.com", - Name = "Hard Surfaces", - SortOrder = 2 - }, - new - { - Id = new Guid("33333333-3333-3333-3333-333333333333"), - CreatedAt = new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), - CreatedBy = "system@printbase.com", - Description = "Paper-based printing products", - IsActive = true, - ModifiedAt = new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), - ModifiedBy = "system@printbase.com", - Name = "Paper", - SortOrder = 3 - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("Amount") - .HasColumnType("decimal(18,2)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("CustomizationDescription") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("CustomizationImageUrl") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("MerchantId") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Notes") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("OrderDate") - .HasColumnType("datetime2"); - - b.Property("OrderStatusId") - .HasColumnType("int"); - - b.Property("OriginalImageUrls") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("ProductId") - .HasColumnType("uniqueidentifier"); - - b.Property("ProductVariantId") - .HasColumnType("uniqueidentifier"); - - b.Property("Quantity") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("ShippingStatusId") - .HasColumnType("int"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Order_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Order_CreatedBy"); - - b.HasIndex("MerchantId") - .HasDatabaseName("IX_Order_MerchantId"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Order_ModifiedAt"); - - b.HasIndex("OrderDate") - .HasDatabaseName("IX_Order_OrderDate"); - - b.HasIndex("OrderStatusId") - .HasDatabaseName("IX_Order_OrderStatusId"); - - b.HasIndex("ProductId") - .HasDatabaseName("IX_Order_ProductId"); - - b.HasIndex("ProductVariantId") - .HasDatabaseName("IX_Order_ProductVariantId"); - - b.HasIndex("ShippingStatusId") - .HasDatabaseName("IX_Order_ShippingStatusId"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_Order_UserId"); - - b.HasIndex("MerchantId", "OrderDate") - .HasDatabaseName("IX_Order_Merchant_Date"); - - b.HasIndex("ProductId", "OrderDate") - .HasDatabaseName("IX_Order_Product_Date"); - - b.HasIndex("UserId", "OrderDate") - .HasDatabaseName("IX_Order_User_Date"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.OrderAddress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("AddressLine1") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("AddressLine2") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("AddressType") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("ApartmentNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("BuildingNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Company") - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("FirstName") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Floor") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("Instructions") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("LastName") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); - - b.Property("PhoneNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("State") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_OrderAddress_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_OrderAddress_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_OrderAddress_ModifiedAt"); - - b.HasIndex("OrderId") - .IsUnique() - .HasDatabaseName("IX_OrderAddress_OrderId"); - - b.ToTable("OrderAddresses"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.OrderStatus", b => - { - b.Property("Id") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("IX_OrderStatus_Name"); - - b.ToTable("OrderStatuses"); - - b.HasData( - new - { - Id = 0, - Name = "Pending" - }, - new - { - Id = 1, - Name = "Processing" - }, - new - { - Id = 2, - Name = "Completed" - }, - new - { - Id = 3, - Name = "Cancelled" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("BasePrice") - .HasColumnType("decimal(18,2)"); - - b.Property("CategoryId") - .HasColumnType("uniqueidentifier"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("ImageUrl") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("IsCustomizable") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId") - .HasDatabaseName("IX_Product_CategoryId"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Product_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Product_CreatedBy"); - - b.HasIndex("IsActive") - .HasDatabaseName("IX_Product_IsActive"); - - b.HasIndex("IsCustomizable") - .HasDatabaseName("IX_Product_IsCustomizable"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Product_ModifiedAt"); - - b.HasIndex("Name") - .HasDatabaseName("IX_Product_Name"); - - b.HasIndex("CategoryId", "IsActive") - .HasDatabaseName("IX_Product_Category_Active"); - - b.HasIndex("IsActive", "IsCustomizable") - .HasDatabaseName("IX_Product_Active_Customizable"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("ImageUrl") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.Property("ProductId") - .HasColumnType("uniqueidentifier"); - - b.Property("Size") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("Sku") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("StockQuantity") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(0); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_ProductVariant_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_ProductVariant_CreatedBy"); - - b.HasIndex("IsActive") - .HasDatabaseName("IX_ProductVariant_IsActive"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_ProductVariant_ModifiedAt"); - - b.HasIndex("ProductId") - .HasDatabaseName("IX_ProductVariant_ProductId"); - - b.HasIndex("Sku") - .IsUnique() - .HasDatabaseName("IX_ProductVariant_SKU"); - - b.HasIndex("ProductId", "Size", "Color") - .IsUnique() - .HasDatabaseName("IX_ProductVariant_Product_Size_Color") - .HasFilter("[Color] IS NOT NULL"); - - b.ToTable("ProductVariants"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Role", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.Property("RoleName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("RoleName") - .IsUnique() - .HasDatabaseName("IX_Role_RoleName"); - - b.ToTable("Roles"); - - b.HasData( - new - { - Id = new Guid("22222222-2222-2222-2222-222222222222"), - RoleName = "Merchant" - }, - new - { - Id = new Guid("33333333-3333-3333-3333-333333333333"), - RoleName = "Admin" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.ShippingStatus", b => - { - b.Property("Id") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("IX_ShippingStatus_Name"); - - b.ToTable("ShippingStatuses"); - - b.HasData( - new - { - Id = 0, - Name = "Prepping" - }, - new - { - Id = 1, - Name = "Packaging" - }, - new - { - Id = 2, - Name = "Shipped" - }, - new - { - Id = 3, - Name = "Delivered" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.User", b => - { - b.Property("Id") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailVerified") - .HasColumnType("bit"); - - b.Property("FirstName") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("LastName") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Nickname") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("PhoneNumber") - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.HasKey("Id"); - - b.HasIndex("Email") - .IsUnique() - .HasDatabaseName("IX_User_Email"); - - b.HasIndex("IsActive") - .HasDatabaseName("IX_User_IsActive"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.UserRole", b => - { - b.Property("UserId") - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId") - .HasDatabaseName("IX_UserRole_RoleId"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_UserRole_UserId"); - - b.ToTable("UserRole"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Address", b => - { - b.HasOne("Imprink.Domain.Entities.User", "User") - .WithMany("Addresses") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Category", b => - { - b.HasOne("Imprink.Domain.Entities.Category", "ParentCategory") - .WithMany("SubCategories") - .HasForeignKey("ParentCategoryId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("ParentCategory"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Order", b => - { - b.HasOne("Imprink.Domain.Entities.User", "Merchant") - .WithMany("MerchantOrders") - .HasForeignKey("MerchantId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Imprink.Domain.Entities.OrderStatus", "OrderStatus") - .WithMany("Orders") - .HasForeignKey("OrderStatusId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Product", "Product") - .WithMany("Orders") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.ProductVariant", "ProductVariant") - .WithMany("Orders") - .HasForeignKey("ProductVariantId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("Imprink.Domain.Entities.ShippingStatus", "ShippingStatus") - .WithMany("Orders") - .HasForeignKey("ShippingStatusId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.User", "User") - .WithMany("Orders") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Merchant"); - - b.Navigation("OrderStatus"); - - b.Navigation("Product"); - - b.Navigation("ProductVariant"); - - b.Navigation("ShippingStatus"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.OrderAddress", b => - { - b.HasOne("Imprink.Domain.Entities.Order", "Order") - .WithOne("OrderAddress") - .HasForeignKey("Imprink.Domain.Entities.OrderAddress", "OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Order"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product", b => - { - b.HasOne("Imprink.Domain.Entities.Category", "Category") - .WithMany("Products") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.SetNull); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => - { - b.HasOne("Imprink.Domain.Entities.Product", "Product") - .WithMany("ProductVariants") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Product"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.UserRole", b => - { - b.HasOne("Imprink.Domain.Entities.Role", "Role") - .WithMany("UserRoles") - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.User", "User") - .WithMany("UserRoles") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Role"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Category", b => - { - b.Navigation("Products"); - - b.Navigation("SubCategories"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Order", b => - { - b.Navigation("OrderAddress") - .IsRequired(); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.OrderStatus", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product", b => - { - b.Navigation("Orders"); - - b.Navigation("ProductVariants"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Role", b => - { - b.Navigation("UserRoles"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.ShippingStatus", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.User", b => - { - b.Navigation("Addresses"); - - b.Navigation("MerchantOrders"); - - b.Navigation("Orders"); - - b.Navigation("UserRoles"); - }); -#pragma warning restore 612, 618 - } - } -} From 0f5ac084705570aeee0775857c5654c546628a54 Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Mon, 30 Jun 2025 00:53:30 +0300 Subject: [PATCH 6/7] Cleanup startup --- src/Imprink.Application/Dtos/{ => Orders}/OrderAddressDto.cs | 0 src/Imprink.Application/Dtos/{ => Orders}/OrderDto.cs | 0 src/Imprink.Application/Dtos/{ => Orders}/OrderStatusDto.cs | 0 src/Imprink.Application/Dtos/{ => Orders}/ShippingStatusDto.cs | 0 src/Imprink.Application/Dtos/{ => Products}/CategoryDto.cs | 0 src/Imprink.Application/Dtos/{ => Products}/ProductDto.cs | 0 src/Imprink.Application/Dtos/{ => Products}/ProductVariantDto.cs | 0 src/Imprink.Application/Dtos/{ => Users}/AddressDto.cs | 0 src/Imprink.Application/Dtos/{ => Users}/RoleDto.cs | 0 src/Imprink.Application/Dtos/{ => Users}/UserDto.cs | 0 src/Imprink.Application/Dtos/{ => Users}/UserRoleDto.cs | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename src/Imprink.Application/Dtos/{ => Orders}/OrderAddressDto.cs (100%) rename src/Imprink.Application/Dtos/{ => Orders}/OrderDto.cs (100%) rename src/Imprink.Application/Dtos/{ => Orders}/OrderStatusDto.cs (100%) rename src/Imprink.Application/Dtos/{ => Orders}/ShippingStatusDto.cs (100%) rename src/Imprink.Application/Dtos/{ => Products}/CategoryDto.cs (100%) rename src/Imprink.Application/Dtos/{ => Products}/ProductDto.cs (100%) rename src/Imprink.Application/Dtos/{ => Products}/ProductVariantDto.cs (100%) rename src/Imprink.Application/Dtos/{ => Users}/AddressDto.cs (100%) rename src/Imprink.Application/Dtos/{ => Users}/RoleDto.cs (100%) rename src/Imprink.Application/Dtos/{ => Users}/UserDto.cs (100%) rename src/Imprink.Application/Dtos/{ => Users}/UserRoleDto.cs (100%) diff --git a/src/Imprink.Application/Dtos/OrderAddressDto.cs b/src/Imprink.Application/Dtos/Orders/OrderAddressDto.cs similarity index 100% rename from src/Imprink.Application/Dtos/OrderAddressDto.cs rename to src/Imprink.Application/Dtos/Orders/OrderAddressDto.cs diff --git a/src/Imprink.Application/Dtos/OrderDto.cs b/src/Imprink.Application/Dtos/Orders/OrderDto.cs similarity index 100% rename from src/Imprink.Application/Dtos/OrderDto.cs rename to src/Imprink.Application/Dtos/Orders/OrderDto.cs diff --git a/src/Imprink.Application/Dtos/OrderStatusDto.cs b/src/Imprink.Application/Dtos/Orders/OrderStatusDto.cs similarity index 100% rename from src/Imprink.Application/Dtos/OrderStatusDto.cs rename to src/Imprink.Application/Dtos/Orders/OrderStatusDto.cs diff --git a/src/Imprink.Application/Dtos/ShippingStatusDto.cs b/src/Imprink.Application/Dtos/Orders/ShippingStatusDto.cs similarity index 100% rename from src/Imprink.Application/Dtos/ShippingStatusDto.cs rename to src/Imprink.Application/Dtos/Orders/ShippingStatusDto.cs diff --git a/src/Imprink.Application/Dtos/CategoryDto.cs b/src/Imprink.Application/Dtos/Products/CategoryDto.cs similarity index 100% rename from src/Imprink.Application/Dtos/CategoryDto.cs rename to src/Imprink.Application/Dtos/Products/CategoryDto.cs diff --git a/src/Imprink.Application/Dtos/ProductDto.cs b/src/Imprink.Application/Dtos/Products/ProductDto.cs similarity index 100% rename from src/Imprink.Application/Dtos/ProductDto.cs rename to src/Imprink.Application/Dtos/Products/ProductDto.cs diff --git a/src/Imprink.Application/Dtos/ProductVariantDto.cs b/src/Imprink.Application/Dtos/Products/ProductVariantDto.cs similarity index 100% rename from src/Imprink.Application/Dtos/ProductVariantDto.cs rename to src/Imprink.Application/Dtos/Products/ProductVariantDto.cs diff --git a/src/Imprink.Application/Dtos/AddressDto.cs b/src/Imprink.Application/Dtos/Users/AddressDto.cs similarity index 100% rename from src/Imprink.Application/Dtos/AddressDto.cs rename to src/Imprink.Application/Dtos/Users/AddressDto.cs diff --git a/src/Imprink.Application/Dtos/RoleDto.cs b/src/Imprink.Application/Dtos/Users/RoleDto.cs similarity index 100% rename from src/Imprink.Application/Dtos/RoleDto.cs rename to src/Imprink.Application/Dtos/Users/RoleDto.cs diff --git a/src/Imprink.Application/Dtos/UserDto.cs b/src/Imprink.Application/Dtos/Users/UserDto.cs similarity index 100% rename from src/Imprink.Application/Dtos/UserDto.cs rename to src/Imprink.Application/Dtos/Users/UserDto.cs diff --git a/src/Imprink.Application/Dtos/UserRoleDto.cs b/src/Imprink.Application/Dtos/Users/UserRoleDto.cs similarity index 100% rename from src/Imprink.Application/Dtos/UserRoleDto.cs rename to src/Imprink.Application/Dtos/Users/UserRoleDto.cs From 30705638c281fd679c534f16bb1fedbd6d529d3e Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Mon, 30 Jun 2025 01:52:49 +0300 Subject: [PATCH 7/7] Orders page --- .../Dtos/Orders/StatusDto.cs | 6 + ui/src/app/orders/page.tsx | 615 ++++++++++++++++++ 2 files changed, 621 insertions(+) create mode 100644 src/Imprink.Application/Dtos/Orders/StatusDto.cs create mode 100644 ui/src/app/orders/page.tsx diff --git a/src/Imprink.Application/Dtos/Orders/StatusDto.cs b/src/Imprink.Application/Dtos/Orders/StatusDto.cs new file mode 100644 index 0000000..500501c --- /dev/null +++ b/src/Imprink.Application/Dtos/Orders/StatusDto.cs @@ -0,0 +1,6 @@ +namespace Imprink.Application.Dtos; + +public class StatusDto +{ + +} \ No newline at end of file diff --git a/ui/src/app/orders/page.tsx b/ui/src/app/orders/page.tsx new file mode 100644 index 0000000..190fb58 --- /dev/null +++ b/ui/src/app/orders/page.tsx @@ -0,0 +1,615 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { + Box, + Container, + Typography, + Card, + CardContent, + Grid, + Chip, + Avatar, + Skeleton, + Alert, + Paper, + Divider, + Dialog, + DialogContent, + IconButton, + Fade, + Collapse, + Button, +} from '@mui/material'; +import { + ShoppingBag, + LocalShipping, + Close, + ZoomIn, + ExpandMore, + ExpandLess, +} from '@mui/icons-material'; +import clientApi from '@/lib/clientApi'; + +interface OrderStatus { + id: number; + name: string; + description: string; +} + +interface ShippingStatus { + id: number; + name: string; + description: string; +} + +interface Category { + id: string; + name: string; + description: string; + imageUrl: string; + sortOrder: number; + isActive: boolean; + parentCategoryId: string; + createdAt: string; + modifiedAt: string; +} + +interface Product { + id: string; + name: string; + description: string; + basePrice: number; + isCustomizable: boolean; + isActive: boolean; + imageUrl: string; + categoryId: string; + category: Category; + createdAt: string; + modifiedAt: string; +} + +interface ProductVariant { + id: string; + productId: string; + size: string; + color: string; + price: number; + imageUrl: string; + sku: string; + stockQuantity: number; + isActive: boolean; + product: Product; + createdAt: string; + modifiedAt: string; +} + +interface OrderAddress { + id: string; + orderId: string; + addressType: string; + firstName: string; + lastName: string; + company: string; + addressLine1: string; + addressLine2: string; + apartmentNumber: string; + buildingNumber: string; + floor: string; + city: string; + state: string; + postalCode: string; + country: string; + phoneNumber: string; + instructions: string; + createdAt: string; + updatedAt: string; +} + +interface Order { + id: string; + userId: string; + orderDate: string; + amount: number; + quantity: number; + productId: string; + productVariantId: string; + orderStatusId: number; + shippingStatusId: number; + notes: string; + merchantId: string; + customizationImageUrl: string; + originalImageUrls: string[]; + customizationDescription: string; + createdAt: string; + updatedAt: string; + orderStatus: OrderStatus; + shippingStatus: ShippingStatus; + orderAddress: OrderAddress; + product: Product; + productVariant: ProductVariant; +} + +const getStatusColor = (statusName: string) => { + const normalizedStatus = statusName.toLowerCase(); + if (normalizedStatus.includes('pending') || normalizedStatus.includes('processing')) { + return 'warning'; + } + if (normalizedStatus.includes('completed') || normalizedStatus.includes('delivered')) { + return 'success'; + } + if (normalizedStatus.includes('cancelled') || normalizedStatus.includes('failed')) { + return 'error'; + } + return 'default'; +}; + +const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + }).format(amount); +}; + +const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); +}; + +export default function OrdersPage() { + const [orders, setOrders] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [selectedImage, setSelectedImage] = useState(null); + const [expandedOrders, setExpandedOrders] = useState>(new Set()); + + useEffect(() => { + const fetchOrders = async () => { + try { + setLoading(true); + const response = await clientApi.get('/orders/user/me?includeDetails=true'); + console.log("Data", response.data); + setOrders(response.data); + } catch (err: any) { + setError(err.response?.data?.message || err.message || 'Failed to fetch orders'); + } finally { + setLoading(false); + } + }; + + fetchOrders(); + }, []); + + const handleImageClick = (imageUrl: string) => { + setSelectedImage(imageUrl); + }; + + const handleCloseImage = () => { + setSelectedImage(null); + }; + + const toggleOrderExpansion = (orderId: string) => { + const newExpanded = new Set(expandedOrders); + if (newExpanded.has(orderId)) { + newExpanded.delete(orderId); + } else { + newExpanded.add(orderId); + } + setExpandedOrders(newExpanded); + }; + + if (loading) { + return ( + + + My Orders + + + {[...Array(3)].map((_, index) => ( + + + + + + + + + + + + + + + + + + + + ))} + + + ); + } + + if (error) { + return ( + + + {error} + + + ); + } + + if (orders.length === 0) { + return ( + + + My Orders + + + + + No orders found + + + Your orders will appear here once you make a purchase. + + + + ); + } + + return ( + + + My Orders + + + + {orders.map((order) => { + const isExpanded = expandedOrders.has(order.id); + + return ( + + + toggleOrderExpansion(order.id)} + > + + + + + Order #{order.id.slice(-8).toUpperCase()} + + + {formatDate(order.orderDate)} + + + + + } + label={order.orderStatus.name} + color={getStatusColor(order.orderStatus.name) as any} + size="small" + /> + } + label={order.shippingStatus.name} + color={getStatusColor(order.shippingStatus.name) as any} + size="small" + variant="outlined" + /> + + + + + + + {formatCurrency(order.amount)} + + + Qty: {order.quantity} + + + + + {isExpanded ? : } + + + + + + + + + + + + + + {order.product.name} + + {order.product.description} + + + Variant: {order.productVariant.size} - {order.productVariant.color} + + + SKU: {order.productVariant.sku} + + {order.customizationDescription && ( + + Customization: {order.customizationDescription} + + )} + + + + + + + Shipping Address + + + {order.orderAddress.firstName} {order.orderAddress.lastName} +
+ {order.orderAddress.company && ( + <> + {order.orderAddress.company} +
+ + )} + {order.orderAddress.addressLine1} +
+ {order.orderAddress.addressLine2 && ( + <> + {order.orderAddress.addressLine2} +
+ + )} + {order.orderAddress.city}, {order.orderAddress.state} {order.orderAddress.postalCode} +
+ {order.orderAddress.country} +
+
+ + {(order.productVariant.imageUrl || order.customizationImageUrl || order.originalImageUrls.length > 0) && ( + + + + Images + + + + {order.productVariant.imageUrl && ( + + + Product Variant + + handleImageClick(order.productVariant.imageUrl)} + > + + + + + + + )} + + {order.customizationImageUrl && ( + + + Customized + + handleImageClick(order.customizationImageUrl)} + > + + + + + + + )} + + {order.originalImageUrls.length > 0 && ( + + + Original Images ({order.originalImageUrls.length}) + + + {order.originalImageUrls.slice(0, 3).map((imageUrl, index) => ( + handleImageClick(imageUrl)} + > + + {index === 2 && order.originalImageUrls.length > 3 && ( + + +{order.originalImageUrls.length - 3} + + )} + + + + + ))} + + + )} + + + )} + + {order.notes && ( + + + + Notes + + + {order.notes} + + + )} +
+
+
+
+
+
+ ); + })} +
+ + + + + + + {selectedImage && ( + + + + )} + + +
+ ); +} \ No newline at end of file