commit
This commit is contained in:
parent
98aa111734
commit
c91f5b0f65
237
package-lock.json
generated
237
package-lock.json
generated
@ -10,15 +10,19 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"flowbite": "^2.5.1",
|
"flowbite": "^2.5.1",
|
||||||
"flowbite-svelte": "^0.46.16",
|
"flowbite-svelte": "^0.46.16",
|
||||||
"flowbite-svelte-icons": "^1.6.1"
|
"flowbite-svelte-icons": "^1.6.1",
|
||||||
|
"motion": "^11.17.0",
|
||||||
|
"pocketbase": "^0.25.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.28.1",
|
"@playwright/test": "^1.28.1",
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
|
"@sveltejs/adapter-node": "^5.2.11",
|
||||||
"@sveltejs/kit": "^2.5.27",
|
"@sveltejs/kit": "^2.5.27",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||||
"@tailwindcss/typography": "^0.5.14",
|
"@tailwindcss/typography": "^0.5.14",
|
||||||
"@types/eslint": "^9.6.0",
|
"@types/eslint": "^9.6.0",
|
||||||
|
"@types/node": "^22.10.5",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
@ -799,6 +803,92 @@
|
|||||||
"url": "https://opencollective.com/popperjs"
|
"url": "https://opencollective.com/popperjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/plugin-commonjs": {
|
||||||
|
"version": "28.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.2.tgz",
|
||||||
|
"integrity": "sha512-BEFI2EDqzl+vA1rl97IDRZ61AIwGH093d9nz8+dThxJNH8oSoB7MjWvPCX3dkaK1/RCJ/1v/R1XB15FuSs0fQw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/pluginutils": "^5.0.1",
|
||||||
|
"commondir": "^1.0.1",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
|
"fdir": "^6.2.0",
|
||||||
|
"is-reference": "1.2.1",
|
||||||
|
"magic-string": "^0.30.3",
|
||||||
|
"picomatch": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0 || 14 >= 14.17"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^2.68.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-commonjs/node_modules/fdir": {
|
||||||
|
"version": "6.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz",
|
||||||
|
"integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"picomatch": "^3 || ^4"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"picomatch": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-commonjs/node_modules/is-reference": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-commonjs/node_modules/picomatch": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-json": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/pluginutils": "^5.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/plugin-node-resolve": {
|
"node_modules/@rollup/plugin-node-resolve": {
|
||||||
"version": "15.2.3",
|
"version": "15.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
|
||||||
@ -1083,6 +1173,47 @@
|
|||||||
"@sveltejs/kit": "^2.0.0"
|
"@sveltejs/kit": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@sveltejs/adapter-node": {
|
||||||
|
"version": "5.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.11.tgz",
|
||||||
|
"integrity": "sha512-lR7/dfUaKFf3aI408KRDy/BVDYoqUws7zNOJz2Hl4JoshlTnMgdha3brXBRFXB+cWtYvJjjPhvmq3xqpbioi4w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^28.0.1",
|
||||||
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||||
|
"rollup": "^4.9.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@sveltejs/kit": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sveltejs/adapter-node/node_modules/@rollup/plugin-node-resolve": {
|
||||||
|
"version": "16.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.0.tgz",
|
||||||
|
"integrity": "sha512-0FPvAeVUT/zdWoO0jnb/V5BlBsUSNfkIOtFHzMO4H9MOklrmQFY6FduVHKucNb/aTFxvnGhj4MNj/T1oNdDfNg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/pluginutils": "^5.0.1",
|
||||||
|
"@types/resolve": "1.20.2",
|
||||||
|
"deepmerge": "^4.2.2",
|
||||||
|
"is-module": "^1.0.0",
|
||||||
|
"resolve": "^1.22.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^2.78.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sveltejs/kit": {
|
"node_modules/@sveltejs/kit": {
|
||||||
"version": "2.5.28",
|
"version": "2.5.28",
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.28.tgz",
|
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.28.tgz",
|
||||||
@ -1203,6 +1334,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "22.10.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
|
||||||
|
"integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/resolve": {
|
"node_modules/@types/resolve": {
|
||||||
"version": "1.20.2",
|
"version": "1.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||||
@ -1996,6 +2137,13 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/commondir": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@ -2639,6 +2787,33 @@
|
|||||||
"url": "https://github.com/sponsors/rawify"
|
"url": "https://github.com/sponsors/rawify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "11.17.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.17.0.tgz",
|
||||||
|
"integrity": "sha512-uTNLH9JPMD3ad14WBt3KYRTR+If4tGPLgKTKTIIPaEBMkvazs6EkWNcmCh65qA/tyinOqIbQiuCorXX0qQsNoQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^11.16.4",
|
||||||
|
"motion-utils": "^11.16.0",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
@ -3179,6 +3354,47 @@
|
|||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/motion": {
|
||||||
|
"version": "11.17.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion/-/motion-11.17.0.tgz",
|
||||||
|
"integrity": "sha512-mWZhIOWH2slNXPUWhr6cEu98bl9NMX7u9r7vdNI+Bm3/jrOEa3e44GmyUuwXr9hWR+rWII27YTnKb6CDD1vU2g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"framer-motion": "^11.17.0",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "11.16.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.16.4.tgz",
|
||||||
|
"integrity": "sha512-2wuCie206pCiP2K23uvwJeci4pMFfyQKpWI0Vy6HrCTDzDCer4TsYtT7IVnuGbDeoIV37UuZiUr6SZMHEc1Vww==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^11.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "11.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.16.0.tgz",
|
||||||
|
"integrity": "sha512-ngdWPjg31rD4WGXFi0eZ00DQQqKKu04QExyv/ymlC+3k+WIgYVFbt6gS5JsFPbJODTF/r8XiE/X+SsoT9c0ocw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/mri": {
|
"node_modules/mri": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||||
@ -3481,6 +3697,12 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pocketbase": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-xbjiQG/tnh2HsjZrTW7ZEJASvl4hmGAB5PQAmNRkRU8BmrPib7zwKyXdiYJl34QN7ADpqykZD2lAMdDtrrQbuw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.47",
|
"version": "8.4.47",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||||
@ -4696,6 +4918,12 @@
|
|||||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
@ -4747,6 +4975,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "6.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||||
|
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
|
||||||
|
@ -17,10 +17,12 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.28.1",
|
"@playwright/test": "^1.28.1",
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
|
"@sveltejs/adapter-node": "^5.2.11",
|
||||||
"@sveltejs/kit": "^2.5.27",
|
"@sveltejs/kit": "^2.5.27",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||||
"@tailwindcss/typography": "^0.5.14",
|
"@tailwindcss/typography": "^0.5.14",
|
||||||
"@types/eslint": "^9.6.0",
|
"@types/eslint": "^9.6.0",
|
||||||
|
"@types/node": "^22.10.5",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
@ -41,6 +43,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"flowbite": "^2.5.1",
|
"flowbite": "^2.5.1",
|
||||||
"flowbite-svelte": "^0.46.16",
|
"flowbite-svelte": "^0.46.16",
|
||||||
"flowbite-svelte-icons": "^1.6.1"
|
"flowbite-svelte-icons": "^1.6.1",
|
||||||
|
"motion": "^11.17.0",
|
||||||
|
"pocketbase": "^0.25.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
@import 'tailwindcss/base';
|
@import 'tailwindcss/base';
|
||||||
@import 'tailwindcss/components';
|
@import 'tailwindcss/components';
|
||||||
@import 'tailwindcss/utilities';
|
@import 'tailwindcss/utilities';
|
||||||
|
body {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100svh;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
@ -9,34 +9,38 @@
|
|||||||
Input,
|
Input,
|
||||||
Avatar
|
Avatar
|
||||||
} from 'flowbite-svelte';
|
} from 'flowbite-svelte';
|
||||||
import { CartOutline, SearchOutline } from 'flowbite-svelte-icons';
|
import { CartOutline, SearchOutline, StarOutline } from 'flowbite-svelte-icons';
|
||||||
|
import ShoppingCart from './ShoppingCart.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Navbar>
|
<Navbar class="sticky top-0 z-50 bg-white shadow-sm">
|
||||||
<NavBrand href="/">
|
<NavBrand href="/">
|
||||||
<img
|
<img
|
||||||
src="https://images.unsplash.com/photo-1520763185298-1b434c919102?q=80&w=1932&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
src="https://www.edvring.info/dam/jcr:334766c1-59b9-40ed-b5fa-727d549d4e88/edvLogo.svg"
|
||||||
class="me-3 h-6 sm:h-9"
|
class="me-3 h-7 sm:h-9"
|
||||||
alt="Flowbite Logo"
|
alt="Flowbite Logo"
|
||||||
/>
|
/>
|
||||||
<span class="self-center whitespace-nowrap text-xl font-semibold dark:text-white">Webshop</span>
|
<span class="self-center whitespace-nowrap text-xl font-semibold dark:text-white">Webshop</span>
|
||||||
</NavBrand>
|
</NavBrand>
|
||||||
|
|
||||||
<div class="relative hidden md:block">
|
<!-- <div class="relative hidden md:block">
|
||||||
<div class="pointer-events-none absolute inset-y-0 start-0 flex items-center ps-3">
|
<div class="pointer-events-none absolute inset-y-0 start-0 flex items-center ps-3">
|
||||||
<SearchOutline class="h-4 w-4" />
|
<SearchOutline class="h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<Input id="search-navbar" class="ps-10" placeholder="Search..." />
|
<Input id="search-navbar" class="w-96 ps-10" placeholder="Search..." />
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<Avatar
|
<Avatar
|
||||||
href="/profile"
|
href="/profile"
|
||||||
src="https://images.unsplash.com/photo-1520763185298-1b434c919102?q=80&w=1932&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
src="https://media.istockphoto.com/id/1223671392/de/vektor/standardprofilbild-avatar-fotoplatzhalter-vektor-illustration.jpg?s=612x612&w=0&k=20&c=vtYE5RcgwgrJ1Zg6r66xN25LpXS_xsxZ8NqtvRQ9w6I="
|
||||||
class="m-1 me-4"
|
class="m-1 me-4 h-10 border-2"
|
||||||
/>
|
/>
|
||||||
<Button>
|
<a href="/favourites">
|
||||||
<CartOutline size="lg"></CartOutline>
|
<Button class="me-2 h-12">
|
||||||
|
<StarOutline size="lg" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</a>
|
||||||
|
<ShoppingCart />
|
||||||
</div>
|
</div>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
@ -1,28 +1,21 @@
|
|||||||
import type { User } from './types';
|
const USER_KEY = 'user';
|
||||||
|
const PASS_KEY = 'username';
|
||||||
|
|
||||||
const key = 'user';
|
export const login = (user: string, password: string) => {
|
||||||
const key2 = 'username';
|
localStorage.setItem(USER_KEY, user);
|
||||||
|
localStorage.setItem(PASS_KEY, password);
|
||||||
|
};
|
||||||
|
|
||||||
function login(user: User) {
|
export const logout = () => {
|
||||||
localStorage.setItem(key, btoa(`${user.username}:${user.password}`));
|
localStorage.removeItem(USER_KEY);
|
||||||
localStorage.setItem(key2, user.username);
|
localStorage.removeItem(PASS_KEY);
|
||||||
}
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
function logout() {
|
export const loggedIn = () => localStorage.getItem(USER_KEY) !== null;
|
||||||
localStorage.removeItem(key);
|
|
||||||
localStorage.removeItem(key2);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loggedIn() {
|
export const name = () => localStorage.getItem(PASS_KEY) ?? '';
|
||||||
return localStorage.getItem(key) !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function name() {
|
export const auth = () => localStorage.getItem(USER_KEY);
|
||||||
return localStorage.getItem(key2) ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function auth() {
|
|
||||||
return localStorage.getItem(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { login, logout, loggedIn, name, auth };
|
export default { login, logout, loggedIn, name, auth };
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import Footer from '../../components/Footer.svelte';
|
||||||
import Navbar from '../../components/navbar.svelte';
|
import Navbar from '../../components/navbar.svelte';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Navbar></Navbar>
|
<Navbar></Navbar>
|
||||||
|
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
<Footer></Footer>
|
||||||
|
@ -1,38 +1,461 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Card, Button } from 'flowbite-svelte';
|
import { addToCart } from '$lib/stores/cartStore';
|
||||||
import { CartPlusOutline, StarOutline } from 'flowbite-svelte-icons';
|
import { removeFromFavorites, addToFavorites, appStore, favorites } from '$lib/stores/store';
|
||||||
|
import { Card, Button, Badge, Input, Select, Toast, Spinner } from 'flowbite-svelte';
|
||||||
|
import { CartPlusOutline, HeartOutline, HeartSolid, SearchOutline } from 'flowbite-svelte-icons';
|
||||||
|
import ImageModal from '../../../components/imageModal.svelte';
|
||||||
|
import { derived, writable, type Writable } from 'svelte/store';
|
||||||
|
import { fade, fly, scale } from 'svelte/transition';
|
||||||
|
import { quintOut } from 'svelte/easing';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
// Create an array to loop over
|
interface Product {
|
||||||
let cards = Array(9).fill({
|
id: number;
|
||||||
title: 'Tulpe',
|
title: string;
|
||||||
description: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit.',
|
description: string;
|
||||||
|
imageUrl: string;
|
||||||
|
price: number;
|
||||||
|
category?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Products array and state management
|
||||||
|
const products: Product[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'Tulip',
|
||||||
|
description: 'Beautiful tulips for your garden.',
|
||||||
imageUrl:
|
imageUrl:
|
||||||
'https://images.unsplash.com/photo-1520763185298-1b434c919102?q=80&w=1932&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'
|
'https://images.unsplash.com/photo-1520763185298-1b434c919102?q=80&w=1932&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
|
||||||
|
price: 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'Rose',
|
||||||
|
description: 'Elegant roses for special occasions.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1582794543139-8ac9cb0f7b11?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
|
||||||
|
price: 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: 'Sunflower',
|
||||||
|
description: 'Bright sunflowers to light up your day.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1535382985264-7ea131537c07?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTQxfHxmbG93ZXJ8ZW58MHx8MHx8fDA%3D',
|
||||||
|
price: 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: 'Orchid',
|
||||||
|
description: 'Exotic orchids for a touch of elegance.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1531217182035-78d279dcdb7f?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8T3JjaGlkfGVufDB8fDB8fHww',
|
||||||
|
price: 25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: 'Lily',
|
||||||
|
description: 'Fragrant lilies for a serene atmosphere.',
|
||||||
|
imageUrl:
|
||||||
|
'https://plus.unsplash.com/premium_photo-1676654936609-e264dd7b9dab?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTM1fHxmbG93ZXJ8ZW58MHx8MHx8fDA%3D',
|
||||||
|
price: 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
title: 'Daisy',
|
||||||
|
description: 'Cheerful daisies for a fresh look.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1496098570671-efe44b569a00?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTQ4fHxmbG93ZXJ8ZW58MHx8MHx8fDA%3D',
|
||||||
|
price: 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
title: 'Carnation',
|
||||||
|
description: 'Classic carnations for any occasion.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1587316830148-c9b01df2da38?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Njl8fGZsb3dlcnxlbnwwfHwwfHx8MA%3D%3D',
|
||||||
|
price: 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
title: 'Peony',
|
||||||
|
description: 'Lush peonies for a luxurious feel.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1579053778004-3a4d3f0fae19?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTU2fHxmbG93ZXJ8ZW58MHx8MHx8fDA%3D',
|
||||||
|
price: 22
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
title: 'Hydrangea',
|
||||||
|
description: 'Stunning hydrangeas for a bold statement.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1552409905-46aa1e84e2e8?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTY5fHxmbG93ZXJ8ZW58MHx8MHx8fDA%3D',
|
||||||
|
price: 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
title: 'Lavender',
|
||||||
|
description: 'Soothing lavender for relaxation.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1490163212432-2c8e584dc243?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MjI3fHxmbG93ZXJ8ZW58MHx8MHx8fDA%3D',
|
||||||
|
price: 14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
title: 'Iris',
|
||||||
|
description: 'Vibrant irises for a pop of color.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1583693034345-b6c1d5b2ffa6?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MjUwfHxmbG93ZXJ8ZW58MHx8MHx8fDA%3D',
|
||||||
|
price: 16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
title: 'Daffodil',
|
||||||
|
description: 'Bright daffodils to welcome spring.',
|
||||||
|
imageUrl:
|
||||||
|
'https://plus.unsplash.com/premium_photo-1676070094538-7663fb7c2745?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MXx8RGFmZm9kaWx8ZW58MHx8MHx8fDA%3D',
|
||||||
|
price: 11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
title: 'Poppy',
|
||||||
|
description: 'Vivid poppies for a bold statement.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1527703137818-60612b595c72?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MjIwfHxmbG93ZXJ8ZW58MHx8MHx8fDA%3D',
|
||||||
|
price: 13
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
title: 'Marigold',
|
||||||
|
description: 'Golden marigolds for a festive touch.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1559563362-c667ba5f5480?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MjA5fHxmbG93ZXJ8ZW58MHx8MHx8fDA%3D',
|
||||||
|
price: 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
title: 'Chrysanthemum',
|
||||||
|
description: 'Elegant chrysanthemums for autumn.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1498323094960-d1a30fae4c5c?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTgwfHxmbG93ZXJ8ZW58MHx8MHx8fDA%3D',
|
||||||
|
price: 17
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 16,
|
||||||
|
title: 'Gerbera',
|
||||||
|
description: 'Cheerful gerberas for a joyful vibe.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1478801928079-ff8b78b1bef2?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTcyfHxmbG93ZXJ8ZW58MHx8MHx8fDA%3D',
|
||||||
|
price: 19
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 17,
|
||||||
|
title: 'Anemone',
|
||||||
|
description: 'Delicate anemones for a subtle charm.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1496571330383-9b977f4a021d?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8OTR8fGZsb3dlcnxlbnwwfHwwfHx8MA%3D%3D',
|
||||||
|
price: 21
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 18,
|
||||||
|
title: 'Ranunculus',
|
||||||
|
description: 'Layered ranunculus for a romantic touch.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1578972497170-bfc780c65f65?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Nzh8fGZsb3dlcnxlbnwwfHwwfHx8MA%3D%3D',
|
||||||
|
price: 23
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 19,
|
||||||
|
title: 'Freesia',
|
||||||
|
description: 'Fragrant freesias for a sweet aroma.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1496062031456-07b8f162a322?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NjF8fGZsb3dlcnxlbnwwfHwwfHx8MA%3D%3D',
|
||||||
|
price: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 20,
|
||||||
|
title: 'Amaryllis',
|
||||||
|
description: 'Striking amaryllis for a dramatic effect.',
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1487139975590-b4f1dce9b035?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8M3x8Zmxvd2VyfGVufDB8fDB8fHww',
|
||||||
|
price: 26
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const itemsPerPage = 9;
|
||||||
|
const currentPage: Writable<number> = writable(1);
|
||||||
|
const searchQuery: Writable<string> = writable('');
|
||||||
|
const selectedCategory: Writable<string> = writable('all');
|
||||||
|
const isLoading: Writable<boolean> = writable(false);
|
||||||
|
const showToast: Writable<boolean> = writable(false);
|
||||||
|
const toastMessage: Writable<string> = writable('');
|
||||||
|
|
||||||
|
// Add loading simulation
|
||||||
|
const simulateLoading = async () => {
|
||||||
|
isLoading.set(true);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 800));
|
||||||
|
isLoading.set(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enhanced filtered products with proper typing
|
||||||
|
const filteredProducts = derived<[Writable<string>, Writable<string>], Product[]>(
|
||||||
|
[searchQuery, selectedCategory],
|
||||||
|
([$searchQuery, $selectedCategory], set) => {
|
||||||
|
simulateLoading().then(() => {
|
||||||
|
if (!$searchQuery && $selectedCategory === 'all') {
|
||||||
|
set(products);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchLower = $searchQuery.toLowerCase();
|
||||||
|
const filtered = products.filter((product) => {
|
||||||
|
if ($selectedCategory !== 'all' && product.category !== $selectedCategory) return false;
|
||||||
|
if (!searchLower) return true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
product.title.toLowerCase().includes(searchLower) ||
|
||||||
|
product.description.toLowerCase().includes(searchLower)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
set(filtered);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[] as Product[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const paginatedProducts = derived<[typeof filteredProducts, Writable<number>], Product[]>(
|
||||||
|
[filteredProducts, currentPage],
|
||||||
|
([$filteredProducts, $currentPage]) => {
|
||||||
|
const start = ($currentPage - 1) * itemsPerPage;
|
||||||
|
return $filteredProducts.slice(start, start + itemsPerPage);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalPages = derived<typeof filteredProducts, number>(
|
||||||
|
filteredProducts,
|
||||||
|
($filteredProducts) => Math.ceil($filteredProducts.length / itemsPerPage)
|
||||||
|
);
|
||||||
|
|
||||||
|
const goToPage = (page: number) => {
|
||||||
|
currentPage.set(page);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
|
let searchTimeout: NodeJS.Timeout;
|
||||||
|
const handleSearch = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
isLoading.set(true);
|
||||||
|
searchTimeout = setTimeout(() => {
|
||||||
|
searchQuery.set(target.value);
|
||||||
|
currentPage.set(1);
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCategoryChange = (event: Event) => {
|
||||||
|
const target = event.target as HTMLSelectElement;
|
||||||
|
selectedCategory.set(target.value);
|
||||||
|
currentPage.set(1);
|
||||||
|
simulateLoading();
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleFavorite = (product: Product) => {
|
||||||
|
const isFavorite = $favorites.some((item: Product) => item.id === product.id);
|
||||||
|
if (isFavorite) {
|
||||||
|
removeFromFavorites(product.id);
|
||||||
|
showToast.set(true);
|
||||||
|
toastMessage.set('Removed from favorites');
|
||||||
|
} else {
|
||||||
|
addToFavorites(product);
|
||||||
|
showToast.set(true);
|
||||||
|
toastMessage.set('Added to favorites');
|
||||||
|
}
|
||||||
|
setTimeout(() => showToast.set(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddToCart = (product: Product) => {
|
||||||
|
addToCart({ ...product, quantity: 1 });
|
||||||
|
showToast.set(true);
|
||||||
|
toastMessage.set('Added to cart');
|
||||||
|
setTimeout(() => showToast.set(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
let modalOpen = false;
|
||||||
|
let selectedImage = {
|
||||||
|
url: '',
|
||||||
|
title: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const openImageModal = (product: Product) => {
|
||||||
|
selectedImage = {
|
||||||
|
url: product.imageUrl,
|
||||||
|
title: product.title
|
||||||
|
};
|
||||||
|
modalOpen = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeImageModal = () => {
|
||||||
|
modalOpen = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Navigate to item page
|
||||||
|
const goToItemPage = (productId: number) => {
|
||||||
|
goto(`/item/${productId}`);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mx-auto my-6 grid w-fit grid-cols-3 gap-10">
|
<main class="container mx-auto px-4 py-8">
|
||||||
{#each cards as card}
|
<div class="mb-8 space-y-4" in:fly={{ y: -20, duration: 800, delay: 200 }}>
|
||||||
<Card img={card.imageUrl} class="">
|
<p class="text-lg text-gray-600 dark:text-gray-400">
|
||||||
<div class="holder flex">
|
Discover our beautiful collection of fresh flowers
|
||||||
<div class="left w-fit">
|
|
||||||
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
|
|
||||||
{card.title}
|
|
||||||
</h5>
|
|
||||||
<p class="mb-3 font-normal leading-tight text-gray-700 dark:text-gray-400">
|
|
||||||
{card.description}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right grid min-w-fit gap-4">
|
<div class="mb-6 grid gap-4 sm:grid-cols-2" in:fly={{ y: -20, duration: 600, delay: 200 }}>
|
||||||
<Button>
|
<div class="relative">
|
||||||
<CartPlusOutline class="h-6 w-6 text-white" />
|
<Input
|
||||||
</Button>
|
type="text"
|
||||||
<Button>
|
placeholder="Search products..."
|
||||||
<StarOutline class=" h-6 w-6 text-white" />
|
value={$searchQuery}
|
||||||
|
on:input={handleSearch}
|
||||||
|
>
|
||||||
|
<SearchOutline slot="left" class="h-5 w-5" />
|
||||||
|
</Input>
|
||||||
|
</div>
|
||||||
|
<Select on:change={handleCategoryChange} value={$selectedCategory}>
|
||||||
|
<option value="all">All Categories</option>
|
||||||
|
<option value="bouquets">Bouquets</option>
|
||||||
|
<option value="single">Single Flowers</option>
|
||||||
|
<option value="arrangement">Arrangements</option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if $isLoading}
|
||||||
|
<div class="flex justify-center py-12" in:fade>
|
||||||
|
<Spinner size="12" />
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
|
{#each $paginatedProducts as product, i (product.id)}
|
||||||
|
<div in:fly={{ y: 20, duration: 400, delay: i * 100 }} out:fade={{ duration: 200 }}>
|
||||||
|
<Card
|
||||||
|
padding="none"
|
||||||
|
class="h-fit overflow-hidden transition-shadow duration-300 hover:shadow-lg"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="relative aspect-[4/3] w-full overflow-hidden"
|
||||||
|
on:click={() => openImageModal(product)}
|
||||||
|
on:keydown={(e) => e.key === 'Enter' && openImageModal(product)}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={product.imageUrl}
|
||||||
|
alt={product.title}
|
||||||
|
class="absolute h-full w-full object-cover transition-transform duration-300 hover:scale-110"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="absolute right-2 top-2 rounded-full bg-white/80 p-2 transition-all duration-300 hover:scale-110 hover:bg-white"
|
||||||
|
on:click|stopPropagation={() => toggleFavorite(product)}
|
||||||
|
>
|
||||||
|
{#if $favorites.some((item: Product) => item.id === product.id)}
|
||||||
|
<div in:scale={{ duration: 200 }}>
|
||||||
|
<HeartSolid class="h-6 w-6 text-red-500" />
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div in:scale={{ duration: 200 }}>
|
||||||
|
<HeartOutline class="h-6 w-6 text-gray-500" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex h-full flex-col p-4">
|
||||||
|
<div class="flex-grow space-y-2">
|
||||||
|
<h5
|
||||||
|
on:click={() => goToItemPage(product.id)}
|
||||||
|
class="text-xl font-bold tracking-tight text-gray-900 dark:text-white"
|
||||||
|
>
|
||||||
|
{product.title}
|
||||||
|
</h5>
|
||||||
|
<p class="text-sm text-gray-700 dark:text-gray-400">
|
||||||
|
{product.description}
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<p class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
|
${product.price.toFixed(2)}
|
||||||
|
</p>
|
||||||
|
{#if $favorites.some((item: Product) => item.id === product.id)}
|
||||||
|
<Badge color="red" class="animate-pulse">Favorite</Badge>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<Button
|
||||||
|
class="w-full transition-transform duration-200 hover:scale-105"
|
||||||
|
on:click={() => handleAddToCart(product)}
|
||||||
|
>
|
||||||
|
<CartPlusOutline class="mr-2 h-5 w-5" />
|
||||||
|
Add to Cart
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if $filteredProducts.length === 0 && products.length === 0}
|
||||||
|
<div class="mt-8 text-center" in:fade>
|
||||||
|
<p class="text-lg text-gray-600">No products found matching your criteria.</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $totalPages > 1}
|
||||||
|
<div class="mt-8 flex flex-wrap justify-center gap-2" in:fly={{ y: 20, duration: 800 }}>
|
||||||
|
<Button
|
||||||
|
color="light"
|
||||||
|
disabled={$currentPage === 1}
|
||||||
|
on:click={() => goToPage($currentPage - 1)}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
{#each Array($totalPages) as _, i}
|
||||||
|
<Button
|
||||||
|
color={$currentPage === i + 1 ? 'primary' : 'light'}
|
||||||
|
class="transition-transform duration-200 hover:scale-105"
|
||||||
|
on:click={() => goToPage(i + 1)}
|
||||||
|
>
|
||||||
|
{i + 1}
|
||||||
|
</Button>
|
||||||
|
{/each}
|
||||||
|
<Button
|
||||||
|
color="light"
|
||||||
|
disabled={$currentPage === $totalPages}
|
||||||
|
on:click={() => goToPage($currentPage + 1)}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{#if $showToast}
|
||||||
|
<div
|
||||||
|
class="fixed bottom-4 right-4 z-50"
|
||||||
|
in:fly={{ x: 20, duration: 300, easing: quintOut }}
|
||||||
|
out:fade
|
||||||
|
>
|
||||||
|
<Toast>
|
||||||
|
{$toastMessage}
|
||||||
|
</Toast>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<ImageModal
|
||||||
|
bind:open={modalOpen}
|
||||||
|
imageUrl={selectedImage.url}
|
||||||
|
title={selectedImage.title}
|
||||||
|
onClose={closeImageModal}
|
||||||
|
/>
|
||||||
|
@ -1,23 +1,115 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Button, Card, Input } from 'flowbite-svelte';
|
import { favorites } from '$lib/stores/store';
|
||||||
|
import { Card, Button, Badge, Input, Textarea } from 'flowbite-svelte';
|
||||||
|
import { StarSolid, CheckCircleOutline, PenOutline } from 'flowbite-svelte-icons';
|
||||||
|
|
||||||
|
// Example user data
|
||||||
|
const user = {
|
||||||
|
name: 'John Doe',
|
||||||
|
email: 'john.doe@example.com',
|
||||||
|
address: '123 Main St, City, Country'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example order history
|
||||||
|
const orders = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
date: '2023-10-01',
|
||||||
|
items: ['Tulip Bouquet', 'Rose Bouquet'],
|
||||||
|
total: 60,
|
||||||
|
status: 'Delivered'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
date: '2023-09-25',
|
||||||
|
items: ['Sunflower Bouquet'],
|
||||||
|
total: 30,
|
||||||
|
status: 'Shipped'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// State for editing profile
|
||||||
|
let isEditing = false;
|
||||||
|
let name = user.name;
|
||||||
|
let email = user.email;
|
||||||
|
let address = user.address;
|
||||||
|
|
||||||
|
// Handle profile update
|
||||||
|
const updateProfile = () => {
|
||||||
|
user.name = name;
|
||||||
|
user.email = email;
|
||||||
|
user.address = address;
|
||||||
|
isEditing = false;
|
||||||
|
// You could add an API call here to update the user's profile
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-[90svh] flex-wrap content-center">
|
<!-- Welcome Section -->
|
||||||
<Card class="mx-auto max-h-fit">
|
<section class="bg-gradient-to-r from-pink-100 to-purple-100 py-12">
|
||||||
<form action="">
|
<div class="container mx-auto px-4">
|
||||||
<div class="formItem">
|
<h1 class="text-4xl font-bold text-gray-900">Welcome, {user.name}!</h1>
|
||||||
<label for="">Company Name:</label>
|
<p class="text-lg text-gray-600">Manage your orders, favorites, and account settings here.</p>
|
||||||
<Input type="text"></Input>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="formItem">
|
</section>
|
||||||
<label for="">Email:</label>
|
|
||||||
<Input type="email"></Input>
|
<!-- Dashboard Content -->
|
||||||
|
<div class="container mx-auto grid grid-cols-1 gap-8 px-4 py-8 lg:grid-cols-3">
|
||||||
|
<!-- Order History -->
|
||||||
|
<div class="lg:col-span-2">
|
||||||
|
<h2 class="mb-6 text-2xl font-bold text-gray-900">Order History</h2>
|
||||||
|
<div class="space-y-4">
|
||||||
|
{#each orders as order}
|
||||||
|
<Card class="p-6">
|
||||||
|
<div class="mb-4 flex items-center justify-between">
|
||||||
|
<h3 class="text-xl font-bold text-gray-900">Order #{order.id}</h3>
|
||||||
|
<Badge color={order.status === 'Delivered' ? 'green' : 'blue'}>
|
||||||
|
{order.status}
|
||||||
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div class="formItem">
|
<p class="mb-2 text-gray-600">
|
||||||
<label for="">GLN:</label>
|
<strong>Date:</strong>
|
||||||
<Input type="number"></Input>
|
{order.date}
|
||||||
</div>
|
</p>
|
||||||
<Button>Submit</Button>
|
<p class="mb-2 text-gray-600">
|
||||||
</form>
|
<strong>Items:</strong>
|
||||||
|
{order.items.join(', ')}
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-600">
|
||||||
|
<strong>Total:</strong> ${order.total.toFixed(2)}
|
||||||
|
</p>
|
||||||
</Card>
|
</Card>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Account Settings -->
|
||||||
|
<div class="space-y-8">
|
||||||
|
<!-- Account Settings -->
|
||||||
|
<div>
|
||||||
|
<h2 class="mb-6 text-2xl font-bold text-gray-900">Account Settings</h2>
|
||||||
|
<Card class="p-6">
|
||||||
|
{#if isEditing}
|
||||||
|
<form on:submit|preventDefault={updateProfile} class="space-y-4">
|
||||||
|
<Input label="Name" bind:value={name} required />
|
||||||
|
<Input label="Email" type="email" bind:value={email} required />
|
||||||
|
<Textarea label="Address" bind:value={address} required />
|
||||||
|
<div class="flex justify-end space-x-4">
|
||||||
|
<Button color="red" on:click={() => (isEditing = false)}>Cancel</Button>
|
||||||
|
<Button type="submit">Save</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{:else}
|
||||||
|
<div class="space-y-4">
|
||||||
|
<p class="text-gray-600"><strong>Name:</strong> {user.name}</p>
|
||||||
|
<p class="text-gray-600"><strong>Email:</strong> {user.email}</p>
|
||||||
|
<p class="text-gray-600"><strong>Address:</strong> {user.address}</p>
|
||||||
|
<Button on:click={() => (isEditing = true)}>
|
||||||
|
<PenOutline class="mr-2 h-5 w-5" />
|
||||||
|
Edit Profile
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
import { fade, slide } from 'svelte/transition';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{@render children?.()}
|
<div transition:slide>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
|
@ -2,6 +2,6 @@ import session from '$lib/session.svelte';
|
|||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
export async function load() {
|
export async function load() {
|
||||||
const page = session.loggedIn() ? '/main' : '/login';
|
const page = session.loggedIn() ? '/landing' : '/login';
|
||||||
redirect(307, page);
|
redirect(307, page);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Card, Button, Label, Input, Checkbox } from 'flowbite-svelte';
|
import { Card, Button, Label, Input, Checkbox } from 'flowbite-svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import sessionSvelte from '$lib/session.svelte';
|
||||||
|
|
||||||
let username: string = '';
|
let username: string = '';
|
||||||
let password: string = '';
|
let password: string = '';
|
||||||
@ -13,7 +14,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
loading = true;
|
loading = true;
|
||||||
// success = await API.login({ username, password });
|
sessionSvelte.login(username, password);
|
||||||
|
success = true;
|
||||||
loading = false;
|
loading = false;
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -25,7 +27,7 @@
|
|||||||
<div class="flex h-[100svh] flex-wrap content-center">
|
<div class="flex h-[100svh] flex-wrap content-center">
|
||||||
<Card class="mx-auto max-h-fit">
|
<Card class="mx-auto max-h-fit">
|
||||||
<form class="flex flex-col space-y-6" action="/">
|
<form class="flex flex-col space-y-6" action="/">
|
||||||
<h3 class="text-xl font-medium text-gray-900 dark:text-white">Sign in to our platform</h3>
|
<h3 class="text-xl font-medium text-gray-900 dark:text-white">Sign in</h3>
|
||||||
<Label class="space-y-2">
|
<Label class="space-y-2">
|
||||||
<span>Email</span>
|
<span>Email</span>
|
||||||
<Input
|
<Input
|
||||||
@ -48,12 +50,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<Button type="submit" class="w-full" on:click={login}>Login to your account</Button>
|
<Button type="submit" class="w-full" on:click={login}>Login to your account</Button>
|
||||||
<span class="self-center text-red-500" class:invisible={success}>Login failed</span>
|
<span class="self-center text-red-500" class:invisible={success}>Login failed</span>
|
||||||
|
|
||||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-300">
|
|
||||||
Not registered? <a href="/" class="text-primary-700 hover:underline dark:text-primary-500">
|
|
||||||
Create account
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,6 @@ import { redirect } from '@sveltejs/kit';
|
|||||||
|
|
||||||
export async function load() {
|
export async function load() {
|
||||||
if (session.loggedIn()) {
|
if (session.loggedIn()) {
|
||||||
redirect(307, '/');
|
redirect(307, '/landing');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import adapter from '@sveltejs/adapter-auto';
|
import adapter from '@sveltejs/adapter-node';
|
||||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
@ -13,16 +13,16 @@ export default {
|
|||||||
colors: {
|
colors: {
|
||||||
// flowbite-svelte
|
// flowbite-svelte
|
||||||
primary: {
|
primary: {
|
||||||
50: '#FFF5F2',
|
50: '#f2fbf2',
|
||||||
100: '#FFF1EE',
|
100: '#e6f7e6',
|
||||||
200: '#FFE4DE',
|
200: '#c0eac0',
|
||||||
300: '#FFD5CC',
|
300: '#99dc99',
|
||||||
400: '#FFBCAD',
|
400: '#66c566',
|
||||||
500: '#FE795D',
|
500: '#266d26' /* Base color */,
|
||||||
600: '#EF562F',
|
600: '#205b20',
|
||||||
700: '#EB4F27',
|
700: '#1a491a',
|
||||||
800: '#CC4522',
|
800: '#143714',
|
||||||
900: '#A5371B'
|
900: '#0e260e'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user