diff --git a/.gitignore b/.gitignore index 03d4673..5900128 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .DS_Store - +src/local-cdn/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..92390e4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2e86dac --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ + + + +# Use an official Python runtime as a parent image +FROM python:3.11.2-slim + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +# Set the working directory in the container +WORKDIR /app + + + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + libpq-dev + +RUN venv/bin/activate + +# Install Python dependencies +COPY requirements.txt requirements.txt +RUN pip install -r requirements.txt + +# Copy the current directory contents into the container at /app/ +COPY . /app/ + +# Expose port 8000 to allow communication to/from the Django application +EXPOSE 8000 + +# Run Django application +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..49f1385 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1403 @@ +{ + "name": "micro-ecommerce", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "flowbite": "^2.1.0", + "tailwindcss": "^3.4.1" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flowbite": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.1.0.tgz", + "integrity": "sha512-s8Dey263bdHhFSQUsh3yFTkATi6aBatODDRZbbdBn/+BCpLgg2j9d5zAgB+n1XxQBi6wOPzGyAL4K9HqKx7cpg==", + "dev": true, + "dependencies": { + "@popperjs/core": "^2.9.3", + "mini-svg-data-uri": "^1.4.3" + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true, + "engines": { + "node": ">= 14" + } + } + } +} diff --git a/package.json b/package.json index c19f80d..3240fa6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "flowbite": "^1.6.4", - "tailwindcss": "^3.2.7" + "flowbite": "^2.1.0", + "tailwindcss": "^3.4.1" } -} +} \ No newline at end of file diff --git a/rav.yaml b/rav.yaml index 94a3666..bd9b1ec 100644 --- a/rav.yaml +++ b/rav.yaml @@ -17,4 +17,7 @@ scripts: - mkdir -p src/static/vendor/htmx/ - mkdir -p src/static/vendor/flowbite/ - curl -L https://unpkg.com/htmx.org@1.8.2 -o ./src/static/vendor/htmx/htmx.min.js - - cp ./node_modules/flowbite/dist/flowbite.min.js ./src/static/vendor/flowbite/flowbite.min.js \ No newline at end of file + - cp ./node_modules/flowbite/dist/flowbite.min.js ./src/static/vendor/flowbite/flowbite.min.js + build: docker build -f Dockerfile -t micro-ecommerce . + docker-dev: docker run --env-file .env -p 8001:8000 --rm --name micro-ecommerce + docker-prod: docker run --env-file .env -p 8002:8000 --rm --name micro-ecommerce-dev -it micro-ecommerce \ No newline at end of file diff --git a/src/accounts/__init__.py b/src/accounts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/accounts/admin.py b/src/accounts/admin.py new file mode 100644 index 0000000..694323f --- /dev/null +++ b/src/accounts/admin.py @@ -0,0 +1 @@ +from django.contrib import admin diff --git a/src/accounts/apps.py b/src/accounts/apps.py new file mode 100644 index 0000000..3e3c765 --- /dev/null +++ b/src/accounts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'accounts' diff --git a/src/accounts/forms.py b/src/accounts/forms.py new file mode 100644 index 0000000..b011c53 --- /dev/null +++ b/src/accounts/forms.py @@ -0,0 +1,15 @@ +from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth.models import User +from django.forms import ModelForm +from django import forms + +input_css_class='reg-form' +class CreateUserForm(UserCreationForm): + class Meta: + model=User + fields=['username','email','password1','password2'] + def __init__(self,*args,**kwargs): + super().__init__(*args,**kwargs) + for field in self.fields: + # self.fields[field].widget.attrs['class']=forms.HiddenInput() + self.fields[field].widget.attrs['class']=input_css_class \ No newline at end of file diff --git a/src/accounts/migrations/0001_initial.py b/src/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..80ef57c --- /dev/null +++ b/src/accounts/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 4.1.13 on 2024-02-06 15:17 + +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='CustomUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/src/accounts/migrations/0002_delete_customuser.py b/src/accounts/migrations/0002_delete_customuser.py new file mode 100644 index 0000000..2b93ac8 --- /dev/null +++ b/src/accounts/migrations/0002_delete_customuser.py @@ -0,0 +1,16 @@ +# Generated by Django 4.1.13 on 2024-02-07 17:09 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0001_initial'), + ] + + operations = [ + migrations.DeleteModel( + name='CustomUser', + ), + ] diff --git a/src/accounts/migrations/__init__.py b/src/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/accounts/models.py b/src/accounts/models.py new file mode 100644 index 0000000..0553623 --- /dev/null +++ b/src/accounts/models.py @@ -0,0 +1,5 @@ +# models.py +from django.contrib.auth.models import AbstractUser +from django.db import models + + diff --git a/src/accounts/tests.py b/src/accounts/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/accounts/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/accounts/urls.py b/src/accounts/urls.py new file mode 100644 index 0000000..ef5c6ac --- /dev/null +++ b/src/accounts/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from . import views +app_name="accounts" +urlpatterns=[ + path('login/',views.login_view,name="login"), + path('register/',views.registerview,name="register"), + path('logout/',views.logout_user,name='logout') +] \ No newline at end of file diff --git a/src/accounts/views.py b/src/accounts/views.py new file mode 100644 index 0000000..014478c --- /dev/null +++ b/src/accounts/views.py @@ -0,0 +1,43 @@ +from django.shortcuts import render,redirect,HttpResponse +from django.contrib.auth.forms import UserCreationForm +from .forms import CreateUserForm +from django.contrib import messages +from django.contrib.auth import authenticate,login,logout +from django.contrib.auth.decorators import login_required + + +def registerview(request): + if request.user.is_authenticated: + return redirect('home') + else: + form=CreateUserForm() + if request.method=="POST": + form=CreateUserForm(request.POST) + if form.is_valid: + form.save() + user=form.cleaned_data.get('username') + messages.success(request,' The account is created for ' + user) + return redirect('home') + context={'form':form} + return render(request,"accounts/register.html",context) + +def login_view(request): + if request.user.is_authenticated: + return redirect('home') + else: + if request.method=='POST': + username=request.POST.get('username') + password=request.POST.get('password') + user=authenticate(request,username=username,password=password) + if user is not None: + login(request,user) + return redirect('home') + else: + print("failed") + messages.warning(request,'Username or Password is not valid!!') + context={} + return render(request,"accounts/login.html",context) + +def logout_user(request): + logout(request) + return redirect('accounts:login') \ No newline at end of file diff --git a/src/cfehome/db.py b/src/cfehome/db.py new file mode 100644 index 0000000..6885895 --- /dev/null +++ b/src/cfehome/db.py @@ -0,0 +1,14 @@ +# To use Neon with Django, you have to create a Project on Neon and specify the project connection settings in your settings.py in the same way as for standalone Postgres. +from cfehome.env import config +import dj_database_url + +DATABASE_URL=config('DATABASE_URL') +if DATABASE_URL is not None: + DATABASES = { + 'default': dj_database_url.config(default=DATABASE_URL, + conn_max_age=800, + conn_health_checks=True, + ), + + } + \ No newline at end of file diff --git a/src/cfehome/env.py b/src/cfehome/env.py new file mode 100644 index 0000000..2ee87a9 --- /dev/null +++ b/src/cfehome/env.py @@ -0,0 +1,14 @@ +from pathlib import Path +from decouple import config as decouple_config,Config,RepositoryEnv +from functools import lru_cache +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent +PROJECT_DIR=BASE_DIR.parent +ENV_FILE_PATH=PROJECT_DIR/".env" +@lru_cache() +def get_config(): + if ENV_FILE_PATH.exists(): + return Config(RepositoryEnv(str(ENV_FILE_PATH))) + return decouple_config + +config=get_config() \ No newline at end of file diff --git a/src/cfehome/settings.py b/src/cfehome/settings.py index 9f9af97..bb42ab7 100644 --- a/src/cfehome/settings.py +++ b/src/cfehome/settings.py @@ -11,7 +11,7 @@ """ from pathlib import Path - +from cfehome.env import config # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -20,12 +20,16 @@ # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-(q3)w+fzzwwc616scd%9q*iuwfnkd6jec8n24y*!51gx*tt4z0' +SECRET_KEY = config("DJANGO_SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +# DEBUG = config("DJANGO_DEBUG",cast=bool,default=False) +DEBUG=True ALLOWED_HOSTS = [] +# ALLOWED_HOST =config("ALLOWED_HOSTS",cast=str,default="") +# if ALLOWED_HOST: +# ALLOWED_HOSTS.append(ALLOWED_HOST.strip()) # Application definition @@ -37,6 +41,9 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'products', + 'purchase', + 'accounts' ] MIDDLEWARE = [ @@ -74,14 +81,14 @@ # Database # https://docs.djangoproject.com/en/4.1/ref/settings/#databases -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', - } -} - +# DATABASES = { +# 'default': { +# 'ENGINE': 'django.db.backends.sqlite3', +# 'NAME': BASE_DIR / 'db.sqlite3', +# } +# } +from .db import * # Password validation # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators @@ -118,8 +125,14 @@ STATIC_URL = 'static/' STATICFILES_DIRS = [BASE_DIR / "static"] +STATIC_ROOT=BASE_DIR/'local-cdn'/'static' +MEDIA_URL='media/' +MEDIA_ROOT=BASE_DIR/'local-cdn'/'media' +PROTECTED_MEDIA_ROOT=BASE_DIR/'local-cdn'/'protected' # Default primary key field type # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# settings.py diff --git a/src/cfehome/urls.py b/src/cfehome/urls.py index 80769df..2f44616 100644 --- a/src/cfehome/urls.py +++ b/src/cfehome/urls.py @@ -14,11 +14,20 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path,include +from django.conf import settings +from django.conf.urls.static import static +# from .views import product_create_view from . import views urlpatterns = [ - path('', views.home_view), + path('', views.home_view,name='home'), path('admin/', admin.site.urls), + path('products/', include('products.urls')), + path('purchase/', include('purchase.urls')), + path('accounts/', include('accounts.urls')) ] +if settings.DEBUG: + urlpatterns+=static(settings.STATIC_URL,document_root=settings.STATIC_ROOT) + urlpatterns+=static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT) diff --git a/src/cfehome/views.py b/src/cfehome/views.py index 893a8df..3bc1f8d 100644 --- a/src/cfehome/views.py +++ b/src/cfehome/views.py @@ -1,6 +1,6 @@ from django.shortcuts import render +from django.contrib.auth.decorators import login_required - - +@login_required(login_url='/accounts/login') def home_view(request): return render(request, "home.html") \ No newline at end of file diff --git a/src/config/entrypoint.sh b/src/config/entrypoint.sh new file mode 100644 index 0000000..6b9ccab --- /dev/null +++ b/src/config/entrypoint.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +APP_PORT=${PORT:-8000} +cd /app/ +/opt/venv/bin/gunicorn cfehome.wsgi:application --bind "0.0.0.0:${APP_PORT}" \ No newline at end of file diff --git a/src/products/__init__.py b/src/products/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/products/admin.py b/src/products/admin.py new file mode 100644 index 0000000..7cb1352 --- /dev/null +++ b/src/products/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from .models import Product,ProductAttachment +# Register your models here. +admin.site.register(Product) +admin.site.register(ProductAttachment) +# admin.site.register(CustomUser) \ No newline at end of file diff --git a/src/products/apps.py b/src/products/apps.py new file mode 100644 index 0000000..145a2ac --- /dev/null +++ b/src/products/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ProductsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'products' diff --git a/src/products/forms.py b/src/products/forms.py new file mode 100644 index 0000000..a9a22a3 --- /dev/null +++ b/src/products/forms.py @@ -0,0 +1,86 @@ +from collections.abc import Mapping +from typing import Any +from django.core.files.base import File +from django.db.models.base import Model +from django.forms.utils import ErrorList +from .models import Product,ProductAttachment +from django import forms +from django.forms import inlineformset_factory,modelformset_factory,HiddenInput +from django.contrib.auth.models import User + +input_css_class='form-control' +# label_class='labelling' +input_css_class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" + +class ProductForm(forms.ModelForm): + name=forms.CharField(widget=forms.TextInput(attrs={'class':"form-control"})) + class Meta: + model=Product + fields={ + 'name', + 'handle', + 'price' + } + field_order=['name','handle','price'] + def __init__(self,*args,**kwargs): + super().__init__(*args,**kwargs) + for field in self.fields: + # self.fields['label'].widget.attrs['class']=label_class + self.fields[field].widget.attrs['class']=input_css_class + + +class ProductUpdateForm(forms.ModelForm): + name=forms.CharField(widget=forms.TextInput(attrs={'class':"form-control"})) + class Meta: + model=Product + fields={ + 'image', + 'name', + 'handle', + 'price' + } + field_order=['name','handle','price','image'] + def __init__(self,*args,**kwargs): + super().__init__(*args,**kwargs) + for field in self.fields: + # self.fields['label'].widget.attrs['class']=label_class + self.fields[field].widget.attrs['class']=input_css_class + + +class ProductAttachmentForm(forms.ModelForm): + # name=forms.CharField(widget=forms.TextInput(attrs={'class':"form-control"})) + class Meta: + model=ProductAttachment + fields=[ + 'file', + 'name', + 'active', + 'is_free' + ] + + # field_order=['name','handle','price','file'] + def __init__(self,*args,**kwargs): + super().__init__(*args,**kwargs) + for field in self.fields: + if field in ['active','is_free']: + continue + # self.fields['label'].widget.attrs['class']=label_class + self.fields[field].widget.attrs['class']=input_css_class + + + +ProductAttachmentModelFormset=modelformset_factory( + ProductAttachment, + form=ProductAttachmentForm, + fields=['file','name','is_free','active'], + extra=0, + can_delete=False) + +ProductAttachmentInlineFormset=inlineformset_factory(Product, + ProductAttachment, + form=ProductAttachmentForm, + formset=ProductAttachmentModelFormset, + fields=['file','name','is_free','active'], + extra=0, + can_delete=True) + diff --git a/src/products/migrations/0001_initial.py b/src/products/migrations/0001_initial.py new file mode 100644 index 0000000..dbd0542 --- /dev/null +++ b/src/products/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 4.1.13 on 2024-01-12 08:54 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=120)), + ('handle', models.SlugField(unique=True)), + ('price', models.DecimalField(decimal_places=2, default=9.99, max_digits=10)), + ('og_price', models.DecimalField(decimal_places=2, default=9.99, max_digits=10)), + ('stripe_price', models.IntegerField(default=999)), + ('price_changed_timestramp', models.DateTimeField(blank=True, null=True)), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('user', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/src/products/migrations/0002_product_image.py b/src/products/migrations/0002_product_image.py new file mode 100644 index 0000000..c8197ba --- /dev/null +++ b/src/products/migrations/0002_product_image.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.13 on 2024-01-16 10:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='product', + name='image', + field=models.ImageField(blank=True, null=True, upload_to='products/'), + ), + ] diff --git a/src/products/migrations/0003_productattachment.py b/src/products/migrations/0003_productattachment.py new file mode 100644 index 0000000..e7b17a4 --- /dev/null +++ b/src/products/migrations/0003_productattachment.py @@ -0,0 +1,28 @@ +# Generated by Django 4.1.13 on 2024-01-17 07:33 + +import django.core.files.storage +from django.db import migrations, models +import django.db.models.deletion +import products.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0002_product_image'), + ] + + operations = [ + migrations.CreateModel( + name='ProductAttachment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('file', models.FileField(blank=True, null=True, storage=django.core.files.storage.FileSystemStorage(location='C:\\Users\\Sail\\dev\\micro-ecommerce\\src\\local-cdn\\protected'), upload_to=products.models.handle_product_attachment_upload)), + ('is_free', models.BooleanField(default=False)), + ('active', models.BooleanField(default=True)), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.product')), + ], + ), + ] diff --git a/src/products/migrations/0004_productattachment_name.py b/src/products/migrations/0004_productattachment_name.py new file mode 100644 index 0000000..0a6900e --- /dev/null +++ b/src/products/migrations/0004_productattachment_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.13 on 2024-01-18 07:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0003_productattachment'), + ] + + operations = [ + migrations.AddField( + model_name='productattachment', + name='name', + field=models.CharField(blank=True, max_length=120, null=True), + ), + ] diff --git a/src/products/migrations/0005_product_stripe_price_id_product_stripe_product_id.py b/src/products/migrations/0005_product_stripe_price_id_product_stripe_product_id.py new file mode 100644 index 0000000..c854da8 --- /dev/null +++ b/src/products/migrations/0005_product_stripe_price_id_product_stripe_product_id.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.13 on 2024-01-22 14:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0004_productattachment_name'), + ] + + operations = [ + migrations.AddField( + model_name='product', + name='stripe_price_id', + field=models.CharField(blank=True, max_length=220, null=True), + ), + migrations.AddField( + model_name='product', + name='stripe_product_id', + field=models.CharField(blank=True, max_length=220, null=True), + ), + ] diff --git a/src/products/migrations/0006_alter_product_stripe_price_id.py b/src/products/migrations/0006_alter_product_stripe_price_id.py new file mode 100644 index 0000000..1307bf8 --- /dev/null +++ b/src/products/migrations/0006_alter_product_stripe_price_id.py @@ -0,0 +1,20 @@ +# Generated by Django 4.1.13 on 2024-01-24 17:37 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0005_product_stripe_price_id_product_stripe_product_id'), + ] + + operations = [ + migrations.AlterField( + model_name='product', + name='stripe_price_id', + field=models.CharField(default=django.utils.timezone.now, max_length=220), + preserve_default=False, + ), + ] diff --git a/src/products/migrations/0007_alter_product_stripe_price_id.py b/src/products/migrations/0007_alter_product_stripe_price_id.py new file mode 100644 index 0000000..1f3c131 --- /dev/null +++ b/src/products/migrations/0007_alter_product_stripe_price_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.13 on 2024-01-24 17:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0006_alter_product_stripe_price_id'), + ] + + operations = [ + migrations.AlterField( + model_name='product', + name='stripe_price_id', + field=models.CharField(blank=True, default=0, max_length=220, null=True), + ), + ] diff --git a/src/products/migrations/0008_alter_product_stripe_price_id.py b/src/products/migrations/0008_alter_product_stripe_price_id.py new file mode 100644 index 0000000..b38724e --- /dev/null +++ b/src/products/migrations/0008_alter_product_stripe_price_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.13 on 2024-01-25 16:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0007_alter_product_stripe_price_id'), + ] + + operations = [ + migrations.AlterField( + model_name='product', + name='stripe_price_id', + field=models.CharField(blank=True, max_length=220, null=True), + ), + ] diff --git a/src/products/migrations/0009_product_sp_id_alter_product_stripe_price_id.py b/src/products/migrations/0009_product_sp_id_alter_product_stripe_price_id.py new file mode 100644 index 0000000..dea7a56 --- /dev/null +++ b/src/products/migrations/0009_product_sp_id_alter_product_stripe_price_id.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.13 on 2024-01-26 10:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0008_alter_product_stripe_price_id'), + ] + + operations = [ + migrations.AddField( + model_name='product', + name='sp_id', + field=models.CharField(blank=True, max_length=320, null=True), + ), + migrations.AlterField( + model_name='product', + name='stripe_price_id', + field=models.CharField(blank=True, max_length=320, null=True), + ), + ] diff --git a/src/products/migrations/0010_customuser.py b/src/products/migrations/0010_customuser.py new file mode 100644 index 0000000..37da823 --- /dev/null +++ b/src/products/migrations/0010_customuser.py @@ -0,0 +1,43 @@ +# Generated by Django 4.1.13 on 2024-02-07 17:09 + +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('products', '0009_product_sp_id_alter_product_stripe_price_id'), + ] + + operations = [ + migrations.CreateModel( + name='CustomUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/src/products/migrations/0011_delete_customuser.py b/src/products/migrations/0011_delete_customuser.py new file mode 100644 index 0000000..4e8abf1 --- /dev/null +++ b/src/products/migrations/0011_delete_customuser.py @@ -0,0 +1,16 @@ +# Generated by Django 4.1.13 on 2024-02-10 18:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0010_customuser'), + ] + + operations = [ + migrations.DeleteModel( + name='CustomUser', + ), + ] diff --git a/src/products/migrations/__init__.py b/src/products/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/products/models.py b/src/products/models.py new file mode 100644 index 0000000..6b50eca --- /dev/null +++ b/src/products/models.py @@ -0,0 +1,132 @@ +from django.db import models +from django.conf import settings +from django.core.files.storage import FileSystemStorage +from django.utils import timezone +import pathlib +import stripe +from django.urls import reverse +# from django.contrib.auth.models import AbstractUser + + + +from cfehome.env import config + +STRIPE_SECRET_KEY = config("STRIPE_SECRET_KEY",default=None) +stripe.api_key=STRIPE_SECRET_KEY + +PROTECTED_MEDIA_ROOT=settings.PROTECTED_MEDIA_ROOT +protected_storage=FileSystemStorage(location=str(PROTECTED_MEDIA_ROOT)) +# Create your models here. +class Product(models.Model): + user=models.ForeignKey(settings.AUTH_USER_MODEL,default=1,on_delete=models.CASCADE) + name=models.CharField(max_length=120) + handle=models.SlugField(unique=True) + image=models.ImageField(upload_to='products/',blank=True,null=True) + price=models.DecimalField(max_digits=10,decimal_places=2,default=9.99) + og_price=models.DecimalField(max_digits=10,decimal_places=2,default=9.99) + stripe_product_id=models.CharField(max_length=220,blank=True,null=True) + stripe_price_id=models.CharField(max_length=320,blank=True,null=True) + sp_id=models.CharField(max_length=320,blank=True,null=True) + stripe_price=models.IntegerField(default=999)#100*price + price_changed_timestramp=models.DateTimeField(auto_now=False,auto_now_add=False,blank=True,null=True) + timestamp=models.DateTimeField(auto_now_add=True) + updated=models.DateTimeField(auto_now=True) + + + def save(self,*args,**kwargs): + if self.name: + stripe_product_r=stripe.Product.create(name=self.name) + self.stripe_product_id=stripe_product_r.id + stripe_price_obj=stripe.Price.create(product=self.stripe_product_id + ,unit_amount=self.stripe_price, + currency="usd" + ) + self.stripe_price_id=stripe_price_obj.id + # self.stripe_price_id=stripe.Price.retrieve(id=stripe_price_obj.id).id + if not self.stripe_price_id: + stripe_product_r=stripe.Product.create(name=self.name) + self.stripe_product_id=stripe_product_r.id + stripe_price_obj=stripe.Price.create(product=self.stripe_product_id + ,unit_amount=self.stripe_price, + currency="usd" + ) + # self.stripe_price_id=stripe.Price.retrieve(id=stripe_price_obj.id).id + self.stripe_price_id=stripe_price_obj.id + if self.price!=self.og_price: + #price changed + self.og_price=self.price + self.stripe_price=int(self.price*100) + #trigger an API request + if self.stripe_product_id: + stripe_price_obj=stripe.Price.create(product=self.stripe_product_id + ,unit_amount=self.stripe_price, + currency="usd" + ) + self.stripe_price_id=stripe_price_obj.id + self.price_changed_timestramp=timezone.now() + super().save(*args,**kwargs) + + + + # def get_stripe_id(self): + # if not self.sp_id: + # stripe_product_r=stripe.Product.create(name=self.name) + # self.stripe_product_id=stripe_product_r.id + # stripe_price_obj=stripe.Price.create(product=self.stripe_product_id + # ,unit_amount=self.stripe_price, + # currency="usd" + # ) + # self.sp_id=stripe.Price.retrieve(id=stripe_price_obj.id).id + + # # stripe_product_r=stripe.Product.create(name=self.name) + # # self.stripe_product_id=stripe_product_r.id + # # stripe_price_obj=stripe.Price.create(product=self.stripe_product_id + # # ,unit_amount=self.stripe_price, + # # currency="usd" + # # ) + # # self.sp_id=stripe.Price.retrieve(id=stripe_price_obj.id).id + # super().save() + + + @property + def display_name(self): + return self.name + + @property + def display_price(self): + return self.price + + def __str__(self): + return self.display_name + + def get_absolute_url(self): + return reverse("products:detail",kwargs={'handle':self.handle}) + + def get_manage_url(self): + return reverse("products:manage",kwargs={'handle':self.handle}) + +def handle_product_attachment_upload(instance,filename): + return f"products/{instance.product.handle}/attachments/{filename}" + + +class ProductAttachment(models.Model): + product=models.ForeignKey(Product,on_delete=models.CASCADE) + file=models.FileField(upload_to=handle_product_attachment_upload,blank=True,null=True,storage=protected_storage) + is_free=models.BooleanField(default=False) + name=models.CharField(max_length=120,null=True,blank=True) + active=models.BooleanField(default=True) + timestamp=models.DateTimeField(auto_now_add=True) + updated=models.DateTimeField(auto_now=True) + + # def save(self,*args,**kwargs): + # if self.file and not self.name: + # self.name=pathlib.Path(self.file.name).name #suffix + # super().save(*args,**kwargs) + + @property + def display_name(self): + return self.name or pathlib.Path(self.file.name).name + + def get_download_url(self): + return reverse("products:download_attachment",kwargs={'handle':self.product.handle,'pk':self.pk}) + diff --git a/src/products/tests.py b/src/products/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/products/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/products/urls.py b/src/products/urls.py new file mode 100644 index 0000000..bcdfebf --- /dev/null +++ b/src/products/urls.py @@ -0,0 +1,12 @@ +from django.urls import path + +from . import views + +app_name='products' +urlpatterns=[ + path('',views.product_list_view,name='list'), + path('create/',views.product_create_view,name='create'), + path('/',views.product_detail_view,name='detail'), + path('/manage/',views.product_manage_detail_view,name='manage'), + path('/download/',views.product_attachment_download_view,name="download_attachment") +] \ No newline at end of file diff --git a/src/products/utils.py b/src/products/utils.py new file mode 100644 index 0000000..fb6db09 --- /dev/null +++ b/src/products/utils.py @@ -0,0 +1,37 @@ +import stripe + +from cfehome.env import config + +STRIPE_SECRET_KEY = config("STRIPE_SECRET_KEY",default=None) +stripe.api_key=STRIPE_SECRET_KEY +stripe.api_version = '2020-08-27' + +def product_sales_pipeline(product_name="My Product",product_price=1000): + stripe_product_obj=stripe.Product.create(name=product_name) + stripe_product_id=stripe_product_obj.id + stripe_price_obj=stripe.Price.create(product=stripe_product_id + ,unit_amount=product_price, + currency="usd" + ) + stripe_price_id=stripe_price_obj.id + stripe.Price.retrieve(id=stripe_price_id) + base_endpoint='http://127.0.0.1:8000/' + success_url=f"{base_endpoint}/purchase/success/" + cancel_url=f"{base_endpoint}/purchase/stopped/" + checkout_session=stripe.checkout.Session.create( + line_items=[ + { + 'price':stripe_price_id, + 'quantity':1, + } + ], + mode="payment", + success_url=success_url, + cancel_url=cancel_url + ) + print(checkout_session.url) + print(checkout_session) + print(stripe_product_id) + print(stripe.Price.retrieve(id=stripe_price_id).id) + + # print(stripe.Price.re) \ No newline at end of file diff --git a/src/products/views.py b/src/products/views.py new file mode 100644 index 0000000..73a40df --- /dev/null +++ b/src/products/views.py @@ -0,0 +1,107 @@ +from django.shortcuts import render,redirect,get_object_or_404 +from .models import Product,ProductAttachment +import mimetypes +from django.http import HttpResponseBadRequest,FileResponse +from .forms import ProductForm,ProductUpdateForm,ProductAttachmentModelFormset,ProductAttachmentInlineFormset +# from django.contrib.auth import authenticate,get_user,base_user + +# Create your views here. +def product_create_view(request): + context={} + form=ProductForm(request.POST or None) + if form.is_valid(): + obj=form.save(commit=False) + if request.user.is_authenticated: + obj.user=request.user + obj.save() + return redirect(obj.get_manage_url()) + else: + form.add_error(None,"User must be logged in") + + context['form']=form + return render(request,'products/create.html',context) + +def product_list_view(request): + obj_list=Product.objects.all() + return render(request,'products/list.html',{'objects':obj_list}) + + +def product_detail_view(request,handle=None): + obj=get_object_or_404(Product,handle=handle) + attachments=ProductAttachment.objects.filter(product=obj) + # attachments=obj.productattachmentset.all() + is_owner=False + if request.user.is_authenticated: + is_owner=request.user.purchase_set.all().filter(product=obj,completed=True).exists() + context={'obj':obj,'is_owner':is_owner,'attachments':attachments,'product':Product} + return render(request,'products/detail.html',context) + + +# def product_manage_detail_view(request, handle=None): + obj = get_object_or_404(Product, handle=handle) + attachments = ProductAttachment.objects.filter(product=obj) + is_manager = False + + if request.user.is_authenticated: + is_manager = obj.user == request.user + + context = {'obj': obj} + + if not is_manager: + return HttpResponseBadRequest() + + form = ProductUpdateForm(request.POST or None, request.FILES or None, instance=obj) + formset = ProductAttachmentInlineFormset(request.POST or None, queryset=attachments) + + if form.is_valid() and formset.is_valid(): + # Save the product instance + form.save() + + # Save the formset instances + for attachment_obj in formset: + attachment_obj.product = obj + attachment_obj.save() + + return redirect(obj.get_manage_url()) + + context['form'] = form + context['formset'] = formset + + return render(request, 'products/manager.html', context) + +def product_manage_detail_view(request,handle=None): + obj=get_object_or_404(Product,handle=handle) + attachments=ProductAttachment.objects.filter(product=obj) + is_manager=False + if request.user.is_authenticated: + is_manager=obj.user==request.user + context={'obj':obj} + if not is_manager: + return HttpResponseBadRequest() + form=ProductUpdateForm(request.POST or None,request.FILES or None,instance=obj) + formset=ProductAttachmentInlineFormset(request.POST or None,queryset=attachments) + if form.is_valid() or formset.is_valid(): + print(attachments) + instance=form.save(commit=False) + instance.save() + attachment_obj=formset.save(commit=False) + attachment_obj.save() + return redirect(obj.get_manage_url()) + context['form']=form + context['formset']=formset + return render(request,'products/manager.html',context) + +def product_attachment_download_view(request,handle=None,pk=None): + attachment=get_object_or_404(ProductAttachment,product__handle=handle,pk=pk) + can_download=attachment.is_free or False + if request.user.is_authenticated: + can_download=True + if can_download is False: + return HttpResponseBadRequest() + file=attachment.file.open(mode='rb') + filename=attachment.file.name + content_type=mimetypes.guess_type(filename) + response=FileResponse(file) + response['Content-Type']=content_type or 'application/octet-stream' + response['Content-Disposition']=f'attachment;filename={file.name}' + return response diff --git a/src/purchase/__init__.py b/src/purchase/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/purchase/admin.py b/src/purchase/admin.py new file mode 100644 index 0000000..df6b397 --- /dev/null +++ b/src/purchase/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from .models import Purchase +# Register your models here. +admin.site.register(Purchase) \ No newline at end of file diff --git a/src/purchase/apps.py b/src/purchase/apps.py new file mode 100644 index 0000000..2373392 --- /dev/null +++ b/src/purchase/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PurchaseConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'purchase' diff --git a/src/purchase/migrations/0001_initial.py b/src/purchase/migrations/0001_initial.py new file mode 100644 index 0000000..f70b95e --- /dev/null +++ b/src/purchase/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 4.1.13 on 2024-01-22 08:12 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('products', '0004_productattachment_name'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Purchase', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=120)), + ('completed', models.BooleanField(default=False)), + ('stripe_price', models.IntegerField(default=0)), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('product', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='products.product')), + ('user', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/src/purchase/migrations/0002_remove_purchase_name.py b/src/purchase/migrations/0002_remove_purchase_name.py new file mode 100644 index 0000000..8103659 --- /dev/null +++ b/src/purchase/migrations/0002_remove_purchase_name.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1.13 on 2024-01-22 09:14 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('purchase', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='purchase', + name='name', + ), + ] diff --git a/src/purchase/migrations/0003_purchase_stripe_checkout_session_id.py b/src/purchase/migrations/0003_purchase_stripe_checkout_session_id.py new file mode 100644 index 0000000..b729de4 --- /dev/null +++ b/src/purchase/migrations/0003_purchase_stripe_checkout_session_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.13 on 2024-01-22 15:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('purchase', '0002_remove_purchase_name'), + ] + + operations = [ + migrations.AddField( + model_name='purchase', + name='stripe_checkout_session_id', + field=models.CharField(blank=True, max_length=220, null=True), + ), + ] diff --git a/src/purchase/migrations/__init__.py b/src/purchase/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/purchase/models.py b/src/purchase/models.py new file mode 100644 index 0000000..4755600 --- /dev/null +++ b/src/purchase/models.py @@ -0,0 +1,23 @@ +from django.db import models +from django.conf import settings +from products.models import Product +# Create your models here. +class Purchase(models.Model): + user=models.ForeignKey(settings.AUTH_USER_MODEL,default=1,on_delete=models.CASCADE) + product=models.ForeignKey(Product,null=True,on_delete=models.SET_NULL) + completed=models.BooleanField(default=False) + stripe_price=models.IntegerField(default=0) + stripe_checkout_session_id=models.CharField(max_length=220,null=True,blank=True) + timestamp=models.DateTimeField(auto_now_add=True) + + @property + def display_name(self): + if self.completed: + return f"purchase_Successful_{self.pk}" + else: + return f"purchase_Unsuccessful_{self.pk}" + + + def __str__(self): + return self.display_name + diff --git a/src/purchase/tests.py b/src/purchase/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/purchase/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/purchase/urls.py b/src/purchase/urls.py new file mode 100644 index 0000000..0175ae8 --- /dev/null +++ b/src/purchase/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from purchase import views + +app_name='purchase' +urlpatterns=[ + path('start/',views.purchase_start_view,name='start'), + path('success/',views.purchase_success_view,name='success'), + path('stopped/',views.purchase_stopped_view,name='stopped'), + +] \ No newline at end of file diff --git a/src/purchase/views.py b/src/purchase/views.py new file mode 100644 index 0000000..42eecc5 --- /dev/null +++ b/src/purchase/views.py @@ -0,0 +1,116 @@ +from django.shortcuts import get_object_or_404,redirect +from django.http import HttpResponseBadRequest,HttpResponse,HttpResponseRedirect +from .models import Purchase +from products.models import Product +from products.models import Product +import stripe +from cfehome.env import config +from django.urls import reverse +from django.conf import settings,urls + +# stripe_pricef_id=Product.get_stripe_id(Product) +STRIPE_SECRET_KEY = config("STRIPE_SECRET_KEY",default=None) +stripe.api_key=STRIPE_SECRET_KEY +base_endpoint="http://127.0.0.1:8000/" + + + +def purchase_start_view(request): + if request.method != 'POST' or not request.user.is_authenticated: + return HttpResponseBadRequest("Invalid request") + + handle = request.POST.get('handle') + obj = get_object_or_404(Product, handle=handle) + + purchase = Purchase.objects.create(user=request.user, product=obj) + request.session['purchase_id'] = purchase.id + + success_path = reverse("purchase:success") + cancel_path = reverse("purchase:stopped") + success_url = f"{base_endpoint}{success_path}" # Assuming base_endpoint is defined somewhere + cancel_url = f"{base_endpoint}{cancel_path}" + + stripe_p_id = obj.stripe_price_id + print(stripe_p_id) + # try: + checkout_session = stripe.checkout.Session.create( + line_items=[ + { + 'price':stripe_p_id, + 'quantity':1, + } + ], + mode='payment', + success_url=success_url, + cancel_url=cancel_url + ) + + purchase.stripe_checkout_session_id = checkout_session.id + purchase.save() + + print(checkout_session) + # print(stripe_price_id) + + return HttpResponseRedirect(checkout_session.url) + + # except stripe.error.StripeError as e: + # print(f"Stripe Error: {e.error.message}") + # return HttpResponseBadRequest("Error creating Checkout Session") + +# def purchase_start_view(request): + if not request.method=='POST': + return HttpResponseBadRequest() + if not request.user.is_authenticated: + return HttpResponseBadRequest() + handle=request.POST.get('handle') + obj=Product.objects.get(handle=handle) + + # if stripe_price_id is None: + # return HttpResponseBadRequest("Something went wrong") + purchase=Purchase.objects.create(user=request.user,product=obj) + request.session['purchase_id']=purchase.id + success_path=reverse("purchase:success") + if not success_path.startswith("/"): + success_path=f"/{success_path}" + cancel_path=reverse("purchase:stopped") + success_url=f"{base_endpoint}{success_path}" + cancel_url=f"{base_endpoint}{cancel_path}" + stripe_price_id=obj.stripe_price_id + checkout_session=stripe.checkout.Session.create( + line_items=[ + { + 'price_data': { + 'currency': 'usd', + 'product': stripe_price_id, + 'unit_amount': 1000, # Replace with your actual amount in cents + }, + 'quantity':1, + } + ], + mode="payment", + success_url=success_url, + cancel_url=cancel_url + ) + purchase.stripe_checkout_session_id=checkout_session.id + purchase.save() + print(checkout_session) + # print(success_path) + # print(cancel_url) + print(stripe_price_id) + # return HttpResponse("Start") + return HttpResponseRedirect(checkout_session.url) + +def purchase_success_view(request): + + purchase_id=request.session.get('purchase_id') + if purchase_id: + purchase=Purchase.objects.get(id=purchase_id) + purchase.completed=True + purchase.save() + # p=request.product + # product_handle=request.POST.get('product') + return redirect(reverse('/products/')) + + +def purchase_stopped_view(request): + return HttpResponse('stopped') \ No newline at end of file diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..1fd7de6 --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1,46 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --output-file=src/requirements.txt src/requirements/requirements.in +# +asgiref==3.7.2 + # via django +certifi==2023.11.17 + # via requests +charset-normalizer==3.3.2 + # via requests +dj-database-url==2.1.0 + # via -r src/requirements/requirements.in +django==4.1.13 + # via + # -r src/requirements/requirements.in + # dj-database-url +gunicorn==21.2.0 + # via -r src/requirements/requirements.in +idna==3.6 + # via requests +packaging==23.2 + # via gunicorn +pillow==10.2.0 + # via -r src/requirements/requirements.in +psycopg2==2.9.9 + # via -r src/requirements/requirements.in +psycopg2-binary==2.9.9 + # via -r src/requirements/requirements.in +python-decouple==3.8 + # via -r src/requirements/requirements.in +requests==2.31.0 + # via stripe +sqlparse==0.4.4 + # via django +stripe==7.13.0 + # via -r src/requirements/requirements.in +typing-extensions==4.9.0 + # via + # dj-database-url + # stripe +tzdata==2023.4 + # via django +urllib3==2.1.0 + # via requests diff --git a/src/requirements/requirements.in b/src/requirements/requirements.in index 4d4a0fb..b472235 100644 --- a/src/requirements/requirements.in +++ b/src/requirements/requirements.in @@ -1 +1,8 @@ -Django>4.1,<4.2 \ No newline at end of file +Django>4.1,<4.2 +psycopg2 +dj-database-url +python-decouple +pillow +stripe +gunicorn +psycopg2-binary \ No newline at end of file diff --git a/src/static/css/output.css b/src/static/css/output.css new file mode 100644 index 0000000..083cd26 --- /dev/null +++ b/src/static/css/output.css @@ -0,0 +1,2758 @@ +/* +! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #E5E7EB; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9CA3AF; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9CA3AF; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; +} + +.tooltip-arrow,.tooltip-arrow:before { + position: absolute; + width: 8px; + height: 8px; + background: inherit; +} + +.tooltip-arrow { + visibility: hidden; +} + +.tooltip-arrow:before { + content: ""; + visibility: visible; + transform: rotate(45deg); +} + +[data-tooltip-style^='light'] + .tooltip > .tooltip-arrow:before { + border-style: solid; + border-color: #e5e7eb; +} + +[data-tooltip-style^='light'] + .tooltip[data-popper-placement^='top'] > .tooltip-arrow:before { + border-bottom-width: 1px; + border-right-width: 1px; +} + +[data-tooltip-style^='light'] + .tooltip[data-popper-placement^='right'] > .tooltip-arrow:before { + border-bottom-width: 1px; + border-left-width: 1px; +} + +[data-tooltip-style^='light'] + .tooltip[data-popper-placement^='bottom'] > .tooltip-arrow:before { + border-top-width: 1px; + border-left-width: 1px; +} + +[data-tooltip-style^='light'] + .tooltip[data-popper-placement^='left'] > .tooltip-arrow:before { + border-top-width: 1px; + border-right-width: 1px; +} + +.tooltip[data-popper-placement^='top'] > .tooltip-arrow { + bottom: -4px; +} + +.tooltip[data-popper-placement^='bottom'] > .tooltip-arrow { + top: -4px; +} + +.tooltip[data-popper-placement^='left'] > .tooltip-arrow { + right: -4px; +} + +.tooltip[data-popper-placement^='right'] > .tooltip-arrow { + left: -4px; +} + +.tooltip.invisible > .tooltip-arrow:before { + visibility: hidden; +} + +[data-popper-arrow],[data-popper-arrow]:before { + position: absolute; + width: 8px; + height: 8px; + background: inherit; +} + +[data-popper-arrow] { + visibility: hidden; +} + +[data-popper-arrow]:before { + content: ""; + visibility: visible; + transform: rotate(45deg); +} + +[data-popper-arrow]:after { + content: ""; + visibility: visible; + transform: rotate(45deg); + position: absolute; + width: 9px; + height: 9px; + background: inherit; +} + +[role="tooltip"] > [data-popper-arrow]:before { + border-style: solid; + border-color: #e5e7eb; +} + +.dark [role="tooltip"] > [data-popper-arrow]:before { + border-style: solid; + border-color: #4b5563; +} + +[role="tooltip"] > [data-popper-arrow]:after { + border-style: solid; + border-color: #e5e7eb; +} + +.dark [role="tooltip"] > [data-popper-arrow]:after { + border-style: solid; + border-color: #4b5563; +} + +[data-popover][role="tooltip"][data-popper-placement^='top'] > [data-popper-arrow]:before { + border-bottom-width: 1px; + border-right-width: 1px; +} + +[data-popover][role="tooltip"][data-popper-placement^='top'] > [data-popper-arrow]:after { + border-bottom-width: 1px; + border-right-width: 1px; +} + +[data-popover][role="tooltip"][data-popper-placement^='right'] > [data-popper-arrow]:before { + border-bottom-width: 1px; + border-left-width: 1px; +} + +[data-popover][role="tooltip"][data-popper-placement^='right'] > [data-popper-arrow]:after { + border-bottom-width: 1px; + border-left-width: 1px; +} + +[data-popover][role="tooltip"][data-popper-placement^='bottom'] > [data-popper-arrow]:before { + border-top-width: 1px; + border-left-width: 1px; +} + +[data-popover][role="tooltip"][data-popper-placement^='bottom'] > [data-popper-arrow]:after { + border-top-width: 1px; + border-left-width: 1px; +} + +[data-popover][role="tooltip"][data-popper-placement^='left'] > [data-popper-arrow]:before { + border-top-width: 1px; + border-right-width: 1px; +} + +[data-popover][role="tooltip"][data-popper-placement^='left'] > [data-popper-arrow]:after { + border-top-width: 1px; + border-right-width: 1px; +} + +[data-popover][role="tooltip"][data-popper-placement^='top'] > [data-popper-arrow] { + bottom: -5px; +} + +[data-popover][role="tooltip"][data-popper-placement^='bottom'] > [data-popper-arrow] { + top: -5px; +} + +[data-popover][role="tooltip"][data-popper-placement^='left'] > [data-popper-arrow] { + right: -5px; +} + +[data-popover][role="tooltip"][data-popper-placement^='right'] > [data-popper-arrow] { + left: -5px; +} + +[role="tooltip"].invisible > [data-popper-arrow]:before { + visibility: hidden; +} + +[role="tooltip"].invisible > [data-popper-arrow]:after { + visibility: hidden; +} + +[type='text'],[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #fff; + border-color: #6B7280; + border-width: 1px; + border-radius: 0px; + padding-top: 0.5rem; + padding-right: 0.75rem; + padding-bottom: 0.5rem; + padding-left: 0.75rem; + font-size: 1rem; + line-height: 1.5rem; + --tw-shadow: 0 0 #0000; +} + +[type='text']:focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #1C64F2; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + border-color: #1C64F2; +} + +input::-moz-placeholder, textarea::-moz-placeholder { + color: #6B7280; + opacity: 1; +} + +input::placeholder,textarea::placeholder { + color: #6B7280; + opacity: 1; +} + +::-webkit-datetime-edit-fields-wrapper { + padding: 0; +} + +::-webkit-date-and-time-value { + min-height: 1.5em; +} + +select:not([size]) { + background-image: url("data:image/svg+xml,%3csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 10 6'%3e %3cpath stroke='%236B7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 1 4 4 4-4'/%3e %3c/svg%3e"); + background-position: right 0.75rem center; + background-repeat: no-repeat; + background-size: 0.75em 0.75em; + padding-right: 2.5rem; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} + +:is([dir=rtl]) select:not([size]) { + background-position: left 0.75rem center; + padding-right: 0.75rem; + padding-left: 0; +} + +[multiple] { + background-image: initial; + background-position: initial; + background-repeat: unset; + background-size: initial; + padding-right: 0.75rem; + -webkit-print-color-adjust: unset; + print-color-adjust: unset; +} + +[type='checkbox'],[type='radio'] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + padding: 0; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + display: inline-block; + vertical-align: middle; + background-origin: border-box; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + flex-shrink: 0; + height: 1rem; + width: 1rem; + color: #1C64F2; + background-color: #fff; + border-color: #6B7280; + border-width: 1px; + --tw-shadow: 0 0 #0000; +} + +[type='checkbox'] { + border-radius: 0px; +} + +[type='radio'] { + border-radius: 100%; +} + +[type='checkbox']:focus,[type='radio']:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 2px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #1C64F2; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); +} + +[type='checkbox']:checked,[type='radio']:checked,.dark [type='checkbox']:checked,.dark [type='radio']:checked { + border-color: transparent; + background-color: currentColor; + background-size: 0.55em 0.55em; + background-position: center; + background-repeat: no-repeat; +} + +[type='checkbox']:checked { + background-image: url("data:image/svg+xml,%3csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 12'%3e %3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M1 5.917 5.724 10.5 15 1.5'/%3e %3c/svg%3e"); + background-repeat: no-repeat; + background-size: 0.55em 0.55em; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} + +[type='radio']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); + background-size: 1em 1em; +} + +.dark [type='radio']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); + background-size: 1em 1em; +} + +[type='checkbox']:indeterminate { + background-image: url("data:image/svg+xml,%3csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 12'%3e %3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M1 5.917 5.724 10.5 15 1.5'/%3e %3c/svg%3e"); + background-color: currentColor; + border-color: transparent; + background-position: center; + background-repeat: no-repeat; + background-size: 0.55em 0.55em; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} + +[type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus { + border-color: transparent; + background-color: currentColor; +} + +[type='file'] { + background: unset; + border-color: inherit; + border-width: 0; + border-radius: 0; + padding: 0; + font-size: unset; + line-height: inherit; +} + +[type='file']:focus { + outline: 1px auto inherit; +} + +input[type=file]::file-selector-button { + color: white; + background: #1F2937; + border: 0; + font-weight: 500; + font-size: 0.875rem; + cursor: pointer; + padding-top: 0.625rem; + padding-bottom: 0.625rem; + padding-left: 2rem; + padding-right: 1rem; + margin-inline-start: -1rem; + margin-inline-end: 1rem; +} + +input[type=file]::file-selector-button:hover { + background: #374151; +} + +:is([dir=rtl]) input[type=file]::file-selector-button { + padding-right: 2rem; + padding-left: 1rem; +} + +.dark input[type=file]::file-selector-button { + color: white; + background: #4B5563; +} + +.dark input[type=file]::file-selector-button:hover { + background: #6B7280; +} + +input[type="range"]::-webkit-slider-thumb { + height: 1.25rem; + width: 1.25rem; + background: #1C64F2; + border-radius: 9999px; + border: 0; + appearance: none; + -moz-appearance: none; + -webkit-appearance: none; + cursor: pointer; +} + +input[type="range"]:disabled::-webkit-slider-thumb { + background: #9CA3AF; +} + +.dark input[type="range"]:disabled::-webkit-slider-thumb { + background: #6B7280; +} + +input[type="range"]:focus::-webkit-slider-thumb { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + --tw-ring-opacity: 1px; + --tw-ring-color: rgb(164 202 254 / var(--tw-ring-opacity)); +} + +input[type="range"]::-moz-range-thumb { + height: 1.25rem; + width: 1.25rem; + background: #1C64F2; + border-radius: 9999px; + border: 0; + appearance: none; + -moz-appearance: none; + -webkit-appearance: none; + cursor: pointer; +} + +input[type="range"]:disabled::-moz-range-thumb { + background: #9CA3AF; +} + +.dark input[type="range"]:disabled::-moz-range-thumb { + background: #6B7280; +} + +input[type="range"]::-moz-range-progress { + background: #3F83F8; +} + +input[type="range"]::-ms-fill-lower { + background: #3F83F8; +} + +.toggle-bg:after { + content: ""; + position: absolute; + top: 0.125rem; + left: 0.125rem; + background: white; + border-color: #D1D5DB; + border-width: 1px; + border-radius: 9999px; + height: 1.25rem; + width: 1.25rem; + transition-property: background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter; + transition-duration: .15s; + box-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); +} + +input:checked + .toggle-bg:after { + transform: translateX(100%);; + border-color: white; +} + +input:checked + .toggle-bg { + background: #1C64F2; + border-color: #1C64F2; +} + +.form-control { + display: block; + width: 100%; + border-radius: 0.5rem; + border-width: 1px; + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); + padding: 0.625rem; + font-size: 0.875rem; + line-height: 1.25rem; + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.form-control:focus { + --tw-border-opacity: 1; + border-color: rgb(63 131 248 / var(--tw-border-opacity)); + --tw-ring-opacity: 1; + --tw-ring-color: rgb(63 131 248 / var(--tw-ring-opacity)); +} + +:is(.dark .form-control) { + --tw-border-opacity: 1; + border-color: rgb(75 85 99 / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +:is(.dark .form-control)::-moz-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +:is(.dark .form-control)::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +:is(.dark .form-control:focus) { + --tw-border-opacity: 1; + border-color: rgb(63 131 248 / var(--tw-border-opacity)); + --tw-ring-opacity: 1; + --tw-ring-color: rgb(63 131 248 / var(--tw-ring-opacity)); +} + +.btn-submit { + width: 100%; + border-radius: 0.5rem; + --tw-bg-opacity: 1; + background-color: rgb(26 86 219 / var(--tw-bg-opacity)); + padding-left: 1.25rem; + padding-right: 1.25rem; + padding-top: 0.625rem; + padding-bottom: 0.625rem; + text-align: center; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); +} + +.btn-submit:hover { + --tw-bg-opacity: 1; + background-color: rgb(26 86 219 / var(--tw-bg-opacity)); +} + +.btn-submit:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + --tw-ring-opacity: 1; + --tw-ring-color: rgb(164 202 254 / var(--tw-ring-opacity)); +} + +:is(.dark .btn-submit) { + --tw-bg-opacity: 1; + background-color: rgb(28 100 242 / var(--tw-bg-opacity)); +} + +:is(.dark .btn-submit:hover) { + --tw-bg-opacity: 1; + background-color: rgb(26 86 219 / var(--tw-bg-opacity)); +} + +:is(.dark .btn-submit:focus) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(30 66 159 / var(--tw-ring-opacity)); +} + +@media (min-width: 640px) { + .btn-submit { + width: auto; + } +} + +.labelling { + margin-bottom: 0.5rem; + display: block; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +:is(.dark .labelling) { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.para { + margin-bottom: 0.75rem; + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + +.para::first-letter { + float: inline-start; + margin-inline-end: 0.75rem; + font-size: 4.5rem; + line-height: 1; + font-weight: 700; + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.para::first-line { + text-transform: uppercase; + letter-spacing: 0.1em; +} + +:is(.dark .para) { + --tw-text-opacity: 1; + color: rgb(156 163 175 / var(--tw-text-opacity)); +} + +:is(.dark .para)::first-letter { + --tw-text-opacity: 1; + color: rgb(243 244 246 / var(--tw-text-opacity)); +} + +.reg-form { + display: block; + width: 100%; + border-radius: 0.5rem; + border-width: 1px; + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity)); + padding: 0.625rem; + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.reg-form:focus { + --tw-border-opacity: 1; + border-color: rgb(28 100 242 / var(--tw-border-opacity)); + --tw-ring-opacity: 1; + --tw-ring-color: rgb(28 100 242 / var(--tw-ring-opacity)); +} + +:is(.dark .reg-form) { + --tw-border-opacity: 1; + border-color: rgb(75 85 99 / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +:is(.dark .reg-form)::-moz-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +:is(.dark .reg-form)::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +:is(.dark .reg-form:focus) { + --tw-border-opacity: 1; + border-color: rgb(63 131 248 / var(--tw-border-opacity)); + --tw-ring-opacity: 1; + --tw-ring-color: rgb(63 131 248 / var(--tw-ring-opacity)); +} + +@media (min-width: 640px) { + .reg-form { + font-size: 0.875rem; + line-height: 1.25rem; + } +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(63 131 248 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(63 131 248 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +.\!container { + width: 100% !important; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .\!container { + max-width: 640px !important; + } + + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .\!container { + max-width: 768px !important; + } + + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .\!container { + max-width: 1024px !important; + } + + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .\!container { + max-width: 1280px !important; + } + + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .\!container { + max-width: 1536px !important; + } + + .container { + max-width: 1536px; + } +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.visible { + visibility: visible; +} + +.invisible { + visibility: hidden; +} + +.collapse { + visibility: collapse; +} + +.static { + position: static; +} + +.\!fixed { + position: fixed !important; +} + +.fixed { + position: fixed; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.\!sticky { + position: sticky !important; +} + +.sticky { + position: sticky; +} + +.inset-0 { + inset: 0px; +} + +.bottom-0 { + bottom: 0px; +} + +.bottom-\[60px\] { + bottom: 60px; +} + +.left-0 { + left: 0px; +} + +.right-0 { + right: 0px; +} + +.top-0 { + top: 0px; +} + +.z-10 { + z-index: 10; +} + +.z-20 { + z-index: 20; +} + +.z-30 { + z-index: 30; +} + +.z-40 { + z-index: 40; +} + +.z-50 { + z-index: 50; +} + +.m-4 { + margin: 1rem; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.mb-1 { + margin-bottom: 0.25rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.me-2 { + margin-inline-end: 0.5rem; +} + +.ml-3 { + margin-left: 0.75rem; +} + +.mr-4 { + margin-right: 1rem; +} + +.ms-2 { + margin-inline-start: 0.5rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mt-3 { + margin-top: 0.75rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.inline { + display: inline; +} + +.flex { + display: flex; +} + +.inline-flex { + display: inline-flex; +} + +.table { + display: table; +} + +.table-caption { + display: table-caption; +} + +.table-cell { + display: table-cell; +} + +.grid { + display: grid; +} + +.contents { + display: contents; +} + +.\!hidden { + display: none !important; +} + +.hidden { + display: none; +} + +.h-3 { + height: 0.75rem; +} + +.h-3\.5 { + height: 0.875rem; +} + +.h-4 { + height: 1rem; +} + +.h-5 { + height: 1.25rem; +} + +.h-6 { + height: 1.5rem; +} + +.h-9 { + height: 2.25rem; +} + +.h-auto { + height: auto; +} + +.min-h-screen { + min-height: 100vh; +} + +.w-1\/2 { + width: 50%; +} + +.w-3 { + width: 0.75rem; +} + +.w-3\.5 { + width: 0.875rem; +} + +.w-4 { + width: 1rem; +} + +.w-6 { + width: 1.5rem; +} + +.w-64 { + width: 16rem; +} + +.w-full { + width: 100%; +} + +.max-w-full { + max-width: 100%; +} + +.max-w-screen-sm { + max-width: 640px; +} + +.max-w-screen-xl { + max-width: 1280px; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-shrink { + flex-shrink: 1; +} + +.-translate-x-full { + --tw-translate-x: -100%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.-translate-y-full { + --tw-translate-y: -100%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.translate-x-0 { + --tw-translate-x: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.translate-x-full { + --tw-translate-x: 100%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.translate-y-full { + --tw-translate-y: 100%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.rotate-180 { + --tw-rotate: 180deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.transform-none { + transform: none; +} + +.cursor-default { + cursor: default; +} + +.cursor-not-allowed { + cursor: not-allowed; +} + +.cursor-pointer { + cursor: pointer; +} + +.resize { + resize: both; +} + +.grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.grid-cols-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); +} + +.flex-col { + flex-direction: column; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.items-start { + align-items: flex-start; +} + +.items-end { + align-items: flex-end; +} + +.items-center { + align-items: center; +} + +.items-baseline { + align-items: baseline; +} + +.justify-start { + justify-content: flex-start; +} + +.justify-end { + justify-content: flex-end; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.gap-4 { + gap: 1rem; +} + +.gap-8 { + gap: 2rem; +} + +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-y-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); +} + +.space-y-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); +} + +.space-y-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} + +.space-y-5 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1.25rem * var(--tw-space-y-reverse)); +} + +.self-center { + align-self: center; +} + +.overflow-hidden { + overflow: hidden; +} + +.overflow-x-auto { + overflow-x: auto; +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.whitespace-nowrap { + white-space: nowrap; +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.rounded-e-lg { + border-start-end-radius: 0.5rem; + border-end-end-radius: 0.5rem; +} + +.rounded-l-lg { + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + +.rounded-r-lg { + border-top-right-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; +} + +.rounded-s-lg { + border-start-start-radius: 0.5rem; + border-end-start-radius: 0.5rem; +} + +.border { + border-width: 1px; +} + +.border-0 { + border-width: 0px; +} + +.border-b { + border-bottom-width: 1px; +} + +.border-none { + border-style: none; +} + +.border-blue-600 { + --tw-border-opacity: 1; + border-color: rgb(28 100 242 / var(--tw-border-opacity)); +} + +.border-blue-700 { + --tw-border-opacity: 1; + border-color: rgb(26 86 219 / var(--tw-border-opacity)); +} + +.border-gray-100 { + --tw-border-opacity: 1; + border-color: rgb(243 244 246 / var(--tw-border-opacity)); +} + +.border-gray-200 { + --tw-border-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-border-opacity)); +} + +.border-gray-300 { + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + +.border-white { + --tw-border-opacity: 1; + border-color: rgb(255 255 255 / var(--tw-border-opacity)); +} + +.bg-blue-700 { + --tw-bg-opacity: 1; + background-color: rgb(26 86 219 / var(--tw-bg-opacity)); +} + +.bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + +.bg-gray-200 { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + +.bg-gray-50 { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity)); +} + +.bg-gray-700 { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); +} + +.bg-gray-800 { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +.bg-gray-900\/50 { + background-color: rgb(17 24 39 / 0.5); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.bg-white\/50 { + background-color: rgb(255 255 255 / 0.5); +} + +.bg-blue-800 { + --tw-bg-opacity: 1; + background-color: rgb(30 66 159 / var(--tw-bg-opacity)); +} + +.bg-\[url\(\'https\:\/\/flowbite\.s3\.amazonaws\.com\/docs\/jumbotron\/hero-pattern\.svg\'\)\] { + background-image: url('https://flowbite.s3.amazonaws.com/docs/jumbotron/hero-pattern.svg'); +} + +.bg-\[url\(\'https\:\/\/images\.pexels\.com\/photos\/5632402\/pexels-photo-5632402\.jpeg\'\)\] { + background-image: url('https://images.pexels.com/photos/5632402/pexels-photo-5632402.jpeg'); +} + +.bg-gradient-to-br { + background-image: linear-gradient(to bottom right, var(--tw-gradient-stops)); +} + +.from-cyan-500 { + --tw-gradient-from: #06b6d4 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(6 182 212 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.to-blue-500 { + --tw-gradient-to: #3F83F8 var(--tw-gradient-to-position); +} + +.bg-center { + background-position: center; +} + +.bg-repeat { + background-repeat: repeat; +} + +.bg-no-repeat { + background-repeat: no-repeat; +} + +.object-cover { + -o-object-fit: cover; + object-fit: cover; +} + +.p-0 { + padding: 0px; +} + +.p-0\.5 { + padding: 0.125rem; +} + +.p-1 { + padding: 0.25rem; +} + +.p-2 { + padding: 0.5rem; +} + +.p-2\.5 { + padding: 0.625rem; +} + +.p-4 { + padding: 1rem; +} + +.p-5 { + padding: 1.25rem; +} + +.p-6 { + padding: 1.5rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.px-5 { + padding-left: 1.25rem; + padding-right: 1.25rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-2\.5 { + padding-top: 0.625rem; + padding-bottom: 0.625rem; +} + +.py-24 { + padding-top: 6rem; + padding-bottom: 6rem; +} + +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.py-8 { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.pb-3 { + padding-bottom: 0.75rem; +} + +.pl-3 { + padding-left: 0.75rem; +} + +.pr-4 { + padding-right: 1rem; +} + +.pt-2 { + padding-top: 0.5rem; +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; +} + +.text-5xl { + font-size: 3rem; + line-height: 1; +} + +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.font-bold { + font-weight: 700; +} + +.font-extrabold { + font-weight: 800; +} + +.font-light { + font-weight: 300; +} + +.font-medium { + font-weight: 500; +} + +.font-normal { + font-weight: 400; +} + +.font-semibold { + font-weight: 600; +} + +.uppercase { + text-transform: uppercase; +} + +.lowercase { + text-transform: lowercase; +} + +.leading-6 { + line-height: 1.5rem; +} + +.leading-9 { + line-height: 2.25rem; +} + +.leading-none { + line-height: 1; +} + +.leading-tight { + line-height: 1.25; +} + +.tracking-tight { + letter-spacing: -0.025em; +} + +.text-black { + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); +} + +.text-blue-600 { + --tw-text-opacity: 1; + color: rgb(28 100 242 / var(--tw-text-opacity)); +} + +.text-blue-700 { + --tw-text-opacity: 1; + color: rgb(26 86 219 / var(--tw-text-opacity)); +} + +.text-gray-300 { + --tw-text-opacity: 1; + color: rgb(209 213 219 / var(--tw-text-opacity)); +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + +.text-gray-700 { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity)); +} + +.text-gray-800 { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity)); +} + +.text-gray-900 { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.opacity-0 { + opacity: 0; +} + +.opacity-100 { + opacity: 1; +} + +.opacity-50 { + opacity: 0.5; +} + +.opacity-10 { + opacity: 0.1; +} + +.opacity-40 { + opacity: 0.4; +} + +.bg-blend-multiply { + background-blend-mode: multiply; +} + +.shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-lg { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-md { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.outline { + outline-style: solid; +} + +.blur { + --tw-blur: blur(8px); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.\!invert { + --tw-invert: invert(100%) !important; + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) !important; +} + +.invert { + --tw-invert: invert(100%); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.\!filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) !important; +} + +.filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-opacity { + transition-property: opacity; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-transform { + transition-property: transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-75 { + transition-duration: 75ms; +} + +.ease-in { + transition-timing-function: cubic-bezier(0.4, 0, 1, 1); +} + +.ease-out { + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); +} + +.hover\:border-gray-300:hover { + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + +.hover\:bg-blue-800:hover { + --tw-bg-opacity: 1; + background-color: rgb(30 66 159 / var(--tw-bg-opacity)); +} + +.hover\:bg-gray-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + +.hover\:bg-white:hover { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.hover\:text-blue-600:hover { + --tw-text-opacity: 1; + color: rgb(28 100 242 / var(--tw-text-opacity)); +} + +.hover\:text-gray-600:hover { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity)); +} + +.hover\:text-gray-900:hover { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.hover\:text-white:hover { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.hover\:underline:hover { + text-decoration-line: underline; +} + +.focus\:border-blue-500:focus { + --tw-border-opacity: 1; + border-color: rgb(63 131 248 / var(--tw-border-opacity)); +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-4:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-blue-300:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(164 202 254 / var(--tw-ring-opacity)); +} + +.focus\:ring-blue-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(63 131 248 / var(--tw-ring-opacity)); +} + +.focus\:ring-cyan-200:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(165 243 252 / var(--tw-ring-opacity)); +} + +.focus\:ring-gray-200:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(229 231 235 / var(--tw-ring-opacity)); +} + +.focus\:ring-gray-400:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(156 163 175 / var(--tw-ring-opacity)); +} + +.group:hover .group-hover\:bg-opacity-0 { + --tw-bg-opacity: 0; +} + +.group:hover .group-hover\:from-cyan-500 { + --tw-gradient-from: #06b6d4 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(6 182 212 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.group:hover .group-hover\:to-blue-500 { + --tw-gradient-to: #3F83F8 var(--tw-gradient-to-position); +} + +:is(.dark .dark\:border) { + border-width: 1px; +} + +:is(.dark .dark\:border-blue-500) { + --tw-border-opacity: 1; + border-color: rgb(63 131 248 / var(--tw-border-opacity)); +} + +:is(.dark .dark\:border-gray-600) { + --tw-border-opacity: 1; + border-color: rgb(75 85 99 / var(--tw-border-opacity)); +} + +:is(.dark .dark\:border-gray-700) { + --tw-border-opacity: 1; + border-color: rgb(55 65 81 / var(--tw-border-opacity)); +} + +:is(.dark .dark\:border-transparent) { + border-color: transparent; +} + +:is(.dark .dark\:bg-blue-600) { + --tw-bg-opacity: 1; + background-color: rgb(28 100 242 / var(--tw-bg-opacity)); +} + +:is(.dark .dark\:bg-gray-600) { + --tw-bg-opacity: 1; + background-color: rgb(75 85 99 / var(--tw-bg-opacity)); +} + +:is(.dark .dark\:bg-gray-700) { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); +} + +:is(.dark .dark\:bg-gray-800) { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +:is(.dark .dark\:bg-gray-800\/50) { + background-color: rgb(31 41 55 / 0.5); +} + +:is(.dark .dark\:bg-gray-900) { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); +} + +:is(.dark .dark\:bg-gray-900\/80) { + background-color: rgb(17 24 39 / 0.8); +} + +:is(.dark .dark\:bg-\[url\(\'https\:\/\/flowbite\.s3\.amazonaws\.com\/docs\/jumbotron\/hero-pattern-dark\.svg\'\)\]) { + background-image: url('https://flowbite.s3.amazonaws.com/docs/jumbotron/hero-pattern-dark.svg'); +} + +:is(.dark .dark\:text-blue-500) { + --tw-text-opacity: 1; + color: rgb(63 131 248 / var(--tw-text-opacity)); +} + +:is(.dark .dark\:text-gray-300) { + --tw-text-opacity: 1; + color: rgb(209 213 219 / var(--tw-text-opacity)); +} + +:is(.dark .dark\:text-gray-400) { + --tw-text-opacity: 1; + color: rgb(156 163 175 / var(--tw-text-opacity)); +} + +:is(.dark .dark\:text-white) { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +:is(.dark .dark\:placeholder-gray-400)::-moz-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +:is(.dark .dark\:placeholder-gray-400)::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +:is(.dark .dark\:ring-offset-gray-800) { + --tw-ring-offset-color: #1F2937; +} + +:is(.dark .dark\:hover\:bg-blue-500:hover) { + --tw-bg-opacity: 1; + background-color: rgb(63 131 248 / var(--tw-bg-opacity)); +} + +:is(.dark .dark\:hover\:bg-blue-700:hover) { + --tw-bg-opacity: 1; + background-color: rgb(26 86 219 / var(--tw-bg-opacity)); +} + +:is(.dark .dark\:hover\:bg-gray-600:hover) { + --tw-bg-opacity: 1; + background-color: rgb(75 85 99 / var(--tw-bg-opacity)); +} + +:is(.dark .dark\:hover\:bg-gray-700:hover) { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); +} + +:is(.dark .dark\:hover\:bg-gray-800:hover) { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +:is(.dark .dark\:hover\:text-blue-500:hover) { + --tw-text-opacity: 1; + color: rgb(63 131 248 / var(--tw-text-opacity)); +} + +:is(.dark .dark\:hover\:text-gray-300:hover) { + --tw-text-opacity: 1; + color: rgb(209 213 219 / var(--tw-text-opacity)); +} + +:is(.dark .dark\:hover\:text-white:hover) { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +:is(.dark .dark\:focus\:border-blue-500:focus) { + --tw-border-opacity: 1; + border-color: rgb(63 131 248 / var(--tw-border-opacity)); +} + +:is(.dark .dark\:focus\:ring-blue-500:focus) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(63 131 248 / var(--tw-ring-opacity)); +} + +:is(.dark .dark\:focus\:ring-blue-600:focus) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(28 100 242 / var(--tw-ring-opacity)); +} + +:is(.dark .dark\:focus\:ring-blue-800:focus) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(30 66 159 / var(--tw-ring-opacity)); +} + +:is(.dark .dark\:focus\:ring-blue-900:focus) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(35 56 118 / var(--tw-ring-opacity)); +} + +:is(.dark .dark\:focus\:ring-cyan-800:focus) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(21 94 117 / var(--tw-ring-opacity)); +} + +:is(.dark .dark\:focus\:ring-gray-600:focus) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(75 85 99 / var(--tw-ring-opacity)); +} + +@media (min-width: 640px) { + .sm\:ms-4 { + margin-inline-start: 1rem; + } + + .sm\:mt-0 { + margin-top: 0px; + } + + .sm\:flex { + display: flex; + } + + .sm\:max-w-md { + max-width: 28rem; + } + + .sm\:flex-row { + flex-direction: row; + } + + .sm\:justify-center { + justify-content: center; + } + + .sm\:space-y-0 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0px * var(--tw-space-y-reverse)); + } + + .sm\:rounded-lg { + border-radius: 0.5rem; + } + + .sm\:rounded-none { + border-radius: 0px; + } + + .sm\:rounded-l-lg { + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; + } + + .sm\:p-8 { + padding: 2rem; + } + + .sm\:px-16 { + padding-left: 4rem; + padding-right: 4rem; + } + + .sm\:px-4 { + padding-left: 1rem; + padding-right: 1rem; + } + + .sm\:text-center { + text-align: center; + } + + .sm\:text-sm { + font-size: 0.875rem; + line-height: 1.25rem; + } + + .sm\:text-xl { + font-size: 1.25rem; + line-height: 1.75rem; + } +} + +@media (min-width: 768px) { + .md\:mr-6 { + margin-right: 1.5rem; + } + + .md\:mt-0 { + margin-top: 0px; + } + + .md\:block { + display: block; + } + + .md\:flex { + display: flex; + } + + .md\:hidden { + display: none; + } + + .md\:h-screen { + height: 100vh; + } + + .md\:w-1\/4 { + width: 25%; + } + + .md\:w-2\/4 { + width: 50%; + } + + .md\:w-3\/4 { + width: 75%; + } + + .md\:w-auto { + width: auto; + } + + .md\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .md\:flex-row { + flex-direction: row; + } + + .md\:items-center { + align-items: center; + } + + .md\:justify-between { + justify-content: space-between; + } + + .md\:space-x-8 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(2rem * var(--tw-space-x-reverse)); + margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse))); + } + + .md\:space-y-6 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1.5rem * var(--tw-space-y-reverse)); + } + + .md\:border-0 { + border-width: 0px; + } + + .md\:bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + } + + .md\:p-0 { + padding: 0px; + } + + .md\:p-6 { + padding: 1.5rem; + } + + .md\:text-2xl { + font-size: 1.5rem; + line-height: 2rem; + } + + .md\:text-5xl { + font-size: 3rem; + line-height: 1; + } + + .md\:text-sm { + font-size: 0.875rem; + line-height: 1.25rem; + } + + .md\:font-medium { + font-weight: 500; + } + + .md\:hover\:bg-transparent:hover { + background-color: transparent; + } + + .md\:hover\:text-blue-700:hover { + --tw-text-opacity: 1; + color: rgb(26 86 219 / var(--tw-text-opacity)); + } + + :is(.dark .md\:dark\:bg-gray-900) { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); + } + + :is(.dark .md\:dark\:hover\:bg-transparent:hover) { + background-color: transparent; + } + + :is(.dark .md\:dark\:hover\:text-white:hover) { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); + } +} + +@media (min-width: 1024px) { + .lg\:mb-16 { + margin-bottom: 4rem; + } + + .lg\:px-48 { + padding-left: 12rem; + padding-right: 12rem; + } + + .lg\:px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + + .lg\:py-0 { + padding-top: 0px; + padding-bottom: 0px; + } + + .lg\:py-16 { + padding-top: 4rem; + padding-bottom: 4rem; + } + + .lg\:py-56 { + padding-top: 14rem; + padding-bottom: 14rem; + } + + .lg\:text-6xl { + font-size: 3.75rem; + line-height: 1; + } + + .lg\:text-xl { + font-size: 1.25rem; + line-height: 1.75rem; + } +} + +@media (min-width: 1280px) { + .xl\:p-0 { + padding: 0px; + } +} + +.rtl\:rotate-180:where([dir="rtl"], [dir="rtl"] *) { + --tw-rotate: 180deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.rtl\:text-right:where([dir="rtl"], [dir="rtl"] *) { + text-align: right; +} diff --git a/src/static/tailwind/tailwind-input.css b/src/static/tailwind/tailwind-input.css index bd6213e..cc178b6 100644 --- a/src/static/tailwind/tailwind-input.css +++ b/src/static/tailwind/tailwind-input.css @@ -1,3 +1,24 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +@layer base { + .form-control{ + @apply bg-gray-800 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500; + } + .btn-submit { + @apply text-black bg-blue-700 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800; + } + .labelling{ + @apply block mb-2 text-sm font-medium text-gray-900 dark:text-white; + } + .para { + @apply mb-3 text-gray-500 dark:text-gray-400 first-line:uppercase first-line:tracking-widest first-letter:text-7xl first-letter:font-bold first-letter:text-gray-900 dark:first-letter:text-gray-100 first-letter:me-3 first-letter:float-start; + } + .regi-form{ + @apply bg-gray-800 border border-gray-300 text-white-90 sm:text-sm rounded-lg block w-full p-2.5 dark:bg-white-70 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500; + } + .reg-form { + @apply bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500; + } +} diff --git a/src/templates/accounts/login.html b/src/templates/accounts/login.html new file mode 100644 index 0000000..9571633 --- /dev/null +++ b/src/templates/accounts/login.html @@ -0,0 +1,46 @@ +{% extends 'base.html' %} +{% block content %} + + +
+
+
+
+

+ Sign in to your account +

+
{% csrf_token %} +
+ + +
+
+ + +
+
+
+
+ +
+
+ +
+
+ Forgot password? +
+ +

+ Don’t have an account yet? Sign up +

+
+ {% for msg in messages %} + {{msg}} + {% endfor %} +
+
+ +
+
+ + {% endblock %} diff --git a/src/templates/accounts/register.html b/src/templates/accounts/register.html new file mode 100644 index 0000000..0767521 --- /dev/null +++ b/src/templates/accounts/register.html @@ -0,0 +1,31 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+
+
+

+ Create an account +

+
{% csrf_token %} + {% for fields in form %} +
    +
  • {{fields.label}}
  • +
  • {{fields}}
  • +
+ {% endfor %} + + +
+

+ Already have an account? Login here +

+ {% for message in messages %} + {{message}} + {% endfor %} +
+
+
+
+ {% endblock %} \ No newline at end of file diff --git a/src/templates/base.html b/src/templates/base.html index f928511..9cab217 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -13,15 +13,18 @@ - + {% include 'base/navbar.html' %} + + +
{% block content %} {% endblock content %}
- +
{% include 'base/footer.html' %} {% include 'base/js.html' %} diff --git a/src/templates/base/navbar.html b/src/templates/base/navbar.html index 8442965..c0e6927 100644 --- a/src/templates/base/navbar.html +++ b/src/templates/base/navbar.html @@ -1,24 +1,64 @@ + - \ No newline at end of file + {% else %} + Login + {% endif %} + + + diff --git a/src/templates/home.html b/src/templates/home.html index 8f76132..b2d7dfd 100644 --- a/src/templates/home.html +++ b/src/templates/home.html @@ -2,11 +2,36 @@ {% block content %} -
-
-

eCommerce with Python has never been easier.

+
+
+
+

Ecommerce is not just a trend It's a revolution in the way we shop

+ +
+
+ Ecommerce
+
+ + + {% endblock %} \ No newline at end of file diff --git a/src/templates/products/attachment_table.html b/src/templates/products/attachment_table.html new file mode 100644 index 0000000..ca3315d --- /dev/null +++ b/src/templates/products/attachment_table.html @@ -0,0 +1,38 @@ +{% if attachments.exists %} +
+ + + + + + + + + + {% for attachment in attachments %} + + + + + + {% endfor %} + +
+ Product name + + File + + Action +
+ {{ attachment.display_name }} + + {% if attachment.is_free %} Free Download {% endif %} + + {% if is_owner and attachment.is_free %} + Download + {% else %} +

Purchase to Download

+ {% endif %} +
+
+{% endif %} \ No newline at end of file diff --git a/src/templates/products/create.html b/src/templates/products/create.html new file mode 100644 index 0000000..7b64cb8 --- /dev/null +++ b/src/templates/products/create.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block content %} +
{% csrf_token %} +
    + {{form.as_ul}} +
+
+ +
+ +{% endblock %} diff --git a/src/templates/products/detail.html b/src/templates/products/detail.html new file mode 100644 index 0000000..03c78bd --- /dev/null +++ b/src/templates/products/detail.html @@ -0,0 +1,58 @@ +{% extends 'base.html' %} {% block content %} +
+
+ +

{{obj.name}}

+ +

+ Track work across the enterprise through an open, collaborative platform. + Link issues across Jira and ingest data from other software development + tools, so your IT support and operations teams have richer contextual + information to rapidly respond to requests, incidents, and changes. +

+

+ Deliver great service experiences fast - without the complexity of + traditional ITSM solutions.Accelerate critical development work, eliminate + toil, and deploy changes with ease, with a complete audit trail for every + change. +

+ {% include 'products/attachment_table.html' with attachments=attachments is_owner=is_owner %} + +
+ + +
+ + {% if is_owner %} +

You are Owner

+ {% else %} +
+ {% include 'purchases/buy-btn-form.html' with product=obj %} +
+ {% endif %} {% if obj.image %} +
+
+ +
+
+ {% endif %} {% if form %} +
+

Update Product

+
+ {% csrf_token %} +
    + {{form.as_ul}} +
+
+ +
+
+ {% endif %} +
+
+ +{% endblock %} diff --git a/src/templates/products/list.html b/src/templates/products/list.html new file mode 100644 index 0000000..2b2a7c2 --- /dev/null +++ b/src/templates/products/list.html @@ -0,0 +1,25 @@ +{% extends 'base.html' %} {% block content %} + +
+
+
+

+ Products +

+

+ Explore the New Digital Products by eCommerce +

+
+
+ {% for obj in objects %} + {% include 'products/list_card.html' with object=obj %} + {% endfor %} +
+
+
+ +{% endblock %} diff --git a/src/templates/products/list_card.html b/src/templates/products/list_card.html new file mode 100644 index 0000000..6d4ba66 --- /dev/null +++ b/src/templates/products/list_card.html @@ -0,0 +1,32 @@ +
+ {% if object.image %} + + {{object.name}} + + {% endif %} +
+

+ {{object.name}} +

+ {{object.handle}} +

+ {{object.description}} +

+ +
+
diff --git a/src/templates/products/manager.html b/src/templates/products/manager.html new file mode 100644 index 0000000..0b4a405 --- /dev/null +++ b/src/templates/products/manager.html @@ -0,0 +1,98 @@ +{% extends 'base.html' %} {% block content %} +
+ {% csrf_token %} +
+
    + {{form.as_ul}} +
+
+
+ {{formset.management_form}} +
+ {% for form in formset %} +
+
    + {{ form.as_ul}} +
+
+ {% endfor %} +
+
+ + + + + +
+ + + + + +{% endblock %} diff --git a/src/templates/purchases/buy-btn-form.html b/src/templates/purchases/buy-btn-form.html new file mode 100644 index 0000000..8ece2d9 --- /dev/null +++ b/src/templates/purchases/buy-btn-form.html @@ -0,0 +1,30 @@ +{% if product %} +
{% csrf_token %} + +
+ $ + {{product.display_price}} +
+{% if not request.user.is_authenticated %} + +{% else %} + +{% endif %} +
+{% endif %} \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index adda348..7442f6c 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,12 +2,17 @@ module.exports = { content: [ "./src/**/*.{html,js}", - "./node_modules/flowbite/**/*.js" + "./node_modules/flowbite/**/*.js", + "src/products/forms.py", + "src/accounts/forms.py" ], theme: { - extend: {}, + extend: { + }, + }, plugins: [ require('flowbite/plugin') ] } +