Add Zipline as service #5
31
README.md
31
README.md
@@ -1,26 +1,22 @@
|
|||||||
### `.env Example`
|
### `.env Example`
|
||||||
|
|
||||||
```env
|
```env
|
||||||
# SQL Server Configuration
|
SQL_SERVER=sqlserver
|
||||||
SQL_SERVER=localhost
|
SQL_DATABASE=ExampleDB
|
||||||
SQL_DATABASE=ExampleDatabase
|
|
||||||
SQL_USER_ID=admin
|
SQL_USER_ID=admin
|
||||||
SQL_PASSWORD=ExamplePassword123!
|
SQL_PASSWORD=SuperSecure(!)Pass123
|
||||||
|
|
||||||
# Auth0 Configuration
|
AUTH0_SECRET=1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd
|
||||||
AUTH0_SECRET=0000000000000000000000000000000000000000000000000000000000000000
|
AUTH0_ISSUER_BASE_URL=auth.example.com
|
||||||
AUTH0_ISSUER_BASE_URL=https://auth.example.com
|
AUTH0_CLIENT_ID=abcDEF123456xyz7890
|
||||||
AUTH0_CLIENT_ID=EXAMPLE_CLIENT_ID_12345
|
AUTH0_CLIENT_SECRET=SecretClientKeyHereThatLooksRealButIsFake1234567890
|
||||||
AUTH0_CLIENT_SECRET=EXAMPLE_CLIENT_SECRET_ABCDE12345
|
AUTH0_AUDIENCE=https://example.com
|
||||||
AUTH0_AUDIENCE=https://api.example.com
|
|
||||||
AUTH0_SCOPE=openid profile email
|
AUTH0_SCOPE=openid profile email
|
||||||
AUTH0_DOMAIN=https://example.auth0.com/
|
AUTH0_DOMAIN=https://dev-12345678.us.auth0.com/
|
||||||
AUTH0_AUTHORITY=https://auth.example.com/
|
AUTH0_AUTHORITY=https://auth.example.com/
|
||||||
|
|
||||||
# App Configuration
|
|
||||||
APP_BASE_URL=https://example.com
|
APP_BASE_URL=https://example.com
|
||||||
|
|
||||||
# ASP.NET Core Configuration
|
|
||||||
ASPNETCORE_ENVIRONMENT=Development
|
ASPNETCORE_ENVIRONMENT=Development
|
||||||
ASPNETCORE_HTTP_PORTS=8080
|
ASPNETCORE_HTTP_PORTS=8080
|
||||||
ASPNETCORE_APPLY_MIGRATIONS_AT_STARTUP=true
|
ASPNETCORE_APPLY_MIGRATIONS_AT_STARTUP=true
|
||||||
@@ -30,12 +26,15 @@ ASPNETCORE_LOGGING_LEVEL=Information
|
|||||||
ASPNETCORE_LOGGING_LEVEL_EFCORE=Information
|
ASPNETCORE_LOGGING_LEVEL_EFCORE=Information
|
||||||
ASPNETCORE_LOGGING_LEVEL_APP=Information
|
ASPNETCORE_LOGGING_LEVEL_APP=Information
|
||||||
|
|
||||||
# Seq UI URL
|
|
||||||
SEQ_BASE_URI=https://example.com/seq
|
SEQ_BASE_URI=https://example.com/seq
|
||||||
|
|
||||||
# Node.js / Next.js Configuration
|
ZIPLINE_SECRET=somefakesecretkey123
|
||||||
|
ZIPLINE_URL=https://example.com/res
|
||||||
|
ZIPLINE_ROUTE=/res
|
||||||
|
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
COOKIE_DOMAIN=.example.com
|
COOKIE_DOMAIN=.example.com
|
||||||
NEXT_PUBLIC_API_URL=https://example.com/api
|
NEXT_PUBLIC_API_URL=https://example.com/api
|
||||||
NEXT_PUBLIC_AUTH0_DOMAIN=auth.example.com
|
NEXT_PUBLIC_AUTH0_DOMAIN=auth.example.com
|
||||||
NEXT_PUBLIC_AUTH0_CLIENT_ID=EXAMPLE_PUBLIC_CLIENT_ID
|
NEXT_PUBLIC_AUTH0_CLIENT_ID=abcDEF123456xyz7890
|
||||||
|
```
|
||||||
|
|||||||
@@ -97,6 +97,38 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
|
|
||||||
|
zipline:
|
||||||
|
image: ghcr.io/diced/zipline:latest
|
||||||
|
expose:
|
||||||
|
- "3000"
|
||||||
|
environment:
|
||||||
|
- CORE_RETURN_HTTPS=true
|
||||||
|
- CORE_SECRET=${ZIPLINE_SECRET}
|
||||||
|
- CORE_HOST=0.0.0.0
|
||||||
|
- CORE_PORT=3000
|
||||||
|
- CORE_DATABASE_URL=file:./zipline.db
|
||||||
|
- CORE_LOGGER=true
|
||||||
|
- URLS_ROUTE=${ZIPLINE_ROUTE}
|
||||||
|
- URLS_LENGTH=6
|
||||||
|
- UPLOADER_DEFAULT_FORMAT=RANDOM
|
||||||
|
- UPLOADER_DISABLED_EXTENSIONS=
|
||||||
|
- UPLOADER_FORMAT_DATE=%Y-%m-%d_%H-%M-%S
|
||||||
|
- FEATURES_INVITES=false
|
||||||
|
- FEATURES_REGISTRATION=false
|
||||||
|
- FEATURES_HEADLESS=true
|
||||||
|
- FEATURES_USER_REGISTRATION=false
|
||||||
|
- FEATURES_OAUTH_REGISTRATION=false
|
||||||
|
- FEATURES_ANONYMOUS_UPLOAD=true
|
||||||
|
- WEBSITE_TITLE=File Upload
|
||||||
|
- WEBSITE_EXTERNAL_LINKS=${ZIPLINE_URL}
|
||||||
|
volumes:
|
||||||
|
- zipline_data:/zipline/uploads
|
||||||
|
- zipline_public:/zipline/public
|
||||||
|
- zipline_db:/zipline
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
app-network:
|
app-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
@@ -21,6 +21,10 @@ http {
|
|||||||
server seq:80;
|
server seq:80;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upstream zipline {
|
||||||
|
server zipline:3000;
|
||||||
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name impr.ink;
|
server_name impr.ink;
|
||||||
@@ -81,6 +85,25 @@ http {
|
|||||||
rewrite ^/seq/(.*)$ /$1 break;
|
rewrite ^/seq/(.*)$ /$1 break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /res {
|
||||||
|
proxy_pass http://zipline;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Prefix /res;
|
||||||
|
|
||||||
|
proxy_request_buffering off;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
|
||||||
|
rewrite ^/res/(.*)$ /$1 break;
|
||||||
|
rewrite ^/res$ / break;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://webui/;
|
proxy_pass http://webui/;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|||||||
168
webui/package-lock.json
generated
168
webui/package-lock.json
generated
@@ -12,7 +12,11 @@
|
|||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@mui/icons-material": "^7.1.1",
|
"@mui/icons-material": "^7.1.1",
|
||||||
|
"@mui/material": "^7.1.1",
|
||||||
|
"@mui/x-data-grid": "^8.5.2",
|
||||||
|
"@mui/x-date-pickers": "^8.5.2",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
|
"lucide-react": "^0.516.0",
|
||||||
"next": "15.3.3",
|
"next": "15.3.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
@@ -824,7 +828,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.1.tgz",
|
||||||
"integrity": "sha512-yBckQs4aQ8mqukLnPC6ivIRv6guhaXi8snVl00VtyojBbm+l6VbVhyTSZ68Abcx7Ah8B+GZhrB7BOli+e+9LkQ==",
|
"integrity": "sha512-yBckQs4aQ8mqukLnPC6ivIRv6guhaXi8snVl00VtyojBbm+l6VbVhyTSZ68Abcx7Ah8B+GZhrB7BOli+e+9LkQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/mui-org"
|
"url": "https://opencollective.com/mui-org"
|
||||||
@@ -861,7 +864,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.1.1.tgz",
|
||||||
"integrity": "sha512-mTpdmdZCaHCGOH3SrYM41+XKvNL0iQfM9KlYgpSjgadXx/fEKhhvOktxm8++Xw6FFeOHoOiV+lzOI8X1rsv71A==",
|
"integrity": "sha512-mTpdmdZCaHCGOH3SrYM41+XKvNL0iQfM9KlYgpSjgadXx/fEKhhvOktxm8++Xw6FFeOHoOiV+lzOI8X1rsv71A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.1",
|
"@babel/runtime": "^7.27.1",
|
||||||
"@mui/core-downloads-tracker": "^7.1.1",
|
"@mui/core-downloads-tracker": "^7.1.1",
|
||||||
@@ -911,7 +913,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.1.tgz",
|
||||||
"integrity": "sha512-M8NbLUx+armk2ZuaxBkkMk11ultnWmrPlN0Xe3jUEaBChg/mcxa5HWIWS1EE4DF36WRACaAHVAvyekWlDQf0PQ==",
|
"integrity": "sha512-M8NbLUx+armk2ZuaxBkkMk11ultnWmrPlN0Xe3jUEaBChg/mcxa5HWIWS1EE4DF36WRACaAHVAvyekWlDQf0PQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.1",
|
"@babel/runtime": "^7.27.1",
|
||||||
"@mui/utils": "^7.1.1",
|
"@mui/utils": "^7.1.1",
|
||||||
@@ -939,7 +940,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.1.tgz",
|
||||||
"integrity": "sha512-R2wpzmSN127j26HrCPYVQ53vvMcT5DaKLoWkrfwUYq3cYytL6TQrCH8JBH3z79B6g4nMZZVoaXrxO757AlShaw==",
|
"integrity": "sha512-R2wpzmSN127j26HrCPYVQ53vvMcT5DaKLoWkrfwUYq3cYytL6TQrCH8JBH3z79B6g4nMZZVoaXrxO757AlShaw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.1",
|
"@babel/runtime": "^7.27.1",
|
||||||
"@emotion/cache": "^11.13.5",
|
"@emotion/cache": "^11.13.5",
|
||||||
@@ -974,7 +974,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.1.1.tgz",
|
||||||
"integrity": "sha512-Kj1uhiqnj4Zo7PDjAOghtXJtNABunWvhcRU0O7RQJ7WOxeynoH6wXPcilphV8QTFtkKaip8EiNJRiCD+B3eROA==",
|
"integrity": "sha512-Kj1uhiqnj4Zo7PDjAOghtXJtNABunWvhcRU0O7RQJ7WOxeynoH6wXPcilphV8QTFtkKaip8EiNJRiCD+B3eROA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.1",
|
"@babel/runtime": "^7.27.1",
|
||||||
"@mui/private-theming": "^7.1.1",
|
"@mui/private-theming": "^7.1.1",
|
||||||
@@ -1015,7 +1014,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.3.tgz",
|
||||||
"integrity": "sha512-2UCEiK29vtiZTeLdS2d4GndBKacVyxGvReznGXGr+CzW/YhjIX+OHUdCIczZjzcRAgKBGmE9zCIgoV9FleuyRQ==",
|
"integrity": "sha512-2UCEiK29vtiZTeLdS2d4GndBKacVyxGvReznGXGr+CzW/YhjIX+OHUdCIczZjzcRAgKBGmE9zCIgoV9FleuyRQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.1"
|
"@babel/runtime": "^7.27.1"
|
||||||
},
|
},
|
||||||
@@ -1033,7 +1031,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.1.tgz",
|
||||||
"integrity": "sha512-BkOt2q7MBYl7pweY2JWwfrlahhp+uGLR8S+EhiyRaofeRYUWL2YKbSGQvN4hgSN1i8poN0PaUiii1kEMrchvzg==",
|
"integrity": "sha512-BkOt2q7MBYl7pweY2JWwfrlahhp+uGLR8S+EhiyRaofeRYUWL2YKbSGQvN4hgSN1i8poN0PaUiii1kEMrchvzg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.1",
|
"@babel/runtime": "^7.27.1",
|
||||||
"@mui/types": "^7.4.3",
|
"@mui/types": "^7.4.3",
|
||||||
@@ -1059,6 +1056,131 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mui/x-data-grid": {
|
||||||
|
"version": "8.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.5.2.tgz",
|
||||||
|
"integrity": "sha512-4KzawLZqRKp3KcGKsTDVz7zkEjACllQD5Zb8ds1QKlA6C3/oIoSU7PsemFLj+RL3rT5aORsLMBl97/egQ5tUhA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.27.6",
|
||||||
|
"@mui/utils": "^7.1.1",
|
||||||
|
"@mui/x-internals": "8.5.2",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"use-sync-external-store": "^1.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.9.0",
|
||||||
|
"@emotion/styled": "^11.8.1",
|
||||||
|
"@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||||
|
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@emotion/styled": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/x-date-pickers": {
|
||||||
|
"version": "8.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-8.5.2.tgz",
|
||||||
|
"integrity": "sha512-KN0GK5aVetGFB3n4W7XsUI79uTSxftTEhHtDCdQjOOeri2lZSY55MVn/CeYZdpuWlBOD1eTLPtCFzueeUp3m6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.27.6",
|
||||||
|
"@mui/utils": "^7.1.1",
|
||||||
|
"@mui/x-internals": "8.5.2",
|
||||||
|
"@types/react-transition-group": "^4.4.12",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react-transition-group": "^4.4.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.9.0",
|
||||||
|
"@emotion/styled": "^11.8.1",
|
||||||
|
"@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||||
|
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||||
|
"date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0",
|
||||||
|
"date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0",
|
||||||
|
"dayjs": "^1.10.7",
|
||||||
|
"luxon": "^3.0.2",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"moment-hijri": "^2.1.2 || ^3.0.0",
|
||||||
|
"moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@emotion/styled": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"date-fns": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"date-fns-jalali": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"dayjs": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"luxon": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment-hijri": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment-jalaali": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/x-internals": {
|
||||||
|
"version": "8.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.5.2.tgz",
|
||||||
|
"integrity": "sha512-5YhB2AekK7G8d0YrAjg3WNf0uy3V73JD98WNxJhbIlCraQgl8QOQzr2zNO7MAf/X7mZQtjpjuAsiG3+gI2NVyg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.27.6",
|
||||||
|
"@mui/utils": "^7.1.1",
|
||||||
|
"reselect": "^5.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "15.3.3",
|
"version": "15.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.3.tgz",
|
||||||
@@ -1207,7 +1329,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/popperjs"
|
"url": "https://opencollective.com/popperjs"
|
||||||
@@ -1514,8 +1635,7 @@
|
|||||||
"version": "15.7.15",
|
"version": "15.7.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.7",
|
"version": "19.1.7",
|
||||||
@@ -1532,7 +1652,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||||
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
@@ -1643,7 +1762,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
@@ -1783,7 +1901,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.8.7",
|
"@babel/runtime": "^7.8.7",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@@ -2390,7 +2507,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
},
|
},
|
||||||
@@ -2398,6 +2514,15 @@
|
|||||||
"loose-envify": "cli.js"
|
"loose-envify": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lucide-react": {
|
||||||
|
"version": "0.516.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.516.0.tgz",
|
||||||
|
"integrity": "sha512-aybBJzLHcw1CIn3rUcRkztB37dsJATtpffLNX+0/w+ws2p21nYIlOwX/B5fqxq8F/BjqVemnJX8chKwRidvROg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.17",
|
"version": "0.30.17",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||||
@@ -2597,7 +2722,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -2687,7 +2811,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
@@ -2698,8 +2821,7 @@
|
|||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/proxy-from-env": {
|
"node_modules/proxy-from-env": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
@@ -2732,15 +2854,13 @@
|
|||||||
"version": "19.1.0",
|
"version": "19.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
|
||||||
"integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
|
"integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/react-transition-group": {
|
"node_modules/react-transition-group": {
|
||||||
"version": "4.4.5",
|
"version": "4.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"dom-helpers": "^5.0.1",
|
"dom-helpers": "^5.0.1",
|
||||||
@@ -2752,6 +2872,12 @@
|
|||||||
"react-dom": ">=16.6.0"
|
"react-dom": ">=16.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reselect": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.10",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
|
|||||||
@@ -13,7 +13,11 @@
|
|||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@mui/icons-material": "^7.1.1",
|
"@mui/icons-material": "^7.1.1",
|
||||||
|
"@mui/material": "^7.1.1",
|
||||||
|
"@mui/x-data-grid": "^8.5.2",
|
||||||
|
"@mui/x-date-pickers": "^8.5.2",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
|
"lucide-react": "^0.516.0",
|
||||||
"next": "15.3.3",
|
"next": "15.3.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 1.0 MiB |
@@ -1,26 +0,0 @@
|
|||||||
@import "tailwindcss";
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--background: #ffffff;
|
|
||||||
--foreground: #171717;
|
|
||||||
}
|
|
||||||
|
|
||||||
@theme inline {
|
|
||||||
--color-background: var(--background);
|
|
||||||
--color-foreground: var(--foreground);
|
|
||||||
--font-sans: var(--font-geist-sans);
|
|
||||||
--font-mono: var(--font-geist-mono);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background: #0a0a0a;
|
|
||||||
--foreground: #ededed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--foreground);
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,25 @@
|
|||||||
import { Geist, Geist_Mono } from "next/font/google";
|
'use client'
|
||||||
import "./globals.css";
|
|
||||||
|
|
||||||
const geistSans = Geist({
|
import { CssBaseline } from '@mui/material';
|
||||||
variable: "--font-geist-sans",
|
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||||
subsets: ["latin"],
|
|
||||||
|
const theme = createTheme({
|
||||||
|
palette: {
|
||||||
|
mode: 'light',
|
||||||
|
primary: {
|
||||||
|
main: '#1976d2',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const geistMono = Geist_Mono({
|
|
||||||
variable: "--font-geist-mono",
|
|
||||||
subsets: ["latin"],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metadata = {
|
|
||||||
title: "Create Next App",
|
|
||||||
description: "Generated by create next app",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({ children }) {
|
export default function RootLayout({ children }) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body
|
<body className='antialiased'>
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
<ThemeProvider theme={theme}>
|
||||||
>
|
<CssBaseline />
|
||||||
{children}
|
{children}
|
||||||
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,192 +1,423 @@
|
|||||||
'use client';
|
'use client'
|
||||||
|
|
||||||
import { useUser } from "@auth0/nextjs-auth0";
|
import {useEffect, useState} from 'react';
|
||||||
import {useEffect, useState} from "react";
|
import axios from 'axios';
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
AppBar,
|
||||||
|
Badge,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardActions,
|
||||||
|
CardContent,
|
||||||
|
CardMedia,
|
||||||
|
Chip,
|
||||||
|
Container,
|
||||||
|
Fab,
|
||||||
|
Grid,
|
||||||
|
IconButton,
|
||||||
|
Skeleton,
|
||||||
|
Toolbar,
|
||||||
|
Typography
|
||||||
|
} from '@mui/material';
|
||||||
|
import {createTheme, ThemeProvider} from '@mui/material/styles';
|
||||||
|
import CssBaseline from '@mui/material/CssBaseline';
|
||||||
|
import {Menu, Palette, Search, ShoppingCart} from 'lucide-react';
|
||||||
|
|
||||||
export default function Home() {
|
const theme = createTheme({
|
||||||
const { user, error, isLoading } = useUser();
|
palette: {
|
||||||
|
mode: 'dark',
|
||||||
|
primary: {
|
||||||
|
main: '#D0BCFF',
|
||||||
|
light: '#EADDFF',
|
||||||
|
dark: '#9A82DB',
|
||||||
|
contrastText: '#21005D'
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
main: '#CCC2DC',
|
||||||
|
light: '#E8DEF8',
|
||||||
|
dark: '#A8A2BA',
|
||||||
|
contrastText: '#332D41'
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
default: '#101418',
|
||||||
|
paper: '#1D1B20'
|
||||||
|
},
|
||||||
|
surface: {
|
||||||
|
main: '#1D1B20',
|
||||||
|
variant: '#49454F'
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
main: '#F2B8B5',
|
||||||
|
light: '#FFDAD6',
|
||||||
|
dark: '#BA1A1A',
|
||||||
|
contrastText: '#410002'
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
main: '#A6D4A3',
|
||||||
|
light: '#C4F0B8',
|
||||||
|
dark: '#52B788'
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
primary: '#E6E0E9',
|
||||||
|
secondary: '#CAC4D0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shape: {
|
||||||
|
borderRadius: 16
|
||||||
|
},
|
||||||
|
typography: {
|
||||||
|
fontFamily: '"Google Sans", "Roboto", "Helvetica", "Arial", sans-serif',
|
||||||
|
h4: {
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: '2rem'
|
||||||
|
},
|
||||||
|
h6: {
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: '1.25rem'
|
||||||
|
},
|
||||||
|
body1: {
|
||||||
|
fontSize: '1rem',
|
||||||
|
lineHeight: 1.5
|
||||||
|
},
|
||||||
|
body2: {
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
lineHeight: 1.43
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MuiCard: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
backgroundImage: 'none',
|
||||||
|
backgroundColor: '#1D1B20',
|
||||||
|
border: '1px solid #49454F',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
'&:hover': {
|
||||||
|
transform: 'translateY(-4px)',
|
||||||
|
borderColor: '#D0BCFF',
|
||||||
|
boxShadow: '0 8px 32px rgba(208, 188, 255, 0.1)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MuiButton: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
textTransform: 'none',
|
||||||
|
fontWeight: 500,
|
||||||
|
borderRadius: 20,
|
||||||
|
paddingLeft: 24,
|
||||||
|
paddingRight: 24,
|
||||||
|
height: 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MuiTextField: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
borderRadius: 12,
|
||||||
|
backgroundColor: '#1D1B20',
|
||||||
|
'& fieldset': {
|
||||||
|
borderColor: '#49454F'
|
||||||
|
},
|
||||||
|
'&:hover fieldset': {
|
||||||
|
borderColor: '#CAC4D0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MuiChip: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
borderRadius: 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ProductCard = ({ product }) => {
|
||||||
|
const [imageLoaded, setImageLoaded] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Box sx={{ position: 'relative', overflow: 'hidden' }}>
|
||||||
|
{!imageLoaded && (
|
||||||
|
<Skeleton variant="rectangular" height={200} />
|
||||||
|
)}
|
||||||
|
<CardMedia
|
||||||
|
component="img"
|
||||||
|
height="200"
|
||||||
|
image={product.imageUrl}
|
||||||
|
alt={product.name}
|
||||||
|
onLoad={() => setImageLoaded(true)}
|
||||||
|
sx={{
|
||||||
|
display: imageLoaded ? 'block' : 'none',
|
||||||
|
objectFit: 'cover',
|
||||||
|
transition: 'transform 0.3s ease',
|
||||||
|
'&:hover': {
|
||||||
|
transform: 'scale(1.05)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 12,
|
||||||
|
right: 12,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 1
|
||||||
|
}}>
|
||||||
|
{product.isCustomizable && (
|
||||||
|
<Chip
|
||||||
|
icon={<Palette size={16} />}
|
||||||
|
label="Customizable"
|
||||||
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
sx={{ fontSize: '0.75rem' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Chip
|
||||||
|
label={product.category.name}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ fontSize: '0.75rem' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<CardContent sx={{ flexGrow: 1, pb: 1 }}>
|
||||||
|
<Typography variant="h6" component="h3" gutterBottom>
|
||||||
|
{product.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
|
{product.description}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h5" color="primary.main" fontWeight="bold">
|
||||||
|
${product.basePrice.toFixed(2)}
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
|
||||||
|
<CardActions sx={{ p: 2, pt: 0 }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
startIcon={<ShoppingCart size={18} />}
|
||||||
|
sx={{
|
||||||
|
background: 'linear-gradient(45deg, #D0BCFF 30%, #EADDFF 90%)',
|
||||||
|
color: '#21005D'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add to Cart
|
||||||
|
</Button>
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const LoadingSkeleton = () => (
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
{[...Array(8)].map((_, index) => (
|
||||||
|
<Grid item xs={12} sm={6} md={4} lg={3} key={index}>
|
||||||
|
<Card>
|
||||||
|
<Skeleton variant="rectangular" height={200} />
|
||||||
|
<CardContent>
|
||||||
|
<Skeleton variant="text" height={32} />
|
||||||
|
<Skeleton variant="text" height={20} />
|
||||||
|
<Skeleton variant="text" height={20} width="60%" />
|
||||||
|
<Skeleton variant="text" height={28} width="40%" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ImprintLanding = () => {
|
||||||
|
const [products, setProducts] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [cartCount, setCartCount] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAccessToken = async () => {
|
const fetchProducts = async () => {
|
||||||
if (user) {
|
try {
|
||||||
try {
|
setLoading(true);
|
||||||
await fetch('/token');
|
const response = await axios.get('https://impr.ink/api/products', {
|
||||||
} catch (error) {
|
params: {
|
||||||
console.error("Error fetching token");
|
PageNumber: 1,
|
||||||
}
|
PageSize: 20
|
||||||
} else {
|
}
|
||||||
try {
|
});
|
||||||
await fetch('/untoken');
|
setProducts(response.data.items);
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
console.error('Error in /api/untoken:', e);
|
setError('Failed to load products. Please try again later.');
|
||||||
}
|
console.error('Error fetching products:', err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchAccessToken().then(r => console.log(r));
|
fetchProducts().then(r => console.log(r));
|
||||||
}, [user]);
|
}, []);
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900 flex items-center justify-center">
|
|
||||||
<div className="relative">
|
|
||||||
<div className="w-16 h-16 border-4 border-white/20 border-t-white rounded-full animate-spin"></div>
|
|
||||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
|
||||||
<div className="w-10 h-10 border-4 border-transparent border-t-purple-400 rounded-full animate-spin"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gradient-to-br from-red-900 via-pink-900 to-purple-900 flex items-center justify-center p-4">
|
|
||||||
<div className="bg-white/10 backdrop-blur-xl rounded-2xl p-6 border border-white/20 shadow-2xl">
|
|
||||||
<div className="text-white/80 mb-4">{error.message}</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<a
|
|
||||||
href="/auth/login"
|
|
||||||
className="group relative inline-flex items-center gap-2 px-8 py-3 bg-gradient-to-r from-purple-500 to-blue-500 rounded-xl font-bold text-white shadow-2xl hover:shadow-purple-500/25 transition-all duration-300 hover:scale-105 active:scale-95"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 bg-gradient-to-r from-purple-600 to-blue-600 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
||||||
<span className="relative flex items-center gap-2">
|
|
||||||
Sign In
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
onClick={() => checkValidity()}
|
|
||||||
className="group relative px-6 py-3 bg-gradient-to-r from-red-500 to-pink-500 rounded-xl font-bold text-white shadow-2xl hover:shadow-red-500/25 transition-all duration-300 hover:scale-105 active:scale-95"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 bg-gradient-to-r from-red-600 to-pink-600 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
||||||
<span className="relative flex items-center gap-2">
|
|
||||||
Checkaaaaa
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const featuredProducts = products.slice(0, 4);
|
||||||
return (
|
return (
|
||||||
<div
|
<ThemeProvider theme={theme}>
|
||||||
className="min-h-screen bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900 relative overflow-hidden">
|
<CssBaseline />
|
||||||
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
{user ? (
|
{/* App Bar */}
|
||||||
<div className="w-full max-w-5xl">
|
<AppBar position="sticky" sx={{ backgroundColor: 'background.paper', borderBottom: '1px solid #49454F' }}>
|
||||||
<div className="text-center mb-6">
|
<Toolbar>
|
||||||
<div
|
<IconButton edge="start" color="inherit" sx={{ mr: 2 }}>
|
||||||
className="inline-flex items-center justify-center w-16 h-16 bg-gradient-to-r from-purple-500 to-blue-500 rounded-full mb-3 shadow-2xl">
|
<Menu />
|
||||||
{user.picture ? (
|
</IconButton>
|
||||||
<img
|
<Typography variant="h6" component="div" sx={{ flexGrow: 1, color: 'primary.main', fontWeight: 'bold' }}>
|
||||||
src={user.picture}
|
impr.ink
|
||||||
alt="Profile"
|
</Typography>
|
||||||
className="w-full h-full rounded-full object-cover border-3 border-white/20"
|
<IconButton color="inherit" sx={{ mr: 1 }}>
|
||||||
/>
|
<Search />
|
||||||
) : (
|
</IconButton>
|
||||||
<div className="text-white text-xl font-bold">
|
<IconButton color="inherit">
|
||||||
{user.name?.charAt(0) || user.email?.charAt(0) || '👤'}
|
<Badge badgeContent={cartCount} color="primary">
|
||||||
</div>
|
<ShoppingCart />
|
||||||
)}
|
</Badge>
|
||||||
</div>
|
</IconButton>
|
||||||
<h1 className="text-2xl pb-1 font-bold bg-gradient-to-r from-white via-purple-200 to-blue-200 bg-clip-text text-transparent">
|
</Toolbar>
|
||||||
Just testing :P
|
</AppBar>
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-white/10 backdrop-blur-xl rounded-2xl border border-white/20 shadow-2xl overflow-hidden mb-4">
|
{/* Hero Section */}
|
||||||
<div className="bg-gradient-to-r from-purple-500/20 to-blue-500/20 p-4 border-b border-white/10">
|
<Box sx={{
|
||||||
<h2 className="text-xl font-bold text-white flex items-center gap-2">
|
background: 'linear-gradient(135deg, #1D1B20 0%, #2D1B69 50%, #1D1B20 100%)',
|
||||||
Auth Details
|
py: 8,
|
||||||
</h2>
|
position: 'relative',
|
||||||
</div>
|
overflow: 'hidden'
|
||||||
<div className="p-5">
|
}}>
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<Container maxWidth="lg">
|
||||||
<div className="space-y-4">
|
<Box sx={{ textAlign: 'center', position: 'relative', zIndex: 1 }}>
|
||||||
<div>
|
<Typography variant="h2" component="h1" gutterBottom sx={{
|
||||||
<label
|
fontWeight: 300,
|
||||||
className="text-purple-300 text-xs font-semibold uppercase tracking-wider">Name</label>
|
background: 'linear-gradient(45deg, #D0BCFF, #EADDFF)',
|
||||||
<div
|
backgroundClip: 'text',
|
||||||
className="text-white text-base mt-1 p-2 bg-white/5 rounded-lg border border-white/10">
|
WebkitBackgroundClip: 'text',
|
||||||
{user.name || 'Not provided'}
|
WebkitTextFillColor: 'transparent',
|
||||||
</div>
|
mb: 2
|
||||||
</div>
|
}}>
|
||||||
<div>
|
Custom Printing Made Beautiful
|
||||||
<label
|
</Typography>
|
||||||
className="text-purple-300 text-xs font-semibold uppercase tracking-wider">Email</label>
|
<Typography variant="h5" color="text.secondary" sx={{ mb: 4, maxWidth: 600, mx: 'auto' }}>
|
||||||
<div
|
High-quality custom printing solutions for caps, flyers, coasters, and more.
|
||||||
className="text-white text-base mt-1 p-2 bg-white/5 rounded-lg border border-white/10">
|
Premium materials with excellent print adhesion and longevity.
|
||||||
{user.email || 'Not provided'}
|
</Typography>
|
||||||
</div>
|
<Button
|
||||||
</div>
|
variant="contained"
|
||||||
<div>
|
size="large"
|
||||||
<label
|
sx={{
|
||||||
className="text-purple-300 text-xs font-semibold uppercase tracking-wider">User
|
background: 'linear-gradient(45deg, #D0BCFF 30%, #EADDFF 90%)',
|
||||||
ID</label>
|
color: '#21005D',
|
||||||
<div
|
px: 4,
|
||||||
className="text-white/80 text-xs mt-1 p-2 bg-white/5 rounded-lg border border-white/10 font-mono break-all">
|
py: 1.5,
|
||||||
{user.sub || 'Not available'}
|
fontSize: '1.1rem'
|
||||||
</div>
|
}}
|
||||||
</div>
|
|
||||||
{user.nickname && (
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
className="text-purple-300 text-xs font-semibold uppercase tracking-wider">Nickname</label>
|
|
||||||
<div
|
|
||||||
className="text-white text-base mt-1 p-2 bg-white/5 rounded-lg border border-white/10">
|
|
||||||
{user.nickname}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
className="text-purple-300 text-xs font-semibold uppercase tracking-wider mb-2 block">
|
|
||||||
Raw User Data
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
className="bg-black/30 rounded-lg p-3 border border-white/10 h-64 overflow-auto">
|
|
||||||
<pre
|
|
||||||
className="text-green-300 text-xs font-mono leading-tight whitespace-pre-wrap">
|
|
||||||
{JSON.stringify(user, null, 2)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-center">
|
|
||||||
<a
|
|
||||||
onClick={() => checkValidity()}
|
|
||||||
className="group relative px-6 py-3 bg-gradient-to-r from-red-500 to-pink-500 rounded-xl font-bold text-white shadow-2xl hover:shadow-red-500/25 transition-all duration-300 hover:scale-105 active:scale-95"
|
|
||||||
>
|
>
|
||||||
<div
|
Explore Products
|
||||||
className="absolute inset-0 bg-gradient-to-r from-red-600 to-pink-600 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
</Button>
|
||||||
<span className="relative flex items-center gap-2">
|
</Box>
|
||||||
Checkaaaaa
|
</Container>
|
||||||
</span>
|
</Box>
|
||||||
</a>
|
|
||||||
<a
|
<Container maxWidth="lg" sx={{ py: 6 }}>
|
||||||
href="/auth/logout"
|
{error && (
|
||||||
className="group relative px-6 py-3 bg-gradient-to-r from-red-500 to-pink-500 rounded-xl font-bold text-white shadow-2xl hover:shadow-red-500/25 transition-all duration-300 hover:scale-105 active:scale-95"
|
<Alert severity="error" sx={{ mb: 4 }}>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Featured Products */}
|
||||||
|
<Box sx={{ mb: 6 }}>
|
||||||
|
<Typography variant="h4" component="h2" gutterBottom sx={{ mb: 4, textAlign: 'center' }}>
|
||||||
|
Featured Products
|
||||||
|
</Typography>
|
||||||
|
{loading ? (
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
{[...Array(4)].map((_, index) => (
|
||||||
|
<Grid item xs={12} sm={6} md={3} key={index}>
|
||||||
|
<Card>
|
||||||
|
<Skeleton variant="rectangular" height={200} />
|
||||||
|
<CardContent>
|
||||||
|
<Skeleton variant="text" height={32} />
|
||||||
|
<Skeleton variant="text" height={20} />
|
||||||
|
<Skeleton variant="text" height={28} width="40%" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
) : (
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
{featuredProducts.map((product) => (
|
||||||
|
<Grid item xs={12} sm={6} md={3} key={product.id}>
|
||||||
|
<ProductCard product={product} />
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* All Products */}
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h4" component="h2" gutterBottom sx={{ mb: 4, textAlign: 'center' }}>
|
||||||
|
All Products
|
||||||
|
</Typography>
|
||||||
|
{loading ? (
|
||||||
|
<LoadingSkeleton />
|
||||||
|
) : (
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
{products.map((product) => (
|
||||||
|
<Grid item xs={12} sm={6} md={4} lg={3} key={product.id}>
|
||||||
|
<ProductCard product={product} />
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Load More Button */}
|
||||||
|
{!loading && products.length > 0 && (
|
||||||
|
<Box sx={{ textAlign: 'center', mt: 6 }}>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="large"
|
||||||
|
sx={{ px: 4 }}
|
||||||
>
|
>
|
||||||
<div
|
Load More Products
|
||||||
className="absolute inset-0 bg-gradient-to-r from-red-600 to-pink-600 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
</Button>
|
||||||
<span className="relative flex items-center gap-2">
|
</Box>
|
||||||
Sign Out
|
)}
|
||||||
</span>
|
</Container>
|
||||||
</a>
|
|
||||||
</div>
|
{/* Floating Action Button */}
|
||||||
</div>
|
<Fab
|
||||||
) : (
|
color="primary"
|
||||||
<div></div>
|
sx={{
|
||||||
)}
|
position: 'fixed',
|
||||||
</div>
|
bottom: 16,
|
||||||
</div>
|
right: 16,
|
||||||
|
background: 'linear-gradient(45deg, #D0BCFF 30%, #EADDFF 90%)',
|
||||||
|
color: '#21005D'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Badge badgeContent={cartCount} color="error">
|
||||||
|
<ShoppingCart />
|
||||||
|
</Badge>
|
||||||
|
</Fab>
|
||||||
|
</Box>
|
||||||
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default ImprintLanding;
|
||||||
Reference in New Issue
Block a user