Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/egeuysall/ryva-archive/llms.txt

Use this file to discover all available pages before exploring further.

Frontend Architecture

The Ryva frontend is built with Next.js 16 using the App Router, React 19, and TypeScript in strict mode. It follows modern React best practices with server-first rendering and client-side interactivity where needed.

Technology Stack

Core Framework

  • Next.js 16 - App Router with Server Components
  • React 19 - Latest features and performance improvements
  • TypeScript 5.9 - Strict mode, no any types

Styling & UI

  • Tailwind CSS v4 - Utility-first styling
  • Radix UI - Accessible component primitives
  • Lucide React - Icon library
  • Motion - Animation library

State Management

  • TanStack Query - Server state, caching, mutations
  • Zustand - Client state, UI state
  • React Hook Form - Form state management

Data & APIs

  • Supabase - Authentication (SSR-compatible)
  • Custom API Client - Backend communication
  • Stripe - Payment processing
  • Sanity - Content management (blog)

Project Structure

apps/web/src/
├── app/                    # Next.js App Router
│   ├── (auth)/            # Auth route group (login, signup)
│   ├── (dashbord)/        # Dashboard route group (app)
│   ├── (marketing)/       # Marketing pages (landing, docs)
│   ├── (admin)/           # Admin panel (Sanity Studio)
│   ├── layout.tsx         # Root layout
│   └── page.tsx           # Home page
├── components/            # Shared UI components
│   └── ui/               # Radix UI-based components
├── modules/              # Feature modules
│   ├── auth/            # Authentication module
│   ├── organizations/   # Organization management
│   ├── billing/         # Subscription & billing
│   ├── waitlist/        # Waitlist signup
│   └── blog/            # Blog (Sanity CMS)
├── lib/                  # Utilities and configurations
│   ├── api/             # API client
│   ├── supabase/        # Supabase client (SSR)
│   ├── stripe/          # Stripe integration
│   └── utils.ts         # Helper functions
├── stores/              # Zustand stores
│   ├── auth.ts         # Auth state
│   └── waitlist.ts     # Waitlist state
├── hooks/              # Shared React hooks
├── contexts/           # React contexts
├── sanity/             # Sanity CMS configuration
└── styles/             # Global styles

App Router Architecture

Route Groups

Ryva uses Next.js route groups to organize pages by feature without affecting the URL structure:
  • /login - Login page
  • /signup - Registration page
  • /forgot-password - Password reset request
  • /reset-password - Password reset form
  • /onboarding - New user onboarding
  • /auth/callback - OAuth callback handler
Layout: Minimal layout with centered form
  • /app - Main dashboard
  • /app/projects - Projects view
  • /app/settings/* - User settings
  • /app/settings/organizations - Org settings
  • /app/settings/billing - Billing management
  • /app/invitations - Organization invitations
  • /app/checkout - Subscription checkout
Layout: Full dashboard with sidebar navigation
  • / - Landing page
  • /docs/* - Documentation (MDX)
  • /blog/* - Blog posts (Sanity CMS)
  • /pricing - Pricing page
Layout: Marketing header and footer
  • /admin - Sanity Studio
Layout: Full-screen Sanity Studio

Server vs Client Components

Ryva follows the Server Components by default principle. Client Components are only used when necessary.
Server Components (default):
  • Used for: Data fetching, layouts, static content
  • Benefits: Faster initial load, smaller bundle, SEO-friendly
  • Example: Page layouts, marketing pages, documentation
Client Components ('use client'):
  • Used for: Interactivity, browser APIs, state, effects
  • Required for: Event handlers, useState, useEffect, browser-only APIs
  • Example: Forms, modals, interactive widgets
// Server Component (default)
export default async function DashboardPage() {
  // Can fetch data directly
  return <DashboardContent />;
}

// Client Component
'use client';

import { useState } from 'react';

export function LoginForm() {
  const [email, setEmail] = useState('');
  // Can use hooks, event handlers, browser APIs
  return <form onSubmit={handleSubmit}>...</form>;
}

State Management Strategy

TanStack Query - Server State

Used for all data fetching, caching, and synchronization with the backend API.
Features:
  • Automatic caching and deduplication
  • Background refetching
  • Optimistic updates
  • Retry logic
  • Loading and error states
Example Usage:
// apps/web/src/modules/auth/hooks/use-auth-api.ts
import { useQuery, useMutation } from '@tanstack/react-query';
import { apiClient } from '@/lib/api/client';

export function useMe() {
  return useQuery({
    queryKey: ['auth', 'me'],
    queryFn: () => apiClient.get('/v1/auth/me'),
    staleTime: 5 * 60 * 1000, // 5 minutes
  });
}

export function useUpdateProfile() {
  return useMutation({
    mutationFn: (data) => apiClient.patch('/v1/auth/profile', data),
    onSuccess: () => {
      queryClient.invalidateQueries(['auth', 'me']);
    },
  });
}

Zustand - Client State

Used for client-side UI state, user preferences, and ephemeral data that doesn’t need to sync with the server.
Example (apps/web/src/stores/auth.ts:1-23):
import { create } from 'zustand';
import type { User as SupabaseUser } from '@supabase/supabase-js';

interface AuthState {
  user: SupabaseUser | null;
  isLoading: boolean;
  setUser: (user: SupabaseUser | null) => void;
  setLoading: (loading: boolean) => void;
  logout: () => void;
}

export const useAuthStore = create<AuthState>(set => ({
  user: null,
  isLoading: true,
  setUser: user => set({ user, isLoading: false }),
  setLoading: isLoading => set({ isLoading }),
  logout: () => set({ user: null }),
}));
Usage:
const { user, isLoading } = useAuthStore();

When to Use Each

Use TanStack QueryUse Zustand
User profile dataUI state (modals, dropdowns)
Organizations listTheme preference
Subscription statusCurrent sidebar state
API-sourced dataForm drafts (before submit)
Cached server stateClient-only preferences

Module Architecture

Each feature module follows a consistent structure:
modules/auth/
├── components/           # Feature-specific components
│   ├── login-form.tsx
│   ├── signup-form.tsx
│   └── index.ts         # Barrel export
├── hooks/               # Feature-specific hooks
│   └── use-auth-api.ts # TanStack Query hooks
├── types/              # TypeScript types
│   └── index.ts
└── index.ts            # Module exports

Example: Auth Module

Components (apps/web/src/modules/auth/components/login-form.tsx):
  • Handles UI rendering and user interactions
  • Uses React Hook Form for form state
  • Calls Supabase auth methods
  • Displays errors via toast notifications
Hooks (apps/web/src/modules/auth/hooks/use-auth-api.ts):
  • Wraps API calls in TanStack Query hooks
  • Provides query keys for cache management
  • Handles loading and error states
  • Implements optimistic updates
Types (apps/web/src/modules/auth/types/index.ts):
  • Defines TypeScript interfaces for the module
  • Ensures type safety across components and hooks

API Client Architecture

The API client (apps/web/src/lib/api/client.ts:1-164) handles all communication with the Go backend:

Features

Authentication

Automatically attaches JWT token from Supabase session to all requests

Type Safety

Generic types ensure responses are properly typed

Error Handling

Standardized error responses with automatic retry logic

Timeout Handling

30-second timeout with AbortController

Implementation

class APIClient {
  private baseURL: string;

  async request<T>(endpoint: string, options: RequestInit = {}): Promise<APIResponse<T>> {
    const token = await this.getAuthToken();
    
    const headers = {
      'Content-Type': 'application/json',
      ...(token && { 'Authorization': `Bearer ${token}` }),
    };
    
    const response = await fetch(`${this.baseURL}${endpoint}`, {
      ...options,
      headers,
    });
    
    if (!response.ok) {
      throw data;
    }
    
    return data as APIResponse<T>;
  }
  
  async get<T>(endpoint: string) { ... }
  async post<T>(endpoint: string, body?: unknown) { ... }
  async patch<T>(endpoint: string, body?: unknown) { ... }
  async delete<T>(endpoint: string) { ... }
}

export const apiClient = new APIClient();

Usage

// Direct usage
const response = await apiClient.get<User>('/v1/auth/me');

// With TanStack Query
const { data } = useQuery({
  queryKey: ['user'],
  queryFn: () => apiClient.get<User>('/v1/auth/me'),
});

Authentication Flow

Supabase SSR Integration

Ryva uses Supabase Auth with SSR support for seamless authentication:
  1. Client-side (apps/web/src/lib/supabase/client.ts):
    • Used in Client Components
    • Handles auth state changes
    • Provides session management
  2. Server-side (apps/web/src/lib/supabase/server.ts):
    • Used in Server Components and API routes
    • Cookie-based session management
    • SSR-compatible authentication

Auth Flow Diagram

┌─────────────┐
│   Login     │
│   Form      │
└──────┬──────┘

       │ supabase.auth.signInWithPassword()

┌─────────────────┐
│  Supabase Auth  │
└──────┬──────────┘

       │ JWT Token

┌──────────────────┐
│   Auth Store     │  ← setUser()
│   (Zustand)      │
└──────┬───────────┘

       │ Redirect to /app

┌──────────────────┐
│   Dashboard      │
└──────┬───────────┘

       │ API calls with JWT

┌──────────────────┐
│   Go Backend     │
└──────────────────┘

Form Handling

React Hook Form + Zod

All forms use React Hook Form with Zod schema validation:
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const loginSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
});

type LoginFormData = z.infer<typeof loginSchema>;

export function LoginForm() {
  const form = useForm<LoginFormData>({
    resolver: zodResolver(loginSchema),
    defaultValues: {
      email: '',
      password: '',
    },
  });
  
  const onSubmit = async (data: LoginFormData) => {
    // Handle form submission
  };
  
  return (
    <form onSubmit={form.handleSubmit(onSubmit)}>
      <input {...form.register('email')} />
      {form.formState.errors.email && <span>{form.formState.errors.email.message}</span>}
    </form>
  );
}

UI Component Library

Radix UI Components

The components/ui/ directory contains reusable components built on Radix UI primitives:
  • Button - Styled button with variants
  • Dialog - Accessible modal dialogs
  • Dropdown Menu - Context menus and dropdowns
  • Select - Custom select inputs
  • Checkbox - Accessible checkboxes
  • Switch - Toggle switches
  • Tooltip - Hover tooltips

Component Pattern

// components/ui/button.tsx
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md text-sm font-medium',
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
        outline: 'border border-input bg-background hover:bg-accent',
      },
      size: {
        default: 'h-10 px-4 py-2',
        sm: 'h-9 px-3',
        lg: 'h-11 px-8',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : 'button';
    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    );
  }
);

Button.displayName = 'Button';

export { Button, buttonVariants };

Styling Strategy

Tailwind CSS v4

Ryva uses Tailwind CSS v4 with the new PostCSS plugin for improved performance.
Key Features:
  • Utility-first CSS
  • Dark mode support (dark: prefix)
  • Custom design tokens
  • Responsive design (sm:, md:, lg:, xl:)
Example:
<div className="flex flex-col gap-4 p-6 bg-background dark:bg-background-dark">
  <h1 className="text-2xl font-bold text-foreground">
    Dashboard
  </h1>
  <Button className="w-full sm:w-auto">
    Click me
  </Button>
</div>

Class Variance Authority (CVA)

Used for creating component variants:
const buttonVariants = cva(
  'base-classes',
  {
    variants: {
      variant: {
        default: 'bg-primary',
        secondary: 'bg-secondary',
      },
      size: {
        sm: 'text-sm',
        lg: 'text-lg',
      },
    },
  }
);

Performance Optimizations

Server Components

Default to Server Components to reduce JavaScript bundle size and improve initial load time

Code Splitting

Next.js App Router automatically code-splits by route

Image Optimization

Use next/image for automatic optimization and lazy loading

Query Caching

TanStack Query caches API responses and deduplicates requests

Font Optimization

Next.js automatically optimizes and inlines fonts

Bundle Analysis

Use @next/bundle-analyzer to identify large dependencies

Testing Strategy

Unit Tests (Vitest)

pnpm test              # Run tests
pnpm test:watch        # Watch mode
pnpm test:coverage     # Coverage report
Example:
import { render, screen } from '@testing-library/react';
import { Button } from '@/components/ui/button';

test('renders button with text', () => {
  render(<Button>Click me</Button>);
  expect(screen.getByText('Click me')).toBeInTheDocument();
});

E2E Tests (Playwright)

pnpm test:e2e          # Run E2E tests
pnpm test:e2e:ui       # UI mode
pnpm test:e2e:debug    # Debug mode

Development Workflow

Local Development

cd apps/web
pnpm dev               # Start Next.js dev server (http://localhost:3000)

Build & Type Check

pnpm build             # Build for production
pnpm type-check        # TypeScript type checking
pnpm lint              # ESLint
pnpm format            # Prettier

Best Practices

  • Keep components small and focused
  • Use Server Components by default
  • Extract reusable logic into hooks
  • Co-locate related files in modules
  • No any types (enforced by ESLint)
  • Define interfaces for all props
  • Use Zod for runtime validation
  • Type all API responses
  • Lazy load heavy components
  • Use React.memo() sparingly (profile first)
  • Optimize images with next/image
  • Keep bundle size under control
  • Use semantic HTML
  • Radix UI components are accessible by default
  • Test with keyboard navigation
  • Provide ARIA labels where needed

Next Steps

Backend Architecture

Learn about the Go API structure and clean architecture

State Management

Deep dive into TanStack Query and Zustand usage

Build docs developers (and LLMs) love