diff --git a/.changeset/config.json b/.changeset/config.json deleted file mode 100644 index 945bdfa..0000000 --- a/.changeset/config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", - "changelog": "@changesets/cli/changelog", - "commit": true, - "fixed": [], - "linked": [], - "access": "restricted", - "baseBranch": "origin/main", - "updateInternalDependencies": "patch", - "ignore": [], - "privatePackages": { - "version": true, - "tag": true - } -} diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml deleted file mode 100644 index 1024df8..0000000 --- a/.github/workflows/cd.yml +++ /dev/null @@ -1,226 +0,0 @@ -name: Deploy Server, Migrate Database, and Deploy Web -run-name: Deploying to ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment || github.ref_name == 'main' && 'production' || github.ref_name == 'staging' && 'staging' || 'preview' }} Environment by ${{ github.actor }} - -on: - pull_request: - branches: [main] - types: [closed] - workflow_run: - workflows: ["Ready to Merge"] - types: [completed] - branches: [main, staging] - workflow_dispatch: - inputs: - deploy_server: - description: "Whether to deploy the server" - required: false - default: true - type: boolean - deploy_web: - description: "Whether to deploy the web application" - required: false - default: true - type: boolean - migrate_database: - description: "Whether to run database migrations" - required: false - default: true - type: boolean - create_release: - description: "Create Release" - required: false - default: true - type: boolean - pr_main: - description: "Create Pull Request to Main" - required: false - default: false - type: boolean - environment: - description: "Environment to deploy to" - required: true - default: "preview" - type: choice - options: ["production", "staging", "preview"] - -env: - WORKFLOW_DEPLOY_ENV: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment || github.ref_name == 'main' && 'production' || github.ref_name == 'staging' && 'staging' || 'preview' }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - -jobs: - deploy: - if: ${{ (github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success') && !startsWith(github.ref_name, 'release/') }} - runs-on: ubuntu-latest - timeout-minutes: 60 - - # Determine environment based on branch or input - environment: - name: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment || github.ref_name == 'main' && 'production' || github.ref_name == 'staging' && 'staging' || 'preview' }} - - steps: - - uses: actions/checkout@master - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Install dependencies - run: bun install --frozen-lockfile - - - name: Check migrations - if: ${{ github.event.inputs.migrate_database != 'false' }} - env: - DATABASE_URL: ${{ secrets.DATABASE_URL }} - run: cd packages/db && bun run check - - - name: Download deployment info - if: ${{ github.event_name == 'workflow_run' && github.event.inputs.deploy_server != 'false' }} - uses: actions/download-artifact@master - with: - name: deployment-info - github-token: ${{ secrets.GITHUB_TOKEN }} - run-id: ${{ github.event.workflow_run.id }} - - - name: Set deployment variables - if: ${{ github.event_name == 'workflow_run' && github.event.inputs.deploy_server != 'false' }} - run: | - if [ -f "deployment_name.txt" ]; then - DEPLOYMENT_NAME=$(cat deployment_name.txt) - echo "DEPLOY_COMMAND=deploy --env $WORKFLOW_DEPLOY_ENV --name $DEPLOYMENT_NAME" >> $GITHUB_ENV - else - echo "DEPLOY_COMMAND=deploy --env $WORKFLOW_DEPLOY_ENV" >> $GITHUB_ENV - fi - - - name: Deploy Server Worker - if: ${{ github.event.inputs.deploy_server != 'false' }} - id: deploy-server-worker - - env: - GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} - GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} - MICROSOFT_CLIENT_ID: ${{ secrets.MICROSOFT_CLIENT_ID }} - MICROSOFT_CLIENT_SECRET: ${{ secrets.MICROSOFT_CLIENT_SECRET }} - BETTER_AUTH_SECRET: ${{ secrets.BETTER_AUTH_SECRET }} - BETTER_AUTH_URL: ${{ secrets.BETTER_AUTH_URL }} - SERVER_PORT: ${{ secrets.SERVER_PORT }} - WEB_PORT: ${{ secrets.WEB_PORT }} - BACKEND_URL: ${{ secrets.BACKEND_URL }} - TRUSTED_ORIGINS: ${{ secrets.TRUSTED_ORIGINS }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - DATABASE_HOST: ${{ secrets.DATABASE_HOST }} - POSTGRES_PORT: ${{ secrets.POSTGRES_PORT }} - POSTGRES_USER: ${{ secrets.POSTGRES_USER }} - POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} - POSTGRES_DB: ${{ secrets.POSTGRES_DB }} - UPSTASH_REDIS_REST_URL: ${{ secrets.UPSTASH_REDIS_REST_URL }} - UPSTASH_REDIS_REST_TOKEN: ${{ secrets.UPSTASH_REDIS_REST_TOKEN }} - EMAIL_FROM: ${{ secrets.EMAIL_FROM }} - RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} - NODE_ENV: ${{ secrets.NODE_ENV }} - IS_EDGE_RUNTIME: ${{ secrets.IS_EDGE_RUNTIME }} - - uses: cloudflare/wrangler-action@v3 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - - packageManager: bun - workingDirectory: "apps/server" - - command: ${{ env.DEPLOY_COMMAND }} - # This is required for wrangler to know which environment to deploy to even though --env ${{ env.WORKFLOW_DEPLOY_ENV }} is specified in the command - environment: ${{ env.WORKFLOW_DEPLOY_ENV }} - - secrets: | - GOOGLE_CLIENT_ID - GOOGLE_CLIENT_SECRET - MICROSOFT_CLIENT_ID - MICROSOFT_CLIENT_SECRET - BETTER_AUTH_SECRET - BETTER_AUTH_URL - SERVER_PORT - WEB_PORT - BACKEND_URL - TRUSTED_ORIGINS - DATABASE_URL - DATABASE_HOST - POSTGRES_PORT - POSTGRES_USER - POSTGRES_PASSWORD - POSTGRES_DB - UPSTASH_REDIS_REST_URL - UPSTASH_REDIS_REST_TOKEN - EMAIL_FROM - RESEND_API_KEY - NODE_ENV - IS_EDGE_RUNTIME - - - name: Print Server deployment URL - if: ${{ github.event.inputs.deploy_server != 'false' }} - env: - SERVER_DEPLOYMENT_URL: ${{ steps.deploy-server-worker.outputs.deployment-url }} - run: echo $SERVER_DEPLOYMENT_URL - - - name: Run migrations - if: ${{ github.event.inputs.migrate_database != 'false' }} - env: - DATABASE_URL: ${{ secrets.DATABASE_URL }} - run: cd packages/db && bun run migrate - - - name: Deploy Web Worker - if: ${{ github.event.inputs.deploy_web != 'false' }} - id: deploy-web-worker - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - NODE_ENV: ${{ secrets.NODE_ENV }} - VITE_BACKEND_URL: ${{ secrets.VITE_BACKEND_URL }} - VITE_FRONTEND_URL: ${{ secrets.VITE_FRONTEND_URL }} - run: | - cd apps/web - bun run cf:build - bun run cf:deploy:${{ env.WORKFLOW_DEPLOY_ENV }} | tee web-build.log - tail_output=$(tail -n 4 web-build.log) - rm web-build.log - echo "$tail_output" - echo "tail_output<> $GITHUB_OUTPUT - echo "$tail_output" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Print Web deployment output - if: ${{ github.event.inputs.deploy_web != 'false' }} - env: - WEB_DEPLOYMENT_OUTPUT: ${{ steps.deploy-web-worker.outputs.tail_output }} - run: echo $WEB_DEPLOYMENT_OUTPUT - - - name: Setup Git - if: ${{ github.ref_name == 'staging' && github.event.inputs.create_release != 'false' }} - run: | - git config --global user.name "${{ github.actor || 'GitHub Actions' }}" - git config --global user.email "${{ github.actor || 'github-actions' }}@users.noreply.github.com" - - - name: Create Release - if: ${{ github.ref_name == 'staging' && github.event.inputs.create_release != 'false' }} - id: create-release - run: ./scripts/release.sh - - - name: Create Pull Request to Main - if: ${{ github.ref_name == 'staging' && github.event.inputs.pr_main != 'false' }} - id: create-pr-to-main - uses: peter-evans/create-pull-request@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - branch: main - title: "Release: ${{ github.sha }}" - body: >- - Automated release PR from staging branch - - - Deployment URL: - ${{ steps.deploy-server-worker.outputs.deployment-url }} - - - Web Deployment Output: - ${{ steps.deploy-web-worker.outputs.tail_output }} - - This PR is created automatically after successful staging deployment. - labels: release, automated diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index dbb1d65..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,124 +0,0 @@ -name: CI -run-name: "Branch: ${{ github.ref_name }}. Event: ${{ github.event_name }}. By: ${{ github.actor }}." - -on: - push: - branches: [main, staging] - pull_request: - branches: [main, staging] - -jobs: - ci: - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:16 - ports: - - 5432:5432 - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: nimbus-test - options: >- - --health-cmd "pg_isready -U postgres" --health-interval 10s --health-timeout 5s --health-retries 5 - minio: - image: minio/minio:edge-cicd - ports: - - 9000:9000 - env: - MINIO_ROOT_USER: minioadmin - MINIO_ROOT_PASSWORD: minioadmin - MINIO_REGION: us-east-1 - options: >- - --health-cmd "curl -f http://localhost:9000/minio/health/ready" --health-interval 30s --health-timeout 10s --health-retries 3 - - steps: - - uses: actions/checkout@master - - uses: actions/setup-node@v4 - with: - node-version: latest - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Install dependencies - run: bun install --frozen-lockfile - - - name: Run formatter - run: bun format - - - name: Run linter - run: bun lint - - - name: Run tests with coverage - run: bun run test:coverage - - - name: Build application - env: - NODE_ENV: production - DATABASE_URL: postgres://postgres:postgres@localhost:5432/nimbus-test - VITE_BACKEND_URL: http://localhost:1284 - VITE_FRONTEND_URL: http://localhost:3000 - run: bun run build - - - name: Create coverage badge - if: github.ref == 'refs/heads/staging' - run: | - # Extract coverage percentage from JSON summary - if [ -f "coverage/coverage-summary.json" ]; then - coverage=$(node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync('coverage/coverage-summary.json')); const total=data.total; if(total && total.lines && total.lines.pct !== undefined) { console.log(total.lines.pct); } else { console.log('0'); }") - else - coverage=0 - fi - - # If coverage extraction fails, default to 0 - if [ -z "$coverage" ] || [ "$coverage" = "null" ]; then coverage=0; fi - - # Round coverage to integer - coverage=$(printf "%.0f" "$coverage") - - # Determine color based on coverage - if [ "$coverage" -ge 80 ]; then - color=green - elif [ "$coverage" -ge 50 ]; then - color=yellow - else - color=red - fi - - # Create a simpler badge using shields.io style URL - echo "Coverage: $coverage%" - continue-on-error: true - - - name: Commit coverage report - if: github.ref == 'refs/heads/staging' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git config user.name "github-actions" - git config user.email "github-actions@github.com" - - # Update README with actual coverage percentage using shields.io badge - if [ -f "coverage/coverage-summary.json" ]; then - coverage=$(node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync('coverage/coverage-summary.json')); const total=data.total; if(total && total.lines && total.lines.pct !== undefined) { console.log(total.lines.pct); } else { console.log('0'); }") - else - coverage=0 - fi - - if [ -z "$coverage" ] || [ "$coverage" = "null" ]; then coverage=0; fi - coverage=$(printf "%.0f" "$coverage") - - if [ "$coverage" -ge 80 ]; then - color=green - elif [ "$coverage" -ge 50 ]; then - color=yellow - else - color=red - fi - - sed -i "s/coverage-[0-9]*%25-[a-z]*/coverage-$coverage%25-$color/" README.md - git add README.md - git commit -m "Update coverage badge [skip ci]" || echo "No changes to commit" - git push - continue-on-error: true diff --git a/apps/web/src/components/auth/forgot-password-form.tsx b/apps/web/src/components/auth/forgot-password-form.tsx index 39d56c1..d55cadd 100644 --- a/apps/web/src/components/auth/forgot-password-form.tsx +++ b/apps/web/src/components/auth/forgot-password-form.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { forgotPasswordSchema, type ForgotPasswordFormData } from "@nimbus/shared"; import { useForm, type SubmitHandler } from "react-hook-form"; @@ -14,7 +12,7 @@ import { Input } from "@/components/ui/input"; import type { ComponentProps } from "react"; export function ForgotPasswordForm({ ...props }: ComponentProps<"div">) { - const { isLoading, forgotPassword } = useForgotPassword(); + const { mutate, isPending } = useForgotPassword(); const { register, @@ -28,7 +26,7 @@ export function ForgotPasswordForm({ ...props }: ComponentProps<"div">) { }); const onSubmit: SubmitHandler = async data => { - await forgotPassword(data); + mutate(data); }; return ( @@ -69,8 +67,8 @@ export function ForgotPasswordForm({ ...props }: ComponentProps<"div">) { - diff --git a/apps/web/src/components/auth/shared/auth-provider-buttons.tsx b/apps/web/src/components/auth/shared/auth-provider-buttons.tsx index 6e090b9..5c08d20 100644 --- a/apps/web/src/components/auth/shared/auth-provider-buttons.tsx +++ b/apps/web/src/components/auth/shared/auth-provider-buttons.tsx @@ -1,142 +1,34 @@ -"use client"; - -import { useGoogleAuth, useMicrosoftAuth } from "@/hooks/useAuth"; import { SocialAuthButton } from "./social-auth-button"; import type { DriveProvider } from "@nimbus/shared"; -// import { Button } from "@/components/ui/button"; +import { useSocialAuth } from "@/hooks/useAuth"; import { Loader2 } from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; type AuthProviderButtonsProps = { - onProviderClick?: (provider: DriveProvider) => Promise | void; - isLoading?: boolean | Record; action: "signin" | "signup"; - showS3Button?: boolean; callbackURL?: string; - onAuthSuccess?: () => void; - onS3Click?: () => void; }; -export function AuthProviderButtons({ - onProviderClick, - isLoading: externalIsLoading, - action, - showS3Button = false, - callbackURL, - onAuthSuccess, - onS3Click, -}: AuthProviderButtonsProps) { - // Use external loading state or manage internal loading state - const [internalIsLoading, setInternalIsLoading] = useState>({ - google: false, - microsoft: false, - box: false, - dropbox: false, - s3: false, - }); - - const isLoading = externalIsLoading || internalIsLoading; - - const getIsLoading = (provider: DriveProvider) => { - return typeof isLoading === "boolean" ? isLoading : isLoading[provider]; - }; - - const { signInWithGoogleProvider } = useGoogleAuth(); - const { signInWithMicrosoftProvider } = useMicrosoftAuth(); - // const { signInWithBoxProvider } = useBoxAuth(); - // const { signInWithDropboxProvider } = useDropboxAuth(); - - const handleSocialAuth = async (provider: Exclude) => { - try { - // If external handler is provided, use it - if (onProviderClick) { - return await onProviderClick(provider); - } - - // Otherwise, handle internally - setInternalIsLoading(prev => ({ ...prev, [provider]: true })); - - if (provider === "google") { - await signInWithGoogleProvider({ callbackURL }); - } else if (provider === "microsoft") { - await signInWithMicrosoftProvider({ callbackURL }); - // } else if (provider === "box") { - // await signInWithBoxProvider({ callbackURL }); - // } else if (provider === "dropbox") { - // await signInWithDropboxProvider({ callbackURL }); - } - - onAuthSuccess?.(); - } catch (error) { - console.error(`Error signing in with ${provider}:`, error); - toast.error(`${provider.charAt(0).toUpperCase() + provider.slice(1)} authentication failed`); - } finally { - setInternalIsLoading(prev => ({ ...prev, [provider]: false })); - } - }; - - const handleProviderClick = async (provider: DriveProvider) => { - if (provider === "s3") { - if (onProviderClick) { - await onProviderClick("s3"); - } else if (onS3Click) { - onS3Click(); - } - } else { - await handleSocialAuth(provider); - } - }; +export function AuthProviderButtons({ action, callbackURL }: AuthProviderButtonsProps) { + const socialAuthMutation = useSocialAuth(); + const isLoading = socialAuthMutation.isPending; return ( <> handleProviderClick("google")} - disabled={getIsLoading("google")} - > - {getIsLoading("google") && } - + onClick={() => socialAuthMutation.mutate({ provider: "google", callbackURL })} + disabled={isLoading} + className={`{isLoading ? "opacity-50 cursor-not-allowed" : ""}`} + > handleProviderClick("microsoft")} - disabled={getIsLoading("microsoft")} - > - {getIsLoading("microsoft") && } - - {/* - handleProviderClick("box")} - disabled={getIsLoading("box")} - > - {getIsLoading("box") && } - - - handleSocialAuth("dropbox")} - disabled={getIsLoading("dropbox")} - > - {getIsLoading("dropbox") && } - - - {showS3Button && ( - - )} */} + onClick={() => socialAuthMutation.mutate({ provider: "microsoft", callbackURL })} + disabled={isLoading} + className={`{isLoading ? "opacity-50 cursor-not-allowed" : ""}`} + > ); } diff --git a/apps/web/src/components/auth/shared/password-input.tsx b/apps/web/src/components/auth/shared/password-input.tsx index 27b96c8..f982565 100644 --- a/apps/web/src/components/auth/shared/password-input.tsx +++ b/apps/web/src/components/auth/shared/password-input.tsx @@ -1,5 +1,3 @@ -"use client"; - import type { PasswordInputProps } from "@/lib/types"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; diff --git a/apps/web/src/components/auth/shared/social-auth-button.tsx b/apps/web/src/components/auth/shared/social-auth-button.tsx index 1150643..f267df9 100644 --- a/apps/web/src/components/auth/shared/social-auth-button.tsx +++ b/apps/web/src/components/auth/shared/social-auth-button.tsx @@ -1,8 +1,7 @@ -"use client"; - import { BoxIcon, DropboxIcon, GoogleIcon, MicrosoftIcon } from "@/components/icons"; import type { SocialAuthButtonProps } from "@/lib/types"; import { Button } from "@/components/ui/button"; +import type { PropsWithChildren } from "react"; const providerConfig = { google: { @@ -23,12 +22,7 @@ const providerConfig = { }, } as const; -export function SocialAuthButton({ - provider, - action, - children, - ...props -}: React.PropsWithChildren) { +export function SocialAuthButton({ provider, action, children, ...props }: PropsWithChildren) { const config = providerConfig[provider]; const IconComponent = config.icon; @@ -40,7 +34,7 @@ export function SocialAuthButton({ )} @@ -224,7 +220,7 @@ export function SignupForm({ className, ...props }: ComponentProps<"div">) {

By signing up, you agree to our{" "} - + terms of service . diff --git a/apps/web/src/components/auth/verify-email-content.tsx b/apps/web/src/components/auth/verify-email-content.tsx index 719df22..5a78ca5 100644 --- a/apps/web/src/components/auth/verify-email-content.tsx +++ b/apps/web/src/components/auth/verify-email-content.tsx @@ -1,101 +1,101 @@ -"use client"; +// import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +// import { AuthErrorCard } from "./shared/auth-error-card"; +import { type ComponentProps } from "react"; +// import { useSearchParams } from "next/navigation"; +// import { ArrowLeft, Loader2 } from "lucide-react"; +// import { Button } from "@/components/ui/button"; +// import { Link } from "@tanstack/react-router"; +// import axios, { AxiosError } from "axios"; +// import env from "@nimbus/env/client"; +// import { toast } from "sonner"; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; -import { AuthErrorCard } from "./shared/auth-error-card"; -import { type ComponentProps, useState } from "react"; -import { useSearchParams } from "next/navigation"; -import { ArrowLeft, Loader2 } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import axios, { AxiosError } from "axios"; -import env from "@nimbus/env/client"; -import { toast } from "sonner"; -import Link from "next/link"; +export function VerifyEmailContent({ ...props }: ComponentProps<"div">) {} +// TODO: This stuff -export function VerifyEmailContent({ ...props }: ComponentProps<"div">) { - const searchParams = useSearchParams(); - const error = searchParams.get("error"); - const token = searchParams.get("token"); - const [isLoading, setIsLoading] = useState(false); - const [isVerified, setIsVerified] = useState(false); - const callbackURL = searchParams.get("callbackURL"); +// const searchParams = useSearchParams(); +// const error = searchParams.get("error"); +// const token = searchParams.get("token"); +// const [isLoading, setIsLoading] = useState(false); +// const [isVerified, setIsVerified] = useState(false); +// const callbackURL = searchParams.get("callbackURL"); - const onClick = async () => { - if (!token) return; - setIsLoading(true); - try { - await axios.get(`${import.meta.env.VITE_BACKEND_URL}/api/auth/verify-email`, { - withCredentials: true, - params: { - token, - callbackURL, - }, - }); - onSuccess(); - } catch (error) { - if (!(error instanceof AxiosError)) { - console.error(error); - toast.error("Failed to verify email. Please try again."); - } else { - onSuccess(); - } - } finally { - setIsLoading(false); - } - }; +// const onClick = async () => { +// if (!token) return; +// setIsLoading(true); +// try { +// client +// withCredentials: true, +// params: { +// token, +// callbackURL, +// }, +// }); +// onSuccess(); +// } catch (error) { +// if (!(error instanceof AxiosError)) { +// console.error(error); +// toast.error("Failed to verify email. Please try again."); +// } else { +// onSuccess(); +// } +// } finally { +// setIsLoading(false); +// } +// }; - const onSuccess = () => { - setIsVerified(true); - toast.success("Email verified successfully"); - }; +// const onSuccess = () => { +// setIsVerified(true); +// toast.success("Email verified successfully"); +// }; - if (error === "invalid_token" || !token) { - return ( - - ); - } +// if (error === "invalid_token" || !token) { +// return ( +// +// ); +// } - return ( -

- - -
- -
-
- Verify Email - - Click the button below to verify your email. - -
-
+// return ( +//
+// +// +//
+// +//
+//
+// Verify Email +// +// Click the button below to verify your email. +// +//
+//
- - - +// +// +// - -

- By continuing, you agree to our{" "} - - terms of service - - . -

-
-
-
- ); -} +// +//

+// By continuing, you agree to our{" "} +// +// terms of service +// +// . +//

+//
+//
+//
+// ); +// } diff --git a/apps/web/src/components/contributors/card.tsx b/apps/web/src/components/contributors/card.tsx index 8591595..2b8dabd 100644 --- a/apps/web/src/components/contributors/card.tsx +++ b/apps/web/src/components/contributors/card.tsx @@ -1,8 +1,8 @@ import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; import { Card, CardTitle } from "@/components/ui/card"; +import { Link } from "@tanstack/react-router"; import { Badge } from "@/components/ui/badge"; import { GitCommit } from "lucide-react"; -import Link from "next/link"; export interface Contributor { login: string; @@ -16,7 +16,7 @@ export interface Contributor { export function ContributorCard({ contributor }: { contributor: Contributor }) { return ( - +
diff --git a/apps/web/src/components/contributors/chart.tsx b/apps/web/src/components/contributors/chart.tsx index 32bfcd6..e81f622 100644 --- a/apps/web/src/components/contributors/chart.tsx +++ b/apps/web/src/components/contributors/chart.tsx @@ -1,5 +1,3 @@ -"use client"; - import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@/components/ui/chart"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Area, AreaChart, XAxis } from "recharts"; diff --git a/apps/web/src/components/contributors/footer.tsx b/apps/web/src/components/contributors/footer.tsx index 086a3ac..8144c0b 100644 --- a/apps/web/src/components/contributors/footer.tsx +++ b/apps/web/src/components/contributors/footer.tsx @@ -1,6 +1,6 @@ import { Card, CardContent } from "@/components/ui/card"; import { Star, GitFork, Calendar } from "lucide-react"; -import Link from "next/link"; +import { Link } from "@tanstack/react-router"; export function ContributorFooter({ repoName, @@ -22,7 +22,7 @@ export function ContributorFooter({
{ - const params = new URLSearchParams(searchParams.toString()); - params.delete("id"); - router.replace(`?${params.toString()}`); + const { id, ...restSearchParams } = searchParams; + navigate({ + search: restSearchParams, + replace: true, + }); }; + return ( - !open && handleClose()}> + !open && handleClose()}>

File preview is being revamped. Please check back later.

diff --git a/apps/web/src/components/dashboard/file-browser/file-tabs.tsx b/apps/web/src/components/dashboard/file-browser/file-tabs.tsx deleted file mode 100644 index 1ae1c9a..0000000 --- a/apps/web/src/components/dashboard/file-browser/file-tabs.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { useRouter, useSearchParams } from "next/navigation"; - -export function FileTabs({ type }: { type: string | null }) { - const router = useRouter(); - const searchParams = useSearchParams(); - - const handleTabChange = (value: string) => { - const params = new URLSearchParams(searchParams); - if (value === "all") { - params.delete("type"); - } else { - params.set("type", value); - } - router.push(`?${params.toString()}`); - }; - - return ( - - - - All - - - Folders - - - Documents - - - Media - - - - ); -} diff --git a/apps/web/src/components/dashboard/file-browser/index.tsx b/apps/web/src/components/dashboard/file-browser/index.tsx index fa793aa..2823fea 100644 --- a/apps/web/src/components/dashboard/file-browser/index.tsx +++ b/apps/web/src/components/dashboard/file-browser/index.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Archive, ChevronDown, @@ -25,12 +23,12 @@ import { type SortingState, } from "@tanstack/react-table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useNavigate, useSearch } from "@tanstack/react-router"; import { useDraggable, useDroppable } from "@dnd-kit/react"; -import React, { useMemo, useState, type JSX } from "react"; import { UploadButton } from "@/components/upload-button"; import { pointerIntersection } from "@dnd-kit/collision"; import DragNDropUploader from "./drag-n-drop-uploader"; +import { useMemo, useState, type JSX } from "react"; import { Button } from "@/components/ui/button"; import { formatFileSize } from "@nimbus/shared"; import { PdfIcon } from "@/components/icons"; @@ -79,73 +77,70 @@ const columnHelper = createColumnHelper(); export function FileTable({ files, isLoading, refetch, error }: FileTableProps) { const { tags } = useTags(files[0]?.parentId); - const router = useRouter(); - const searchParams = useSearchParams(); + const navigate = useNavigate({ from: "/dashboard/$providerSlug/$accountId" }); + const searchParams = useSearch({ from: "/_protected/dashboard/$providerSlug/$accountId" }); const [sorting, setSorting] = useState([]); - const [filteredFiles, setFilteredFiles] = useState([]); - const searchType = searchParams.get("type"); - const safeFiles = useMemo(() => { if (isLoading || !files || !Array.isArray(files)) { return []; } + return files; + }, [files, isLoading]); - let result = [...files]; + const filteredFiles = useMemo(() => { + if (!safeFiles.length) return []; - // Apply type filter if specified - if (searchType) { - result = result.filter(file => { - const mimeType = file.mimeType?.toLowerCase() ?? ""; - const fileName = file.name?.toLowerCase() ?? ""; + const searchType = searchParams.type; + if (!searchType) return safeFiles; - switch (searchType) { - case "folder": - return mimeType === "application/vnd.google-apps.folder" || mimeType === "folder"; - case "document": - return ( - // Google Docs - mimeType.includes("application/vnd.google-apps.document") || - mimeType.includes("application/vnd.google-apps.spreadsheet") || - mimeType.includes("application/vnd.google-apps.presentation") || - // Microsoft Office - mimeType.includes("officedocument") || - mimeType.includes("msword") || - // PDFs - mimeType.includes("pdf") || - // Text files - mimeType.includes("text/") || - // Common document extensions - /\.(doc|docx|xls|xlsx|ppt|pptx|pdf|txt|rtf|odt|ods|odp)$/i.test(fileName) - ); - case "media": - return ( - // Images - mimeType.includes("image/") || - // Videos - mimeType.includes("video/") || - // Audio - mimeType.includes("audio/") || - // Common media extensions - /\.(jpg|jpeg|png|gif|bmp|webp|mp4|webm|mov|mp3|wav|ogg)$/i.test(fileName) - ); - default: - return true; - } - }); - } + return safeFiles.filter(file => { + const mimeType = file.mimeType?.toLowerCase() ?? ""; + const fileName = file.name?.toLowerCase() ?? ""; - setFilteredFiles(result); - return result; - }, [files, isLoading, searchType]); + switch (searchType) { + case "folder": + return mimeType === "application/vnd.google-apps.folder" || mimeType === "folder"; + case "document": + return ( + // Google Docs + mimeType.includes("application/vnd.google-apps.document") || + mimeType.includes("application/vnd.google-apps.spreadsheet") || + mimeType.includes("application/vnd.google-apps.presentation") || + // Microsoft Office + mimeType.includes("officedocument") || + mimeType.includes("msword") || + // PDFs + mimeType.includes("pdf") || + // Text files + mimeType.includes("text/") || + // Common document extensions + /\.(doc|docx|xls|xlsx|ppt|pptx|pdf|txt|rtf|odt|ods|odp)$/i.test(fileName) + ); + case "media": + return ( + // Images + mimeType.includes("image/") || + // Videos + mimeType.includes("video/") || + // Audio + mimeType.includes("audio/") || + // Common media extensions + /\.(jpg|jpeg|png|gif|bmp|webp|mp4|webm|mov|mp3|wav|ogg)$/i.test(fileName) + ); + default: + return true; + } + }); + }, [safeFiles, searchParams.type]); const handleRowDoubleClick = (file: File): void => { const fileType = file.mimeType.includes("folder") || file.mimeType === "folder" ? "folder" : "file"; if (fileType === "folder") { - const params = new URLSearchParams(searchParams); - params.set("folderId", file.id); - router.push(`?${params.toString()}`); + navigate({ + search: { ...searchParams, folderId: file.id }, + }); } }; @@ -277,7 +272,7 @@ export function FileTable({ files, isLoading, refetch, error }: FileTableProps) ); const table = useReactTable({ - data: searchType ? filteredFiles : safeFiles, + data: searchParams.type ? filteredFiles : safeFiles, columns, state: { sorting, diff --git a/apps/web/src/components/dashboard/header/file-path.tsx b/apps/web/src/components/dashboard/header/file-path.tsx index 72bd88c..21ef7f9 100644 --- a/apps/web/src/components/dashboard/header/file-path.tsx +++ b/apps/web/src/components/dashboard/header/file-path.tsx @@ -1,9 +1,7 @@ -"use client"; - import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList } from "@/components/ui/breadcrumb"; import { type BreadcrumbItem as BreadcrumbItemType } from "@/hooks/useBreadcrumb"; import { AnimatePresence, motion, type Variants } from "framer-motion"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useNavigate, useSearch } from "@tanstack/react-router"; import { useBreadcrumbPath } from "@/hooks/useBreadcrumb"; import { pointerIntersection } from "@dnd-kit/collision"; import { SourceSelector } from "./source-selector"; @@ -34,25 +32,26 @@ const variants: Variants = { }; export function FileBreadcrumb() { - const searchParams = useSearchParams(); - const router = useRouter(); - const currentFileId = searchParams.get("folderId") || ""; + const searchParams = useSearch({ from: "/_protected/dashboard/$providerSlug/$accountId" }); + const navigate = useNavigate({ from: "/dashboard/$providerSlug/$accountId" }); + const currentFileId = searchParams.folderId || ""; const { data } = useBreadcrumbPath(currentFileId); // Handle clicking a folder navigation - async function handleFolderClick(id: string) { - const params = new URLSearchParams(searchParams); - params.set("folderId", id); - router.push(`?${params.toString()}`); + function handleFolderClick(id: string) { + navigate({ + search: { ...searchParams, folderId: id }, + }); } // Handle clicking the home icon to remove folderId - async function handleHomeClick() { - const params = new URLSearchParams(searchParams); - const folderId = params.get("folderId"); - if (!folderId) return; - params.delete("folderId"); - router.push(`?${params.toString()}`); + function handleHomeClick() { + if (!searchParams.folderId) return; + + const { folderId, ...restSearchParams } = searchParams; + navigate({ + search: restSearchParams, + }); } const { ref: droppableRef, isDropTarget } = useDroppable({ diff --git a/apps/web/src/components/dashboard/sidebar/index.tsx b/apps/web/src/components/dashboard/sidebar/index.tsx index 3369ef1..16b59f7 100644 --- a/apps/web/src/components/dashboard/sidebar/index.tsx +++ b/apps/web/src/components/dashboard/sidebar/index.tsx @@ -1,5 +1,3 @@ -"use client"; - import { type ComponentProps } from "react"; import { Sidebar, SidebarContent, SidebarHeader } from "@/components/ui/sidebar"; diff --git a/apps/web/src/components/dashboard/sidebar/sidebar-folders.tsx b/apps/web/src/components/dashboard/sidebar/sidebar-folders.tsx index b2c42c4..501332d 100644 --- a/apps/web/src/components/dashboard/sidebar/sidebar-folders.tsx +++ b/apps/web/src/components/dashboard/sidebar/sidebar-folders.tsx @@ -1,5 +1,3 @@ -"use client"; - import { SidebarGroup, SidebarGroupContent, @@ -8,10 +6,10 @@ import { SidebarMenuButton, SidebarMenuItem, } from "@/components/ui/sidebar"; +import { useNavigate, useSearch, useParams } from "@tanstack/react-router"; import { ChevronDown, FileText, Folder, PinOff } from "lucide-react"; import { providerToSlug, type DriveProvider } from "@nimbus/shared"; import { usePinnedFiles, useUnpinFile } from "@/hooks/useDriveOps"; -import { useRouter, useSearchParams } from "next/navigation"; import { Skeleton } from "@/components/ui/skeleton"; import type { PinnedFile } from "@nimbus/shared"; import { Button } from "@/components/ui/button"; @@ -29,8 +27,8 @@ function getFileIcon(type: string) { } export default function SidebarPinnedFiles() { - const searchParams = useSearchParams(); - const router = useRouter(); + const searchParams = useSearch({ from: "/_protected/dashboard/$providerSlug/$accountId" }); + const navigate = useNavigate({ from: "/dashboard/$providerSlug/$accountId" }); const [isOpen, setIsOpen] = useState(true); const { data: pinnedFiles, isLoading, error } = usePinnedFiles(); const unpinFile = useUnpinFile(); @@ -40,22 +38,21 @@ export default function SidebarPinnedFiles() { }; const handleNavigate = (file: PinnedFile) => { - const params = new URLSearchParams(searchParams.toString()); if (file.type === "folder") { - params.set("folderId", file.fileId); - } else { - return; + navigate({ + to: "/dashboard/$providerSlug/$accountId", + params: { + providerSlug: providerToSlug(file.provider as DriveProvider), + accountId: file.accountId, + }, + search: { ...searchParams, folderId: file.fileId }, + }); } - const isValidProvider = (provider: string): provider is DriveProvider => { - return provider === "microsoft" || provider === "google"; - }; + // If not a folder, we don't navigate + }; - if (!isValidProvider(file.provider)) { - console.error(`Invalid provider: ${file.provider}`); - return; - } - - router.push(`/dashboard/${providerToSlug(file.provider)}/${file.accountId}?${params.toString()}`); + const isValidProvider = (provider: string): provider is DriveProvider => { + return provider === "microsoft" || provider === "google"; }; return ( diff --git a/apps/web/src/components/dashboard/sidebar/sidebar-footer.tsx b/apps/web/src/components/dashboard/sidebar/sidebar-footer.tsx index 711489e..dd3d8a0 100644 --- a/apps/web/src/components/dashboard/sidebar/sidebar-footer.tsx +++ b/apps/web/src/components/dashboard/sidebar/sidebar-footer.tsx @@ -1,15 +1,13 @@ -"use client"; - import { SidebarFooter, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar"; import { Progress } from "@/components/ui/progress"; import { useDriveInfo } from "@/hooks/useDriveOps"; import { Moon, Settings, Sun } from "lucide-react"; // import { Button } from "@/components/ui/button"; import { formatFileSize } from "@nimbus/shared"; +import { Link } from "@tanstack/react-router"; import { useTheme } from "@/hooks/useTheme"; import { useEffect, useState } from "react"; import { toast } from "sonner"; -import Link from "next/link"; export default function StorageFooter() { const { data, error, isError, isPending } = useDriveInfo(); @@ -81,7 +79,7 @@ export default function StorageFooter() { asChild className="transition-all duration-200 ease-linear hover:bg-neutral-200 dark:hover:bg-neutral-700" > - + Settings diff --git a/apps/web/src/components/dashboard/sidebar/tag-menu.tsx b/apps/web/src/components/dashboard/sidebar/tag-menu.tsx index 8dc1571..437d3f4 100644 --- a/apps/web/src/components/dashboard/sidebar/tag-menu.tsx +++ b/apps/web/src/components/dashboard/sidebar/tag-menu.tsx @@ -1,4 +1,3 @@ -"use client"; import { SidebarGroup, SidebarGroupContent, diff --git a/apps/web/src/components/dashboard/sidebar/user-account.tsx b/apps/web/src/components/dashboard/sidebar/user-account.tsx index 2053a91..08c25e2 100644 --- a/apps/web/src/components/dashboard/sidebar/user-account.tsx +++ b/apps/web/src/components/dashboard/sidebar/user-account.tsx @@ -1,5 +1,3 @@ -"use client"; - import { DropdownMenu, DropdownMenuContent, diff --git a/apps/web/src/components/dialogs/create-folder-dialog.tsx b/apps/web/src/components/dialogs/create-folder-dialog.tsx index 761f750..40a6d45 100644 --- a/apps/web/src/components/dialogs/create-folder-dialog.tsx +++ b/apps/web/src/components/dialogs/create-folder-dialog.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Dialog, DialogContent, diff --git a/apps/web/src/components/dialogs/delete-file-dialog.tsx b/apps/web/src/components/dialogs/delete-file-dialog.tsx index 54614bc..51835bb 100644 --- a/apps/web/src/components/dialogs/delete-file-dialog.tsx +++ b/apps/web/src/components/dialogs/delete-file-dialog.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Dialog, DialogContent, diff --git a/apps/web/src/components/dialogs/rename-file-dialog.tsx b/apps/web/src/components/dialogs/rename-file-dialog.tsx index d67429f..d198cca 100644 --- a/apps/web/src/components/dialogs/rename-file-dialog.tsx +++ b/apps/web/src/components/dialogs/rename-file-dialog.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Dialog, DialogContent, diff --git a/apps/web/src/components/home/NotificationBanner.tsx b/apps/web/src/components/home/NotificationBanner.tsx index 57d76d8..da6e0cf 100644 --- a/apps/web/src/components/home/NotificationBanner.tsx +++ b/apps/web/src/components/home/NotificationBanner.tsx @@ -1,4 +1,4 @@ -import Link from "next/link"; +import { Link } from "@tanstack/react-router"; const WarningBanner = () => { return ( @@ -7,7 +7,7 @@ const WarningBanner = () => {
This project is not currently maintained. If you like what the project is and would like to see it continue, reach out on our{" "} - + Discord
diff --git a/apps/web/src/components/home/footer.tsx b/apps/web/src/components/home/footer.tsx index 49cc91f..e26a533 100644 --- a/apps/web/src/components/home/footer.tsx +++ b/apps/web/src/components/home/footer.tsx @@ -1,5 +1,5 @@ +import { Link } from "@tanstack/react-router"; import { LogoIcon } from "@/components/icons"; -import Link from "next/link"; // this is a copy of Analogs footer component with some changes // https://github.com/analogdotnow/Analog/blob/main/apps/web/src/components/footer.tsx @@ -10,10 +10,10 @@ export default function Footer() {
diff --git a/apps/web/src/components/home/header.tsx b/apps/web/src/components/home/header.tsx index 66cecdf..2bc8e76 100644 --- a/apps/web/src/components/home/header.tsx +++ b/apps/web/src/components/home/header.tsx @@ -1,17 +1,15 @@ -"use client"; - import { DiscordIcon, GitHubIcon, LogoIcon, XPlatformIcon } from "@/components/icons"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { ModeToggle } from "@/components/mode-toggle"; import { Button } from "@/components/ui/button"; +import { Link } from "@tanstack/react-router"; import { Users } from "lucide-react"; -import Link from "next/link"; export default function Header() { return (

- + @@ -22,7 +20,7 @@ export default function Header() { @@ -32,9 +30,9 @@ export default function Header() { Discord @@ -42,9 +40,9 @@ export default function Header() { Github @@ -52,9 +50,9 @@ export default function Header() { X (Twitter) diff --git a/apps/web/src/components/home/hero.tsx b/apps/web/src/components/home/hero.tsx index f7ecba3..adf63d2 100644 --- a/apps/web/src/components/home/hero.tsx +++ b/apps/web/src/components/home/hero.tsx @@ -33,22 +33,22 @@ export default function Hero() {
- {isMobile && ( + {/*{isMobile && ( - )} + )}*/}
-
+ {/*
-
+
*/} -
+ {/*
-
+
*/}

diff --git a/apps/web/src/components/home/waitlist.tsx b/apps/web/src/components/home/waitlist.tsx index 52eeae6..45e2efa 100644 --- a/apps/web/src/components/home/waitlist.tsx +++ b/apps/web/src/components/home/waitlist.tsx @@ -1,5 +1,3 @@ -"use client"; - import { emailObjectSchema, type ApiResponse, type WaitlistCount } from "@nimbus/shared"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { zodResolver } from "@hookform/resolvers/zod"; diff --git a/apps/web/src/components/loading-state-page.tsx b/apps/web/src/components/loading-state-page.tsx index e4ef5c8..147d225 100644 --- a/apps/web/src/components/loading-state-page.tsx +++ b/apps/web/src/components/loading-state-page.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Loader2 } from "lucide-react"; import { useEffect } from "react"; import { cn } from "@/lib/utils"; diff --git a/apps/web/src/components/mode-toggle.tsx b/apps/web/src/components/mode-toggle.tsx index 9897e11..b41acd3 100644 --- a/apps/web/src/components/mode-toggle.tsx +++ b/apps/web/src/components/mode-toggle.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Button } from "@/components/ui/button"; import { Moon, Sun } from "lucide-react"; diff --git a/apps/web/src/components/providers/account-provider.tsx b/apps/web/src/components/providers/account-provider.tsx index f557782..a3917f9 100644 --- a/apps/web/src/components/providers/account-provider.tsx +++ b/apps/web/src/components/providers/account-provider.tsx @@ -1,13 +1,9 @@ -import { - type DriveProvider, - type DriveProviderSlug, - type DriveProviderSlugParam, - providerToSlug, - slugToProvider, -} from "@nimbus/shared"; +import { type DriveProvider, type DriveProviderSlug, providerToSlug, slugToProvider } from "@nimbus/shared"; import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; import { createProtectedClient, type DriveProviderClient } from "@/utils/client"; -import { useParams, usePathname, useRouter } from "next/navigation"; +import { useNavigate } from "@tanstack/react-router"; +import { useLocation } from "@tanstack/react-router"; +import { useParams } from "@tanstack/react-router"; interface AccountProviderContextType { providerId: string | null; @@ -33,9 +29,11 @@ const createClient = (providerId: string | null, accountId: string | null): Prom const AccountProviderContext = createContext(null); export function AccountProvider({ children }: { children: React.ReactNode }) { - const router = useRouter(); - const pathname = usePathname(); - const { providerSlug: providerSlugParam, accountId: accountIdParam } = useParams(); + const navigate = useNavigate(); + const location = useLocation(); + const { providerSlug: providerSlugParam, accountId: accountIdParam } = useParams({ + from: "/_protected/dashboard/$providerSlug/$accountId", + }); const [providerSlug, setProviderSlug] = useState(providerSlugParam); const [providerId, setProviderId] = useState( @@ -73,11 +71,14 @@ export function AccountProvider({ children }: { children: React.ReactNode }) { const navigateToProvider = useCallback( (newProviderSlug: string, newAccountId: string) => { const newPathname = `/dashboard/${newProviderSlug}/${newAccountId}`; - if (pathname !== newPathname) { - router.push(newPathname); + if (location.pathname !== newPathname) { + navigate({ + to: "/dashboard/$providerSlug/$accountId", + params: { providerSlug: newProviderSlug, accountId: newAccountId }, + }); } }, - [pathname, router] + [location.pathname, navigate] ); const setDriveProviderById = useCallback( diff --git a/apps/web/src/components/providers/app-providers.tsx b/apps/web/src/components/providers/app-providers.tsx index 0d132a7..5915bea 100644 --- a/apps/web/src/components/providers/app-providers.tsx +++ b/apps/web/src/components/providers/app-providers.tsx @@ -1,5 +1,3 @@ -"use client"; - import { SigninAccountDialog } from "@/components/auth/signin-account-dialog"; import { AuthProvider, useAuth } from "@/components/providers/auth-provider"; import { setAuthContext } from "@/utils/client"; diff --git a/apps/web/src/components/providers/auth-provider.tsx b/apps/web/src/components/providers/auth-provider.tsx index c508710..a1b658a 100644 --- a/apps/web/src/components/providers/auth-provider.tsx +++ b/apps/web/src/components/providers/auth-provider.tsx @@ -1,5 +1,3 @@ -"use client"; - import { createContext, useContext, useState, useCallback } from "react"; type AuthContextType = { diff --git a/apps/web/src/components/providers/default-account-provider.tsx b/apps/web/src/components/providers/default-account-provider.tsx index baca086..cc4ca3d 100644 --- a/apps/web/src/components/providers/default-account-provider.tsx +++ b/apps/web/src/components/providers/default-account-provider.tsx @@ -1,5 +1,3 @@ -"use client"; - import { createContext, useContext, useEffect, useState, type ReactNode } from "react"; import type { DriveProvider, DriveProviderSlug } from "@nimbus/shared"; import { useNavigate, useLocation } from "@tanstack/react-router"; diff --git a/apps/web/src/components/providers/download-provider.tsx b/apps/web/src/components/providers/download-provider.tsx index d6902a8..b1572aa 100644 --- a/apps/web/src/components/providers/download-provider.tsx +++ b/apps/web/src/components/providers/download-provider.tsx @@ -1,5 +1,3 @@ -"use client"; - import { createContext, useContext, useState, useCallback, type ReactNode } from "react"; import { DownloadProgress } from "@/components/ui/download-progress"; diff --git a/apps/web/src/components/providers/posthog-provider.tsx b/apps/web/src/components/providers/posthog-provider.tsx index d133c93..2440ab7 100644 --- a/apps/web/src/components/providers/posthog-provider.tsx +++ b/apps/web/src/components/providers/posthog-provider.tsx @@ -1,4 +1,3 @@ -"use client"; import { authClient } from "@nimbus/auth/auth-client"; import { PostHogProvider } from "posthog-js/react"; import { type ReactNode, useEffect } from "react"; diff --git a/apps/web/src/components/providers/protected-route.tsx b/apps/web/src/components/providers/protected-route.tsx index d1570f1..78a40c8 100644 --- a/apps/web/src/components/providers/protected-route.tsx +++ b/apps/web/src/components/providers/protected-route.tsx @@ -1,5 +1,3 @@ -"use client"; - import { RouteGuard } from "./route-guard"; interface ProtectedRouteProps { diff --git a/apps/web/src/components/providers/public-route.tsx b/apps/web/src/components/providers/public-route.tsx index d5e095a..4d19cf8 100644 --- a/apps/web/src/components/providers/public-route.tsx +++ b/apps/web/src/components/providers/public-route.tsx @@ -1,5 +1,3 @@ -"use client"; - import { RouteGuard } from "./route-guard"; interface PublicRouteProps { diff --git a/apps/web/src/components/providers/query-provider.tsx b/apps/web/src/components/providers/query-provider.tsx index 6698edf..e975a42 100644 --- a/apps/web/src/components/providers/query-provider.tsx +++ b/apps/web/src/components/providers/query-provider.tsx @@ -1,5 +1,3 @@ -"use client"; - import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { useState, type ReactNode } from "react"; diff --git a/apps/web/src/components/providers/route-guard.tsx b/apps/web/src/components/providers/route-guard.tsx index cd90563..4643b99 100644 --- a/apps/web/src/components/providers/route-guard.tsx +++ b/apps/web/src/components/providers/route-guard.tsx @@ -1,7 +1,5 @@ -"use client"; - +import { useNavigate, useLocation } from "@tanstack/react-router"; import { authClient } from "@nimbus/auth/auth-client"; -import { useRouter } from "next/navigation"; import { useEffect } from "react"; interface RouteGuardProps { @@ -11,7 +9,8 @@ interface RouteGuardProps { } export function RouteGuard({ children, requireAuth = false, redirectTo = "/signin" }: RouteGuardProps) { - const router = useRouter(); + const navigate = useNavigate(); + const location = useLocation(); const { data: session, isPending } = authClient.useSession(); useEffect(() => { @@ -21,15 +20,16 @@ export function RouteGuard({ children, requireAuth = false, redirectTo = "/signi if (requireAuth && !isAuthenticated) { // Redirect to signin if auth is required but user isn't authenticated - const currentPath = window.location.pathname; - const redirectUrl = `${redirectTo}?redirect=${encodeURIComponent(currentPath)}`; - router.push(redirectUrl); - } else if (!requireAuth && isAuthenticated && window.location.pathname === "/signin") { + navigate({ + to: redirectTo, + // search: { redirect: location.pathname }, + }); + } else if (!requireAuth && isAuthenticated && location.pathname === "/signin") { // Redirect to dashboard if already signed in and on signin page - router.push("/dashboard"); + navigate({ to: "/dashboard" }); } } - }, [session, isPending, requireAuth, redirectTo, router]); + }, [session, isPending, requireAuth, redirectTo, location.pathname, navigate]); // Show loading state while checking auth if (isPending) { diff --git a/apps/web/src/components/providers/theme-provider.tsx b/apps/web/src/components/providers/theme-provider.tsx index 050b16d..b537ec0 100644 --- a/apps/web/src/components/providers/theme-provider.tsx +++ b/apps/web/src/components/providers/theme-provider.tsx @@ -1,5 +1,3 @@ -"use client"; - import { ThemeProvider as NextThemesProvider } from "next-themes"; import type { ComponentProps } from "react"; diff --git a/apps/web/src/components/providers/user-info-provider.tsx b/apps/web/src/components/providers/user-info-provider.tsx index 72c6a76..631a7c3 100644 --- a/apps/web/src/components/providers/user-info-provider.tsx +++ b/apps/web/src/components/providers/user-info-provider.tsx @@ -1,5 +1,3 @@ -"use client"; - import type { LimitedAccessAccount } from "@nimbus/shared"; import { createContext, useContext, useMemo } from "react"; import type { SessionUser } from "@nimbus/auth/auth"; diff --git a/apps/web/src/components/search/search-dialog.tsx b/apps/web/src/components/search/search-dialog.tsx index 50628b7..3945b79 100644 --- a/apps/web/src/components/search/search-dialog.tsx +++ b/apps/web/src/components/search/search-dialog.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { FileText, Filter, Folder, Search, Tag } from "lucide-react"; import { Card, CardContent } from "@/components/ui/card"; diff --git a/apps/web/src/components/settings/header.tsx b/apps/web/src/components/settings/header.tsx index 0adedc7..96153bc 100644 --- a/apps/web/src/components/settings/header.tsx +++ b/apps/web/src/components/settings/header.tsx @@ -1,13 +1,18 @@ +import { useNavigate } from "@tanstack/react-router"; import { Button } from "@/components/ui/button"; -import { useRouter } from "next/navigation"; import { ArrowLeft } from "lucide-react"; export function SettingsHeader() { - const router = useRouter(); + const navigate = useNavigate(); + + const handleBack = () => { + navigate({ to: ".." }); + }; + return (
- diff --git a/apps/web/src/components/settings/s3-account-form.tsx b/apps/web/src/components/settings/s3-account-form.tsx index dd74451..905c462 100644 --- a/apps/web/src/components/settings/s3-account-form.tsx +++ b/apps/web/src/components/settings/s3-account-form.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { AWS_REGIONS, createS3AccountSchema, type CreateS3AccountSchema } from "@nimbus/shared"; import { FieldError } from "@/components/ui/field-error"; diff --git a/apps/web/src/components/ui/animated-group.tsx b/apps/web/src/components/ui/animated-group.tsx index 503a5f1..2b197c7 100644 --- a/apps/web/src/components/ui/animated-group.tsx +++ b/apps/web/src/components/ui/animated-group.tsx @@ -1,4 +1,3 @@ -"use client"; import type { ReactNode, JSX, ElementType } from "react"; import { motion, type Variants } from "motion/react"; import { Children, useMemo } from "react"; diff --git a/apps/web/src/components/ui/avatar.tsx b/apps/web/src/components/ui/avatar.tsx index 8f3e752..d1a1d32 100644 --- a/apps/web/src/components/ui/avatar.tsx +++ b/apps/web/src/components/ui/avatar.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Fallback, Image, Root } from "@radix-ui/react-avatar"; import type { ComponentProps } from "react"; diff --git a/apps/web/src/components/ui/checkbox.tsx b/apps/web/src/components/ui/checkbox.tsx index 05c099e..d4fde71 100644 --- a/apps/web/src/components/ui/checkbox.tsx +++ b/apps/web/src/components/ui/checkbox.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Root, Indicator } from "@radix-ui/react-checkbox"; import type { ComponentProps } from "react"; import { CheckIcon } from "lucide-react"; diff --git a/apps/web/src/components/ui/collapsible.tsx b/apps/web/src/components/ui/collapsible.tsx index 454890a..06a1e1a 100644 --- a/apps/web/src/components/ui/collapsible.tsx +++ b/apps/web/src/components/ui/collapsible.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Root, CollapsibleTrigger as CollapsibleTriggerPrimitive, diff --git a/apps/web/src/components/ui/dialog.tsx b/apps/web/src/components/ui/dialog.tsx index f56ca40..4bba125 100644 --- a/apps/web/src/components/ui/dialog.tsx +++ b/apps/web/src/components/ui/dialog.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Close, Content, Description, Overlay, Portal, Root, Title, Trigger } from "@radix-ui/react-dialog"; import { ArrowLeftIcon, XIcon } from "lucide-react"; diff --git a/apps/web/src/components/ui/download-progress.tsx b/apps/web/src/components/ui/download-progress.tsx index 8da21db..d0ed879 100644 --- a/apps/web/src/components/ui/download-progress.tsx +++ b/apps/web/src/components/ui/download-progress.tsx @@ -1,5 +1,3 @@ -"use client"; - import { CheckCircle, Loader2, XCircle } from "lucide-react"; import { Progress } from "@/components/ui/progress"; import { useEffect, useState } from "react"; diff --git a/apps/web/src/components/ui/drawer.tsx b/apps/web/src/components/ui/drawer.tsx index f73b345..3d36fff 100644 --- a/apps/web/src/components/ui/drawer.tsx +++ b/apps/web/src/components/ui/drawer.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Drawer as DrawerPrimitive } from "vaul"; import * as React from "react"; diff --git a/apps/web/src/components/ui/dropdown-menu.tsx b/apps/web/src/components/ui/dropdown-menu.tsx index a6fde47..5cd0ddb 100644 --- a/apps/web/src/components/ui/dropdown-menu.tsx +++ b/apps/web/src/components/ui/dropdown-menu.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Root, Portal, diff --git a/apps/web/src/components/ui/label.tsx b/apps/web/src/components/ui/label.tsx index 97ecdbe..f128d76 100644 --- a/apps/web/src/components/ui/label.tsx +++ b/apps/web/src/components/ui/label.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Root } from "@radix-ui/react-label"; import type { ComponentProps } from "react"; diff --git a/apps/web/src/components/ui/progress.tsx b/apps/web/src/components/ui/progress.tsx index 57a360e..4a49b54 100644 --- a/apps/web/src/components/ui/progress.tsx +++ b/apps/web/src/components/ui/progress.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Indicator, Root } from "@radix-ui/react-progress"; import type { ComponentProps } from "react"; diff --git a/apps/web/src/components/ui/scroll-area.tsx b/apps/web/src/components/ui/scroll-area.tsx index fe0b6b6..620ce34 100644 --- a/apps/web/src/components/ui/scroll-area.tsx +++ b/apps/web/src/components/ui/scroll-area.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Corner, Root, ScrollAreaScrollbar, ScrollAreaThumb, Viewport } from "@radix-ui/react-scroll-area"; import type { ComponentProps } from "react"; import { cn } from "@/lib/utils"; diff --git a/apps/web/src/components/ui/select.tsx b/apps/web/src/components/ui/select.tsx index 5983b2c..d9ec300 100644 --- a/apps/web/src/components/ui/select.tsx +++ b/apps/web/src/components/ui/select.tsx @@ -1,5 +1,3 @@ -"use client"; - import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"; import * as SelectPrimitive from "@radix-ui/react-select"; import * as React from "react"; diff --git a/apps/web/src/components/ui/separator.tsx b/apps/web/src/components/ui/separator.tsx index 095ac80..2b043a3 100644 --- a/apps/web/src/components/ui/separator.tsx +++ b/apps/web/src/components/ui/separator.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Root } from "@radix-ui/react-separator"; import type { ComponentProps } from "react"; diff --git a/apps/web/src/components/ui/sheet.tsx b/apps/web/src/components/ui/sheet.tsx index fbd3361..065a5b3 100644 --- a/apps/web/src/components/ui/sheet.tsx +++ b/apps/web/src/components/ui/sheet.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Description, Root, Trigger, Close, Portal, Overlay, Title, Content } from "@radix-ui/react-dialog"; import type { ComponentProps } from "react"; import { XIcon } from "lucide-react"; diff --git a/apps/web/src/components/ui/sidebar.tsx b/apps/web/src/components/ui/sidebar.tsx index 856b25a..97f5e99 100644 --- a/apps/web/src/components/ui/sidebar.tsx +++ b/apps/web/src/components/ui/sidebar.tsx @@ -1,5 +1,3 @@ -"use client"; - import type { VariantProps } from "class-variance-authority"; import { cva } from "class-variance-authority"; import { PanelLeftIcon } from "lucide-react"; diff --git a/apps/web/src/components/ui/table.tsx b/apps/web/src/components/ui/table.tsx index b6a65ff..7d20e48 100644 --- a/apps/web/src/components/ui/table.tsx +++ b/apps/web/src/components/ui/table.tsx @@ -1,5 +1,3 @@ -"use client"; - import * as React from "react"; import { cn } from "@/lib/utils"; diff --git a/apps/web/src/components/ui/tabs.tsx b/apps/web/src/components/ui/tabs.tsx index 9d2482c..1627bea 100644 --- a/apps/web/src/components/ui/tabs.tsx +++ b/apps/web/src/components/ui/tabs.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Root, List, Trigger, Content } from "@radix-ui/react-tabs"; import type { ComponentProps } from "react"; diff --git a/apps/web/src/components/ui/text-loop.tsx b/apps/web/src/components/ui/text-loop.tsx index d9b88be..aa9fc52 100644 --- a/apps/web/src/components/ui/text-loop.tsx +++ b/apps/web/src/components/ui/text-loop.tsx @@ -1,4 +1,3 @@ -"use client"; import { motion, AnimatePresence, type Transition, type Variants, type AnimatePresenceProps } from "motion/react"; import { useState, useEffect, Children, type ReactNode } from "react"; import { cn } from "@/lib/utils"; diff --git a/apps/web/src/components/ui/tooltip.tsx b/apps/web/src/components/ui/tooltip.tsx index a51aa20..4d1e703 100644 --- a/apps/web/src/components/ui/tooltip.tsx +++ b/apps/web/src/components/ui/tooltip.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Provider, Root, Trigger, Content, Portal, Arrow } from "@radix-ui/react-tooltip"; import type { ComponentProps } from "react"; diff --git a/apps/web/src/components/upload-button.tsx b/apps/web/src/components/upload-button.tsx index 0b764a8..a63dd3d 100644 --- a/apps/web/src/components/upload-button.tsx +++ b/apps/web/src/components/upload-button.tsx @@ -1,4 +1,3 @@ -"use client"; import { DropdownMenu, DropdownMenuContent, @@ -9,7 +8,7 @@ import { UploadFolderDialog } from "@/components/dialogs/upload-folder-dialog"; import { CreateFolderDialog } from "@/components/dialogs/create-folder-dialog"; import { UploadFileDialog } from "@/components/dialogs/upload-files-dialog"; import { FolderPlus, Plus, Upload } from "lucide-react"; -import { useSearchParams } from "next/navigation"; +import { useSearch } from "@tanstack/react-router"; import { Button } from "@/components/ui/button"; import { useState } from "react"; @@ -17,8 +16,8 @@ export function UploadButton({ name }: { name: string }) { const [uploadFileOpen, setUploadFileOpen] = useState(false); const [uploadFolderOpen, setUploadFolderOpen] = useState(false); const [createFolderOpen, setCreateFolderOpen] = useState(false); - const searchParams = useSearchParams(); - const folderId = searchParams.get("folderId") ?? "root"; + const searchParams = useSearch({ from: "/_protected/dashboard/$providerSlug/$accountId" }); + const folderId = searchParams.folderId ?? "root"; return ( <> diff --git a/apps/web/src/components/user-profile.tsx b/apps/web/src/components/user-profile.tsx index e99f043..af0d6b7 100644 --- a/apps/web/src/components/user-profile.tsx +++ b/apps/web/src/components/user-profile.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { cva, type VariantProps } from "class-variance-authority"; import { useIsMounted } from "@/hooks/useIsMounted"; diff --git a/apps/web/src/hooks/useAuth.ts b/apps/web/src/hooks/useAuth.ts index f3c7a88..59e6813 100644 --- a/apps/web/src/hooks/useAuth.ts +++ b/apps/web/src/hooks/useAuth.ts @@ -7,37 +7,14 @@ import { type SignInFormData, type SignUpFormData, } from "@nimbus/shared"; -import { useSearchParamsSafely } from "@/hooks/useSearchParamsSafely"; import { authClient } from "@nimbus/auth/auth-client"; +import { useNavigate } from "@tanstack/react-router"; import { useMutation } from "@tanstack/react-query"; import { publicClient } from "@/utils/client"; -import { useCallback, useState } from "react"; -import { useRouter } from "next/navigation"; import env from "@nimbus/env/client"; import { toast } from "sonner"; -interface AuthState { - isLoading: boolean; - error: string | null; -} - -const signInWithProvider = async (provider: DriveProvider) => { - return authClient.signIn.social({ - provider, - callbackURL: `${import.meta.env.VITE_FRONTEND_URL}/dashboard`, - }); -}; - -const linkSessionWithProvider = async ( - provider: DriveProvider, - callbackURL: string = `${import.meta.env.VITE_FRONTEND_URL}/dashboard` -) => { - return authClient.linkSocial({ - provider, - callbackURL, - }); -}; - +// Simple error handler const handleAuthError = (error: unknown, defaultMessage: string): string => { if (error instanceof Error) { return error.message || defaultMessage; @@ -45,334 +22,186 @@ const handleAuthError = (error: unknown, defaultMessage: string): string => { return defaultMessage; }; -const getProviderDisplayName = (provider: DriveProvider): string => { - return provider.charAt(0).toUpperCase() + provider.slice(1); -}; +// Social auth hook +export const useSocialAuth = () => { + const navigate = useNavigate(); -const useSocialAuth = (provider: DriveProvider) => { - const [isLoading, setIsLoading] = useState(false); - const providerName = getProviderDisplayName(provider); + return useMutation({ + mutationFn: async (options: { provider: DriveProvider; callbackURL?: string }) => { + const isLoggedIn = await authClient.getSession(); + const action = isLoggedIn.data?.session ? "link" : "signin"; + const providerName = options.provider.charAt(0).toUpperCase() + options.provider.slice(1); - const handleAuth = useCallback( - async (options?: { callbackURL?: string }) => { - setIsLoading(true); + const authPromise = + action === "link" + ? authClient.linkSocial({ + provider: options.provider, + callbackURL: options.callbackURL || `${env.VITE_FRONTEND_URL}/dashboard`, + }) + : authClient.signIn.social({ + provider: options.provider, + callbackURL: `${env.VITE_FRONTEND_URL}/dashboard`, + }); - try { - const isLoggedIn = await authClient.getSession(); - // If the user is already logged in, link the provider - const action = isLoggedIn.data?.session ? "link" : "signin"; - - const authPromise = - action === "link" ? linkSessionWithProvider(provider, options?.callbackURL) : signInWithProvider(provider); - - toast.promise(authPromise, { - loading: action === "link" ? `Linking ${providerName} account...` : `Signing in with ${providerName}...`, - success: action === "link" ? `Successfully linked ${providerName} account` : `Signed in with ${providerName}`, - error: (error: unknown) => handleAuthError(error, `${providerName} authentication failed`), - }); - - return true; - } catch (error) { - const errorMessage = handleAuthError(error, `${providerName} authentication failed`); - toast.error(errorMessage); - return false; - } finally { - setIsLoading(false); - } + return toast.promise(authPromise, { + loading: action === "link" ? `Linking ${providerName} account...` : `Signing in with ${providerName}...`, + success: action === "link" ? `Successfully linked ${providerName} account` : `Signed in with ${providerName}`, + error: error => handleAuthError(error, `${options.provider} authentication failed`), + }); }, - [provider, providerName] - ); - - return { handleAuth, isLoading }; -}; - -export const useGoogleAuth = () => { - const { handleAuth, isLoading } = useSocialAuth("google"); - return { - signInWithGoogleProvider: handleAuth, - isLoading, - }; -}; - -export const useMicrosoftAuth = () => { - const { handleAuth, isLoading } = useSocialAuth("microsoft"); - return { - signInWithMicrosoftProvider: handleAuth, - isLoading, - }; -}; - -export const useBoxAuth = () => { - const { handleAuth, isLoading } = useSocialAuth("box"); - return { - signInWithBoxProvider: handleAuth, - isLoading, - }; -}; - -export const useDropboxAuth = () => { - const { handleAuth, isLoading } = useSocialAuth("dropbox"); - return { - signInWithDropboxProvider: handleAuth, - isLoading, - }; -}; - -const useRedirect = () => { - const router = useRouter(); - const { getParam } = useSearchParamsSafely(); - - const getRedirectUrl = useCallback(() => { - return getParam("redirect") || "/dashboard"; - }, [getParam]); - - const redirectToDashboard = useCallback(() => { - const redirectUrl = getRedirectUrl(); - router.push(redirectUrl); - router.refresh(); - }, [router, getRedirectUrl]); - - return { getRedirectUrl, redirectToDashboard }; -}; - -const useAuthMutation = (mutationFn: (data: TData) => Promise) => { - const [state, setState] = useState({ isLoading: false, error: null }); - - const mutate = useCallback( - async (data: TData, options: Parameters>[1]) => { - setState({ isLoading: true, error: null }); - try { - toast.promise(mutationFn(data), options); - } catch (error) { - const errorMessage = handleAuthError(error, "An unexpected error occurred."); - setState(prev => ({ ...prev, error: errorMessage })); - throw error; // Re-throw for form-level error handling - } finally { - setState(prev => ({ ...prev, isLoading: false })); - } + onSuccess: () => { + navigate({ to: "/dashboard" }); }, - [mutationFn] - ); - - return { ...state, mutate }; + }); }; +// Sign in hook export const useSignIn = () => { - const { redirectToDashboard } = useRedirect(); + const navigate = useNavigate(); - const signInMutation = useCallback( - (data: SignInFormData) => - authClient.signIn.email( + return useMutation({ + mutationFn: async (data: SignInFormData) => { + await authClient.signIn.email( { email: data.email, password: data.password, rememberMe: data.remember, }, { - onSuccess: redirectToDashboard, + onSuccess: () => { + const redirectUrl = new URLSearchParams(window.location.search).get("redirect") || "/dashboard"; + navigate({ to: redirectUrl }); + }, onError: ctx => { throw ctx.error; }, } - ), - [redirectToDashboard] - ); - - const { mutate, ...state } = useAuthMutation(signInMutation); - - const signInWithCredentials = (data: SignInFormData) => - mutate(data, { - loading: "Signing you in...", - success: `Welcome back, ${data.email}!`, - error: error => handleAuthError(error, "Unable to sign in. Please try again."), - }); - - return { ...state, signInWithCredentials }; + ); + return data.email; + }, + onSuccess: email => { + toast.success(`Welcome back, ${email}!`); + }, + onError: error => { + toast.error(handleAuthError(error, "Unable to sign in. Please try again.")); + }, + }); }; +// Sign up hook export const useSignUp = () => { - const { redirectToDashboard } = useRedirect(); + const navigate = useNavigate(); - const signUpMutation = useCallback( - async (data: SignUpFormData) => { + return useMutation({ + mutationFn: async (data: SignUpFormData) => { const fullName = `${data.firstName} ${data.lastName}`; await authClient.signUp.email({ name: fullName, email: data.email, password: data.password, - callbackURL: `${import.meta.env.VITE_FRONTEND_URL}/dashboard`, + callbackURL: `${env.VITE_FRONTEND_URL}/dashboard`, }); - redirectToDashboard(); + return fullName; }, - [redirectToDashboard] - ); - - const { mutate, ...state } = useAuthMutation(signUpMutation); - - const signUpWithCredentials = (data: SignUpFormData) => { - const fullName = `${data.firstName} ${data.lastName}`; - return mutate(data, { - loading: "Creating your account...", - success: `Welcome to Nimbus, ${fullName}!`, - error: error => { - if (error instanceof Error) { - if (error.message.toLowerCase().includes("exists")) { - return "An account with this email already exists. Please sign in instead."; - } else if (error.message.toLowerCase().includes("password")) { - return "Password doesn't meet requirements. Please check and try again."; - } - return error.message; + onSuccess: fullName => { + navigate({ to: "/dashboard" }); + }, + onError: error => { + let errorMessage = "Unable to create your account. Please try again."; + if (error instanceof Error) { + if (error.message.toLowerCase().includes("exists")) { + errorMessage = "An account with this email already exists. Please sign in instead."; + } else if (error.message.toLowerCase().includes("password")) { + errorMessage = "Password doesn't meet requirements. Please check and try again."; + } else { + errorMessage = error.message; } - return "Unable to create your account. Please try again."; - }, - }); - }; - - return { ...state, signUpWithCredentials }; -}; - -export const useSignOut = () => { - const router = useRouter(); - const [isLoading, setIsLoading] = useState(false); - - const signOut = useCallback( - async (options?: { redirectTo?: string }) => { - setIsLoading(true); - try { - const response = await authClient.signOut(); - const data = response.data; - const success = data?.success ?? false; - - if (!success) { - throw new Error("Sign out failed"); - } - - toast.success("Signed out successfully"); - - // Redirect to the specified path or default to signin - const redirectPath = options?.redirectTo || "/signin"; - router.push(redirectPath); - router.refresh(); - - return { success: true }; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : "Sign out failed"; - toast.error(errorMessage); - return { success: false, error: errorMessage }; - } finally { - setIsLoading(false); } + toast.error(errorMessage); }, - [router] - ); - - return { - signOut, - isLoading, - }; -}; - -const checkEmailExists = async (email: string): Promise => { - try { - const body = { - email, - }; - const result = emailObjectSchema.safeParse(body); - if (!result.success) { - throw new Error(result.error.message); - } - const response = await publicClient.api.auth["check-email"].$post({ json: body }); - return (await response.json()) as CheckEmailExists; - } catch (error) { - if (error instanceof Error) { - throw new Error(error.message || "Failed to check email existence"); - } - throw error; - } -}; - -export const useCheckEmailExists = () => { - return useMutation({ - mutationFn: checkEmailExists, }); }; -export const useForgotPassword = () => { - const [state, setState] = useState({ isLoading: false, error: null }); +// Sign out hook +export const useSignOut = () => { + const navigate = useNavigate(); - const forgotPassword = useCallback(async (data: ForgotPasswordFormData) => { - setState({ isLoading: true, error: null }); + return useMutation({ + mutationFn: async (options?: { redirectTo?: string }) => { + const response = await authClient.signOut(); + const data = response.data; + const success = data?.success ?? false; - try { - toast.promise( - authClient.forgetPassword({ - email: data.email, - redirectTo: `${window.location.origin}/reset-password`, - }), - { - loading: "Sending password reset email...", - success: "If an account exists with this email, you will receive a password reset link.", - error: error => handleAuthError(error, "Failed to send password reset email. Please try again."), - } - ); - return true; - } catch (error) { - const errorMessage = handleAuthError(error, "Failed to send password reset email."); - setState({ isLoading: false, error: errorMessage }); - throw error; - } finally { - setState(prev => ({ ...prev, isLoading: false })); - } - }, []); + if (!success) { + throw new Error("Sign out failed"); + } - return { ...state, forgotPassword }; + return { success: true, redirectPath: options?.redirectTo || "/signin" }; + }, + onSuccess: ({ redirectPath }) => { + toast.success("Signed out successfully"); + navigate({ to: redirectPath }); + }, + onError: error => { + toast.error(handleAuthError(error, "Sign out failed")); + }, + }); }; -export const useResetPassword = () => { - const router = useRouter(); - const [state, setState] = useState({ isLoading: false, error: null }); +// Check email exists hook +export const useCheckEmailExists = () => { + return useMutation({ + mutationFn: async (email: string) => { + const body = { email }; + const result = emailObjectSchema.safeParse(body); + if (!result.success) { + throw new Error(result.error.message); + } + const response = await publicClient.api.auth["check-email"].$post({ json: body }); + return (await response.json()) as CheckEmailExists; + }, + }); +}; - const resetPassword = useCallback( - async (data: ResetPasswordFormData, token: string) => { +// Forgot password hook +export const useForgotPassword = () => { + return useMutation({ + mutationFn: async (data: ForgotPasswordFormData) => { + await authClient.forgetPassword({ + email: data.email, + redirectTo: `${window.location.origin}/reset-password`, + }); + }, + onSuccess: () => { + toast.success("If an account exists with this email, you will receive a password reset link."); + }, + onError: error => { + toast.error(handleAuthError(error, "Failed to send password reset email. Please try again.")); + }, + }); +}; + +// Reset password hook +export const useResetPassword = () => { + const navigate = useNavigate(); + + return useMutation({ + mutationFn: async ({ data, token }: { data: ResetPasswordFormData; token: string }) => { if (!token) { throw new Error("Reset token is missing"); } - setState({ isLoading: true, error: null }); - - try { - toast.promise( - authClient.resetPassword({ - token, - newPassword: data.password, - }), - { - loading: "Resetting your password...", - success: () => { - router.push("/signin"); - return "Your password has been reset successfully. You can now sign in with your new password."; - }, - error: error => - handleAuthError(error, "Failed to reset password. The link may have expired or is invalid."), - } - ); - return true; - } catch (error) { - const errorMessage = handleAuthError( - error, - "Failed to reset password. The link may have expired or is invalid." - ); - setState(prev => ({ ...prev, error: errorMessage })); - throw error; - } finally { - setState(prev => ({ ...prev, isLoading: false })); - } + await authClient.resetPassword({ + token, + newPassword: data.password, + }); }, - [router] - ); - - return { - ...state, - resetPassword, - }; + onSuccess: () => { + navigate({ to: "/signin" }); + toast.success("Your password has been reset successfully. You can now sign in with your new password."); + }, + onError: error => { + toast.error(handleAuthError(error, "Failed to reset password. The link may have expired or is invalid.")); + }, + }); }; diff --git a/apps/web/src/hooks/useSearchParamsSafely.ts b/apps/web/src/hooks/useSearchParamsSafely.ts deleted file mode 100644 index 4d53745..0000000 --- a/apps/web/src/hooks/useSearchParamsSafely.ts +++ /dev/null @@ -1,17 +0,0 @@ -"use client"; - -import { useSearchParams as useNextSearchParams } from "next/navigation"; -import { useCallback } from "react"; - -export const useSearchParamsSafely = () => { - const searchParams = useNextSearchParams(); - - const getParam = useCallback( - (key: string) => { - return searchParams?.get(key); - }, - [searchParams] - ); - - return { getParam, searchParams }; -}; diff --git a/apps/web/src/lib/types.ts b/apps/web/src/lib/types.ts index c2b326f..e21828b 100644 --- a/apps/web/src/lib/types.ts +++ b/apps/web/src/lib/types.ts @@ -18,7 +18,6 @@ export interface UploadFileDialogProps { export interface AuthCardProps extends ComponentProps<"div"> { title: string; - description: string; navigationType: "signin" | "signup"; children: ReactNode; } diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index 63b73fd..ff63c11 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -1,4 +1,5 @@ import { RouterProvider, createRouter } from "@tanstack/react-router"; +import { LoadingStatePage } from "./components/loading-state-page"; import { routeTree } from "./routeTree.gen"; import ReactDOM from "react-dom/client"; import { StrictMode } from "react"; @@ -10,14 +11,7 @@ const router = createRouter({ defaultPreload: "intent", defaultPreloadStaleTime: 0, context: undefined!, - defaultPendingComponent: () => ( -
-
-
-

Loading...

-
-
- ), + defaultPendingComponent: () => LoadingStatePage, }); // Register the router instance for type safety diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts index a01512c..3c8c268 100644 --- a/apps/web/src/routeTree.gen.ts +++ b/apps/web/src/routeTree.gen.ts @@ -9,7 +9,7 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from "./routes/__root" -import { Route as DebugRouteImport } from "./routes/debug" +import { Route as DevelopersRouteImport } from "./routes/developers" import { Route as PublicRouteImport } from "./routes/_public" import { Route as ProtectedRouteImport } from "./routes/_protected" import { Route as IndexRouteImport } from "./routes/index" @@ -26,9 +26,9 @@ import { Route as ProtectedDashboardIndexRouteImport } from "./routes/_protected import { Route as ProtectedDashboardSettingsRouteImport } from "./routes/_protected/dashboard/settings" import { Route as ProtectedDashboardProviderSlugAccountIdRouteImport } from "./routes/_protected/dashboard/$providerSlug.$accountId" -const DebugRoute = DebugRouteImport.update({ - id: "/debug", - path: "/debug", +const DevelopersRoute = DevelopersRouteImport.update({ + id: "/developers", + path: "/developers", getParentRoute: () => rootRouteImport, } as any) const PublicRoute = PublicRouteImport.update({ @@ -109,7 +109,7 @@ const ProtectedDashboardProviderSlugAccountIdRoute = export interface FileRoutesByFullPath { "/": typeof IndexRoute - "/debug": typeof DebugRoute + "/developers": typeof DevelopersRoute "/dashboard": typeof ProtectedDashboardRouteWithChildren "/contributors": typeof PublicContributorsRoute "/forgot-password": typeof PublicForgotPasswordRoute @@ -125,7 +125,7 @@ export interface FileRoutesByFullPath { } export interface FileRoutesByTo { "/": typeof IndexRoute - "/debug": typeof DebugRoute + "/developers": typeof DevelopersRoute "/contributors": typeof PublicContributorsRoute "/forgot-password": typeof PublicForgotPasswordRoute "/privacy": typeof PublicPrivacyRoute @@ -143,7 +143,7 @@ export interface FileRoutesById { "/": typeof IndexRoute "/_protected": typeof ProtectedRouteWithChildren "/_public": typeof PublicRouteWithChildren - "/debug": typeof DebugRoute + "/developers": typeof DevelopersRoute "/_protected/dashboard": typeof ProtectedDashboardRouteWithChildren "/_public/contributors": typeof PublicContributorsRoute "/_public/forgot-password": typeof PublicForgotPasswordRoute @@ -161,7 +161,7 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | "/" - | "/debug" + | "/developers" | "/dashboard" | "/contributors" | "/forgot-password" @@ -177,7 +177,7 @@ export interface FileRouteTypes { fileRoutesByTo: FileRoutesByTo to: | "/" - | "/debug" + | "/developers" | "/contributors" | "/forgot-password" | "/privacy" @@ -194,7 +194,7 @@ export interface FileRouteTypes { | "/" | "/_protected" | "/_public" - | "/debug" + | "/developers" | "/_protected/dashboard" | "/_public/contributors" | "/_public/forgot-password" @@ -213,16 +213,16 @@ export interface RootRouteChildren { IndexRoute: typeof IndexRoute ProtectedRoute: typeof ProtectedRouteWithChildren PublicRoute: typeof PublicRouteWithChildren - DebugRoute: typeof DebugRoute + DevelopersRoute: typeof DevelopersRoute } declare module "@tanstack/react-router" { interface FileRoutesByPath { - "/debug": { - id: "/debug" - path: "/debug" - fullPath: "/debug" - preLoaderRoute: typeof DebugRouteImport + "/developers": { + id: "/developers" + path: "/developers" + fullPath: "/developers" + preLoaderRoute: typeof DevelopersRouteImport parentRoute: typeof rootRouteImport } "/_public": { @@ -390,7 +390,7 @@ const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, ProtectedRoute: ProtectedRouteWithChildren, PublicRoute: PublicRouteWithChildren, - DebugRoute: DebugRoute, + DevelopersRoute: DevelopersRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx index c5faf5c..64cd607 100644 --- a/apps/web/src/routes/__root.tsx +++ b/apps/web/src/routes/__root.tsx @@ -5,6 +5,7 @@ import { ThemeProvider } from "@/components/providers/theme-provider"; import { AppProviders } from "@/components/providers/app-providers"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { geistSans, geistMono, manrope } from "@/utils/fonts"; +import { TanStackDevtools } from "@tanstack/react-devtools"; import { Toaster } from "sonner"; import { Suspense } from "react"; @@ -22,19 +23,25 @@ function RootComponent() {
- Loading...
}> + - +
- - - - - - + , + }, + { + name: "React Query", + render: , + }, + ]} + /> ); } diff --git a/apps/web/src/routes/_protected/dashboard.tsx b/apps/web/src/routes/_protected/dashboard.tsx index 19f2ade..198e119 100644 --- a/apps/web/src/routes/_protected/dashboard.tsx +++ b/apps/web/src/routes/_protected/dashboard.tsx @@ -1,5 +1,6 @@ import { DefaultAccountProvider } from "@/components/providers/default-account-provider"; import { UserInfoProvider } from "@/components/providers/user-info-provider"; +import { AccountProvider } from "@/components/providers/account-provider"; import { createFileRoute, Outlet } from "@tanstack/react-router"; export const Route = createFileRoute("/_protected/dashboard")({ @@ -8,10 +9,12 @@ export const Route = createFileRoute("/_protected/dashboard")({ function DashboardLayout() { return ( - - - - - + + + + + + + ); } diff --git a/apps/web/src/routes/_protected/dashboard/$providerSlug.$accountId.tsx b/apps/web/src/routes/_protected/dashboard/$providerSlug.$accountId.tsx index 78728eb..f1d778d 100644 --- a/apps/web/src/routes/_protected/dashboard/$providerSlug.$accountId.tsx +++ b/apps/web/src/routes/_protected/dashboard/$providerSlug.$accountId.tsx @@ -1,3 +1,4 @@ +import { AccountProvider } from "@/components/providers/account-provider"; import DndKitProvider from "@/components/providers/dnd-kit-provider"; import { FileTable } from "@/components/dashboard/file-browser"; import { createFileRoute } from "@tanstack/react-router"; @@ -7,6 +8,8 @@ import { Suspense } from "react"; type DashboardSearch = { folderId?: string; + type?: string; + id?: string; }; export const Route = createFileRoute("/_protected/dashboard/$providerSlug/$accountId")({ @@ -14,6 +17,8 @@ export const Route = createFileRoute("/_protected/dashboard/$providerSlug/$accou validateSearch: (search: Record): DashboardSearch => { return { folderId: (search.folderId as string) || undefined, + type: (search.type as string) || undefined, + id: (search.id as string) || undefined, }; }, }); diff --git a/apps/web/src/routes/_protected/dashboard/settings.tsx b/apps/web/src/routes/_protected/dashboard/settings.tsx index 7e9b616..894220e 100644 --- a/apps/web/src/routes/_protected/dashboard/settings.tsx +++ b/apps/web/src/routes/_protected/dashboard/settings.tsx @@ -60,7 +60,7 @@ function SettingsPage() { if (user?.email !== email) { await authClient.changeEmail({ newEmail: email, - callbackURL: `${import.meta.env.VITE_FRONTEND_URL}/verify-email`, + callbackURL: `${env.VITE_FRONTEND_URL}/verify-email`, }); isUpdated = true; } diff --git a/apps/web/src/routes/_public/reset-password.tsx b/apps/web/src/routes/_public/reset-password.tsx index 728e1f9..d56b733 100644 --- a/apps/web/src/routes/_public/reset-password.tsx +++ b/apps/web/src/routes/_public/reset-password.tsx @@ -4,6 +4,10 @@ import { Suspense } from "react"; export const Route = createFileRoute("/_public/reset-password")({ component: ResetPasswordPage, + validateSearch: (search: Record) => ({ + token: search.token as string, + error: search.error as string, + }), }); function ResetPasswordContent() { diff --git a/apps/web/src/routes/_public/signin.tsx b/apps/web/src/routes/_public/signin.tsx index 0355ad3..bad7285 100644 --- a/apps/web/src/routes/_public/signin.tsx +++ b/apps/web/src/routes/_public/signin.tsx @@ -7,7 +7,7 @@ export const Route = createFileRoute("/_public/signin")({ component: SigninPage, validateSearch: (search: Record) => { return { - redirect: (search.redirect as string) || undefined, + redirectTo: (search.redirectTo as string) || undefined, }; }, }); diff --git a/apps/web/src/routes/_public/verify-email.tsx b/apps/web/src/routes/_public/verify-email.tsx index 87b79de..002632b 100644 --- a/apps/web/src/routes/_public/verify-email.tsx +++ b/apps/web/src/routes/_public/verify-email.tsx @@ -4,6 +4,12 @@ import { Suspense } from "react"; export const Route = createFileRoute("/_public/verify-email")({ component: VerifyEmailPage, + validateSearch: (search: Record) => { + return { + token: (search.token as string) || undefined, + error: (search.error as string) || undefined, + }; + }, }); function VerifyEmailPage() { diff --git a/apps/web/src/routes/debug.tsx b/apps/web/src/routes/debug.tsx deleted file mode 100644 index 31d96e9..0000000 --- a/apps/web/src/routes/debug.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { authClient } from "@nimbus/auth/auth-client"; -import { useState, useEffect } from "react"; - -export const Route = createFileRoute("/debug")({ - component: DebugPage, -}); - -function DebugPage() { - const [authState, setAuthState] = useState(null); - const [authError, setAuthError] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const checkAuth = async () => { - try { - const session = await authClient.getSession(); - setAuthState(session); - } catch (error) { - setAuthError(error instanceof Error ? error.message : "Unknown error"); - } finally { - setLoading(false); - } - }; - checkAuth(); - }, []); - - return ( -
-
-
-

Debug Information

-

TanStack Router + Vite Migration Debug Page

-
- - {/* Router Status */} -
-

✅ Router Status

-

If you can see this page, TanStack Router is working correctly!

-
-
- Current Path: - {window.location.pathname} -
-
- Search: - {window.location.search || "(none)"} -
-
-
- - {/* Authentication Status */} -
-

🔐 Authentication Status

- {loading ? ( -

Checking authentication...

- ) : authError ? ( -
-

Error: {authError}

-
- ) : ( -
-
- Authenticated: - {authState?.user ? "Yes" : "No"} -
- {authState?.user && ( - <> -
- User Email: - {authState.user.email} -
-
- User Name: - {authState.user.name || "(not set)"} -
- - )} -
- )} -
- - {/* Environment Check */} -
-

🌍 Environment

-
-
- Mode: - {import.meta.env.MODE} -
-
- Dev: - {import.meta.env.DEV ? "Yes" : "No"} -
-
- Prod: - {import.meta.env.PROD ? "Yes" : "No"} -
-
-
- - {/* Quick Links */} - - - {/* System Info */} -
-

💻 System Info

-
-
- User Agent: - {navigator.userAgent} -
-
- Viewport: - - {window.innerWidth} x {window.innerHeight} - -
-
-
- - {/* Console Log */} -
-

📝 Instructions

-
-

1. Open your browser's developer console (F12)

-

2. Check the Console tab for any errors

-

3. Check the Network tab to see if all assets are loading

-

4. Try navigating to different routes using the links above

-

5. If you see a blank page on other routes, check the console for errors

-
-
-
-
- ); -} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index d3a0ae2..3a52a71 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,4 +1,5 @@ import tanstackRouter from "@tanstack/router-plugin/vite"; +import { devtools } from "@tanstack/devtools-vite"; import tsconfigPaths from "vite-tsconfig-paths"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; @@ -6,6 +7,13 @@ import path from "path"; export default defineConfig({ plugins: [ + devtools({ + removeDevtoolsOnBuild: true, + logging: true, + enhancedLogs: { + enabled: true, + }, + }), tanstackRouter({ target: "react", autoCodeSplitting: true, @@ -34,4 +42,7 @@ export default defineConfig({ optimizeDeps: { exclude: ["@nimbus/auth", "@nimbus/env", "@nimbus/server", "@nimbus/shared"], }, + define: { + "process.env": {}, + }, }); diff --git a/bun.lock b/bun.lock index aa4324e..7f8a7eb 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,8 @@ "": { "name": "nimbus", "dependencies": { + "@tanstack/devtools-vite": "^0.3.6", + "@tanstack/react-devtools": "^0.7.6", "stripe": "^19.0.0", }, "devDependencies": { @@ -311,7 +313,7 @@ "@babel/traverse": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="], - "@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], + "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], "@better-auth/stripe": ["@better-auth/stripe@1.3.24", "", { "dependencies": { "zod": "^4.1.5" }, "peerDependencies": { "better-auth": "1.3.24", "stripe": "^18" } }, "sha512-7Ib0w4FRbUSgWTVljLU/H7NKZzu8l1iJ0CdZgY24qKahpNy0yoTV/tQbaUv1eQ/bw4+X46g8EpsNZsxgdwDZJQ=="], @@ -847,6 +849,14 @@ "@smithy/util-waiter": ["@smithy/util-waiter@4.0.7", "", { "dependencies": { "@smithy/abort-controller": "^4.0.5", "@smithy/types": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-mYqtQXPmrwvUljaHyGxYUIIRI3qjBTEb/f5QFi3A6VlxhpmZd5mWXn9W+qUkf2pVE1Hv3SqxefiZOPGdxmO64A=="], + "@solid-primitives/event-listener": ["@solid-primitives/event-listener@2.4.3", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg=="], + + "@solid-primitives/keyboard": ["@solid-primitives/keyboard@1.3.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9dQHTTgLBqyAI7aavtO+HnpTVJgWQA1ghBSrmLtMu1SMxLPDuLfuNr+Tk5udb4AL4Ojg7h9JrKOGEEDqsJXWJA=="], + + "@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ=="], + + "@solid-primitives/utils": ["@solid-primitives/utils@6.3.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ=="], + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], @@ -883,12 +893,26 @@ "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.14", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.14", "@tailwindcss/oxide": "4.1.14", "postcss": "^8.4.41", "tailwindcss": "4.1.14" } }, "sha512-BdMjIxy7HUNThK87C7BC8I1rE8BVUsfNQSI5siQ4JK3iIa3w0XyVvVL9SXLWO//CtYTcp1v7zci0fYwJOjB+Zg=="], + "@tanstack/devtools": ["@tanstack/devtools@0.6.20", "", { "dependencies": { "@solid-primitives/keyboard": "^1.3.3", "@tanstack/devtools-event-bus": "0.3.2", "@tanstack/devtools-ui": "0.4.2", "clsx": "^2.1.1", "goober": "^2.1.16", "solid-js": "^1.9.9" } }, "sha512-7Sw6bWvwKsHDNLg+8v7xOXhE5tzwx6/KgLWSSP55pJ86wpSXYdIm89vvXm4ED1lgKfEU5l3f4Y6QVagU4rgRiQ=="], + + "@tanstack/devtools-client": ["@tanstack/devtools-client@0.0.2", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.3.3" } }, "sha512-j4XPrLjaZ8GaUe9Lt0QOyfrv0Q2jy/9Og5nLQM5/cFcOLxuBp5mXR/fFrA9/9oVCIld/CxetMEac8CwayXVTHQ=="], + + "@tanstack/devtools-event-bus": ["@tanstack/devtools-event-bus@0.3.2", "", { "dependencies": { "ws": "^8.18.3" } }, "sha512-yJT2As/drc+Epu0nsqCsJaKaLcaNGufiNxSlp/+/oeTD0jsBxF9/PJBfh66XVpYXkKr97b8689mSu7QMef0Rrw=="], + + "@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.3.3", "", {}, "sha512-RfV+OPV/M3CGryYqTue684u10jUt55PEqeBOnOtCe6tAmHI9Iqyc8nHeDhWPEV9715gShuauFVaMc9RiUVNdwg=="], + + "@tanstack/devtools-ui": ["@tanstack/devtools-ui@0.4.2", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16", "solid-js": "^1.9.9" } }, "sha512-xvALRLeD+TYjaLx9f9OrRBBZITAYPIk7RH8LRiESUQHw7lZO/sBU1ggrcSePh7TwKWXl9zLmtUi+7xVIS+j/dQ=="], + + "@tanstack/devtools-vite": ["@tanstack/devtools-vite@0.3.6", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/generator": "^7.28.3", "@babel/parser": "^7.28.4", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@tanstack/devtools-client": "0.0.2", "@tanstack/devtools-event-bus": "0.3.2", "chalk": "^5.6.2", "launch-editor": "^2.11.1" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0" } }, "sha512-OYzyRVxHvrEMJLYIicD5HekQTrQeEqVGpEEHnQuAXmZydRh4qXebUBV+XYDaz+AA6woRuAzvYXALCwBq0kNMfw=="], + "@tanstack/history": ["@tanstack/history@1.132.31", "", {}, "sha512-UCHM2uS0t/uSszqPEo+SBSSoQVeQ+LlOWAVBl5SA7+AedeAbKafIPjFn8huZCXNLAYb0WKV2+wETr7lDK9uz7g=="], "@tanstack/query-core": ["@tanstack/query-core@5.85.5", "", {}, "sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w=="], "@tanstack/query-devtools": ["@tanstack/query-devtools@5.84.0", "", {}, "sha512-fbF3n+z1rqhvd9EoGp5knHkv3p5B2Zml1yNRjh7sNXklngYI5RVIWUrUjZ1RIcEoscarUb0+bOvIs5x9dwzOXQ=="], + "@tanstack/react-devtools": ["@tanstack/react-devtools@0.7.6", "", { "dependencies": { "@tanstack/devtools": "0.6.20" }, "peerDependencies": { "@types/react": ">=16.8", "@types/react-dom": ">=16.8", "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-fP0jY7yed0HVIEhs+rjn8wZqABD/6TUiq6SV8jlyYP8NBK2Jfq3ce+IRw5w+N7KBzEokveLQFktxoLNpt3ZOkA=="], + "@tanstack/react-query": ["@tanstack/react-query@5.85.5", "", { "dependencies": { "@tanstack/query-core": "5.85.5" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A=="], "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.85.5", "", { "dependencies": { "@tanstack/query-devtools": "5.84.0" }, "peerDependencies": { "@tanstack/react-query": "^5.85.5", "react": "^18 || ^19" } }, "sha512-6Ol6Q+LxrCZlQR4NoI5181r+ptTwnlPG2t7H9Sp3klxTBhYGunONqcgBn2YKRPsaKiYM8pItpKMdMXMEINntMQ=="], @@ -1453,6 +1477,8 @@ "kysely": ["kysely@0.28.5", "", {}, "sha512-rlB0I/c6FBDWPcQoDtkxi9zIvpmnV5xoIalfCMSMCa7nuA6VGA3F54TW9mEgX4DVf10sXAWCF5fDbamI/5ZpKA=="], + "launch-editor": ["launch-editor@2.11.1", "", { "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.3" } }, "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg=="], + "leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="], "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], @@ -1775,6 +1801,8 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], @@ -1949,6 +1977,8 @@ "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], @@ -1967,36 +1997,14 @@ "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - "@babel/core/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/generator/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - - "@babel/helper-annotate-as-pure/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/helper-member-expression-to-functions/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - - "@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - - "@babel/helper-optimise-call-expression/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - - "@babel/helper-skip-transparent-expression-wrappers/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - - "@babel/helpers/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - - "@babel/parser/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - - "@babel/template/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - - "@babel/traverse/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - "@changesets/apply-release-plan/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="], "@changesets/parse/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], @@ -2049,6 +2057,8 @@ "@tailwindcss/postcss/tailwindcss": ["tailwindcss@4.1.14", "", {}, "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA=="], + "@tanstack/devtools-vite/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "@tanstack/react-router-devtools/vite": ["vite@7.1.9", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg=="], "@tanstack/react-store/@tanstack/store": ["@tanstack/store@0.7.7", "", {}, "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ=="], @@ -2059,6 +2069,8 @@ "@tanstack/router-generator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@tanstack/router-plugin/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], + "@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@tanstack/router-utils/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], @@ -2067,11 +2079,7 @@ "@types/babel__core/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="], - "@types/babel__generator/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - - "@types/babel__template/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - - "@types/babel__traverse/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], + "@types/babel__core/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], "@types/node-fetch/@types/node": ["@types/node@24.3.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow=="], @@ -2079,8 +2087,6 @@ "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "babel-dead-code-elimination/@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - "box-node-sdk/@types/node": ["@types/node@18.19.123", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg=="], "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], diff --git a/package.json b/package.json index 043e87e..45ed8dc 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,8 @@ "packages/*" ], "dependencies": { + "@tanstack/devtools-vite": "^0.3.6", + "@tanstack/react-devtools": "^0.7.6", "stripe": "^19.0.0" } } diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts index ab47a90..de54dec 100644 --- a/packages/auth/src/auth.ts +++ b/packages/auth/src/auth.ts @@ -2,9 +2,9 @@ import { type Account, type AuthContext, betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import schema, { user as userTable } from "@nimbus/db/schema"; import { cacheClient, type CacheClient } from "@nimbus/cache"; +import { stripe } from "@better-auth/stripe"; // import { genericOAuth } from "better-auth/plugins"; import { sendMail } from "./utils/send-mail"; -import { stripe } from "@better-auth/stripe"; import { env } from "@nimbus/env/server"; import { db, type DB } from "@nimbus/db"; import { eq } from "drizzle-orm"; @@ -52,7 +52,7 @@ export const auth = betterAuth({ minPasswordLength: 8, maxPasswordLength: 100, resetPasswordTokenExpiresIn: 600, // 10 minutes - requireEmailVerification: true, + // requireEmailVerification: true, sendResetPassword: async ({ user, token: _token, url }) => { // const frontendResetUrl = `${env.FRONTEND_URL}/reset-password?token=${token}`; await sendMail(emailContext, { @@ -63,20 +63,20 @@ export const auth = betterAuth({ }, }, - emailVerification: { - sendVerificationEmail: async ({ user, url }) => { - // const urlParts = url.split(`${env.BACKEND_URL}/api/auth`); - // const emailUrl = `${env.FRONTEND_URL}${urlParts[1]}`; - await sendMail(emailContext, { - to: user.email, - subject: "Verify your Nimbus email address", - text: `Click the link to verify your email address: ${url}`, - }); - }, - sendOnSignUp: true, - autoSignInAfterVerification: true, - expiresIn: 3600, // 1 hour - }, + // emailVerification: { + // sendVerificationEmail: async ({ user, url }) => { + // // const urlParts = url.split(`${env.BACKEND_URL}/api/auth`); + // // const emailUrl = `${env.FRONTEND_URL}${urlParts[1]}`; + // await sendMail(emailContext, { + // to: user.email, + // subject: "Verify your Nimbus email address", + // text: `Click the link to verify your email address: ${url}`, + // }); + // }, + // sendOnSignUp: true, + // autoSignInAfterVerification: true, + // expiresIn: 3600, // 1 hour + // }, socialProviders: { google: { @@ -118,7 +118,7 @@ export const auth = betterAuth({ plugins: [ stripe({ stripeClient, - stripeWebhookSecret: env.STRIPE_WEBHOOK_SECRET!, + stripeWebhookSecret: env.STRIPE_WEBHOOK_SECRET, createCustomerOnSignUp: true, }), // genericOAuth({ diff --git a/packages/shared/CHANGELOG.md b/packages/shared/CHANGELOG.md deleted file mode 100644 index f71c534..0000000 --- a/packages/shared/CHANGELOG.md +++ /dev/null @@ -1,36 +0,0 @@ -# @nimbus/shared - -## 0.0.4 - -### Patch Changes - -- @nimbus/db@0.0.3 - -## 0.0.3 - -### Patch Changes - -- 03bb85d: chore: updated everything to latest except zod and Next.js (zod, tying errors, opennextjs-cloudflare only - supports 15.3) CMD: find . -type f -name "package.json" -not -path "_/node_modules/_" -not -path "*/.*next/\*" -exec - sh -c 'echo "\nUpdating $1/..." && (cd "$(dirname "$1")" && ncu -u)' \_ {} \; chore: revert zod upgrade as it breaks - better-auth typing chore: zod broke better-auth typing again... chore: revert back to base sql chore: auth clean - chore: FINALLY fixed schema.ts chore: reset migrations to current database state. removed the rateLimit table since we - use cache now -- Updated dependencies [de391ed] -- Updated dependencies [7ffde8d] -- Updated dependencies [03bb85d] - - @nimbus/db@0.0.2 - -## 0.0.2 - -### Patch Changes - -- 1c1f4a3: Added legal pages. Added legal constants. Grouped all constants in shared/constants/\*. - -## 0.0.1 - -### Patch Changes - -- 7e2271f: init changeset -- Updated dependencies [7e2271f] - - @nimbus/db@0.0.1 diff --git a/packages/shared/src/validators/provider.ts b/packages/shared/src/validators/provider.ts index 94c6247..482840c 100644 --- a/packages/shared/src/validators/provider.ts +++ b/packages/shared/src/validators/provider.ts @@ -1,9 +1,7 @@ import z from "zod"; -// const providers = ["google", "microsoft", "dropbox", "box", "nimbus", "apple", "github"] as const; -// const providerSlugs = ["g", "m", "d", "b", "n", "a", "gh"] as const; -const providers = ["google", "microsoft", "s3", "box", "dropbox"] as const; -const providerSlugs = ["g", "m", "s3", "b", "d"] as const; +const providers = ["credential", "google", "microsoft", "s3", "box", "dropbox"] as const; +const providerSlugs = ["c", "g", "m", "s3", "b", "d"] as const; // Define social providers first export const driveProviderSchema = z.enum(providers); diff --git a/turbo.json b/turbo.json index 6cb9ca3..30fd402 100644 --- a/turbo.json +++ b/turbo.json @@ -4,7 +4,7 @@ "tasks": { "build": { "dependsOn": ["^build"], - "outputs": [".next/**", "!.next/cache/**", "dist/**"] + "outputs": ["dist/**", "apps/**/dist/**", "packages/**/dist/**", "!.cache/**"] }, "dev": { "persistent": true,