diff --git a/README.md b/README.md index 54e77e2..be43563 100644 --- a/README.md +++ b/README.md @@ -101,12 +101,14 @@ This is an overview of the `./src` directory. . ├── app/ │ ├── App.tsx +│ ├── Providers.tsx │ └── store.ts ├── assets -├── common/ -│ ├── components -│ ├── services -│ └── constants +├── components +│ ├── Navbar.tsx +│ └── Footer.tsx +├── services +├── constants ├── features/ │ ├── snippets/ │ │ ├── SnippetCard.tsx @@ -115,14 +117,14 @@ This is an overview of the `./src` directory. ├── pages/ │ ├── Home.tsx │ └── About.tsx -├── index.css +├── styles/ +│ └── global.css └── main.tsx ``` - `app/`: App-wide setup and layout -- `common/`: Shared code and resources - - `components/`: Reusable components across different pages - - `services/`: Interacting with the backend API +- `components/`: Reusable components across different pages +- `services/`: Interacting with the backend API - `features/`: Feature-specific folders - `snippets/`: Components and functions related to snippets - `pages/`: Main pages of the app diff --git a/package-lock.json b/package-lock.json index 48955c0..e6197f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,18 +8,27 @@ "name": "snippit-frontend", "version": "0.0.0", "dependencies": { + "@clerk/clerk-react": "^4.23.2", "@headlessui/react": "^1.7.15", "@heroicons/react": "^2.0.18", + "@hookform/resolvers": "^3.3.1", "@reduxjs/toolkit": "^1.9.5", "axios": "^1.4.0", "babel-plugin-prismjs": "^2.1.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.0.0", "prismjs": "^1.29.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.46.1", "react-redux": "^8.1.1", - "react-router-dom": "^6.14.1" + "react-router-dom": "^6.14.1", + "tailwind-merge": "^1.14.0", + "tailwindcss-animate": "^1.0.7", + "zod": "^3.22.2" }, "devDependencies": { + "@types/node": "^20.5.9", "@types/prismjs": "^1.26.0", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", @@ -50,7 +59,6 @@ "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" }, @@ -429,6 +437,56 @@ "node": ">=6.9.0" } }, + "node_modules/@clerk/clerk-react": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@clerk/clerk-react/-/clerk-react-4.23.2.tgz", + "integrity": "sha512-6MJa8ecr22qHhTfdkMMIJGctMBqj01fLJ4vmfZvr22tIkwkPXoeYJd5XcFKuSoO2dXc1eHD/F9i/HdCqGm68gw==", + "dependencies": { + "@clerk/shared": "^0.21.0", + "@clerk/types": "^3.49.0", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@clerk/clerk-react/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "node_modules/@clerk/shared": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-0.21.0.tgz", + "integrity": "sha512-tkV2OAddFMPBHDjcMbtNNrV3NQD+hGKf2hn3TKv1mRJNZ2oR5Bfk8r0bkaqwoqxX8ndkbHLCa9gwR8SWO7GGow==", + "dependencies": { + "glob-to-regexp": "0.4.1", + "js-cookie": "3.0.1", + "swr": "1.3.0" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@clerk/types": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/@clerk/types/-/types-3.49.0.tgz", + "integrity": "sha512-vAx5R/iYfsgIaIDMiDr6ZKQnAneAmRrUVYz6KCtPG6/hnEAnRYhwXpEUi89e5G0BFmuUfSxe/N/Anfc1PNteXQ==", + "dependencies": { + "csstype": "3.1.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@clerk/types/node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, "node_modules/@esbuild/android-arm": { "version": "0.18.12", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.12.tgz", @@ -875,6 +933,14 @@ "react": ">= 16" } }, + "node_modules/@hookform/resolvers": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.1.tgz", + "integrity": "sha512-K7KCKRKjymxIB90nHDQ7b9nli474ru99ZbqxiqDAWYsYhOsU3/4qLxW91y+1n04ic13ajjZ66L3aXbNef8PELQ==", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", @@ -912,7 +978,6 @@ "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", @@ -926,7 +991,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -935,7 +999,6 @@ "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" } @@ -943,14 +1006,12 @@ "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 + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.18", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -959,14 +1020,12 @@ "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "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" @@ -979,7 +1038,6 @@ "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" } @@ -988,7 +1046,6 @@ "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" @@ -1043,6 +1100,12 @@ "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "node_modules/@types/node": { + "version": "20.5.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.9.tgz", + "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==", + "dev": true + }, "node_modules/@types/prismjs": { "version": "1.26.0", "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz", @@ -1356,14 +1419,12 @@ "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 + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" }, "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" @@ -1375,8 +1436,7 @@ "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 + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, "node_modules/argparse": { "version": "2.0.1", @@ -1452,14 +1512,12 @@ "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 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "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" } @@ -1468,7 +1526,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1478,7 +1535,6 @@ "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" }, @@ -1531,7 +1587,6 @@ "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" } @@ -1574,7 +1629,6 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "funding": [ { "type": "individual", @@ -1601,7 +1655,6 @@ "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" }, @@ -1609,11 +1662,30 @@ "node": ">= 6" } }, + "node_modules/class-variance-authority": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", + "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", + "dependencies": { + "clsx": "2.0.0" + }, + "funding": { + "url": "https://joebell.co.uk" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1644,7 +1716,6 @@ "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" } @@ -1652,8 +1723,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/convert-source-map": { "version": "1.9.0", @@ -1679,7 +1749,6 @@ "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" }, @@ -1726,8 +1795,7 @@ "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 + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, "node_modules/dir-glob": { "version": "3.0.1", @@ -1744,8 +1812,7 @@ "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, "node_modules/doctrine": { "version": "3.0.0", @@ -2131,7 +2198,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -2147,7 +2213,6 @@ "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" }, @@ -2171,7 +2236,6 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -2192,7 +2256,6 @@ "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" }, @@ -2283,14 +2346,12 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -2303,8 +2364,7 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -2339,7 +2399,6 @@ "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" }, @@ -2347,6 +2406,11 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -2386,7 +2450,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -2463,7 +2526,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2472,14 +2534,12 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "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" }, @@ -2491,7 +2551,6 @@ "version": "2.12.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -2503,7 +2562,6 @@ "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" } @@ -2512,7 +2570,6 @@ "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" }, @@ -2524,7 +2581,6 @@ "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" } @@ -2548,11 +2604,18 @@ "version": "1.19.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==", - "dev": true, "bin": { "jiti": "bin/jiti.js" } }, + "node_modules/js-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz", + "integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==", + "engines": { + "node": ">=12" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2623,7 +2686,6 @@ "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" } @@ -2631,8 +2693,7 @@ "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 + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/locate-path": { "version": "6.0.0", @@ -2679,7 +2740,6 @@ "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" } @@ -2688,7 +2748,6 @@ "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" @@ -2720,7 +2779,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2738,7 +2796,6 @@ "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", @@ -2749,7 +2806,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, "funding": [ { "type": "github", @@ -2785,7 +2841,6 @@ "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" } @@ -2803,7 +2858,6 @@ "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" } @@ -2812,7 +2866,6 @@ "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" } @@ -2821,7 +2874,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -2898,7 +2950,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -2915,8 +2966,7 @@ "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 + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-type": { "version": "4.0.0", @@ -2930,14 +2980,12 @@ "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 + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "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" }, @@ -2949,7 +2997,6 @@ "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" } @@ -2958,7 +3005,6 @@ "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" } @@ -2967,7 +3013,6 @@ "version": "8.4.26", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.26.tgz", "integrity": "sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2995,7 +3040,6 @@ "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", @@ -3012,7 +3056,6 @@ "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" }, @@ -3031,7 +3074,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", - "dev": true, "dependencies": { "lilconfig": "^2.0.5", "yaml": "^2.1.1" @@ -3060,7 +3102,6 @@ "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" }, @@ -3079,7 +3120,6 @@ "version": "6.0.13", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", - "dev": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -3091,8 +3131,7 @@ "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 + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -3219,7 +3258,6 @@ "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", @@ -3258,6 +3296,21 @@ "react": "^18.2.0" } }, + "node_modules/react-hook-form": { + "version": "7.46.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.46.1.tgz", + "integrity": "sha512-0GfI31LRTBd5tqbXMGXT1Rdsv3rnvy0FjEk8Gn9/4tp6+s77T7DPZuGEpBRXOauL+NhyGT5iaXzdIM2R6F/E+w==", + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -3344,7 +3397,6 @@ "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" } @@ -3353,7 +3405,6 @@ "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" }, @@ -3391,7 +3442,6 @@ "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, "dependencies": { "is-core-module": "^2.11.0", "path-parse": "^1.0.7", @@ -3417,7 +3467,6 @@ "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" @@ -3458,7 +3507,6 @@ "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", @@ -3552,7 +3600,6 @@ "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" } @@ -3585,7 +3632,6 @@ "version": "3.32.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", @@ -3607,7 +3653,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3639,7 +3684,6 @@ "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" }, @@ -3647,11 +3691,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-1.3.0.tgz", + "integrity": "sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==", + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/tailwind-merge": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", + "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", - "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -3684,6 +3744,14 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3694,7 +3762,6 @@ "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" } @@ -3703,7 +3770,6 @@ "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" }, @@ -3724,7 +3790,6 @@ "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" }, @@ -3735,8 +3800,7 @@ "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 + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, "node_modules/tslib": { "version": "1.14.1", @@ -3846,8 +3910,7 @@ "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 + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { "version": "4.4.4", @@ -3922,8 +3985,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/yallist": { "version": "3.1.1", @@ -3935,7 +3997,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", - "dev": true, "engines": { "node": ">= 14" } @@ -3951,6 +4012,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.2.tgz", + "integrity": "sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 559433d..2d449c1 100644 --- a/package.json +++ b/package.json @@ -10,18 +10,27 @@ "preview": "vite preview" }, "dependencies": { + "@clerk/clerk-react": "^4.23.2", "@headlessui/react": "^1.7.15", "@heroicons/react": "^2.0.18", + "@hookform/resolvers": "^3.3.1", "@reduxjs/toolkit": "^1.9.5", "axios": "^1.4.0", "babel-plugin-prismjs": "^2.1.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.0.0", "prismjs": "^1.29.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.46.1", "react-redux": "^8.1.1", - "react-router-dom": "^6.14.1" + "react-router-dom": "^6.14.1", + "tailwind-merge": "^1.14.0", + "tailwindcss-animate": "^1.0.7", + "zod": "^3.22.2" }, "devDependencies": { + "@types/node": "^20.5.9", "@types/prismjs": "^1.26.0", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", diff --git a/src/app/App.tsx b/src/app/App.tsx index 196a935..ae8ef5b 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,23 +1,50 @@ import { Routes, Route } from 'react-router-dom'; +import { SignedIn, SignedOut } from '@clerk/clerk-react'; -import Navbar from '../common/components/Navbar'; +import Navbar from '../components/Navbar'; import SnippetsFeed from '../pages/SnippetsFeed'; import Contributors from '../pages/Contributors'; import About from '../pages/About'; import Contact from '../pages/Contact'; -import Sidebar from '../common/components/Sidebar'; +import Sidebar from '../components/Sidebar'; import PageNotFound from '../pages/PageNotFound'; import Bookmarks from '../pages/Bookmarks'; import TagPage from '../pages/TagPage'; +import Home from '../pages/Home'; + +import Providers from './Providers'; +import CreateSnippet from '@/pages/CreateSnippet'; +import SignInPage from '@/pages/auth/SignInPage'; +import SignUpPage from '@/pages/auth/SignUpPage'; +import Onboarding from '@/pages/Onboarding'; function App() { return ( - <> + <Providers> <Navbar /> - <Sidebar /> - <main className="mt-16"> + <SignedIn> + <Sidebar /> + </SignedIn> + <main className="bg-background text-text"> <Routes> - <Route path="/" element={<SnippetsFeed />} /> + <Route + path="/" + element={ + <> + <SignedIn> + <SnippetsFeed /> + </SignedIn> + <SignedOut> + <Home /> + </SignedOut> + </> + } + /> + <Route path="/sign-in/*" element={<SignInPage />} /> + <Route path="/sign-up/*" element={<SignUpPage />} /> + + <Route path="/create-snippet" element={<CreateSnippet />} /> + <Route path="/onboarding" element={<Onboarding />} /> <Route path="/contributors" element={<Contributors />} /> <Route path="/about" element={<About />} /> <Route path="/contact" element={<Contact />} /> @@ -26,7 +53,7 @@ function App() { <Route path="/*" element={<PageNotFound />} /> </Routes> </main> - </> + </Providers> ); } diff --git a/src/app/Providers.tsx b/src/app/Providers.tsx new file mode 100644 index 0000000..5152184 --- /dev/null +++ b/src/app/Providers.tsx @@ -0,0 +1,20 @@ +import { Provider } from 'react-redux'; +import store from './store'; +import { ClerkProvider } from '@clerk/clerk-react'; +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +const CLERK_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY as string; + +const Providers = ({ children }: { children: React.ReactNode }) => { + const navigate = useNavigate(); + return ( + <Provider store={store}> + <ClerkProvider publishableKey={CLERK_KEY} navigate={(to) => navigate(to)}> + {children} + </ClerkProvider> + </Provider> + ); +}; + +export default Providers; diff --git a/src/common/components/ProfileMenu.tsx b/src/common/components/ProfileMenu.tsx deleted file mode 100644 index 447995e..0000000 --- a/src/common/components/ProfileMenu.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { Menu, Transition } from '@headlessui/react'; -import { Fragment } from 'react'; -import { currentUser } from '../constants/sample'; - -function classNames({ classes = [] }: { classes?: string[] } = {}) { - return classes.filter(Boolean).join(' '); -} - -const ProfileMenu = () => { - return ( - <Menu as="div" className="relative z-10"> - <Menu.Button className="flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"> - <span className="sr-only">Open user menu</span> - <img - className="h-10 w-10 rounded-full" - src={currentUser.avatar_url} - alt="" - /> - </Menu.Button> - <Transition - as={Fragment} - enter="transition ease-out duration-100" - enterFrom="transform opacity-0 scale-95" - enterTo="transform opacity-100 scale-100" - leave="transition ease-in duration-75" - leaveFrom="transform opacity-100 scale-100" - leaveTo="transform opacity-0 scale-95" - > - <Menu.Items className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"> - <div className="flex flex-col items-center gap-y-4"> - <img - src={currentUser.avatar_url} - className="h-20 w-20 rounded-full" - alt="profile" - /> - <p className="font-semibold">{`${currentUser.firstName} ${currentUser.lastName}`}</p> - </div> - - <Menu.Item> - {({ active }) => ( - <a - href="#" - className={classNames({ - classes: [ - active ? 'bg-gray-100' : '', - 'block px-4 py-2 text-sm text-gray-700', - ], - })} - > - Your Profile - </a> - )} - </Menu.Item> - <Menu.Item> - {({ active }) => ( - <a - href="#" - className={classNames({ - classes: [ - active ? 'bg-gray-100' : '', - 'block px-4 py-2 text-sm text-gray-700', - ], - })} - > - Settings - </a> - )} - </Menu.Item> - <Menu.Item> - {({ active }) => ( - <a - href="#" - className={classNames({ - classes: [ - active ? 'bg-gray-100' : '', - 'block px-4 py-2 text-sm text-gray-700', - ], - })} - > - Sign out - </a> - )} - </Menu.Item> - </Menu.Items> - </Transition> - </Menu> - ); -}; - -export default ProfileMenu; diff --git a/src/common/constants/index.ts b/src/common/constants/index.ts deleted file mode 100644 index c4dd5a4..0000000 --- a/src/common/constants/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum Status { - Idle, - Loading, - Success, - Error, -} diff --git a/src/common/components/Footer.tsx b/src/components/Footer.tsx similarity index 84% rename from src/common/components/Footer.tsx rename to src/components/Footer.tsx index 161ef8a..683f65b 100644 --- a/src/common/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -8,17 +8,17 @@ const Footer = () => { <div className="mb-6 md:mb-0"> <a href="https://Snippit.com/" className="flex items-center"> <CodeBracketIcon className="h-10 w-10 text-blue-500" /> - <span className="self-center text-2xl font-semibold whitespace-nowrap dark:text-white"> + <span className="dark: self-center whitespace-nowrap text-2xl font-semibold"> Snippit </span> </a> </div> - <div className="grid grid-cols-2 gap-8 sm:gap-6 sm:grid-cols-3"> + <div className="grid grid-cols-2 gap-8 sm:grid-cols-3 sm:gap-6"> <div> - <h2 className="mb-6 text-sm font-semibold text-gray-900 uppercase dark:text-white"> + <h2 className="dark: mb-6 text-sm font-semibold uppercase text-gray-900"> Follow us </h2> - <ul className="text-gray-500 dark:text-gray-400 font-medium"> + <ul className="font-medium text-gray-500 dark:text-gray-400"> <li className="mb-4"> <a href="https://github.com/" className="hover:underline "> Github @@ -33,22 +33,22 @@ const Footer = () => { </div> </div> </div> - <hr className="my-6 border-gray-200 sm:mx-auto dark:border-gray-700 lg:my-8" /> + <hr className="my-6 border-gray-200 dark:border-gray-700 sm:mx-auto lg:my-8" /> <div className="sm:flex sm:items-center sm:justify-between"> - <span className="text-sm text-gray-500 sm:text-center dark:text-gray-400"> + <span className="text-sm text-gray-500 dark:text-gray-400 sm:text-center"> © 2023{' '} <a href="https://Snippit.com/" className="hover:underline"> Snippit™ </a> . All Rights Reserved. </span> - <div className="flex mt-4 space-x-5 sm:justify-center sm:mt-0"> + <div className="mt-4 flex space-x-5 sm:mt-0 sm:justify-center"> <a href="#" - className="text-gray-500 hover:text-gray-900 dark:hover:text-white" + className="dark:hover: text-gray-500 hover:text-gray-900" > <svg - className="w-4 h-4" + className="h-4 w-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" @@ -64,10 +64,10 @@ const Footer = () => { </a> <a href="#" - className="text-gray-500 hover:text-gray-900 dark:hover:text-white" + className="dark:hover: text-gray-500 hover:text-gray-900" > <svg - className="w-4 h-4" + className="h-4 w-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" @@ -79,10 +79,10 @@ const Footer = () => { </a> <a href="#" - className="text-gray-500 hover:text-gray-900 dark:hover:text-white" + className="dark:hover: text-gray-500 hover:text-gray-900" > <svg - className="w-4 h-4" + className="h-4 w-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" @@ -98,10 +98,10 @@ const Footer = () => { </a> <a href="#" - className="text-gray-500 hover:text-gray-900 dark:hover:text-white" + className="dark:hover: text-gray-500 hover:text-gray-900" > <svg - className="w-4 h-4" + className="h-4 w-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" @@ -117,10 +117,10 @@ const Footer = () => { </a> <a href="#" - className="text-gray-500 hover:text-gray-900 dark:hover:text-white" + className="dark:hover: text-gray-500 hover:text-gray-900" > <svg - className="w-4 h-4" + className="h-4 w-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" diff --git a/src/common/components/Loading.tsx b/src/components/Loading.tsx similarity index 93% rename from src/common/components/Loading.tsx rename to src/components/Loading.tsx index a9d8c29..948f073 100644 --- a/src/common/components/Loading.tsx +++ b/src/components/Loading.tsx @@ -1,4 +1,4 @@ -import Style from './Loading.module.css'; +import Style from '@/styles/Loading.module.css'; const Loading = ({ title, message }: { title: string; message: string }) => { return ( diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx new file mode 100644 index 0000000..86a5bdf --- /dev/null +++ b/src/components/Modal.tsx @@ -0,0 +1,47 @@ +import { XMarkIcon } from '@heroicons/react/24/outline'; +import { useCallback, useRef, ReactNode } from 'react'; +import { useNavigate } from 'react-router-dom'; + +const Modal = ({ children }: { children: ReactNode }) => { + const overlay = useRef<HTMLDivElement>(null); + const wrapper = useRef<HTMLDivElement>(null); + const navigate = useNavigate(); + + const onDismiss = useCallback(() => { + navigate('/'); + }, [navigate]); + + const handleClick = useCallback( + (e: React.MouseEvent) => { + if (e.target === overlay.current && onDismiss) { + onDismiss(); + } + }, + [onDismiss, overlay], + ); + + return ( + <div + ref={overlay} + className="fixed bottom-0 left-0 right-0 top-0 z-[99] mx-auto bg-black/80" + onClick={handleClick} + > + <div + ref={wrapper} + className="absolute bottom-0 flex h-[90%] w-full flex-col items-center justify-start overflow-auto rounded-t-3xl bg-foreground px-8 pb-72 pt-14 text-text lg:px-40" + > + <button + type="button" + onClick={onDismiss} + className="absolute right-4 top-4 z-50 text-black" + > + <XMarkIcon className="h-6 w-6" /> + <span className="sr-only">Back to home</span> + </button> + {children} + </div> + </div> + ); +}; + +export default Modal; diff --git a/src/common/components/Navbar.tsx b/src/components/Navbar.tsx similarity index 70% rename from src/common/components/Navbar.tsx rename to src/components/Navbar.tsx index e935a59..b656194 100644 --- a/src/common/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -6,7 +6,7 @@ import { XMarkIcon, } from '@heroicons/react/24/outline'; import { useLocation } from 'react-router-dom'; -import ProfileMenu from './ProfileMenu'; +import { UserButton, useUser } from '@clerk/clerk-react'; const navigation = [ { name: 'Home', href: '/' }, @@ -22,19 +22,20 @@ function classNames({ classes = [] }: { classes?: string[] } = {}) { const Navbar = () => { const { pathname } = useLocation(); + const { isSignedIn, user } = useUser(); const isActive = (href: string) => href === pathname; return ( <Disclosure as="nav" - className="fixed left-0 right-0 top-0 z-20 h-16 border-b border-slate-400 bg-white" + className="fixed left-0 right-0 top-0 z-20 h-16 bg-foreground text-text" > {({ open }) => ( <> - <div className="px-2 sm:px-6 lg:px-8"> + <div className="px-4"> <div className="relative flex h-16 items-center justify-between"> <div className="flex items-center"> - <Disclosure.Button className="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white lg:hidden"> + <Disclosure.Button className="inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white md:hidden"> <span className="sr-only">Open main menu</span> {open ? ( <XMarkIcon className="block h-6 w-6" aria-hidden="true" /> @@ -42,9 +43,9 @@ const Navbar = () => { <Bars3Icon className="block h-6 w-6" aria-hidden="true" /> )} </Disclosure.Button> - <div className="flex flex-shrink-0 items-center text-primary-color"> + <div className="flex flex-shrink-0 items-center text-primary"> <CodeBracketIcon className="h-10 w-10 " /> - <h1 className="text-2xl font-bold">Snippit</h1> + <h1 className="text-2xl font-bold text-primary">Snippit</h1> </div> </div> <div className="flexStart inset-y-0 right-0 flex items-center gap-3 pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0"> @@ -55,12 +56,26 @@ const Navbar = () => { <span className="sr-only">View notifications</span> <BellIcon className="h-6 w-6" aria-hidden="true" /> </button> - <ProfileMenu /> + <div className="flex gap-1"> + <UserButton afterSignOutUrl="/" /> + <div className="hidden flex-col md:flex"> + {isSignedIn && ( + <> + <p className="font-medium capitalize"> + {user?.fullName} + </p> + <p className="text-xs text-text/50"> + {user?.primaryEmailAddress?.toString()} + </p> + </> + )} + </div> + </div> </div> </div> </div> - <Disclosure.Panel className="bg-white shadow-sm lg:hidden"> + <Disclosure.Panel className="bg-foreground text-text shadow-sm md:hidden"> <div className="space-y-1 px-2 pb-3 pt-2"> {navigation.map((item) => ( <Disclosure.Button @@ -70,8 +85,8 @@ const Navbar = () => { className={classNames({ classes: [ isActive(item.href) - ? 'bg-accent-color text-white' - : 'text-gray-500 hover:bg-secondary-color hover:text-white', + ? 'bg-secondary ' + : 'text-gray-500 hover:bg-secondary', 'block rounded-md px-3 py-2 text-base font-medium', ], })} diff --git a/src/components/SelectBox.tsx b/src/components/SelectBox.tsx new file mode 100644 index 0000000..f8f0427 --- /dev/null +++ b/src/components/SelectBox.tsx @@ -0,0 +1,96 @@ +import { Listbox, Transition } from '@headlessui/react'; +import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/24/outline'; +import { Fragment } from 'react'; + +const SelectBox = ({ + options, + value, + onChange, +}: { + options: string[]; + value: string; + onChange: (value: string) => void; +}) => { + return ( + <Listbox value={value} onChange={onChange} as="div" className="space-y-2"> + <div className="relative"> + <Listbox.Button + as="button" + className="relative w-full rounded-xl bg-gray-100 py-4 pl-4 pr-10 text-left capitalize focus:outline-none focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300" + > + {value ? value : 'Select a Language'} + <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> + <ChevronUpDownIcon + className="h-5 w-5 text-gray-400" + aria-hidden="true" + /> + </span> + </Listbox.Button> + <Transition + as={Fragment} + leave="transition ease-in duration-100" + leaveFrom="opacity-100" + leaveTo="opacity-0" + > + <Listbox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"> + {options.map((option, optionIdx) => ( + <Listbox.Option + key={optionIdx} + className={({ active }) => + `relative cursor-default select-none py-2 pl-10 pr-4 ${ + active ? 'bg-primary/10 text-primary' : 'text-gray-900' + }` + } + value={option} + > + {({ selected }) => ( + <> + <span + className={`block truncate capitalize ${ + selected ? 'font-medium' : 'font-normal' + }`} + > + {option} + </span> + {selected ? ( + <span className="absolute inset-y-0 left-0 flex items-center pl-3 text-secondary"> + <CheckIcon className="h-5 w-5" aria-hidden="true" /> + </span> + ) : null} + </> + )} + </Listbox.Option> + ))} + </Listbox.Options> + </Transition> + </div> + {/* <Listbox.Options + as="ul" + className="mt-2 space-y-1 rounded-lg border border-gray-200 bg-white py-2 shadow-lg" + > + {options.map((option) => ( + <Listbox.Option + key={option} + value={option} + className={({ active }) => + `${ + active ? 'bg-primary ' : 'text-gray-900' + } relative cursor-pointer select-none px-4 py-2` + } + > + {({ selected }) => ( + <div className="flex"> + {selected && ( + <CheckIcon className="h-5 w-5" aria-hidden="true" /> + )} + <span className="ml-6">{option}</span> + </div> + )} + </Listbox.Option> + ))} + </Listbox.Options> */} + </Listbox> + ); +}; + +export default SelectBox; diff --git a/src/common/components/Sidebar.tsx b/src/components/Sidebar.tsx similarity index 82% rename from src/common/components/Sidebar.tsx rename to src/components/Sidebar.tsx index 3e51d8c..5e09ac0 100644 --- a/src/common/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,26 +1,26 @@ import { - HomeIcon, + Squares2X2Icon, BookmarkIcon, InboxIcon, UsersIcon, PencilSquareIcon, ArrowLeftCircleIcon, DocumentIcon, -} from '@heroicons/react/24/solid'; +} from '@heroicons/react/24/outline'; import { NavLink } from 'react-router-dom'; -import TagsDropDown from '../../features/tags/TagsDropDown'; +import TagsDropDown from '../features/tags/TagsDropDown'; const Sidebar = () => { return ( - <aside - className="fixed left-0 top-16 z-20 h-screen w-64 -translate-x-full border-r transition-transform lg:translate-x-0" + <div + className="fixed inset-y-0 bottom-0 left-0 top-16 z-20 hidden w-[250px] flex-col justify-between bg-foreground md:flex" aria-label="Sidebar" > <div className="h-full overflow-y-auto bg-white px-3 py-4"> <ul className="space-y-2 font-medium"> <li> <NavLink to="/" className="sideMenuItem"> - <HomeIcon className="h-6 w-6" /> + <Squares2X2Icon className="h-6 w-6" /> <span className="ml-3">My Feed</span> </NavLink> </li> @@ -28,7 +28,7 @@ const Sidebar = () => { <NavLink to="/inbox" className="sideMenuItem"> <InboxIcon className="h-6 w-6" /> <span className="ml-3 flex-1 whitespace-nowrap">Inbox</span> - <span className="ml-3 inline-flex h-3 w-3 items-center justify-center rounded-full bg-secondary-color p-3 text-sm font-medium text-primary-color"> + <span className="bg-secondary-color text-primary-color ml-3 inline-flex h-3 w-3 items-center justify-center rounded-full p-3 text-sm font-medium"> 3 </span> </NavLink> @@ -72,7 +72,7 @@ const Sidebar = () => { </li> </ul> </div> - </aside> + </div> ); }; diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..8e6c4f0 --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,20 @@ +export enum Status { + Idle, + Loading, + Success, + Error, +} + +export const Languages = [ + 'javascript', + 'css', + 'html', + 'python', + 'ruby', + 'java', + 'php', + 'c', + 'cpp', + 'sh', + 'go', +]; diff --git a/src/common/constants/sample.ts b/src/constants/sample.ts similarity index 98% rename from src/common/constants/sample.ts rename to src/constants/sample.ts index a0182eb..0dbc793 100644 --- a/src/common/constants/sample.ts +++ b/src/constants/sample.ts @@ -1,4 +1,4 @@ -import { Snippet, User } from '../../app/common.types'; +import { Snippet, User } from '../app/common.types'; export const currentUser: User = { id: 201, diff --git a/src/features/snippets/SnippetCard.tsx b/src/features/snippets/SnippetCard.tsx index 2e1bd1a..8474f60 100644 --- a/src/features/snippets/SnippetCard.tsx +++ b/src/features/snippets/SnippetCard.tsx @@ -8,9 +8,9 @@ import { StarIcon as FilledStarIcon, BookmarkIcon as FilledBookmarkIcon, } from '@heroicons/react/24/solid'; -import { currentUser } from '../../common/constants/sample'; +import { currentUser } from '../../constants/sample'; import { Snippet } from '../../app/common.types'; -import { formatDate } from '../../common/utils/formatDate'; +import { formatDate } from '../../lib/formatDate'; import Prism from 'prismjs'; import { useEffect } from 'react'; @@ -23,9 +23,9 @@ const SnippetCard = (props: Snippet) => { }, []); return ( - <div className="flex max-w-full cursor-pointer flex-col gap-3 rounded-md border bg-white p-5 hover:shadow-md"> + <div className="flex w-full max-w-[700px] flex-col gap-3 rounded-xl border bg-foreground p-4 text-text"> <h3 className="text-2xl font-semibold">{props.title}</h3> - <div className="flex gap-5"> + <div className="flex gap-2"> <div> <img src={props.user?.avatar_url} @@ -43,7 +43,7 @@ const SnippetCard = (props: Snippet) => { <div> <p>{props.description}</p> </div> - <div className="flexStart gap-2 text-accent-color"> + <div className="flexStart text-accent-color gap-2"> {props.tags?.map((tag) => ( <span key={tag.id}>#{tag.name}</span> ))} @@ -79,7 +79,7 @@ const SnippetCard = (props: Snippet) => { className="flexCenter rounded-lg bg-gray-100 p-3 text-gray-500 transition hover:shadow-md hover:ring-1" > {bookmarked ? ( - <FilledBookmarkIcon className="h-6 w-6 text-accent-color" /> + <FilledBookmarkIcon className="text-accent-color h-6 w-6" /> ) : ( <BookmarkIcon className="h-6 w-6" /> )} diff --git a/src/features/snippets/SnippetForm.tsx b/src/features/snippets/SnippetForm.tsx new file mode 100644 index 0000000..aee66e7 --- /dev/null +++ b/src/features/snippets/SnippetForm.tsx @@ -0,0 +1,115 @@ +import SelectBox from '@/components/SelectBox'; +import { Languages } from '@/constants'; +import React, { useState } from 'react'; + +const SnippetForm = () => { + const [snippetData, setSnippetData] = useState({ + title: '', + description: '', + language: '', + code: '', + tags: [] as string[], + }); + + const handleInputChange = ( + e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, + ) => { + const { name, value } = e.target; + setSnippetData({ + ...snippetData, + [name]: value, + }); + }; + + const handleTagsChange = (e: React.ChangeEvent<HTMLInputElement>) => { + const inputValue = e.target.value.trim(); + if (inputValue.length === 0) return; + + const tags = inputValue.split(',') as string[]; + setSnippetData({ + ...snippetData, + tags: tags.map((tag) => tag.trim()), + }); + }; + + const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { + e.preventDefault(); + console.log(snippetData); + + // Reset the form after submission + setSnippetData({ + title: '', + description: '', + language: '', + code: '', + tags: [], + }); + }; + + return ( + <form + className="mx-auto flex w-full max-w-5xl flex-col gap-10 pt-12 text-lg lg:pt-24" + onSubmit={handleSubmit} + > + <div className="flex flex-col"> + <label htmlFor="project-name">Snippet Name</label> + <input + id="project-name" + type="text" + name="title" + value={snippetData.title} + onChange={handleInputChange} + className="formInput" + /> + </div> + <div className="flex flex-col"> + <label htmlFor="project-description">Snippet Description</label> + <textarea + id="project-description" + name="description" + value={snippetData.description} + onChange={handleInputChange} + className="formInput" + /> + </div> + <div className="flex gap-4"> + <div className="flex flex-1 flex-col"> + <label htmlFor="project-language">Snippet Language</label> + <SelectBox + options={Languages} + value={snippetData.language} + onChange={(value) => + setSnippetData({ ...snippetData, language: value }) + } + /> + </div> + <div className="flex flex-1 flex-col"> + <label htmlFor="project-tags">Snippet Tags</label> + <input + id="project-tags" + type="text" + className="formInput" + value={snippetData.tags.join(', ')} + onChange={handleTagsChange} + /> + </div> + </div> + <div className="flex flex-col"> + <label htmlFor="project-code">Paste Code</label> + <textarea + id="project-code" + name="code" + value={snippetData.code} + onChange={handleInputChange} + rows={6} + className="formInput" + /> + </div> + <button type="submit" className="submitBtn"> + Create Snippet + </button> + </form> + ); +}; + +export default SnippetForm; diff --git a/src/features/snippets/SnippetList.tsx b/src/features/snippets/SnippetList.tsx index 9b51317..53a3e69 100644 --- a/src/features/snippets/SnippetList.tsx +++ b/src/features/snippets/SnippetList.tsx @@ -3,9 +3,9 @@ import SnippetCard from './SnippetCard'; const SnippetList = ({ snippets }: { snippets: Snippet[] }) => { return ( - <div className="mx-auto grid w-full gap-5 py-10 sm:max-w-2xl"> + <div className="flex w-full flex-col gap-2 py-10 md:gap-5"> {snippets.map((item) => ( - <div className="overflow-hidden p-2" key={item.id}> + <div className="p-2" key={item.id}> <SnippetCard {...item} /> </div> ))} diff --git a/src/features/snippets/snippetsSlice.ts b/src/features/snippets/snippetsSlice.ts index daea182..3676811 100644 --- a/src/features/snippets/snippetsSlice.ts +++ b/src/features/snippets/snippetsSlice.ts @@ -1,6 +1,6 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; -import ApiService from '../../common/services'; -import { Status } from '../../common/constants'; +import ApiService from '../../services'; +import { Status } from '../../constants'; import { Snippet } from '../../app/common.types'; interface SnippetState { diff --git a/src/features/tags/TagsDropDown.tsx b/src/features/tags/TagsDropDown.tsx index 667856f..6cdc4ee 100644 --- a/src/features/tags/TagsDropDown.tsx +++ b/src/features/tags/TagsDropDown.tsx @@ -1,8 +1,8 @@ import { Disclosure } from '@headlessui/react'; -import { ChevronDownIcon, TagIcon } from '@heroicons/react/24/solid'; +import { ChevronDownIcon, HashtagIcon } from '@heroicons/react/24/outline'; import { useAppDispatch, useAppSelector } from '../../app/hooks'; import { useEffect } from 'react'; -import { Status } from '../../common/constants'; +import { Status } from '../../constants'; import { getAllTags } from './tagsSlice'; import { Link } from 'react-router-dom'; @@ -20,10 +20,10 @@ const TagsDropDown = () => { <Disclosure> <Disclosure.Button type="button" - className="group flex w-full items-center rounded-lg p-2 text-base text-gray-500 transition hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700" + className="dark: group flex w-full items-center rounded-lg p-2 text-base text-gray-500 transition hover:bg-gray-100 dark:hover:bg-gray-700" aria-controls="dropdown-tags" > - <TagIcon className="h-6 w-6" /> + <HashtagIcon className="h-6 w-6" /> <span className="ml-3 flex-1 whitespace-nowrap text-left"> Browse Tags </span> diff --git a/src/features/tags/tagsSlice.ts b/src/features/tags/tagsSlice.ts index fc28e17..09b6104 100644 --- a/src/features/tags/tagsSlice.ts +++ b/src/features/tags/tagsSlice.ts @@ -1,6 +1,6 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; -import ApiService from '../../common/services'; -import { Status } from '../../common/constants'; +import ApiService from '../../services'; +import { Status } from '../../constants'; import { Tag } from '../../app/common.types'; interface TagState { diff --git a/src/index.css b/src/index.css deleted file mode 100644 index 990d226..0000000 --- a/src/index.css +++ /dev/null @@ -1,49 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: 'Open Sans', sans-serif; - height: 100%; - background-color: #eaf3f5; -} - -.flexCenter { - @apply flex items-center justify-center; -} - -.flexBetween { - @apply flex items-center justify-between; -} - -.flexStart { - @apply flex items-center justify-start; -} - -::-webkit-scrollbar { - width: 5px; - height: 4px; -} - -::-webkit-scrollbar-thumb { - background: #888; - border-radius: 12px; -} - -.sideMenuItem { - @apply flex items-center rounded-lg p-2 text-gray-500 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700; -} - -.sideMenuItem.active { - @apply bg-accent-color text-white; -} - -.sideDropItem { - @apply flex w-full items-center rounded-lg p-2 pl-11 text-accent-color transition duration-75 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700; -} diff --git a/src/common/utils/formatDate.ts b/src/lib/formatDate.ts similarity index 100% rename from src/common/utils/formatDate.ts rename to src/lib/formatDate.ts diff --git a/src/lib/validations/user.ts b/src/lib/validations/user.ts new file mode 100644 index 0000000..d9f68a9 --- /dev/null +++ b/src/lib/validations/user.ts @@ -0,0 +1,12 @@ +import * as z from 'zod'; + +export const UserValidation = z.object({ + username: z + .string() + .min(3, { message: 'Minimum 3 characters.' }) + .max(30, { message: 'Maximum 30 caracters.' }), + bio: z + .string() + .min(3, { message: 'Minimum 3 characters.' }) + .max(1000, { message: 'Maximum 1000 caracters.' }), +}); diff --git a/src/main.tsx b/src/main.tsx index e0a0707..0803cee 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,17 +1,14 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; -import { Provider } from 'react-redux'; + import App from './app/App.tsx'; -import store from './app/store.ts'; -import './index.css'; +import './styles/globals.css'; ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <BrowserRouter> - <Provider store={store}> - <App /> - </Provider> + <App /> </BrowserRouter> </React.StrictMode>, ); diff --git a/src/pages/About.tsx b/src/pages/About.tsx index ae579d7..18c0915 100644 --- a/src/pages/About.tsx +++ b/src/pages/About.tsx @@ -22,7 +22,7 @@ const About = () => { <div className="mx-auto max-w-7xl px-6 lg:px-8"> <div className="mx-auto max-w-2xl lg:mx-0"> - <h2 className="text-4xl font-bold tracking-tight text-white sm:text-6xl"> + <h2 className="text-4xl font-bold tracking-tight sm:text-6xl"> Welcome to Snippit! </h2> <p className="mt-6 text-lg leading-8 text-gray-300"> @@ -41,7 +41,7 @@ const About = () => { </p> </div> <div className="mx-auto mt-10 max-w-2xl lg:mx-0 lg:max-w-none"> - <div className="grid grid-cols-1 gap-x-8 gap-y-6 text-base font-semibold leading-7 text-white sm:grid-cols-2 md:flex lg:gap-x-10"> + <div className="grid grid-cols-1 gap-x-8 gap-y-6 text-base font-semibold leading-7 sm:grid-cols-2 md:flex lg:gap-x-10"> {links.map((link) => ( <a key={link.name} href={link.href}> {link.name} <span aria-hidden="true">→</span> @@ -54,7 +54,7 @@ const About = () => { <dt className="text-base leading-7 text-gray-300"> {stat.name} </dt> - <dd className="text-2xl font-bold leading-9 tracking-tight text-white"> + <dd className="text-2xl font-bold leading-9 tracking-tight "> {stat.value} </dd> </div> diff --git a/src/pages/Bookmarks.tsx b/src/pages/Bookmarks.tsx index 0312d4c..b4d7c28 100644 --- a/src/pages/Bookmarks.tsx +++ b/src/pages/Bookmarks.tsx @@ -1,8 +1,8 @@ import { useEffect } from 'react'; import { useAppDispatch, useAppSelector } from '../app/hooks'; -import { Status } from '../common/constants'; +import { Status } from '../constants'; import { getAllSnippets } from '../features/snippets/snippetsSlice'; -import { currentUser } from '../common/constants/sample'; +import { currentUser } from '../constants/sample'; import SnippetList from '../features/snippets/SnippetList'; const Bookmarks = () => { diff --git a/src/pages/CreateSnippet.tsx b/src/pages/CreateSnippet.tsx new file mode 100644 index 0000000..51ce7c6 --- /dev/null +++ b/src/pages/CreateSnippet.tsx @@ -0,0 +1,22 @@ +import Modal from '@/components/Modal'; +import SnippetForm from '@/features/snippets/SnippetForm'; +import { RedirectToSignIn, useAuth } from '@clerk/clerk-react'; + +const CreateSnippet = () => { + const { isLoaded, userId } = useAuth(); + + if (!isLoaded || !userId) { + return <RedirectToSignIn />; + } + + return ( + <Modal> + <h3 className="w-full max-w-5xl text-left text-3xl font-extrabold md:text-5xl"> + Create a New Snippet + </h3> + <SnippetForm /> + </Modal> + ); +}; + +export default CreateSnippet; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 4925dba..f2b585e 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -24,8 +24,8 @@ const Home = () => { </p> <div className="mt-10 flex items-center justify-center gap-x-6"> <a - href="#" - className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + href="/sign-in" + className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" > Get started </a> diff --git a/src/pages/Onboarding.tsx b/src/pages/Onboarding.tsx new file mode 100644 index 0000000..c6dc253 --- /dev/null +++ b/src/pages/Onboarding.tsx @@ -0,0 +1,106 @@ +import { RedirectToSignIn, useUser } from '@clerk/clerk-react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import * as z from 'zod'; +import { UserValidation } from '@/lib/validations/user'; +import { useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; + +const Onboarding = () => { + const { user, isLoaded, isSignedIn } = useUser(); + const navigate = useNavigate(); + + if (!isSignedIn) return <RedirectToSignIn />; + + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm<z.infer<typeof UserValidation>>({ + resolver: zodResolver(UserValidation), + defaultValues: { + username: '', + bio: '', + }, + }); + + useEffect(() => { + if (isLoaded && isSignedIn) + reset({ username: user?.firstName ? user.firstName : '', bio: '' }); + }, [isLoaded, isSignedIn, user, reset]); + + const onSubmit = async (data: z.infer<typeof UserValidation>) => { + try { + // TODO: Create a new user in the database + await user?.update({ + username: data.username, + unsafeMetadata: { + profile: { + bio: data.bio, + }, + }, + }); + // On success, redirect to home page + navigate('/'); + } catch (error) { + console.log(error); + } + }; + + return ( + <section className="padding min-h-screen w-full"> + <div className="mx-auto max-w-5xl"> + <h3 className="mb-8 text-left text-3xl font-extrabold md:text-5xl"> + Welcome, {user?.fullName}! Let's get you set up. + </h3> + <p className="w-full text-left text-lg md:text-xl"> + Please fill out the following information to complete your profile. + </p> + <div className="mt-10 flex gap-8"> + <img + src={user?.imageUrl} + alt="Profile" + className="h-24 w-24 rounded-full border-2 border-white shadow-sm" + /> + <form + className="mx-auto flex w-full max-w-5xl flex-col gap-10 text-lg" + onSubmit={handleSubmit(onSubmit)} + > + <div className="flex flex-col justify-start gap-2"> + <label htmlFor="username">Username</label> + <input + {...register('username')} + className="formInput" + placeholder="Username" + /> + {errors?.username && ( + <p className="text-xs text-red-500"> + {errors.username.message} + </p> + )} + </div> + + <div className="flex flex-col justify-start gap-2"> + <label htmlFor="bio">Bio</label> + <textarea + {...register('bio')} + className="formInput" + placeholder="Short introduction about yourself" + /> + {errors?.bio && ( + <p className="text-xs text-red-500">{errors.bio.message}</p> + )} + </div> + + <button type="submit" className="submitBtn"> + Save + </button> + </form> + </div> + </div> + </section> + ); +}; + +export default Onboarding; diff --git a/src/pages/PageNotFound.tsx b/src/pages/PageNotFound.tsx index 5440215..ff0616e 100644 --- a/src/pages/PageNotFound.tsx +++ b/src/pages/PageNotFound.tsx @@ -1,6 +1,6 @@ const PageNotFound = () => { return ( - <section className="grid min-h-screen place-items-center bg-white px-6 py-24 sm:py-32 lg:ml-64 lg:px-8"> + <section className="grid min-h-screen w-full place-items-center bg-white px-6 py-24 sm:py-32 lg:px-8"> <div className="text-center"> <p className="text-base font-semibold text-indigo-600">404</p> <h1 className="mt-4 text-3xl font-bold tracking-tight text-gray-900 sm:text-5xl"> @@ -12,7 +12,7 @@ const PageNotFound = () => { <div className="mt-10 flex items-center justify-center gap-x-6"> <a href="/" - className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" > Go back home </a> diff --git a/src/pages/SnippetsFeed.tsx b/src/pages/SnippetsFeed.tsx index 74a49f4..4ad0546 100644 --- a/src/pages/SnippetsFeed.tsx +++ b/src/pages/SnippetsFeed.tsx @@ -1,13 +1,16 @@ import { useAppDispatch, useAppSelector } from '../app/hooks'; import { useEffect } from 'react'; -import { Status } from '../common/constants'; +import { Status } from '../constants'; import { getAllSnippets } from '../features/snippets/snippetsSlice'; import SnippetList from '../features/snippets/SnippetList'; -import Loading from '../common/components/Loading'; +import Loading from '../components/Loading'; +import { PlusIcon } from '@heroicons/react/24/outline'; +import { useNavigate } from 'react-router-dom'; const SnippetsFeed = () => { const snippets = useAppSelector((state) => state.snippets); const dispatch = useAppDispatch(); + const navigate = useNavigate(); useEffect(() => { if (snippets.status === Status.Idle) { @@ -17,7 +20,7 @@ const SnippetsFeed = () => { if (snippets.status === Status.Loading) { return ( - <div className="grid min-h-screen place-items-center bg-white px-6 py-24 sm:py-32 lg:px-8"> + <div className="mt-16 grid min-h-screen w-full place-items-center bg-foreground px-6 py-24 sm:py-32 md:ml-[250px] lg:col-span-4 lg:px-8"> <Loading title="Fetching data from API" message="Please be patient. Sometimes it only takes ETERNITY! 😉" @@ -28,14 +31,47 @@ const SnippetsFeed = () => { if (snippets.status === Status.Error) { return ( - <div className="flex min-h-screen flex-col gap-5 bg-white py-10 text-center"> - <h2 className="text-2xl font-semibold">Oops! Something went wrong😯</h2> + <div className="mt-16 flex min-h-screen w-full flex-col gap-5 bg-foreground py-10 text-center md:ml-[250px] lg:col-span-4"> + <h2 className="text-2xl font-semibold"> + Oops! Something went wrong 😯 + </h2> <p className="text-lg font-medium text-rose-500">{snippets.error}</p> </div> ); } - return <SnippetList snippets={snippets.data} />; + return ( + <div className="mt-16 flex max-w-full flex-col gap-2 p-1 pb-8 md:ml-[250px] md:p-4"> + <header className="flex justify-between gap-1"> + <h2 className="text-2xl font-semibold">Snippets Feed</h2> + <button + type="button" + onClick={() => navigate('/create-snippet')} + className="inline-flex items-center rounded-md border border-transparent bg-primary px-4 py-2 font-medium text-white shadow-sm hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2" + > + <PlusIcon className="mr-2 h-6 w-6" /> + Create Snippet + </button> + </header> + <SnippetList snippets={snippets.data} /> + <footer className="my-10 w-full flex-shrink-0 py-10"> + <div className="flex items-center justify-between"> + <p className="text-sm text-gray-500"> + Copyright © 2023 Snippit. All rights reserved. + </p> + <div className="flex gap-2"> + <a href="#" className="text-sm text-gray-500 hover:text-gray-700"> + Terms of Service + </a> + <p className="text-sm text-gray-500">|</p> + <a href="#" className="text-sm text-gray-500 hover:text-gray-700"> + Privacy Policy + </a> + </div> + </div> + </footer> + </div> + ); }; export default SnippetsFeed; diff --git a/src/pages/auth/SignInPage.tsx b/src/pages/auth/SignInPage.tsx new file mode 100644 index 0000000..3894952 --- /dev/null +++ b/src/pages/auth/SignInPage.tsx @@ -0,0 +1,14 @@ +import { SignIn } from '@clerk/clerk-react'; + +const SignInPage = () => { + return ( + <SignIn + path="/sign-in" + routing="path" + signUpUrl="/sign-up" + afterSignInUrl="/" + /> + ); +}; + +export default SignInPage; diff --git a/src/pages/auth/SignUpPage.tsx b/src/pages/auth/SignUpPage.tsx new file mode 100644 index 0000000..3df9518 --- /dev/null +++ b/src/pages/auth/SignUpPage.tsx @@ -0,0 +1,15 @@ +import { SignUp } from '@clerk/clerk-react'; + +const SignUpPage = () => { + return ( + <SignUp + routing="path" + path="/sign-up" + signInUrl="/sign-in" + redirectUrl="/onboarding" + afterSignUpUrl="/onboarding" + /> + ); +}; + +export default SignUpPage; diff --git a/src/common/services/index.ts b/src/services/index.ts similarity index 100% rename from src/common/services/index.ts rename to src/services/index.ts diff --git a/src/common/components/Loading.module.css b/src/styles/Loading.module.css similarity index 100% rename from src/common/components/Loading.module.css rename to src/styles/Loading.module.css diff --git a/src/styles/globals.css b/src/styles/globals.css new file mode 100644 index 0000000..2529b25 --- /dev/null +++ b/src/styles/globals.css @@ -0,0 +1,99 @@ +@import url('https://fonts.googleapis.com/css2?family=Nunito+Sans:opsz,wght@6..12,400;6..12,500;6..12,600;6..12,700&display=swap'); + +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + * { + @apply border-border; + } + body, + html { + @apply bg-background text-foreground; + height: 100%; + font-family: 'Nunito Sans', sans-serif; + } +} + +@layer components { + .maxContainer { + @apply mx-auto max-w-[1440px] px-8 sm:px-16; + } +} + +@layer utilities { + .padding { + @apply px-8 py-12 sm:px-16 sm:py-24; + } + + .xPadding { + @apply px-8 sm:px-16; + } + + .yPadding { + @apply py-12 sm:py-24; + } + + .lPadding { + @apply pl-8 sm:pl-16; + } + + .rPadding { + @apply pr-8 sm:pr-16; + } + + .tPadding { + @apply pt-12 sm:pt-24; + } + + .bPadding { + @apply pb-12 sm:pb-24; + } + + .innerWidth { + @apply w-[100%] lg:w-[80%]; + } + + .flexCenter { + @apply flex items-center justify-center; + } + + .flexBetween { + @apply flex items-center justify-between; + } + + .flexStart { + @apply flex items-center justify-start; + } + + .formInput { + @apply w-full rounded-xl bg-gray-100 p-4 outline-0; + } + + .submitBtn { + @apply w-full max-w-[300px] rounded-xl bg-primary p-4 text-lg font-bold shadow-sm hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2; + } +} + +::-webkit-scrollbar { + width: 5px; + height: 4px; +} + +::-webkit-scrollbar-thumb { + background: #888; + border-radius: 12px; +} + +.sideMenuItem { + @apply flex items-center rounded-lg p-2 text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700; +} + +.sideMenuItem.active { + @apply bg-primary text-white; +} + +.sideDropItem { + @apply flex w-full items-center rounded-lg p-2 pl-11 text-primary transition duration-75 hover:bg-gray-100 dark:hover:bg-gray-700; +} diff --git a/tailwind.config.js b/tailwind.config.js index 2636a63..76a1e25 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,20 +1,35 @@ /** @type {import('tailwindcss').Config} */ -const defaultTheme = require('tailwindcss/defaultTheme'); - -export default { - content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], +module.exports = { + darkMode: ['class'], + content: [ + './pages/**/*.{ts,tsx}', + './components/**/*.{ts,tsx}', + './app/**/*.{ts,tsx}', + './src/**/*.{ts,tsx}', + ], theme: { - extend: { - fontFamily: { - sans: ['Inter var', ...defaultTheme.fontFamily.sans], + container: { + center: true, + padding: '2rem', + screens: { + '2xl': '1400px', }, + }, + extend: { colors: { - 'primary-color': '#d42b63', - 'secondary-color': '#f2c0d0', - 'accent-color': '#be275a', + border: '#ECEFF1', + input: '#E0E0E0', + ring: '#2196F3', + text: '#11142D', + background: '#f4f4f4', + foreground: '#fff', + primary: '#475BE8', + secondary: '#CFC8FF', + destructive: '#FE6D8E', + muted: '#B0BEC5', + accent: '#2ED480', }, }, }, - plugins: [], - darkMode: 'class', + plugins: [require('tailwindcss-animate')], }; diff --git a/tsconfig.json b/tsconfig.json index a7fc6fb..ac9e4c4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,10 +2,13 @@ "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], "module": "ESNext", "skipLibCheck": true, - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, @@ -13,13 +16,25 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + /* Resolving */ + "baseUrl": ".", + "paths": { + "@/*": [ + "./src/*" + ] + } }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] -} + "include": [ + "src" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index a1d8d11..335db37 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,5 +1,6 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import path from 'path'; // https://vitejs.dev/config/ export default defineConfig({ @@ -10,4 +11,9 @@ export default defineConfig({ }, }), ], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, });