diff --git a/.gitignore b/.gitignore index c926378..561b3ed 100644 --- a/.gitignore +++ b/.gitignore @@ -147,4 +147,5 @@ fabric.properties # End of https://www.toptal.com/developers/gitignore/api/webstorm+all,visualstudiocode,nextjs .idea/ -.vscode/ \ No newline at end of file +.vscode/ +.env \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 923df7d..e788456 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "doctor-man", "version": "0.1.0", "dependencies": { + "@prisma/client": "^6.4.1", "clsx": "^2.1.1", "dompurify": "^3.2.4", "next": "14.2.20", @@ -24,6 +25,8 @@ "postcss": "^8.4.49", "postcss-nesting": "^13.0.1", "prettier": "^3.4.2", + "prisma": "^6.4.1", + "tsx": "^4.19.3", "typescript": "^5" } }, @@ -80,6 +83,406 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -763,6 +1166,72 @@ "node": ">=14" } }, + "node_modules/@prisma/client": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.4.1.tgz", + "integrity": "sha512-A7Mwx44+GVZVexT5e2GF/WcKkEkNNKbgr059xpr5mn+oUm2ZW1svhe+0TRNBwCdzhfIZ+q23jEgsNPvKD9u+6g==", + "hasInstallScript": true, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.4.1.tgz", + "integrity": "sha512-Q9xk6yjEGIThjSD8zZegxd5tBRNHYd13GOIG0nLsanbTXATiPXCLyvlYEfvbR2ft6dlRsziQXfQGxAgv7zcMUA==", + "devOptional": true + }, + "node_modules/@prisma/engines": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.4.1.tgz", + "integrity": "sha512-KldENzMHtKYwsOSLThghOIdXOBEsfDuGSrxAZjMnimBiDKd3AE4JQ+Kv+gBD/x77WoV9xIPf25GXMWffXZ17BA==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/debug": "6.4.1", + "@prisma/engines-version": "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d", + "@prisma/fetch-engine": "6.4.1", + "@prisma/get-platform": "6.4.1" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d.tgz", + "integrity": "sha512-Xq54qw55vaCGrGgIJqyDwOq0TtjZPJEWsbQAHugk99hpDf2jcEeQhUcF+yzEsSqegBaDNLA4IC8Nn34sXmkiTQ==", + "devOptional": true + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.4.1.tgz", + "integrity": "sha512-uZ5hVeTmDspx7KcaRCNoXmcReOD+84nwlO2oFvQPRQh9xiFYnnUKDz7l9bLxp8t4+25CsaNlgrgilXKSQwrIGQ==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "6.4.1", + "@prisma/engines-version": "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d", + "@prisma/get-platform": "6.4.1" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.4.1.tgz", + "integrity": "sha512-gXqZaDI5scDkBF8oza7fOD3Q3QMD0e0rBynlzDDZdTWbWmzjuW58PRZtj+jkvKje2+ZigCWkH8SsWZAsH6q1Yw==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "6.4.1" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1593,7 +2062,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, + "devOptional": true, "dependencies": { "ms": "^2.1.3" }, @@ -1869,6 +2338,58 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "devOptional": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "devOptional": true, + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2452,6 +2973,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "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", @@ -3444,7 +3979,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "devOptional": true }, "node_modules/nanoid": { "version": "3.3.8", @@ -3891,6 +4426,35 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prisma": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.4.1.tgz", + "integrity": "sha512-q2uJkgXnua/jj66mk6P9bX/zgYJFI/jn4Yp0aS6SPRrjH/n6VyOV7RDe1vHD0DX8Aanx4MvgmUPPoYnR6MJnPg==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "6.4.1", + "esbuild": ">=0.12 <1", + "esbuild-register": "3.6.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -4684,6 +5248,25 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/tsx": { + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", + "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", + "dev": true, + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4786,7 +5369,7 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 47af989..1a2d696 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,11 @@ "prettier:check": "prettier . --check", "prettier:fix": "prettier . --write" }, + "prisma": { + "seed": "tsx prisma/seed.ts" + }, "dependencies": { + "@prisma/client": "^6.4.1", "clsx": "^2.1.1", "dompurify": "^3.2.4", "next": "14.2.20", @@ -27,6 +31,8 @@ "postcss": "^8.4.49", "postcss-nesting": "^13.0.1", "prettier": "^3.4.2", + "prisma": "^6.4.1", + "tsx": "^4.19.3", "typescript": "^5" } } diff --git a/prisma/migrations/20250303110856_init/migration.sql b/prisma/migrations/20250303110856_init/migration.sql new file mode 100644 index 0000000..9c55391 --- /dev/null +++ b/prisma/migrations/20250303110856_init/migration.sql @@ -0,0 +1,16 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "username" TEXT NOT NULL, + "email" TEXT NOT NULL, + "password" TEXT NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..648c57f --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..3dd0335 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,22 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id String @id @default(uuid()) + name String + username String @unique + email String @unique + password String +} diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..7bd0020 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,20 @@ +import { Prisma, PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +const users: Prisma.UserCreateInput[] = [ + { + name: "علیرضا درانی کریانی", + username: "alireza97d", + email: "alireza97d@gmail.com", + password: "admin", + }, +]; + +export async function main() { + for (const user of users) { + await prisma.user.create({ data: user }); + } +} + +main().then(() => console.log("Done!")); diff --git a/src/app/auth/components/sign-in-form/sign-in-form.component.tsx b/src/app/auth/components/sign-in-form/sign-in-form.component.tsx new file mode 100644 index 0000000..00fd9df --- /dev/null +++ b/src/app/auth/components/sign-in-form/sign-in-form.component.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { FormEvent, ReactElement } from "react"; + +import Image from "next/image"; +import Link from "next/link"; + +import signInImage from "@/assets/images/sign-in.webp"; + +import { ButtonComponent } from "@/components/button/button.component"; +import CardComponent from "@/components/card/card.component"; +import NormalInputComponent from "@/components/normal-input/normal-input.component"; +import PasswordInputComponent from "@/components/password-input/password-input.component"; + +import MingcuteUser3Line from "@/icons/MingcuteUser3Line"; + +import styles from "@/app/auth/styles/auth-form.module.css"; + +export default function SignInFormComponent(): ReactElement { + const formSubmitHandler = async ( + e: FormEvent, + ): Promise => { + e.preventDefault(); + }; + + return ( +
+ +
+
+

ورود!

+
+ } + /> + + ورود + +
+ قبلاً ثبت‌نام نکردید؟ + {` `} + ثبت‌نام کنید. +
+
+
+ +
+
+
+
+ ); +} diff --git a/src/app/auth/components/sign-in-form/sign-in-form.module.css b/src/app/auth/components/sign-in-form/sign-in-form.module.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/auth/components/sign-up-form/sign-up-form.component.tsx b/src/app/auth/components/sign-up-form/sign-up-form.component.tsx new file mode 100644 index 0000000..46681da --- /dev/null +++ b/src/app/auth/components/sign-up-form/sign-up-form.component.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { ReactElement, FormEvent } from "react"; + +import Image from "next/image"; +import Link from "next/link"; + +import signUpImage from "@/assets/images/sign-up.webp"; + +import { ButtonComponent } from "@/components/button/button.component"; +import CardComponent from "@/components/card/card.component"; +import NormalInputComponent from "@/components/normal-input/normal-input.component"; +import PasswordInputComponent from "@/components/password-input/password-input.component"; + +import MingcuteIncognitoModeLine from "@/icons/MingcuteIncognitoModeLine"; +import MingcuteUser3Line from "@/icons/MingcuteUser3Line"; +import MingcuteMailLine from "@/icons/MingcuteMailLine"; + +import styles from "@/app/auth/styles/auth-form.module.css"; + +const SignUpFormComponent = (): ReactElement => { + const formSubmitHandler = async ( + e: FormEvent, + ): Promise => { + e.preventDefault(); + }; + + return ( +
+ +
+
+

ثبت‌نام!

+
+ } + /> + } + /> + } + /> + + ثبت‌نام + +
+ قبلاً ثبت‌نام کردید؟ + {` `} + وارد شوید. +
+
+
+ +
+
+
+
+ ); +}; + +export default SignUpFormComponent; diff --git a/src/app/auth/components/sign-up-form/sign-up-form.module.css b/src/app/auth/components/sign-up-form/sign-up-form.module.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/auth/sign-in/page.module.css b/src/app/auth/sign-in/page.module.css new file mode 100644 index 0000000..dcf1ec3 --- /dev/null +++ b/src/app/auth/sign-in/page.module.css @@ -0,0 +1,6 @@ +.page { + display: grid; + justify-items: center; + + margin-block: 2rem; +} diff --git a/src/app/auth/sign-in/page.tsx b/src/app/auth/sign-in/page.tsx new file mode 100644 index 0000000..e34ac1c --- /dev/null +++ b/src/app/auth/sign-in/page.tsx @@ -0,0 +1,13 @@ +import { ReactElement } from "react"; + +import SignInFormComponent from "@/app/auth/components/sign-in-form/sign-in-form.component"; + +import styles from "./page.module.css"; + +export default function Page(): ReactElement { + return ( +
+ +
+ ); +} diff --git a/src/app/auth/sign-up/page.module.css b/src/app/auth/sign-up/page.module.css new file mode 100644 index 0000000..dcf1ec3 --- /dev/null +++ b/src/app/auth/sign-up/page.module.css @@ -0,0 +1,6 @@ +.page { + display: grid; + justify-items: center; + + margin-block: 2rem; +} diff --git a/src/app/auth/sign-up/page.tsx b/src/app/auth/sign-up/page.tsx new file mode 100644 index 0000000..b9f721c --- /dev/null +++ b/src/app/auth/sign-up/page.tsx @@ -0,0 +1,15 @@ +import { ReactElement } from "react"; + +import SignUpFormComponent from "../components/sign-up-form/sign-up-form.component"; + +import styles from "./page.module.css"; + +const Page = (): ReactElement => { + return ( +
+ +
+ ); +}; + +export default Page; diff --git a/src/app/auth/styles/auth-form.module.css b/src/app/auth/styles/auth-form.module.css new file mode 100644 index 0000000..d103b27 --- /dev/null +++ b/src/app/auth/styles/auth-form.module.css @@ -0,0 +1,56 @@ +.auth-form { + inline-size: min(50rem, 100%); + + .card-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + + .writings { + display: grid; + gap: 1rem; + + h1 { + margin-block-end: 1rem; + padding-inline-start: 0.5rem; + + border-inline-start: 2px dashed var(--color-border); + } + + form { + display: grid; + gap: 1rem; + } + + form > button:last-of-type { + margin-block-start: 0.5rem; + } + + .change-form { + a { + color: var(--color-primary); + + text-decoration: underline; + } + } + } + + .visuals { + min-block-size: 100%; + block-size: 0; + inline-size: 100%; + + img { + block-size: 100%; + inline-size: 100%; + object-fit: contain; + + padding: 0.5rem; + + background-color: var(--color-primary-fade); + + border-radius: var(--border-radius); + } + } + } +} diff --git a/src/app/dashboard/page.module.css b/src/app/dashboard/page.module.css new file mode 100644 index 0000000..f50f869 --- /dev/null +++ b/src/app/dashboard/page.module.css @@ -0,0 +1,3 @@ +.page { + margin-block: 2rem; +} diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..f14262d --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { ReactElement } from "react"; + +import { ButtonComponent } from "@/components/button/button.component"; + +import styles from "./page.module.css"; + +export default function Page(): ReactElement { + const signOutButtonClickHandler = async (): Promise => {}; + + return ( +
+

داشبورد

+ + خروج + +
+ ); +} diff --git a/src/assets/images/sign-in.webp b/src/assets/images/sign-in.webp new file mode 100644 index 0000000..f34457d Binary files /dev/null and b/src/assets/images/sign-in.webp differ diff --git a/src/assets/images/sign-up.webp b/src/assets/images/sign-up.webp new file mode 100644 index 0000000..dec3ce2 Binary files /dev/null and b/src/assets/images/sign-up.webp differ diff --git a/src/components/button/button.component.tsx b/src/components/button/button.component.tsx new file mode 100644 index 0000000..31e2a9f --- /dev/null +++ b/src/components/button/button.component.tsx @@ -0,0 +1,79 @@ +"use client"; + +import { ComponentProps, ReactElement } from "react"; + +import Link from "next/link"; + +import clsx from "clsx"; + +import styles from "./button.module.css"; + +export type ButtonVariant = "default" | "primary" | "danger"; +export type ButtonShape = "inherit" | "solid" | "outlined"; +export type ButtonSize = "medium" | "large"; +export type ButtonPosition = "default" | "inline"; + +type CommonProps = { + variant?: ButtonVariant; + shape?: ButtonShape; + size?: ButtonSize; + position?: ButtonPosition; +}; + +type ButtonComponentProps = ComponentProps<"button"> & CommonProps; + +type ButtonLinkComponentProps = ComponentProps & CommonProps; + +export function ButtonComponent({ + variant = "default", + shape = "solid", + size = "medium", + position = "default", + className, + children, + ...otherProps +}: ButtonComponentProps): ReactElement { + return ( + + ); +} + +export function ButtonLinkComponent({ + variant = "default", + shape = "solid", + size = "medium", + position = "default", + className, + href, + children, + ...otherProps +}: ButtonLinkComponentProps): ReactElement { + return ( + + {children} + + ); +} diff --git a/src/components/button/button.module.css b/src/components/button/button.module.css new file mode 100644 index 0000000..855a7d9 --- /dev/null +++ b/src/components/button/button.module.css @@ -0,0 +1,103 @@ +.button { + background-color: transparent; + color: inherit; + + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + + border: none; + border-radius: var(--border-radius); + + font-size: 1rem; + font-weight: 700; + + cursor: pointer; + + transition: var(--animation-duration-fast) ease-in-out; + transition-property: background-color, color; + + &:not(.inherit) { + padding: 0.5rem 1rem; + + svg { + font-size: 1.5em; + } + } + + &.solid { + background-color: var(--background-color); + color: var(--color); + + &:hover { + background-color: var(--background-color-hover); + } + + &.default { + --background-color: var(--color-surface-700); + --background-color-hover: var(--color-surface-300); + --color: var(--color-text-400); + } + + &.primary { + --background-color: var(--color-primary); + --background-color-hover: var(--color-primary-darker); + --color: var(--color-primary-opposite); + } + + &.danger { + --background-color: var(--color-danger); + --background-color-hover: var(--color-danger-darker); + --color: var(--color-danger-opposite); + } + } + + &.outlined { + background-color: var(--background-color); + color: var(--color); + + border: 1px solid currentcolor; + border-radius: var(--border-radius); + + &:hover { + background-color: var(--background-color-hover); + color: var(--color-hover); + } + + &.default { + --background-color: var(--color-surface-700); + --background-color-hover: var(--color-text-400); + --color: var(--color-text-400); + --color-hover: var(--color-surface-700); + } + + &.primary { + --background-color: var(--color-primary-opposite); + --background-color-hover: var(--color-primary); + --color: var(--color-primary); + --color-hover: var(--color-primary-opposite); + } + + &.danger { + --background-color: var(--color-danger-opposite); + --background-color-hover: var(--color-danger); + --color: var(--color-danger); + --color-hover: var(--color-danger-opposite); + } + + &:hover { + background-color: var(--background-color-hover); + } + } + + &.large { + padding: 1rem 2rem; + } + + &.inline { + padding: 0.2rem 0.5rem; + + font-weight: 700; + } +} diff --git a/src/components/header/header.component.tsx b/src/components/header/header.component.tsx index 34802a9..d852de8 100644 --- a/src/components/header/header.component.tsx +++ b/src/components/header/header.component.tsx @@ -5,9 +5,12 @@ import { ReactElement } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; +import { ButtonLinkComponent } from "../button/button.component"; + import clsx from "clsx"; import { menu, menuType } from "@/constant/header/menu"; + import styles from "./header.module.css"; export default function HeaderComponent(): ReactElement { @@ -33,7 +36,14 @@ export default function HeaderComponent(): ReactElement { })} - + + ورود | ثبت‌نام + ); } diff --git a/src/components/header/header.module.css b/src/components/header/header.module.css index 1b0283c..6ce83e5 100644 --- a/src/components/header/header.module.css +++ b/src/components/header/header.module.css @@ -24,22 +24,6 @@ } .cta { - background-color: transparent; - color: var(--color-primary); - margin-inline-start: auto; - padding: 0.5rem 1rem; - - border: 1px solid currentcolor; - border-radius: var(--border-radius); - - font-weight: 700; - - cursor: pointer; - - &:hover { - background-color: var(--color-primary); - color: var(--color-primary-opposite); - } } } diff --git a/src/components/normal-input/normal-input.component.tsx b/src/components/normal-input/normal-input.component.tsx new file mode 100644 index 0000000..4ed7ad3 --- /dev/null +++ b/src/components/normal-input/normal-input.component.tsx @@ -0,0 +1,49 @@ +import { ComponentProps, ReactElement, forwardRef, ForwardedRef } from "react"; + +import clsx from "clsx"; + +import { ButtonComponent } from "@/components/button/button.component"; + +import styles from "./normal-input.module.css"; + +type Props = ComponentProps<"input"> & { + label: string; + prefixIcon?: ReactElement; + suffixIcon?: ReactElement; + onSuffixClick?: ComponentProps["onClick"]; +}; + +function NormalInputComponent( + { + label, + prefixIcon, + suffixIcon, + onSuffixClick, + className, + ...otherProps + }: Props, + ref: ForwardedRef, +): ReactElement { + return ( + + ); +} + +export default forwardRef(NormalInputComponent); diff --git a/src/components/normal-input/normal-input.module.css b/src/components/normal-input/normal-input.module.css new file mode 100644 index 0000000..4c92244 --- /dev/null +++ b/src/components/normal-input/normal-input.module.css @@ -0,0 +1,50 @@ +.normal-input { + .label-text { + font-size: var(--fz-300); + margin-block-end: 0.5rem; + } + + .box { + color: var(--color-border); + + display: flex; + align-items: center; + gap: 0.5rem; + + padding: 0.5rem; + + border: 1px solid currentcolor; + border-radius: var(--border-radius); + + input { + flex: 1 1 0; + + background-color: var(--color-surface-700); + color: inherit; + + block-size: 1em; + + border: none; + + font-size: 1rem; + line-height: 1; + + &:focus { + outline: none; + } + } + + .prefix-icon, + .suffix-icon { + display: grid; + + font-size: 1.25rem; + } + } + + &:focus-within { + .box { + color: var(--color-primary); + } + } +} diff --git a/src/components/password-input/password-input.component.tsx b/src/components/password-input/password-input.component.tsx new file mode 100644 index 0000000..335a674 --- /dev/null +++ b/src/components/password-input/password-input.component.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { + ComponentProps, + ForwardedRef, + forwardRef, + ReactElement, + useState, +} from "react"; + +import NormalInputComponent from "@/components/normal-input/normal-input.component"; + +import MingcuteKey2Line from "@/icons/MingcuteKey2Line"; +import MingcuteEye2Line from "@/icons/MingcuteEye2Line"; +import MingcuteEyeCloseLine from "@/icons/MingcuteEyeCloseLine"; + +type Props = ComponentProps; + +function PasswordInputComponent( + { ...otherProps }: Props, + ref: ForwardedRef, +): ReactElement { + const [isVisible, setIsVisible] = useState(false); + + return ( + } + suffixIcon={isVisible ? : } + onSuffixClick={() => setIsVisible((old) => !old)} + {...otherProps} + /> + ); +} + +export default forwardRef(PasswordInputComponent); diff --git a/src/components/password-input/password-input.module.css b/src/components/password-input/password-input.module.css new file mode 100644 index 0000000..e69de29 diff --git a/src/icons/MingcuteCalendarMonthLine.tsx b/src/icons/MingcuteCalendarMonthLine.tsx new file mode 100644 index 0000000..864b4f8 --- /dev/null +++ b/src/icons/MingcuteCalendarMonthLine.tsx @@ -0,0 +1,22 @@ +import React, { SVGProps } from "react"; + +export function MingcuteCalendarMonthLine(props: SVGProps) { + return ( + + + + + + + ); +} +export default MingcuteCalendarMonthLine; diff --git a/src/icons/MingcuteEyeCloseLine.tsx b/src/icons/MingcuteEyeCloseLine.tsx new file mode 100644 index 0000000..f49afa9 --- /dev/null +++ b/src/icons/MingcuteEyeCloseLine.tsx @@ -0,0 +1,23 @@ +import React, { SVGProps } from "react"; + +export function MingcuteEyeCloseLine(props: SVGProps) { + return ( + + + + + + + ); +} + +export default MingcuteEyeCloseLine; diff --git a/src/icons/MingcuteIncognitoModeLine.tsx b/src/icons/MingcuteIncognitoModeLine.tsx new file mode 100644 index 0000000..a008539 --- /dev/null +++ b/src/icons/MingcuteIncognitoModeLine.tsx @@ -0,0 +1,23 @@ +import React, { SVGProps } from "react"; + +export function MingcuteIncognitoModeLine(props: SVGProps) { + return ( + + + + + + + ); +} + +export default MingcuteIncognitoModeLine; diff --git a/src/icons/MingcuteKey2Line.tsx b/src/icons/MingcuteKey2Line.tsx new file mode 100644 index 0000000..eb0affa --- /dev/null +++ b/src/icons/MingcuteKey2Line.tsx @@ -0,0 +1,23 @@ +import React, { SVGProps } from "react"; + +export function MingcuteKey2Line(props: SVGProps) { + return ( + + + + + + + ); +} + +export default MingcuteKey2Line; diff --git a/src/icons/MingcuteMailLine.tsx b/src/icons/MingcuteMailLine.tsx new file mode 100644 index 0000000..1d196ea --- /dev/null +++ b/src/icons/MingcuteMailLine.tsx @@ -0,0 +1,23 @@ +import React, { SVGProps } from "react"; + +export function MingcuteMailLine(props: SVGProps) { + return ( + + + + + + + ); +} + +export default MingcuteMailLine; diff --git a/src/icons/MingcuteUser3Line.tsx b/src/icons/MingcuteUser3Line.tsx new file mode 100644 index 0000000..8c580ab --- /dev/null +++ b/src/icons/MingcuteUser3Line.tsx @@ -0,0 +1,23 @@ +import React, { SVGProps } from "react"; + +export function MingcuteUser3Line(props: SVGProps) { + return ( + + + + + + + ); +} + +export default MingcuteUser3Line; diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts new file mode 100644 index 0000000..8dce7b8 --- /dev/null +++ b/src/lib/prisma.ts @@ -0,0 +1,9 @@ +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +const globalForPrisma = global as unknown as { prisma: typeof prisma }; + +if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; + +export default prisma;