feat: show logs with few basic filters
jump to
@@ -0,0 +1,21 @@
+{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": false, + "tailwind": { + "config": "tailwind.config.mjs", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +}
@@ -9,11 +9,25 @@ "start": "next start",
"lint": "next lint" }, "dependencies": { + "@hookform/resolvers": "^3.9.1", + "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-popover": "^1.1.4", + "@radix-ui/react-select": "^2.1.4", + "@radix-ui/react-slot": "^1.1.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "lucide-react": "^0.469.0", "next": "15.1.0", "react": "^19.0.0", + "react-day-picker": "8.10.1", "react-dom": "^19.0.0", "react-hook-form": "^7.54.0", - "recoil": "^0.7.7" + "recoil": "^0.7.7", + "tailwind-merge": "^2.5.5", + "tailwindcss-animate": "^1.0.7", + "zod": "^3.24.1" }, "devDependencies": { "postcss": "^8",
@@ -8,12 +8,45 @@ importers:
.: dependencies: + '@hookform/resolvers': + specifier: ^3.9.1 + version: 3.9.1(react-hook-form@7.54.0(react@19.0.0)) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.4 + version: 2.1.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-label': + specifier: ^2.1.1 + version: 2.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-popover': + specifier: ^1.1.4 + version: 1.1.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-select': + specifier: ^2.1.4 + version: 2.1.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': + specifier: ^1.1.1 + version: 1.1.1(react@19.0.0) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 + lucide-react: + specifier: ^0.469.0 + version: 0.469.0(react@19.0.0) next: specifier: 15.1.0 version: 15.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: ^19.0.0 version: 19.0.0 + react-day-picker: + specifier: 8.10.1 + version: 8.10.1(date-fns@4.1.0)(react@19.0.0) react-dom: specifier: ^19.0.0 version: 19.0.0(react@19.0.0)@@ -23,6 +56,15 @@ version: 7.54.0(react@19.0.0)
recoil: specifier: ^0.7.7 version: 0.7.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + tailwind-merge: + specifier: ^2.5.5 + version: 2.5.5 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.16) + zod: + specifier: ^3.24.1 + version: 3.24.1 devDependencies: postcss: specifier: ^8@@ -39,6 +81,26 @@ engines: {node: '>=10'}
'@emnapi/runtime@1.3.1': resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + + '@floating-ui/core@1.6.8': + resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} + + '@floating-ui/dom@1.6.12': + resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==} + + '@floating-ui/react-dom@2.1.2': + resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.8': + resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} + + '@hookform/resolvers@3.9.1': + resolution: {integrity: sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==} + peerDependencies: + react-hook-form: ^7.0.0 '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}@@ -234,6 +296,327 @@ '@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@radix-ui/number@1.1.0': + resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} + + '@radix-ui/primitive@1.1.1': + resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} + + '@radix-ui/react-arrow@1.1.1': + resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.1': + resolution: {integrity: sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.1': + resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.1': + resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-direction@1.1.0': + resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.3': + resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.4': + resolution: {integrity: sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.1': + resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.1': + resolution: {integrity: sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.0': + resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.1': + resolution: {integrity: sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.1.4': + resolution: {integrity: sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.4': + resolution: {integrity: sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.1': + resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.3': + resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.2': + resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.0.1': + resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.1': + resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.1.4': + resolution: {integrity: sha512-pOkb2u8KgO47j/h7AylCj7dJsm69BXcjkrvTqMptFqsE2i0p8lHkfgneXKjAgPzBMivnoMyt8o4KiV4wYzDdyQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.1.1': + resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.0': + resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.1.0': + resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.0': + resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.0': + resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.0': + resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.0': + resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.0': + resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.1.1': + resolution: {integrity: sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.0': + resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}@@ -266,6 +649,10 @@
arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + aria-hidden@1.2.4: + resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} + engines: {node: '>=10'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}@@ -295,8 +682,15 @@ chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}@@ -325,9 +719,15 @@ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'} hasBin: true + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}@@ -367,6 +767,10 @@
function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'}@@ -432,6 +836,11 @@ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lucide-react@0.469.0: + resolution: {integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}@@ -567,6 +976,12 @@
queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + react-day-picker@8.10.1: + resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==} + peerDependencies: + date-fns: ^2.28.0 || ^3.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom@19.0.0: resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies:@@ -578,6 +993,36 @@ engines: {node: '>=18.0.0'}
peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.6.2: + resolution: {integrity: sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react@19.0.0: resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'}@@ -685,6 +1130,14 @@ supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + tailwind-merge@2.5.5: + resolution: {integrity: sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + tailwindcss@3.4.16: resolution: {integrity: sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==} engines: {node: '>=14.0.0'}@@ -707,6 +1160,26 @@
tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}@@ -728,6 +1201,9 @@ resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==}
engines: {node: '>= 14'} hasBin: true + zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + snapshots: '@alloc/quick-lru@5.2.0': {}@@ -736,6 +1212,27 @@ '@emnapi/runtime@1.3.1':
dependencies: tslib: 2.8.1 optional: true + + '@floating-ui/core@1.6.8': + dependencies: + '@floating-ui/utils': 0.2.8 + + '@floating-ui/dom@1.6.12': + dependencies: + '@floating-ui/core': 1.6.8 + '@floating-ui/utils': 0.2.8 + + '@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@floating-ui/dom': 1.6.12 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@floating-ui/utils@0.2.8': {} + + '@hookform/resolvers@3.9.1(react-hook-form@7.54.0(react@19.0.0))': + dependencies: + react-hook-form: 7.54.0(react@19.0.0) '@img/sharp-darwin-arm64@0.33.5': optionalDependencies:@@ -879,6 +1376,245 @@
'@pkgjs/parseargs@0.11.0': optional: true + '@radix-ui/number@1.1.0': {} + + '@radix-ui/primitive@1.1.1': {} + + '@radix-ui/react-arrow@1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@radix-ui/react-collection@1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(react@19.0.0) + '@radix-ui/react-context': 1.1.1(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@radix-ui/react-compose-refs@1.1.1(react@19.0.0)': + dependencies: + react: 19.0.0 + + '@radix-ui/react-context@1.1.1(react@19.0.0)': + dependencies: + react: 19.0.0 + + '@radix-ui/react-direction@1.1.0(react@19.0.0)': + dependencies: + react: 19.0.0 + + '@radix-ui/react-dismissable-layer@1.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(react@19.0.0) + '@radix-ui/react-use-escape-keydown': 1.1.0(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@radix-ui/react-dropdown-menu@2.1.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(react@19.0.0) + '@radix-ui/react-context': 1.1.1(react@19.0.0) + '@radix-ui/react-id': 1.1.0(react@19.0.0) + '@radix-ui/react-menu': 2.1.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@radix-ui/react-focus-guards@1.1.1(react@19.0.0)': + dependencies: + react: 19.0.0 + + '@radix-ui/react-focus-scope@1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@radix-ui/react-id@1.1.0(react@19.0.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(react@19.0.0) + react: 19.0.0 + + '@radix-ui/react-label@2.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@radix-ui/react-menu@2.1.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(react@19.0.0) + '@radix-ui/react-context': 1.1.1(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.1(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.0(react@19.0.0) + '@radix-ui/react-popper': 1.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(react@19.0.0) + aria-hidden: 1.2.4 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.6.2(react@19.0.0) + + '@radix-ui/react-popover@1.1.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(react@19.0.0) + '@radix-ui/react-context': 1.1.1(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.1(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.0(react@19.0.0) + '@radix-ui/react-popper': 1.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(react@19.0.0) + aria-hidden: 1.2.4 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.6.2(react@19.0.0) + + '@radix-ui/react-popper@1.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-arrow': 1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(react@19.0.0) + '@radix-ui/react-context': 1.1.1(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(react@19.0.0) + '@radix-ui/react-use-rect': 1.1.0(react@19.0.0) + '@radix-ui/react-use-size': 1.1.0(react@19.0.0) + '@radix-ui/rect': 1.1.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@radix-ui/react-portal@1.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@radix-ui/react-presence@1.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@radix-ui/react-primitive@2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-slot': 1.1.1(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@radix-ui/react-roving-focus@1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(react@19.0.0) + '@radix-ui/react-context': 1.1.1(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(react@19.0.0) + '@radix-ui/react-id': 1.1.0(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@radix-ui/react-select@2.1.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/number': 1.1.0 + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(react@19.0.0) + '@radix-ui/react-context': 1.1.1(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.1(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.0(react@19.0.0) + '@radix-ui/react-popper': 1.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(react@19.0.0) + '@radix-ui/react-use-previous': 1.1.0(react@19.0.0) + '@radix-ui/react-visually-hidden': 1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + aria-hidden: 1.2.4 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.6.2(react@19.0.0) + + '@radix-ui/react-slot@1.1.1(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(react@19.0.0) + react: 19.0.0 + + '@radix-ui/react-use-callback-ref@1.1.0(react@19.0.0)': + dependencies: + react: 19.0.0 + + '@radix-ui/react-use-controllable-state@1.1.0(react@19.0.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(react@19.0.0) + react: 19.0.0 + + '@radix-ui/react-use-escape-keydown@1.1.0(react@19.0.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(react@19.0.0) + react: 19.0.0 + + '@radix-ui/react-use-layout-effect@1.1.0(react@19.0.0)': + dependencies: + react: 19.0.0 + + '@radix-ui/react-use-previous@1.1.0(react@19.0.0)': + dependencies: + react: 19.0.0 + + '@radix-ui/react-use-rect@1.1.0(react@19.0.0)': + dependencies: + '@radix-ui/rect': 1.1.0 + react: 19.0.0 + + '@radix-ui/react-use-size@1.1.0(react@19.0.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(react@19.0.0) + react: 19.0.0 + + '@radix-ui/react-visually-hidden@1.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@radix-ui/rect@1.1.0': {} + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.15':@@ -904,6 +1640,10 @@ picomatch: 2.3.1
arg@5.0.2: {} + aria-hidden@1.2.4: + dependencies: + tslib: 2.8.1 + balanced-match@1.0.2: {} binary-extensions@2.3.0: {}@@ -936,8 +1676,14 @@ readdirp: 3.6.0
optionalDependencies: fsevents: 2.3.3 + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + client-only@0.0.1: {} + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4@@ -966,8 +1712,12 @@ which: 2.0.2
cssesc@3.0.0: {} + date-fns@4.1.0: {} + detect-libc@2.0.3: optional: true + + detect-node-es@1.1.0: {} didyoumean@1.2.2: {}@@ -1005,6 +1755,8 @@ optional: true
function-bind@1.1.2: {} + get-nonce@1.0.1: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3@@ -1064,6 +1816,10 @@
lines-and-columns@1.2.4: {} lru-cache@10.4.3: {} + + lucide-react@0.469.0(react@19.0.0): + dependencies: + react: 19.0.0 merge2@1.4.1: {}@@ -1181,6 +1937,11 @@ source-map-js: 1.2.1
queue-microtask@1.2.3: {} + react-day-picker@8.10.1(date-fns@4.1.0)(react@19.0.0): + dependencies: + date-fns: 4.1.0 + react: 19.0.0 + react-dom@19.0.0(react@19.0.0): dependencies: react: 19.0.0@@ -1190,6 +1951,27 @@ react-hook-form@7.54.0(react@19.0.0):
dependencies: react: 19.0.0 + react-remove-scroll-bar@2.3.8(react@19.0.0): + dependencies: + react: 19.0.0 + react-style-singleton: 2.2.3(react@19.0.0) + tslib: 2.8.1 + + react-remove-scroll@2.6.2(react@19.0.0): + dependencies: + react: 19.0.0 + react-remove-scroll-bar: 2.3.8(react@19.0.0) + react-style-singleton: 2.2.3(react@19.0.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(react@19.0.0) + use-sidecar: 1.1.3(react@19.0.0) + + react-style-singleton@2.2.3(react@19.0.0): + dependencies: + get-nonce: 1.0.1 + react: 19.0.0 + tslib: 2.8.1 + react@19.0.0: {} read-cache@1.0.0:@@ -1305,6 +2087,12 @@ ts-interface-checker: 0.1.13
supports-preserve-symlinks-flag@1.0.0: {} + tailwind-merge@2.5.5: {} + + tailwindcss-animate@1.0.7(tailwindcss@3.4.16): + dependencies: + tailwindcss: 3.4.16 + tailwindcss@3.4.16: dependencies: '@alloc/quick-lru': 5.2.0@@ -1348,6 +2136,17 @@ ts-interface-checker@0.1.13: {}
tslib@2.8.1: {} + use-callback-ref@1.3.3(react@19.0.0): + dependencies: + react: 19.0.0 + tslib: 2.8.1 + + use-sidecar@1.1.3(react@19.0.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.0.0 + tslib: 2.8.1 + util-deprecate@1.0.2: {} which@2.0.2:@@ -1367,3 +2166,5 @@ string-width: 5.1.2
strip-ansi: 7.1.0 yaml@2.6.1: {} + + zod@3.24.1: {}
@@ -1,10 +1,19 @@
"use client"; import { useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; import Link from "next/link"; import Button from "@/components/shared/Button"; +import { Terminal, MoreVertical } from "lucide-react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; export default function ApplicationsPage() { + const router = useRouter(); const [applications, setApplications] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null);@@ -41,7 +50,8 @@ setIsLoading(false);
} } - async function handleDeleteApplication(appId) { + async function handleDeleteApplication(appId, e) { + e.stopPropagation(); if ( !confirm( "Are you sure you want to delete this application? This action cannot be undone and will delete all associated logs.",@@ -69,7 +79,7 @@ );
if (!response.ok) throw new Error("Failed to delete application"); - await fetchApplications(); // Refresh the list + await fetchApplications(); } catch (error) { setError("Failed to delete application"); console.error("Error:", error);@@ -121,49 +131,56 @@ );
} return ( - <div className="bg-white shadow rounded-lg divide-y divide-gray-200"> - {applications.map((app) => ( - <ApplicationItem - key={app.id} - application={app} - onDelete={() => handleDeleteApplication(app.id)} - isDeleting={deletingApps.has(app.id)} - /> - ))} - </div> - ); -} + <div className="mx-auto max-w-7xl"> + <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> + {applications.map((app) => ( + <div + key={app.id} + onClick={() => router.push(`/dashboard/applications/${app.id}`)} + className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md cursor-pointer relative" + > + <div className="absolute top-4 right-4"> + <DropdownMenu> + <DropdownMenuTrigger onClick={(e) => e.stopPropagation()}> + <MoreVertical className="h-5 w-5 text-gray-500" /> + </DropdownMenuTrigger> + <DropdownMenuContent> + <DropdownMenuItem + onClick={(e) => { + e.stopPropagation(); + router.push(`/dashboard/applications/${app.id}/edit`); + }} + > + Edit + </DropdownMenuItem> + <DropdownMenuItem + onClick={(e) => handleDeleteApplication(app.id, e)} + disabled={deletingApps.has(app.id)} + > + {deletingApps.has(app.id) ? "Deleting..." : "Delete"} + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + </div> + + <div className="space-y-4"> + <div className="flex items-center gap-2"> + <span className="font-medium">{app.name}</span> + </div> -function ApplicationItem({ application, onDelete, isDeleting }) { - return ( - <div className="px-6 py-4"> - <div className="flex items-center justify-between"> - <div className="space-y-1"> - <h3 className="text-lg font-medium text-gray-900"> - {application.name} - </h3> - <p className="text-sm text-gray-500">{application.description}</p> - <div className="text-xs text-gray-400"> - Created on {new Date(application.created_at).toLocaleDateString()} + <div className="space-y-2"> + <div className="flex justify-between text-sm"> + <span className="text-gray-500">Description:</span> + <span className="text-right">{app.description}</span> + </div> + <div className="flex justify-between text-sm"> + <span className="text-gray-500">Created:</span> + <span>{new Date(app.created_at).toLocaleString()}</span> + </div> + </div> + </div> </div> - </div> - <div className="flex items-center space-x-4"> - <Link - href={`/dashboard/applications/${application.id}`} - className="text-sm text-indigo-600 hover:text-indigo-900" - > - View Details - </Link> - <button - onClick={onDelete} - disabled={isDeleting} - className={`text-sm text-red-600 hover:text-red-900 ${ - isDeleting ? "opacity-50 cursor-not-allowed" : "" - }`} - > - {isDeleting ? "Deleting..." : "Delete"} - </button> - </div> + ))} </div> </div> );
@@ -82,13 +82,13 @@ <p className="text-sm text-gray-600 hover:text-gray-800">
Applications </p> </Link> + <Link href="/dashboard/logs"> + <p className="text-sm text-gray-600 hover:text-gray-800">Logs</p> + </Link> <Link href="/dashboard/monitors"> <p className="text-sm text-gray-600 hover:text-gray-800"> Monitors </p> - </Link> - <Link href="/dashboard/logs"> - <p className="text-sm text-gray-600 hover:text-gray-800">Logs</p> </Link> </div> </div>
@@ -0,0 +1,453 @@
+"use client"; + +import { useState, useEffect } from "react"; +import { useParams } from "next/navigation"; +import { useAuth } from "@/context/AuthContext"; +import Button from "@/components/shared/Button"; +import { Calendar } from "@/components/ui/calendar"; +import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; +import { format } from "date-fns"; +import { CalendarIcon } from "lucide-react"; +import { useForm } from "react-hook-form"; + +export default function ApplicationLogsPage() { + const { application_id } = useParams(); + const [logs, setLogs] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(""); + const [isGeneratingDummy, setIsGeneratingDummy] = useState(false); + const [pageSize] = useState(20); // Default page size + const [cursor, setCursor] = useState(null); + const [hasMore, setHasMore] = useState(false); + const [isLoadingMore, setIsLoadingMore] = useState(false); + const [logLevel, setLogLevel] = useState(""); + const [startTime, setStartTime] = useState(""); + const [endTime, setEndTime] = useState(""); + const [applicationName, setApplicationName] = useState(""); + + useEffect(() => { + fetchLogs(); + }, [application_id, logLevel, startTime, endTime]); + + async function fetchLogs(nextCursor = null) { + if (!application_id) return; + + setIsLoading(true); + try { + const response = await fetch( + "http://localhost:8080/twirp/logs.LogsService/GetLogs", + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + token: localStorage.getItem("token"), + application_id: application_id, + page_size: pageSize, + cursor: nextCursor, + log_level: logLevel === "ALL" ? "" : logLevel, + start_time: startTime, + end_time: endTime, + }), + }, + ); + + const data = await response.json(); + if (data.logs) { + if (nextCursor) { + setLogs((prev) => [...prev, ...data.logs]); + } else { + setLogs(data.logs); + } + setHasMore(data.has_more); + setCursor(data.next_cursor); + setApplicationName(data.application_name); + } + } catch (error) { + console.error("Error fetching logs:", error); + setError("Failed to load logs"); + } finally { + setIsLoading(false); + setIsLoadingMore(false); + } + } + + async function generateDummyLogs() { + if (!application_id) return; + + setIsGeneratingDummy(true); + try { + const response = await fetch( + "http://localhost:8080/twirp/logs.LogsService/GenerateDummyLogs", + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + token: localStorage.getItem("token"), + application_id: application_id, + }), + }, + ); + + if (response.ok) { + await fetchLogs(); // Refresh logs after generating + } + } catch (error) { + console.error("Error generating dummy logs:", error); + setError("Failed to generate dummy logs"); + } finally { + setIsGeneratingDummy(false); + } + } + + async function loadMore() { + if (!hasMore || isLoadingMore) return; + setIsLoadingMore(true); + await fetchLogs(cursor); + } + + const logLevelColors = { + DEBUG: "text-gray-600", + INFO: "text-blue-600", + WARN: "text-yellow-600", + ERROR: "text-red-600", + FATAL: "text-red-800", + }; + + const handleFilterSubmit = (data) => { + setLogLevel(data.logLevels[0] || ""); + setStartTime(data.startTime ? data.startTime.toISOString() : ""); + setEndTime(data.endTime ? data.endTime.toISOString() : ""); + }; + + return ( + <div className="p-4 max-w-7xl mx-auto"> + <div className="mb-6 flex justify-between items-center"> + <div className="flex items-center justify-between w-full"> + <h1 className="text-2xl font-semibold capitalize"> + {applicationName} Logs + </h1> + <div className="flex gap-4"> + <Button + variant="secondary" + onClick={generateDummyLogs} + disabled={isGeneratingDummy} + > + {isGeneratingDummy ? "Generating..." : "Generate Dummy Logs"} + </Button> + <Button onClick={() => fetchLogs()} disabled={isLoading}> + {isLoading ? "Refreshing..." : "Refresh"} + </Button> + </div> + </div> + </div> + + <FiltersForm + logLevel={logLevel} + setLogLevel={setLogLevel} + startTime={startTime} + setStartTime={setStartTime} + endTime={endTime} + setEndTime={setEndTime} + onSubmit={handleFilterSubmit} + /> + + {error && ( + <div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded"> + {error} + </div> + )} + + {logs.length === 0 ? ( + <div className="text-center py-12 bg-white rounded-lg shadow"> + <p className="text-gray-500"> + No logs found this means wither this application has no logs or the + filters applied are too restrictive. + </p> + </div> + ) : ( + <> + <div className="bg-white rounded-lg shadow overflow-hidden"> + <div className="overflow-x-auto"> + <table className="min-w-full divide-y divide-gray-200"> + <thead className="bg-gray-200"> + <tr> + <th className="w-16 px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> + Level + </th> + <th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> + Message + </th> + <th className="w-20 px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> + Timestamp + </th> + </tr> + </thead> + <tbody className="bg-white divide-y divide-gray-200 font-mono"> + {logs.map((log) => ( + <tr key={log.log_id}> + <td className="px-4 py-3 whitespace-nowrap text-sm"> + <span className={logLevelColors[log.level]}> + {log.level} + </span> + </td> + <td className="px-4 py-3 text-sm text-gray-900"> + {log.message} + </td> + <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500"> + {new Date(log.timestamp).toLocaleString()} + </td> + </tr> + ))} + </tbody> + </table> + </div> + </div> + {logs.length > 0 && ( + <div className="mt-4 text-center"> + {hasMore ? ( + <Button + variant="secondary" + onClick={loadMore} + disabled={isLoadingMore} + > + {isLoadingMore ? "Loading..." : "Load More"} + </Button> + ) : ( + <p className="text-gray-500 text-sm">No more logs</p> + )} + </div> + )} + </> + )} + </div> + ); +} + +function FiltersForm({ + logLevel, + setLogLevel, + startTime, + setStartTime, + endTime, + setEndTime, + onSubmit, +}) { + const form = useForm({ + defaultValues: { + logLevels: logLevel ? [logLevel] : [], + startTime: startTime ? new Date(startTime) : undefined, + endTime: endTime ? new Date(endTime) : undefined, + }, + }); + + const handleClearFilters = () => { + form.reset({ + logLevels: [], + startTime: undefined, + endTime: undefined, + }); + setLogLevel(""); + setStartTime(""); + setEndTime(""); + onSubmit(form.getValues()); + }; + + const isFormChanged = () => { + const values = form.getValues(); + return ( + values.logLevels.length > 0 || + values.startTime !== undefined || + values.endTime !== undefined + ); + }; + + const handleSubmit = (data) => { + setLogLevel(data.logLevels[0] === "ALL" ? "" : data.logLevels[0] || ""); + setStartTime(data.startTime ? data.startTime.toISOString() : ""); + setEndTime(data.endTime ? data.endTime.toISOString() : ""); + onSubmit(data); + }; + + return ( + <Form {...form}> + <div className="flex justify-between"> + <div></div> + <form + onSubmit={form.handleSubmit(handleSubmit)} + className="mb-6 flex gap-4" + > + <FormField + control={form.control} + name="logLevels" + render={({ field }) => ( + <FormItem className="flex-1"> + <Select + onValueChange={(value) => field.onChange([value])} + value={field.value?.[0] || ""} + > + <SelectTrigger className="space-x-5"> + <SelectValue placeholder="Select level" /> + </SelectTrigger> + <SelectContent> + <SelectItem value="ALL">All</SelectItem> + <SelectItem value="DEBUG">Debug</SelectItem> + <SelectItem value="INFO">Info</SelectItem> + <SelectItem value="WARN">Warn</SelectItem> + <SelectItem value="ERROR">Error</SelectItem> + <SelectItem value="FATAL">Fatal</SelectItem> + </SelectContent> + </Select> + </FormItem> + )} + /> + <FormField + control={form.control} + name="startTime" + render={({ field }) => ( + <FormItem className="flex-1"> + <Popover> + <PopoverTrigger asChild> + <FormControl> + <Button + variant="outline" + className={cn( + "w-72 border px-4 py-1.5 rounded justify-start text-left font-normal flex flex-row items-center", + !field.value && "text-muted-foreground", + )} + > + <CalendarIcon className="mr-2 h-4 w-4" /> + {field.value ? ( + format(field.value, "PPP HH:mm") + ) : ( + <span>Pick a date</span> + )} + </Button> + </FormControl> + </PopoverTrigger> + <PopoverContent className="w-auto p-0" align="start"> + <Calendar + mode="single" + selected={field.value} + onSelect={field.onChange} + initialFocus + /> + <div className="border-t p-3"> + <input + type="time" + className="w-full rounded-md border px-3 py-2" + onChange={(e) => { + if (field.value && e.target.value) { + const [hours, minutes] = e.target.value.split(":"); + const newDate = new Date(field.value); + newDate.setHours(parseInt(hours)); + newDate.setMinutes(parseInt(minutes)); + field.onChange(newDate); + } + }} + value={ + field.value + ? `${field.value.getHours().toString().padStart(2, "0")}:${field.value + .getMinutes() + .toString() + .padStart(2, "0")}` + : "" + } + /> + </div> + </PopoverContent> + </Popover> + </FormItem> + )} + /> + <FormField + control={form.control} + name="endTime" + render={({ field }) => ( + <FormItem className="flex-1"> + <Popover> + <PopoverTrigger asChild> + <FormControl> + <Button + className={cn( + "w-72 border px-4 py-1.5 rounded justify-start text-left font-normal flex flex-row items-center", + !field.value && "text-muted-foreground", + )} + > + <CalendarIcon className="mr-2 h-4 w-4" /> + <p> + {field.value ? ( + format(field.value, "PPP HH:mm") + ) : ( + <span>Pick a date</span> + )} + </p> + </Button> + </FormControl> + </PopoverTrigger> + <PopoverContent className="w-auto p-0" align="start"> + <Calendar + mode="single" + selected={field.value} + onSelect={field.onChange} + initialFocus + /> + <div className="border-t p-3"> + <input + type="time" + className="w-full rounded-md border px-3 py-2" + onChange={(e) => { + if (field.value && e.target.value) { + const [hours, minutes] = e.target.value.split(":"); + const newDate = new Date(field.value); + newDate.setHours(parseInt(hours)); + newDate.setMinutes(parseInt(minutes)); + field.onChange(newDate); + } + }} + value={ + field.value + ? `${field.value.getHours().toString().padStart(2, "0")}:${field.value + .getMinutes() + .toString() + .padStart(2, "0")}` + : "" + } + /> + </div> + </PopoverContent> + </Popover> + </FormItem> + )} + /> + <div className="flex gap-4"> + <div className="flex gap-4"> + <Button + variant="secondary" + onClick={handleClearFilters} + disabled={!isFormChanged()} + > + Clear Filters + </Button> + <Button type="submit" disabled={!isFormChanged()}> + Apply + </Button> + </div> + </div> + </form> + </div> + </Form> + ); +}
@@ -1,7 +1,99 @@
+"use client"; + +import { useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { useAuth } from "@/context/AuthContext"; +import { Terminal } from "lucide-react"; + export default function LogsPage() { + const { user } = useAuth(); + const router = useRouter(); + const [applications, setApplications] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(""); + + useEffect(() => { + fetchApplications(); + }, []); + + async function fetchApplications() { + setIsLoading(true); + try { + const response = await fetch( + "http://localhost:8080/twirp/applications.ApplicationsService/ListApplications", + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + token: localStorage.getItem("token"), + }), + }, + ); + + const data = await response.json(); + if (data.applications) { + setApplications(data.applications); + } + } catch (error) { + console.error("Error fetching applications:", error); + setError("Failed to load applications"); + } finally { + setIsLoading(false); + } + } + + const handleApplicationClick = (appId) => { + router.push(`/dashboard/logs/${appId}`); + }; + return ( - <div className="p-4"> - <p>To do: Logs</p> + <div className="bg-gray-50/50 p-4 md:p-6"> + <div className="mx-auto max-w-7xl"> + <h1 className="text-2xl font-semibold mb-6">Logs</h1> + + {error && ( + <div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded"> + {error} + </div> + )} + + {isLoading ? ( + <div className="text-center py-12"> + <p className="text-gray-500">Loading applications...</p> + </div> + ) : applications.length === 0 ? ( + <div className="text-center py-12 bg-white rounded-lg shadow"> + <p className="text-gray-500">No applications found.</p> + </div> + ) : ( + <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> + {applications.map((app) => ( + <div + key={app.id} + onClick={() => handleApplicationClick(app.id)} + className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md cursor-pointer" + > + <div className="space-y-4"> + <div className="flex items-center gap-2"> + <span className="font-medium">{app.name}</span> + </div> + + <div className="space-y-2"> + <div className="flex justify-between text-sm"> + <span className="text-gray-500">Description:</span> + <span className="text-right">{app.description}</span> + </div> + <div className="flex justify-between text-sm"> + <span className="text-gray-500">Created:</span> + <span>{new Date(app.created_at).toLocaleString()}</span> + </div> + </div> + </div> + </div> + ))} + </div> + )} + </div> </div> ); }
@@ -12,3 +12,68 @@ background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent black */
z-index: 9999; /* Ensure it is above the content */ pointer-events: none; /* Allow clicks to pass through the overlay */ } + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55% + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +}
@@ -0,0 +1,72 @@
+"use client"; +import * as React from "react" +import { ChevronLeft, ChevronRight } from "lucide-react" +import { DayPicker } from "react-day-picker" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +function Calendar({ + className, + classNames, + showOutsideDays = true, + ...props +}) { + return ( + (<DayPicker + showOutsideDays={showOutsideDays} + className={cn("p-3", className)} + classNames={{ + months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0", + month: "space-y-4", + caption: "flex justify-center pt-1 relative items-center", + caption_label: "text-sm font-medium", + nav: "space-x-1 flex items-center", + nav_button: cn( + buttonVariants({ variant: "outline" }), + "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100" + ), + nav_button_previous: "absolute left-1", + nav_button_next: "absolute right-1", + table: "w-full border-collapse space-y-1", + head_row: "flex", + head_cell: + "text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]", + row: "flex w-full mt-2", + cell: cn( + "relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md", + props.mode === "range" + ? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md" + : "[&:has([aria-selected])]:rounded-md" + ), + day: cn( + buttonVariants({ variant: "ghost" }), + "h-8 w-8 p-0 font-normal aria-selected:opacity-100" + ), + day_range_start: "day-range-start", + day_range_end: "day-range-end", + day_selected: + "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", + day_today: "bg-accent text-accent-foreground", + day_outside: + "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground", + day_disabled: "text-muted-foreground opacity-50", + day_range_middle: + "aria-selected:bg-accent aria-selected:text-accent-foreground", + day_hidden: "invisible", + ...classNames, + }} + components={{ + IconLeft: ({ className, ...props }) => ( + <ChevronLeft className={cn("h-4 w-4", className)} {...props} /> + ), + IconRight: ({ className, ...props }) => ( + <ChevronRight className={cn("h-4 w-4", className)} {...props} /> + ), + }} + {...props} />) + ); +} +Calendar.displayName = "Calendar" + +export { Calendar }
@@ -0,0 +1,134 @@
+"use client"; +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { Controller, FormProvider, useFormContext } from "react-hook-form"; + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" + +const Form = FormProvider + +const FormFieldContext = React.createContext({}) + +const FormField = ( + { + ...props + } +) => { + return ( + (<FormFieldContext.Provider value={{ name: props.name }}> + <Controller {...props} /> + </FormFieldContext.Provider>) + ); +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState, formState } = useFormContext() + + const fieldState = getFieldState(fieldContext.name, formState) + + if (!fieldContext) { + throw new Error("useFormField should be used within <FormField>") + } + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +const FormItemContext = React.createContext({}) + +const FormItem = React.forwardRef(({ className, ...props }, ref) => { + const id = React.useId() + + return ( + (<FormItemContext.Provider value={{ id }}> + <div ref={ref} className={cn("space-y-2", className)} {...props} /> + </FormItemContext.Provider>) + ); +}) +FormItem.displayName = "FormItem" + +const FormLabel = React.forwardRef(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField() + + return ( + (<Label + ref={ref} + className={cn(error && "text-destructive", className)} + htmlFor={formItemId} + {...props} />) + ); +}) +FormLabel.displayName = "FormLabel" + +const FormControl = React.forwardRef(({ ...props }, ref) => { + const { error, formItemId, formDescriptionId, formMessageId } = useFormField() + + return ( + (<Slot + ref={ref} + id={formItemId} + aria-describedby={ + !error + ? `${formDescriptionId}` + : `${formDescriptionId} ${formMessageId}` + } + aria-invalid={!!error} + {...props} />) + ); +}) +FormControl.displayName = "FormControl" + +const FormDescription = React.forwardRef(({ className, ...props }, ref) => { + const { formDescriptionId } = useFormField() + + return ( + (<p + ref={ref} + id={formDescriptionId} + className={cn("text-[0.8rem] text-muted-foreground", className)} + {...props} />) + ); +}) +FormDescription.displayName = "FormDescription" + +const FormMessage = React.forwardRef(({ className, children, ...props }, ref) => { + const { error, formMessageId } = useFormField() + const body = error ? String(error?.message) : children + + if (!body) { + return null + } + + return ( + (<p + ref={ref} + id={formMessageId} + className={cn("text-[0.8rem] font-medium text-destructive", className)} + {...props}> + {body} + </p>) + ); +}) +FormMessage.displayName = "FormMessage" + +export { + useFormField, + Form, + FormItem, + FormLabel, + FormControl, + FormDescription, + FormMessage, + FormField, +}
@@ -0,0 +1,18 @@
+"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva } from "class-variance-authority"; + +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef(({ className, ...props }, ref) => ( + <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} /> +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label }
@@ -0,0 +1,29 @@
+"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverAnchor = PopoverPrimitive.Anchor + +const PopoverContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + <PopoverPrimitive.Portal> + <PopoverPrimitive.Content + ref={ref} + align={align} + sideOffset={sideOffset} + className={cn( + "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + className + )} + {...props} /> + </PopoverPrimitive.Portal> +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
@@ -0,0 +1,121 @@
+"use client" + +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => ( + <SelectPrimitive.Trigger + ref={ref} + className={cn( + "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", + className + )} + {...props}> + {children} + <SelectPrimitive.Icon asChild> + <ChevronDown className="h-4 w-4 opacity-50" /> + </SelectPrimitive.Icon> + </SelectPrimitive.Trigger> +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) => ( + <SelectPrimitive.ScrollUpButton + ref={ref} + className={cn("flex cursor-default items-center justify-center py-1", className)} + {...props}> + <ChevronUp className="h-4 w-4" /> + </SelectPrimitive.ScrollUpButton> +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) => ( + <SelectPrimitive.ScrollDownButton + ref={ref} + className={cn("flex cursor-default items-center justify-center py-1", className)} + {...props}> + <ChevronDown className="h-4 w-4" /> + </SelectPrimitive.ScrollDownButton> +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => ( + <SelectPrimitive.Portal> + <SelectPrimitive.Content + ref={ref} + className={cn( + "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + position === "popper" && + "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", + className + )} + position={position} + {...props}> + <SelectScrollUpButton /> + <SelectPrimitive.Viewport + className={cn("p-1", position === "popper" && + "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]")}> + {children} + </SelectPrimitive.Viewport> + <SelectScrollDownButton /> + </SelectPrimitive.Content> + </SelectPrimitive.Portal> +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef(({ className, ...props }, ref) => ( + <SelectPrimitive.Label + ref={ref} + className={cn("px-2 py-1.5 text-sm font-semibold", className)} + {...props} /> +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef(({ className, children, ...props }, ref) => ( + <SelectPrimitive.Item + ref={ref} + className={cn( + "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", + className + )} + {...props}> + <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center"> + <SelectPrimitive.ItemIndicator> + <Check className="h-4 w-4" /> + </SelectPrimitive.ItemIndicator> + </span> + <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> + </SelectPrimitive.Item> +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => ( + <SelectPrimitive.Separator + ref={ref} + className={cn("-mx-1 my-1 h-px bg-muted", className)} + {...props} /> +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +}
@@ -0,0 +1,6 @@
+import { clsx } from "clsx"; +import { twMerge } from "tailwind-merge" + +export function cn(...inputs) { + return twMerge(clsx(inputs)); +}
@@ -1,32 +1,76 @@
/** @type {import('tailwindcss').Config} */ export default { - content: [ + darkMode: ["class"], + content: [ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", "./src/components/**/*.{js,ts,jsx,tsx,mdx}", "./src/app/**/*.{js,ts,jsx,tsx,mdx}", ], theme: { - extend: { - colors: { - background: "var(--background)", - foreground: "var(--foreground)", - blue: { - 100: "#e0f2ff", - 200: "#cae8ff", - 300: "#b5deff", - 400: "#96cefd", - 500: "#78bbfa", - 600: "#59a7f6", - 700: "#3892f3", - 800: "#147af3", - 900: "#0265dc", - 1000: "#0054b6", - 1100: "#004491", - 1200: "#003571", - 1300: "#002754", - }, - }, - }, + extend: { + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + blue: { + '100': '#e0f2ff', + '200': '#cae8ff', + '300': '#b5deff', + '400': '#96cefd', + '500': '#78bbfa', + '600': '#59a7f6', + '700': '#3892f3', + '800': '#147af3', + '900': '#0265dc', + '1000': '#0054b6', + '1100': '#004491', + '1200': '#003571', + '1300': '#002754' + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + } + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + } + } }, - plugins: [], + plugins: [require("tailwindcss-animate")], };