diff --git a/package.json b/package.json index c26b964..27362fc 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3.3.1", + "@next/eslint-plugin-next": "^15.5.9", "@tailwindcss/postcss": "^4.0.15", "@types/node": "^20.14.10", "@types/react": "^19.0.0", @@ -65,6 +66,7 @@ "eslint": "^9.23.0", "eslint-config-next": "^15.2.3", "eslint-plugin-drizzle": "^0.2.3", + "eslint-plugin-react-hooks": "^7.0.1", "postcss": "^8.5.3", "prettier": "^3.5.3", "prettier-plugin-tailwindcss": "^0.6.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5235236..d5d395c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,10 +67,10 @@ importers: version: 0.553.0(react@19.2.3) next: specifier: ^15.5.9 - version: 15.5.9(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 15.5.9(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next-auth: specifier: 5.0.0-beta.30 - version: 5.0.0-beta.30(next@15.5.9(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nodemailer@7.0.12)(react@19.2.3) + version: 5.0.0-beta.30(next@15.5.9(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nodemailer@7.0.12)(react@19.2.3) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -111,6 +111,9 @@ importers: '@eslint/eslintrc': specifier: ^3.3.1 version: 3.3.3 + '@next/eslint-plugin-next': + specifier: ^15.5.9 + version: 15.5.9 '@tailwindcss/postcss': specifier: ^4.0.15 version: 4.1.18 @@ -135,6 +138,9 @@ importers: eslint-plugin-drizzle: specifier: ^0.2.3 version: 0.2.3(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-react-hooks: + specifier: ^7.0.1 + version: 7.0.1(eslint@9.39.2(jiti@2.6.1)) postcss: specifier: ^8.5.3 version: 8.5.6 @@ -219,10 +225,77 @@ packages: '@auth/drizzle-adapter@1.11.1': resolution: {integrity: sha512-cQTvDZqsyF7RPhDm/B6SvqdVP9EzQhy3oM4Muu7fjjmSYFLbSR203E6dH631ZHSKDn2b4WZkfMnjPDzRsPSAeA==} + '@babel/code-frame@7.28.6': + resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.6': + resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.6': + resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.6': + resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/runtime@7.28.4': resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.6': + resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + engines: {node: '>=6.9.0'} + '@base-ui/react@1.0.0': resolution: {integrity: sha512-4USBWz++DUSLTuIYpbYkSgy1F9ZmNG9S/lXvlUN6qMK0P0RlW+6eQmDUB4DgZ7HVvtXl4pvi4z5J2fv6Z3+9hg==} engines: {node: '>=14.0.0'} @@ -1606,6 +1679,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + baseline-browser-mapping@2.9.15: + resolution: {integrity: sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==} + hasBin: true + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1616,6 +1693,11 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -1670,6 +1752,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + copy-anything@4.0.5: resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} engines: {node: '>=18'} @@ -1837,6 +1922,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -1903,6 +1991,10 @@ packages: engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1980,6 +2072,12 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-react@7.37.5: resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} engines: {node: '>=4'} @@ -2124,6 +2222,10 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2189,6 +2291,12 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + holy-loader@2.3.14: resolution: {integrity: sha512-V7z6jXKy/CZfZY6EAVxMFGbLfE7/TLhYWgpMhODvZPpAL2JTkfjmZhYd0TiZIdZiychtCL5tA5sRN0MUwjRNjA==} peerDependencies: @@ -2349,6 +2457,11 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -2365,6 +2478,11 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -2464,6 +2582,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.553.0: resolution: {integrity: sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==} peerDependencies: @@ -2561,6 +2682,9 @@ packages: sass: optional: true + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + nodemailer@7.0.12: resolution: {integrity: sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==} engines: {node: '>=6.0.0'} @@ -3060,6 +3184,12 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3181,10 +3311,19 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -3244,8 +3383,108 @@ snapshots: - '@simplewebauthn/server' - nodemailer + '@babel/code-frame@7.28.6': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.6': {} + + '@babel/core@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.6': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/parser@7.28.6': + dependencies: + '@babel/types': 7.28.6 + '@babel/runtime@7.28.4': {} + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/traverse@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@base-ui/react@1.0.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@babel/runtime': 7.28.4 @@ -4307,6 +4546,8 @@ snapshots: balanced-match@1.0.2: {} + baseline-browser-mapping@2.9.15: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -4320,6 +4561,14 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.15 + caniuse-lite: 1.0.30001762 + electron-to-chromium: 1.5.267 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + buffer-from@1.1.2: {} call-bind-apply-helpers@1.0.2: @@ -4370,6 +4619,8 @@ snapshots: concat-map@0.0.1: {} + convert-source-map@2.0.0: {} + copy-anything@4.0.5: dependencies: is-what: 5.5.0 @@ -4455,6 +4706,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + electron-to-chromium@1.5.267: {} + emoji-regex@9.2.2: {} enhanced-resolve@5.18.4: @@ -4658,6 +4911,8 @@ snapshots: '@esbuild/win32-ia32': 0.27.2 '@esbuild/win32-x64': 0.27.2 + escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} eslint-config-next@15.5.9(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): @@ -4770,6 +5025,17 @@ snapshots: dependencies: eslint: 9.39.2(jiti@2.6.1) + eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)): + dependencies: + '@babel/core': 7.28.6 + '@babel/parser': 7.28.6 + eslint: 9.39.2(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 3.25.76 + zod-validation-error: 4.0.2(zod@3.25.76) + transitivePeerDependencies: + - supports-color + eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@2.6.1)): dependencies: array-includes: 3.1.9 @@ -4954,6 +5220,8 @@ snapshots: generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5023,6 +5291,12 @@ snapshots: dependencies: function-bind: 1.1.2 + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + holy-loader@2.3.14(react@19.2.3): dependencies: react: 19.2.3 @@ -5184,6 +5458,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -5196,6 +5472,8 @@ snapshots: dependencies: minimist: 1.2.8 + json5@2.2.3: {} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -5277,6 +5555,10 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lucide-react@0.553.0(react@19.2.3): dependencies: react: 19.2.3 @@ -5318,10 +5600,10 @@ snapshots: natural-compare@1.4.0: {} - next-auth@5.0.0-beta.30(next@15.5.9(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nodemailer@7.0.12)(react@19.2.3): + next-auth@5.0.0-beta.30(next@15.5.9(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nodemailer@7.0.12)(react@19.2.3): dependencies: '@auth/core': 0.41.0(nodemailer@7.0.12) - next: 15.5.9(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 15.5.9(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 optionalDependencies: nodemailer: 7.0.12 @@ -5331,7 +5613,7 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - next@15.5.9(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + next@15.5.9(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@next/env': 15.5.9 '@swc/helpers': 0.5.15 @@ -5339,7 +5621,7 @@ snapshots: postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - styled-jsx: 5.1.6(react@19.2.3) + styled-jsx: 5.1.6(@babel/core@7.28.6)(react@19.2.3) optionalDependencies: '@next/swc-darwin-arm64': 15.5.7 '@next/swc-darwin-x64': 15.5.7 @@ -5355,6 +5637,8 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-releases@2.0.27: {} + nodemailer@7.0.12: {} oauth4webapi@3.8.3: {} @@ -5765,10 +6049,12 @@ snapshots: strip-json-comments@3.1.1: {} - styled-jsx@5.1.6(react@19.2.3): + styled-jsx@5.1.6(@babel/core@7.28.6)(react@19.2.3): dependencies: client-only: 0.0.1 react: 19.2.3 + optionalDependencies: + '@babel/core': 7.28.6 superjson@2.2.6: dependencies: @@ -5908,6 +6194,12 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -6028,6 +6320,12 @@ snapshots: word-wrap@1.2.5: {} + yallist@3.1.1: {} + yocto-queue@0.1.0: {} + zod-validation-error@4.0.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + zod@3.25.76: {} diff --git a/public/favicon.ico b/public/favicon.ico index 60c702a..7b04c9b 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/src/app/_components/loops-waitlist-form.tsx b/src/app/_components/loops-waitlist-form.tsx new file mode 100644 index 0000000..a3b3635 --- /dev/null +++ b/src/app/_components/loops-waitlist-form.tsx @@ -0,0 +1,186 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "~/components/ui/button"; +import { Input } from "~/components/ui/input"; + +const INIT = "INIT"; +const SUBMITTING = "SUBMITTING"; +const ERROR = "ERROR"; +const SUCCESS = "SUCCESS"; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const formStates = [INIT, SUBMITTING, ERROR, SUCCESS] as const; +const formStyles = { + id: "cmka5bpaq4l9n0iyohs0tlush", + name: "Default", + formStyle: "inline", + placeholderText: "you@example.com", + buttonText: "Join Waitlist", + successMessage: "Thanks! We'll be in touch!", + userGroup: "waitlist", + team: { + mailingLists: [], + }, +}; +const domain = "app.loops.so"; + +export default function WaitListForm() { + const [email, setEmail] = useState(""); + const [formState, setFormState] = useState<(typeof formStates)[number]>(INIT); + const [errorMessage, setErrorMessage] = useState(""); + + const resetForm = () => { + setEmail(""); + setFormState(INIT); + setErrorMessage(""); + }; + + /** + * Rate limit the number of submissions allowed + * @returns {boolean} true if the form has been successfully submitted in the past minute + */ + const hasRecentSubmission = () => { + const time = new Date(); + const timestamp = time.valueOf(); + const previousTimestamp = localStorage.getItem("loops-form-timestamp"); + + // Indicate if the last sign up was less than a minute ago + if ( + previousTimestamp && + Number(previousTimestamp) + 60 * 1000 > timestamp + ) { + setFormState(ERROR); + setErrorMessage("Too many signups, please try again in a little while"); + return true; + } + + localStorage.setItem("loops-form-timestamp", timestamp.toString()); + return false; + }; + + const handleSubmit = (event: React.FormEvent) => { + // Prevent the default form submission + event.preventDefault(); + + // boundary conditions for submission + if (formState !== INIT) return; + if (!isValidEmail(email)) { + setFormState(ERROR); + setErrorMessage("Please enter a valid email"); + return; + } + if (hasRecentSubmission()) return; + setFormState(SUBMITTING); + + // build body + const formBody = `userGroup=${encodeURIComponent( + formStyles.userGroup, + )}&email=${encodeURIComponent(email)}&mailingLists=`; + + // API request to add user to newsletter + fetch(`https://${domain}/api/newsletter-form/${formStyles.id}`, { + method: "POST", + body: formBody, + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }) + .then((res) => [res.ok, res.json(), res] as const) + .then(([ok, dataPromise, res]) => { + if (ok) { + resetForm(); + setFormState(SUCCESS); + } else { + void dataPromise.then((data: { message?: string }) => { + setFormState(ERROR); + setErrorMessage(data.message ?? res.statusText); + localStorage.setItem("loops-form-timestamp", ""); + }); + } + }) + .catch((error: Error) => { + setFormState(ERROR); + // check for cloudflare error + if (error.message === "Failed to fetch") { + setErrorMessage( + "Too many signups, please try again in a little while", + ); + } else if (error.message) { + setErrorMessage(error.message); + } + localStorage.setItem("loops-form-timestamp", ""); + }); + }; + + switch (formState) { + case SUCCESS: + return ( +
+

+ {formStyles.successMessage} +

+
+ ); + case ERROR: + return ( +
+ + +
+ ); + default: + return ( +
+ setEmail(e.target.value)} + required + className="w-full max-w-xs" + /> +
+
+
+

+ Join the Waitlist +

+

+ Be the first to know when we launch TableTopicker +

+
+ +
+
+
+
@@ -170,10 +178,11 @@ export default async function Home() { something you should practice. - - + + Get Started
diff --git a/src/app/practice/_components/dock-menu.tsx b/src/app/practice/_components/dock-menu.tsx index 498a210..3dd0808 100644 --- a/src/app/practice/_components/dock-menu.tsx +++ b/src/app/practice/_components/dock-menu.tsx @@ -1,9 +1,10 @@ "use client"; import { cn } from "~/lib/utils"; -import { screens } from "../page"; -import { buttonVariants } from "~/components/ui/button"; +// import { screens } from "../page"; +// import { buttonVariants } from "~/components/ui/button"; +// eslint-disable-next-line @typescript-eslint/no-unused-vars export function DockMenu({ activeScreenId }: { activeScreenId: string }) { return (
- {screens.map((screen) => { + {/* {screens.map((screen) => { const Icon = screen.icon; const isActive = activeScreenId === screen.id; @@ -31,7 +32,7 @@ export function DockMenu({ activeScreenId }: { activeScreenId: string }) { ); - })} + })} */}
); } diff --git a/src/app/practice/page.tsx b/src/app/practice/page.tsx.disabled similarity index 100% rename from src/app/practice/page.tsx rename to src/app/practice/page.tsx.disabled diff --git a/src/components/footer.tsx b/src/components/footer.tsx index 5d98be7..d617a04 100644 --- a/src/components/footer.tsx +++ b/src/components/footer.tsx @@ -30,12 +30,12 @@ export function Footer() { > Home - Practice - + */} @@ -44,20 +44,20 @@ export function Footer() { More
- Privacy Policy - - */} + Built by davidasix - +
diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index 2a33cea..76a04b9 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -1,5 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ "use client"; - import { useState, useEffect } from "react"; import Link from "next/link"; import { useSession, signOut } from "next-auth/react"; @@ -21,12 +21,6 @@ export function Navigation() { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const { data: session } = useSession(); const { theme, setTheme } = useTheme(); - const [mounted, setMounted] = useState(false); - - // Prevent hydration mismatch for theme-dependent UI - useEffect(() => { - setMounted(true); - }, []); // Scroll detection useEffect(() => { @@ -62,31 +56,29 @@ export function Navigation() { {/* Desktop Navigation */}
- Practice - + */} {/* Theme Toggle */} - {mounted && ( - - )} + {/* Auth Section */} - {session ? ( + {/* {session ? ( @@ -110,28 +102,26 @@ export function Navigation() { - )} + )} */}
{/* Mobile Menu Button */}
{/* Mobile Theme Toggle */} - {mounted && ( - - )} - + + {/* + */}
{/* Mobile Menu */} - {isMobileMenuOpen && ( + {/* {isMobileMenuOpen && (
- )} + )} */}
); diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index c5b1f25..71cb6bd 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -21,10 +21,10 @@ const buttonVariants = cva( }, size: { default: - "h-9 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5", - xs: "h-6 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3", - sm: "h-8 gap-1 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", - lg: "h-10 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3", + "h-10 gap-1.5 px-10 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5", + xs: "h-6 gap-1 px-6 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3", + sm: "h-8 gap-1 px-8 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + lg: "h-12 gap-1.5 px-12 text-lg has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3", icon: "size-9", "icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3", "icon-sm": "size-8", diff --git a/src/styles/globals.css b/src/styles/globals.css index 60922de..dec9dcd 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -121,7 +121,7 @@ @layer base { * { - @apply border-border outline-ring/50; + @apply border-border outline-ring/50 scroll-smooth; } body { @apply bg-background text-foreground; @@ -169,10 +169,10 @@ transform: rotate(0deg); } 25% { - transform: rotate(-2deg); + transform: rotate(-0.25deg); } 75% { - transform: rotate(2deg); + transform: rotate(0.25deg); } }