From 6c5ccb44974e50817d3e330348d6a4e6f77909ab Mon Sep 17 00:00:00 2001 From: Daniel Xu Date: Sun, 14 Dec 2025 16:18:36 -0800 Subject: [PATCH] add start date feature --- package.json | 2 + pnpm-lock.yaml | 498 +++++++++++++++++++++++++++++ src/components/ui/button.tsx | 6 +- src/components/ui/calendar.tsx | 220 +++++++++++++ src/components/ui/popover.tsx | 48 +++ src/components/upload-zone.tsx | 89 +++++- src/lib/ical.ts | 59 ++-- src/server/api/routers/schedule.ts | 4 +- 8 files changed, 895 insertions(+), 31 deletions(-) create mode 100644 src/components/ui/calendar.tsx create mode 100644 src/components/ui/popover.tsx diff --git a/package.json b/package.json index 83dc613..ddd3d1c 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@clerk/nextjs": "^6.36.0", "@posthog/react": "^1.5.2", "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-slot": "^1.2.4", "@sentry/nextjs": "^10.29.0", "@t3-oss/env-nextjs": "^0.12.0", @@ -50,6 +51,7 @@ "postgres": "^3.4.4", "posthog-js": "^1.302.2", "react": "^19.0.0", + "react-day-picker": "^9.12.0", "react-dom": "^19.0.0", "server-only": "^0.0.1", "superjson": "^2.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a135723..a1efdf0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,9 @@ importers: "@radix-ui/react-accordion": specifier: ^1.2.12 version: 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + "@radix-ui/react-popover": + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) "@radix-ui/react-slot": specifier: ^1.2.4 version: 1.2.4(@types/react@19.2.7)(react@19.2.1) @@ -79,6 +82,9 @@ importers: react: specifier: ^19.0.0 version: 19.2.1 + react-day-picker: + specifier: ^9.12.0 + version: 9.12.0(react@19.2.1) react-dom: specifier: ^19.0.0 version: 19.2.1(react@19.2.1) @@ -372,6 +378,12 @@ packages: } engines: { node: ">=18.17.0" } + "@date-fns/tz@1.4.1": + resolution: + { + integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==, + } + "@drizzle-team/brocli@0.10.2": resolution: { @@ -888,6 +900,33 @@ packages: } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + "@floating-ui/core@1.7.3": + resolution: + { + integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==, + } + + "@floating-ui/dom@1.7.4": + resolution: + { + integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==, + } + + "@floating-ui/react-dom@2.1.6": + resolution: + { + integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==, + } + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + + "@floating-ui/utils@0.2.10": + resolution: + { + integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==, + } + "@humanfs/core@0.19.1": resolution: { @@ -1680,6 +1719,22 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-arrow@1.1.7": + resolution: + { + integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==, + } + 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-collapsible@1.1.12": resolution: { @@ -1748,6 +1803,50 @@ packages: "@types/react": optional: true + "@radix-ui/react-dismissable-layer@1.1.11": + resolution: + { + integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==, + } + 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.3": + resolution: + { + integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==, + } + 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.7": + resolution: + { + integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==, + } + 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.1": resolution: { @@ -1760,6 +1859,54 @@ packages: "@types/react": optional: true + "@radix-ui/react-popover@1.1.15": + resolution: + { + integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==, + } + 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.8": + resolution: + { + integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==, + } + 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.9": + resolution: + { + integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==, + } + 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.5": resolution: { @@ -1816,6 +1963,18 @@ packages: "@types/react": optional: true + "@radix-ui/react-use-callback-ref@1.1.1": + resolution: + { + integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==, + } + 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.2.2": resolution: { @@ -1840,6 +1999,18 @@ packages: "@types/react": optional: true + "@radix-ui/react-use-escape-keydown@1.1.1": + resolution: + { + integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==, + } + 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.1": resolution: { @@ -1852,6 +2023,36 @@ packages: "@types/react": optional: true + "@radix-ui/react-use-rect@1.1.1": + resolution: + { + integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==, + } + 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.1": + resolution: + { + integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/rect@1.1.1": + resolution: + { + integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==, + } + "@rollup/plugin-commonjs@28.0.1": resolution: { @@ -3099,6 +3300,13 @@ packages: integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, } + aria-hidden@1.2.6: + resolution: + { + integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==, + } + engines: { node: ">=10" } + aria-query@5.3.2: resolution: { @@ -3484,6 +3692,12 @@ packages: } engines: { node: ">= 0.4" } + date-fns-jalali@4.1.0-0: + resolution: + { + integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==, + } + date-fns@4.1.0: resolution: { @@ -3547,6 +3761,12 @@ packages: } engines: { node: ">=8" } + detect-node-es@1.1.0: + resolution: + { + integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==, + } + doctrine@2.1.0: resolution: { @@ -4277,6 +4497,13 @@ packages: } engines: { node: ">= 0.4" } + get-nonce@1.0.1: + resolution: + { + integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==, + } + engines: { node: ">=6" } + get-proto@1.0.1: resolution: { @@ -5712,6 +5939,15 @@ packages: integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==, } + react-day-picker@9.12.0: + resolution: + { + integrity: sha512-t8OvG/Zrciso5CQJu5b1A7yzEmebvST+S3pOVQJWxwjjVngyG/CA2htN/D15dLI4uTEuLLkbZyS4YYt480FAtA==, + } + engines: { node: ">=18" } + peerDependencies: + react: ">=16.8.0" + react-dom@19.2.1: resolution: { @@ -5726,6 +5962,45 @@ packages: integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==, } + 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.7.2: + resolution: + { + integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==, + } + 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.2.1: resolution: { @@ -6439,6 +6714,32 @@ packages: integrity: sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==, } + 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 + use-sync-external-store@1.6.0: resolution: { @@ -6804,6 +7105,8 @@ snapshots: - react - react-dom + "@date-fns/tz@1.4.1": {} + "@drizzle-team/brocli@0.10.2": {} "@effect/platform@0.90.3(effect@3.17.7)": @@ -7021,6 +7324,23 @@ snapshots: "@eslint/core": 0.17.0 levn: 0.4.1 + "@floating-ui/core@1.7.3": + dependencies: + "@floating-ui/utils": 0.2.10 + + "@floating-ui/dom@1.7.4": + dependencies: + "@floating-ui/core": 1.7.3 + "@floating-ui/utils": 0.2.10 + + "@floating-ui/react-dom@2.1.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)": + dependencies: + "@floating-ui/dom": 1.7.4 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + + "@floating-ui/utils@0.2.10": {} + "@humanfs/core@0.19.1": {} "@humanfs/node@0.16.7": @@ -7504,6 +7824,15 @@ snapshots: "@types/react": 19.2.7 "@types/react-dom": 19.2.3(@types/react@19.2.7) + "@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)": + dependencies: + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + "@types/react": 19.2.7 + "@types/react-dom": 19.2.3(@types/react@19.2.7) + "@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)": dependencies: "@radix-ui/primitive": 1.1.3 @@ -7550,6 +7879,36 @@ snapshots: optionalDependencies: "@types/react": 19.2.7 + "@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)": + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/react-use-escape-keydown": 1.1.1(@types/react@19.2.7)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + "@types/react": 19.2.7 + "@types/react-dom": 19.2.3(@types/react@19.2.7) + + "@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.7)(react@19.2.1)": + dependencies: + react: 19.2.1 + optionalDependencies: + "@types/react": 19.2.7 + + "@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.2.7)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + "@types/react": 19.2.7 + "@types/react-dom": 19.2.3(@types/react@19.2.7) + "@radix-ui/react-id@1.1.1(@types/react@19.2.7)(react@19.2.1)": dependencies: "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.2.7)(react@19.2.1) @@ -7557,6 +7916,57 @@ snapshots: optionalDependencies: "@types/react": 19.2.7 + "@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)": + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/react-dismissable-layer": 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + "@radix-ui/react-focus-guards": 1.1.3(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/react-focus-scope": 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + "@radix-ui/react-id": 1.1.1(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/react-popper": 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + "@radix-ui/react-portal": 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + "@radix-ui/react-presence": 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + "@radix-ui/react-slot": 1.2.3(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.2.7)(react@19.2.1) + aria-hidden: 1.2.6 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.7)(react@19.2.1) + optionalDependencies: + "@types/react": 19.2.7 + "@types/react-dom": 19.2.3(@types/react@19.2.7) + + "@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)": + dependencies: + "@floating-ui/react-dom": 2.1.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + "@radix-ui/react-arrow": 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/react-use-rect": 1.1.1(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/react-use-size": 1.1.1(@types/react@19.2.7)(react@19.2.1) + "@radix-ui/rect": 1.1.1 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + "@types/react": 19.2.7 + "@types/react-dom": 19.2.3(@types/react@19.2.7) + + "@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)": + dependencies: + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.2.7)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + "@types/react": 19.2.7 + "@types/react-dom": 19.2.3(@types/react@19.2.7) + "@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)": dependencies: "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.2.7)(react@19.2.1) @@ -7590,6 +8000,12 @@ snapshots: optionalDependencies: "@types/react": 19.2.7 + "@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.7)(react@19.2.1)": + dependencies: + react: 19.2.1 + optionalDependencies: + "@types/react": 19.2.7 + "@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.7)(react@19.2.1)": dependencies: "@radix-ui/react-use-effect-event": 0.0.2(@types/react@19.2.7)(react@19.2.1) @@ -7605,12 +8021,35 @@ snapshots: optionalDependencies: "@types/react": 19.2.7 + "@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.7)(react@19.2.1)": + dependencies: + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.2.7)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + "@types/react": 19.2.7 + "@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.7)(react@19.2.1)": dependencies: react: 19.2.1 optionalDependencies: "@types/react": 19.2.7 + "@radix-ui/react-use-rect@1.1.1(@types/react@19.2.7)(react@19.2.1)": + dependencies: + "@radix-ui/rect": 1.1.1 + react: 19.2.1 + optionalDependencies: + "@types/react": 19.2.7 + + "@radix-ui/react-use-size@1.1.1(@types/react@19.2.7)(react@19.2.1)": + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.2.7)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + "@types/react": 19.2.7 + + "@radix-ui/rect@1.1.1": {} + "@rollup/plugin-commonjs@28.0.1(rollup@4.53.3)": dependencies: "@rollup/pluginutils": 5.3.0(rollup@4.53.3) @@ -8398,6 +8837,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + aria-query@5.3.2: {} array-buffer-byte-length@1.0.2: @@ -8631,6 +9074,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + date-fns-jalali@4.1.0-0: {} + date-fns@4.1.0: {} debug@3.2.7: @@ -8659,6 +9104,8 @@ snapshots: detect-libc@2.1.2: {} + detect-node-es@1.1.0: {} + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -9239,6 +9686,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -10004,6 +10453,13 @@ snapshots: dependencies: safe-buffer: 5.2.1 + react-day-picker@9.12.0(react@19.2.1): + dependencies: + "@date-fns/tz": 1.4.1 + date-fns: 4.1.0 + date-fns-jalali: 4.1.0-0 + react: 19.2.1 + react-dom@19.2.1(react@19.2.1): dependencies: react: 19.2.1 @@ -10011,6 +10467,33 @@ snapshots: react-is@16.13.1: {} + react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.1): + dependencies: + react: 19.2.1 + react-style-singleton: 2.2.3(@types/react@19.2.7)(react@19.2.1) + tslib: 2.8.1 + optionalDependencies: + "@types/react": 19.2.7 + + react-remove-scroll@2.7.2(@types/react@19.2.7)(react@19.2.1): + dependencies: + react: 19.2.1 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.7)(react@19.2.1) + react-style-singleton: 2.2.3(@types/react@19.2.7)(react@19.2.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.7)(react@19.2.1) + use-sidecar: 1.1.3(@types/react@19.2.7)(react@19.2.1) + optionalDependencies: + "@types/react": 19.2.7 + + react-style-singleton@2.2.3(@types/react@19.2.7)(react@19.2.1): + dependencies: + get-nonce: 1.0.1 + react: 19.2.1 + tslib: 2.8.1 + optionalDependencies: + "@types/react": 19.2.7 + react@19.2.1: {} readdirp@3.6.0: @@ -10558,6 +11041,21 @@ snapshots: url-template@2.0.8: {} + use-callback-ref@1.3.3(@types/react@19.2.7)(react@19.2.1): + dependencies: + react: 19.2.1 + tslib: 2.8.1 + optionalDependencies: + "@types/react": 19.2.7 + + use-sidecar@1.1.3(@types/react@19.2.7)(react@19.2.1): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.1 + tslib: 2.8.1 + optionalDependencies: + "@types/react": 19.2.7 + use-sync-external-store@1.6.0(react@19.2.1): dependencies: react: 19.2.1 diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 1dd187c..46b3a48 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -38,8 +38,8 @@ const buttonVariants = cva( function Button({ className, - variant, - size, + variant = "default", + size = "default", asChild = false, ...props }: React.ComponentProps<"button"> & @@ -51,6 +51,8 @@ function Button({ return ( diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx new file mode 100644 index 0000000..7c01e39 --- /dev/null +++ b/src/components/ui/calendar.tsx @@ -0,0 +1,220 @@ +"use client"; + +import * as React from "react"; +import { + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, +} from "lucide-react"; +import { + DayPicker, + getDefaultClassNames, + type DayButton, +} from "react-day-picker"; + +import { cn } from "@/lib/utils"; +import { Button, buttonVariants } from "@/components/ui/button"; + +function Calendar({ + className, + classNames, + showOutsideDays = true, + captionLayout = "label", + buttonVariant = "ghost", + formatters, + components, + ...props +}: React.ComponentProps & { + buttonVariant?: React.ComponentProps["variant"]; +}) { + const defaultClassNames = getDefaultClassNames(); + + return ( + svg]:rotate-180`, + String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, + className, + )} + captionLayout={captionLayout} + formatters={{ + formatMonthDropdown: (date) => + date.toLocaleString("default", { month: "short" }), + ...formatters, + }} + classNames={{ + root: cn("w-fit", defaultClassNames.root), + months: cn( + "flex gap-4 flex-col md:flex-row relative", + defaultClassNames.months, + ), + month: cn("flex flex-col w-full gap-4", defaultClassNames.month), + nav: cn( + "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", + defaultClassNames.nav, + ), + button_previous: cn( + buttonVariants({ variant: buttonVariant }), + "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + defaultClassNames.button_previous, + ), + button_next: cn( + buttonVariants({ variant: buttonVariant }), + "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + defaultClassNames.button_next, + ), + month_caption: cn( + "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", + defaultClassNames.month_caption, + ), + dropdowns: cn( + "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", + defaultClassNames.dropdowns, + ), + dropdown_root: cn( + "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", + defaultClassNames.dropdown_root, + ), + dropdown: cn( + "absolute bg-popover inset-0 opacity-0", + defaultClassNames.dropdown, + ), + caption_label: cn( + "select-none font-medium", + captionLayout === "label" + ? "text-sm" + : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", + defaultClassNames.caption_label, + ), + table: "w-full border-collapse", + weekdays: cn("flex", defaultClassNames.weekdays), + weekday: cn( + "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", + defaultClassNames.weekday, + ), + week: cn("flex w-full mt-2", defaultClassNames.week), + week_number_header: cn( + "select-none w-(--cell-size)", + defaultClassNames.week_number_header, + ), + week_number: cn( + "text-[0.8rem] select-none text-muted-foreground", + defaultClassNames.week_number, + ), + day: cn( + "relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", + props.showWeekNumber + ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md" + : "[&:first-child[data-selected=true]_button]:rounded-l-md", + defaultClassNames.day, + ), + range_start: cn( + "rounded-l-md bg-accent", + defaultClassNames.range_start, + ), + range_middle: cn("rounded-none", defaultClassNames.range_middle), + range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end), + today: cn( + "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", + defaultClassNames.today, + ), + outside: cn( + "text-muted-foreground aria-selected:text-muted-foreground", + defaultClassNames.outside, + ), + disabled: cn( + "text-muted-foreground opacity-50", + defaultClassNames.disabled, + ), + hidden: cn("invisible", defaultClassNames.hidden), + ...classNames, + }} + components={{ + Root: ({ className, rootRef, ...props }) => { + return ( +
+ ); + }, + Chevron: ({ className, orientation, ...props }) => { + if (orientation === "left") { + return ( + + ); + } + + if (orientation === "right") { + return ( + + ); + } + + return ( + + ); + }, + DayButton: CalendarDayButton, + WeekNumber: ({ children, ...props }) => { + return ( + +
+ {children} +
+ + ); + }, + ...components, + }} + {...props} + /> + ); +} + +function CalendarDayButton({ + className, + day, + modifiers, + ...props +}: React.ComponentProps) { + const defaultClassNames = getDefaultClassNames(); + + const ref = React.useRef(null); + React.useEffect(() => { + if (modifiers.focused) ref.current?.focus(); + }, [modifiers.focused]); + + return ( + + + + + + + {!startDate && ( +

+ Select the date when your classes begin +

+ )} +
+ + + )} + {/* Export Buttons */} {events && events.length > 0 && (