# SPRINT 10.3 — FRONTEND SETUP (React + Vite + Tailwind)

## METADATA
- Execution: Hodina 3
- Prerekvizity: Sprint 10.1 + 10.2 COMPLETE (Auth + Email API URLs)
- Deliverables: Kompletní React projekt scaffold
- Estimated time: 45-55 min
- Output folder: /mnt/outputs/SKYMAILBOX_SPRINTS/SPRINT_10.3_FRONTEND_SETUP/

## OBJECTIVES
Vytvořit kompletní React projekt pro SkyMailbox webový email klient:
1. Vite + React + TypeScript scaffold
2. Tailwind CSS konfigurace (dark theme SKYNET style)
3. React Router pro navigaci
4. API client utility (fetch wrapper s JWT)
5. Auth store (context/zustand)
6. Základní layout (sidebar + main content area)

## STEP-BY-STEP INSTRUCTIONS

### Krok 1: Inicializovat Vite projekt
```bash
cd /mnt/outputs/SKYMAILBOX_SPRINTS/SPRINT_10.3_FRONTEND_SETUP
npm create vite@latest skymailbox -- --template react-ts
cd skymailbox
npm install
npm install -D tailwindcss @tailwindcss/vite
npm install react-router-dom zustand
```

### Krok 2: Tailwind konfigurace
**Soubor:** `tailwind.config.js`
```js
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
  darkMode: 'class',
  theme: {
    extend: {
      colors: {
        sky: {
          bg: '#0a0a09',
          bg2: '#131311',
          card: '#1a1a18',
          border: '#2a2a27',
          accent: '#f59e0b',
          blue: '#3b82f6',
          green: '#34d399',
          red: '#f87171',
          text: '#e8e8e4',
          text2: '#8a8a84',
          muted: '#58584e'
        }
      },
      fontFamily: {
        sora: ['Sora', 'sans-serif'],
        mono: ['JetBrains Mono', 'monospace']
      }
    }
  },
  plugins: []
}
```

**Soubor:** `src/index.css`
```css
@import 'tailwindcss';
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@300;400;500;600;700&family=JetBrains+Mono:wght@300;400;500&display=swap');

body {
  font-family: 'Sora', sans-serif;
  background: #0a0a09;
  color: #e8e8e4;
  margin: 0;
}
```

### Krok 3: Vite config
**Soubor:** `vite.config.ts`
```ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [react(), tailwindcss()],
  server: { port: 3000 },
  build: { outDir: 'dist' }
})
```

### Krok 4: API Client
**Soubor:** `src/lib/api.ts`
```ts
const AUTH_API = import.meta.env.VITE_AUTH_API_URL || 'https://AUTH_LAMBDA_URL';
const EMAIL_API = import.meta.env.VITE_EMAIL_API_URL || 'https://EMAIL_LAMBDA_URL';

class ApiClient {
  private getToken(): string | null {
    return localStorage.getItem('skymailbox_token');
  }

  private async request(baseUrl: string, path: string, options: RequestInit = {}): Promise<any> {
    const token = this.getToken();
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
      ...((options.headers as Record<string, string>) || {})
    };
    if (token) headers['Authorization'] = `Bearer ${token}`;

    const res = await fetch(`${baseUrl}${path}`, { ...options, headers });

    if (res.status === 401) {
      localStorage.removeItem('skymailbox_token');
      window.location.href = '/login';
      throw new Error('Unauthorized');
    }

    const data = await res.json();
    if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
    return data;
  }

  // Auth endpoints
  async register(email: string, password: string, username: string) {
    return this.request(AUTH_API, '/register', {
      method: 'POST',
      body: JSON.stringify({ email, password, username })
    });
  }

  async login(email: string, password: string) {
    const data = await this.request(AUTH_API, '/login', {
      method: 'POST',
      body: JSON.stringify({ email, password })
    });
    if (data.token) localStorage.setItem('skymailbox_token', data.token);
    return data;
  }

  async getProfile() {
    return this.request(AUTH_API, '/me');
  }

  // Email endpoints
  async getInbox(limit = 50, cursor?: string) {
    const params = new URLSearchParams({ limit: String(limit) });
    if (cursor) params.set('cursor', cursor);
    return this.request(EMAIL_API, `/inbox?${params}`);
  }

  async getSent(limit = 50, cursor?: string) {
    const params = new URLSearchParams({ limit: String(limit) });
    if (cursor) params.set('cursor', cursor);
    return this.request(EMAIL_API, `/sent?${params}`);
  }

  async getEmail(id: string) {
    return this.request(EMAIL_API, `/email/${id}`);
  }

  async sendEmail(to: string, subject: string, body: string, htmlBody?: string) {
    return this.request(EMAIL_API, '/send', {
      method: 'POST',
      body: JSON.stringify({ to, subject, body, html_body: htmlBody })
    });
  }

  async deleteEmail(id: string) {
    return this.request(EMAIL_API, `/email/${id}`, { method: 'DELETE' });
  }

  async moveEmail(emailId: string, targetFolder: string) {
    return this.request(EMAIL_API, '/move', {
      method: 'POST',
      body: JSON.stringify({ email_id: emailId, target_folder: targetFolder })
    });
  }

  async getFolders() {
    return this.request(EMAIL_API, '/folders');
  }

  logout() {
    localStorage.removeItem('skymailbox_token');
    window.location.href = '/login';
  }
}

export const api = new ApiClient();
```

### Krok 5: Auth Store (Zustand)
**Soubor:** `src/store/authStore.ts`
```ts
import { create } from 'zustand';
import { api } from '../lib/api';

interface User {
  user_id: string;
  email: string;
  username: string;
  tier: string;
  wallet_balance: number;
}

interface AuthState {
  user: User | null;
  isLoading: boolean;
  isAuthenticated: boolean;
  login: (email: string, password: string) => Promise<void>;
  register: (email: string, password: string, username: string) => Promise<void>;
  loadProfile: () => Promise<void>;
  logout: () => void;
}

export const useAuthStore = create<AuthState>((set) => ({
  user: null,
  isLoading: true,
  isAuthenticated: false,

  login: async (email, password) => {
    const data = await api.login(email, password);
    set({ user: data, isAuthenticated: true });
  },

  register: async (email, password, username) => {
    const data = await api.register(email, password, username);
    if (data.token) localStorage.setItem('skymailbox_token', data.token);
    set({ user: data, isAuthenticated: true });
  },

  loadProfile: async () => {
    try {
      const token = localStorage.getItem('skymailbox_token');
      if (!token) { set({ isLoading: false }); return; }
      const user = await api.getProfile();
      set({ user, isAuthenticated: true, isLoading: false });
    } catch {
      set({ user: null, isAuthenticated: false, isLoading: false });
    }
  },

  logout: () => {
    api.logout();
    set({ user: null, isAuthenticated: false });
  }
}));
```

### Krok 6: Email Store
**Soubor:** `src/store/emailStore.ts`
```ts
import { create } from 'zustand';
import { api } from '../lib/api';

interface Email {
  email_id: string;
  from: string;
  to: string;
  subject: string;
  body: string;
  html_body?: string;
  date: string;
  is_read: boolean;
  folder: string;
}

interface FolderInfo {
  name: string;
  unread: number;
}

interface EmailState {
  emails: Email[];
  currentEmail: Email | null;
  folders: FolderInfo[];
  currentFolder: string;
  isLoading: boolean;
  cursor: string | null;
  loadInbox: () => Promise<void>;
  loadSent: () => Promise<void>;
  loadEmail: (id: string) => Promise<void>;
  sendEmail: (to: string, subject: string, body: string) => Promise<void>;
  deleteEmail: (id: string) => Promise<void>;
  moveEmail: (id: string, folder: string) => Promise<void>;
  loadFolders: () => Promise<void>;
  setCurrentFolder: (folder: string) => void;
}

export const useEmailStore = create<EmailState>((set) => ({
  emails: [],
  currentEmail: null,
  folders: [],
  currentFolder: 'INBOX',
  isLoading: false,
  cursor: null,

  loadInbox: async () => {
    set({ isLoading: true });
    const data = await api.getInbox();
    set({ emails: data.emails || [], cursor: data.cursor, isLoading: false, currentFolder: 'INBOX' });
  },

  loadSent: async () => {
    set({ isLoading: true });
    const data = await api.getSent();
    set({ emails: data.emails || [], cursor: data.cursor, isLoading: false, currentFolder: 'SENT' });
  },

  loadEmail: async (id) => {
    const data = await api.getEmail(id);
    set({ currentEmail: data });
  },

  sendEmail: async (to, subject, body) => {
    await api.sendEmail(to, subject, body);
  },

  deleteEmail: async (id) => {
    await api.deleteEmail(id);
    set((state) => ({ emails: state.emails.filter(e => e.email_id !== id), currentEmail: null }));
  },

  moveEmail: async (id, folder) => {
    await api.moveEmail(id, folder);
    set((state) => ({ emails: state.emails.filter(e => e.email_id !== id) }));
  },

  loadFolders: async () => {
    const data = await api.getFolders();
    set({ folders: data.folders || [] });
  },

  setCurrentFolder: (folder) => set({ currentFolder: folder })
}));
```

### Krok 7: Router + Layout
**Soubor:** `src/App.tsx`
```tsx
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { useEffect } from 'react';
import { useAuthStore } from './store/authStore';
import Layout from './components/Layout';
import Login from './pages/Login';
import Inbox from './pages/Inbox';
import EmailReader from './pages/EmailReader';
import Compose from './pages/Compose';

function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { isAuthenticated, isLoading } = useAuthStore();
  if (isLoading) return <div className="flex items-center justify-center h-screen bg-sky-bg text-sky-text">Loading...</div>;
  return isAuthenticated ? <>{children}</> : <Navigate to="/login" />;
}

export default function App() {
  const { loadProfile } = useAuthStore();

  useEffect(() => { loadProfile(); }, []);

  return (
    <BrowserRouter>
      <Routes>
        <Route path="/login" element={<Login />} />
        <Route path="/" element={<ProtectedRoute><Layout /></ProtectedRoute>}>
          <Route index element={<Navigate to="/inbox" />} />
          <Route path="inbox" element={<Inbox />} />
          <Route path="sent" element={<Inbox />} />
          <Route path="email/:id" element={<EmailReader />} />
          <Route path="compose" element={<Compose />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}
```

**Soubor:** `src/components/Layout.tsx`
```tsx
import { Outlet, Link, useLocation } from 'react-router-dom';
import { useAuthStore } from '../store/authStore';

export default function Layout() {
  const { user, logout } = useAuthStore();
  const location = useLocation();

  const navItems = [
    { path: '/inbox', label: 'Inbox', icon: '📥' },
    { path: '/sent', label: 'Sent', icon: '📤' },
    { path: '/compose', label: 'Compose', icon: '✏️' }
  ];

  return (
    <div className="flex h-screen bg-sky-bg">
      {/* Sidebar */}
      <aside className="w-56 border-r border-sky-border flex flex-col">
        <div className="p-4 border-b border-sky-border">
          <div className="text-sky-accent font-bold text-sm font-mono tracking-wider">SKYMAILBOX</div>
          <div className="text-sky-text2 text-xs mt-1">{user?.email}</div>
        </div>
        <nav className="flex-1 p-2">
          {navItems.map(item => (
            <Link key={item.path} to={item.path}
              className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm mb-1 transition-colors ${
                location.pathname === item.path
                  ? 'bg-sky-card text-sky-accent border border-sky-border'
                  : 'text-sky-text2 hover:bg-sky-card hover:text-sky-text'
              }`}>
              <span>{item.icon}</span>
              <span>{item.label}</span>
            </Link>
          ))}
        </nav>
        <div className="p-3 border-t border-sky-border">
          <div className="text-xs text-sky-muted mb-1 font-mono">{user?.username} · {user?.tier}</div>
          <button onClick={logout}
            className="text-xs text-sky-red hover:text-red-400 transition-colors">
            Sign Out
          </button>
        </div>
      </aside>
      {/* Main */}
      <main className="flex-1 overflow-auto">
        <Outlet />
      </main>
    </div>
  );
}
```

### Krok 8: Placeholder pages
Vytvořit minimální placeholder soubory pro pages, které budou dokončeny v dalších sprintech:

**Soubor:** `src/pages/Login.tsx` — placeholder s textem "Login — Sprint 10.4"
**Soubor:** `src/pages/Inbox.tsx` — placeholder s textem "Inbox — Sprint 10.5"
**Soubor:** `src/pages/EmailReader.tsx` — placeholder s textem "Reader — Sprint 10.6"
**Soubor:** `src/pages/Compose.tsx` — placeholder s textem "Compose — Sprint 10.7"

Každý placeholder:
```tsx
export default function PageName() {
  return <div className="p-8 text-sky-text2">PageName — Coming in Sprint 10.X</div>;
}
```

### Krok 9: Build test
```bash
cd skymailbox
npm run build
```
Ověřit, že build proběhne bez chyb.

### Krok 10: Vytvořit .env.example
**Soubor:** `.env.example`
```
VITE_AUTH_API_URL=https://xxxxx.lambda-url.us-east-1.on.aws
VITE_EMAIL_API_URL=https://xxxxx.lambda-url.us-east-1.on.aws
```

Pokud AUTH_API_URL.txt a EMAIL_API_URL.txt existují z předchozích sprintů, vytvořit `.env` s reálnými URL.

## COMPLETION CHECKLIST
- [ ] Vite + React + TypeScript projekt inicializován
- [ ] Tailwind CSS funguje s SKYNET dark theme
- [ ] React Router nakonfigurován (login, inbox, sent, email/:id, compose)
- [ ] API client (src/lib/api.ts) komunikuje s oběma Lambda
- [ ] Auth store (Zustand) — login, register, loadProfile, logout
- [ ] Email store (Zustand) — CRUD operace
- [ ] Layout komponenta se sidebarerm a main area
- [ ] Placeholder pages pro všechny routes
- [ ] `npm run build` projde bez chyb
- [ ] .env.example vytvořen

## DELIVERABLES LIST
1. Kompletní `skymailbox/` React projekt
2. `SPRINT_10.3_README.md`
3. `SPRINT_10.3_COMPLETE.md`

## COMPLETION REPORT TEMPLATE
```markdown
# ✅ SPRINT 10.3 — FRONTEND SETUP — COMPLETE

## Timestamp
[ISO datetime]

## Status
COMPLETE / FAILED

## Project Structure
[tree output]

## Build Result
SUCCESS / FAILED
[Build output]

## Dependencies
[package.json dependencies list]

## Issues
[Any issues]

## Next Sprint
SPRINT_10.4_LOGIN_UI
```
