react-typescript-frontend

React TypeScript Frontend

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "react-typescript-frontend" with this command: npx skills add findinfinitelabs/chuuk/findinfinitelabs-chuuk-react-typescript-frontend

React TypeScript Frontend

Overview

Build and maintain the Chuuk Dictionary React frontend using TypeScript, Mantine UI v8, and Vite. Focuses on type-safe development, component patterns, and proper Chuukese text handling.

Tech Stack

  • React 19: Latest React with hooks and concurrent features

  • TypeScript 5.9: Strict type checking

  • Mantine v8: UI component library with dark mode

  • Vite 7: Fast build tool and dev server

  • React Router v7: Client-side routing

  • Axios: HTTP client for API calls

Project Structure

frontend/ ├── src/ │ ├── App.tsx # Main app component │ ├── main.tsx # Entry point with providers │ ├── theme.ts # Mantine theme configuration │ ├── components/ # Reusable components │ │ ├── Footer.tsx │ │ └── GrammarLearning.tsx │ ├── contexts/ # React contexts │ ├── hooks/ # Custom hooks │ ├── pages/ # Page components │ ├── data/ # Static data │ └── assets/ # Images, fonts ├── public/ # Static assets ├── index.html # HTML template ├── vite.config.ts # Vite configuration ├── tsconfig.json # TypeScript config └── package.json

Configuration

tsconfig.json

{ "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "paths": { "@/": ["./src/"] } }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] }

vite.config.ts

import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import path from 'path';

export default defineConfig({ plugins: [react()], resolve: { alias: { '@': path.resolve(__dirname, './src'), }, }, server: { port: 5173, proxy: { '/api': { target: 'http://localhost:5002', changeOrigin: true, }, }, }, build: { outDir: 'dist', sourcemap: true, }, });

Component Patterns

  1. Page Component

// src/pages/DictionaryPage.tsx import { useState, useEffect } from 'react'; import { Container, Title, TextInput, Stack, Loader, Alert } from '@mantine/core'; import { IconSearch, IconAlertCircle } from '@tabler/icons-react'; import { useDictionary } from '@/hooks/useDictionary'; import { DictionaryEntry } from '@/components/DictionaryEntry';

interface DictionaryPageProps { initialQuery?: string; }

export function DictionaryPage({ initialQuery = '' }: DictionaryPageProps) { const [query, setQuery] = useState(initialQuery); const { entries, loading, error, search } = useDictionary();

useEffect(() => { if (query.length >= 2) { search(query); } }, [query, search]);

return ( <Container size="lg" py="xl"> <Title order={1} mb="lg">Chuukese Dictionary</Title>

  &#x3C;TextInput
    placeholder="Search Chuukese or English..."
    value={query}
    onChange={(e) => setQuery(e.target.value)}
    leftSection={&#x3C;IconSearch size={16} />}
    size="lg"
    mb="xl"
    styles={{
      input: {
        fontFamily: "'Noto Sans', sans-serif",
      }
    }}
  />

  {error &#x26;&#x26; (
    &#x3C;Alert icon={&#x3C;IconAlertCircle />} color="red" mb="md">
      {error}
    &#x3C;/Alert>
  )}

  {loading ? (
    &#x3C;Loader />
  ) : (
    &#x3C;Stack gap="md">
      {entries.map((entry) => (
        &#x3C;DictionaryEntry key={entry._id} entry={entry} />
      ))}
    &#x3C;/Stack>
  )}
&#x3C;/Container>

); }

  1. Reusable Component

// src/components/DictionaryEntry.tsx import { Card, Text, Badge, Group, Stack } from '@mantine/core';

interface DictionaryEntryData { _id: string; chuukese_word: string; english_definition: string; part_of_speech?: string; grammar_type?: string; pronunciation?: string; }

interface DictionaryEntryProps { entry: DictionaryEntryData; onClick?: (entry: DictionaryEntryData) => void; }

export function DictionaryEntry({ entry, onClick }: DictionaryEntryProps) { return ( <Card shadow="sm" padding="lg" radius="md" withBorder onClick={() => onClick?.(entry)} style={{ cursor: onClick ? 'pointer' : 'default' }} > <Group justify="space-between" mb="xs"> <Text fw={600} size="xl" style={{ fontFeatureSettings: "'kern' 1" }} > {entry.chuukese_word} </Text> {entry.part_of_speech && ( <Badge color="blue" variant="light"> {entry.part_of_speech} </Badge> )} </Group>

  &#x3C;Text c="dimmed" size="md">
    {entry.english_definition}
  &#x3C;/Text>

  {entry.pronunciation &#x26;&#x26; (
    &#x3C;Text size="sm" c="dimmed" mt="xs" fs="italic">
      /{entry.pronunciation}/
    &#x3C;/Text>
  )}
&#x3C;/Card>

); }

  1. Custom Hook

// src/hooks/useDictionary.ts import { useState, useCallback } from 'react'; import axios from 'axios';

interface DictionaryEntry { _id: string; chuukese_word: string; english_definition: string; part_of_speech?: string; grammar_type?: string; }

interface UseDictionaryResult { entries: DictionaryEntry[]; loading: boolean; error: string | null; search: (query: string) => Promise<void>; getEntry: (word: string) => Promise<DictionaryEntry | null>; }

export function useDictionary(): UseDictionaryResult { const [entries, setEntries] = useState<DictionaryEntry[]>([]); const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null);

const search = useCallback(async (query: string) => { if (!query.trim()) { setEntries([]); return; }

setLoading(true);
setError(null);

try {
  const response = await axios.get&#x3C;{ entries: DictionaryEntry[] }>(
    `/api/dictionary/search`,
    { params: { query } }
  );
  setEntries(response.data.entries);
} catch (err) {
  setError(err instanceof Error ? err.message : 'Search failed');
  setEntries([]);
} finally {
  setLoading(false);
}

}, []);

const getEntry = useCallback(async (word: string): Promise<DictionaryEntry | null> => { try { const response = await axios.get<DictionaryEntry>( /api/dictionary/entry/${encodeURIComponent(word)} ); return response.data; } catch { return null; } }, []);

return { entries, loading, error, search, getEntry }; }

  1. Context Provider

// src/contexts/AuthContext.tsx import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; import axios from 'axios';

interface User { email: string; name: string; role: string; }

interface AuthContextType { user: User | null; loading: boolean; login: (email: string) => Promise<void>; logout: () => Promise<void>; isAuthenticated: boolean; }

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(true);

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

const checkAuth = async () => { try { const response = await axios.get<User>('/api/auth/me'); setUser(response.data); } catch { setUser(null); } finally { setLoading(false); } };

const login = async (email: string) => { await axios.post('/api/auth/magic-link', { email }); };

const logout = async () => { await axios.post('/api/auth/logout'); setUser(null); };

return ( <AuthContext.Provider value={{ user, loading, login, logout, isAuthenticated: !!user, }} > {children} </AuthContext.Provider> ); }

export function useAuth() { const context = useContext(AuthContext); if (context === undefined) { throw new Error('useAuth must be used within an AuthProvider'); } return context; }

API Integration

Axios Setup

// src/api/client.ts import axios from 'axios';

const apiClient = axios.create({ baseURL: '/api', headers: { 'Content-Type': 'application/json', }, withCredentials: true, });

// Request interceptor apiClient.interceptors.request.use((config) => { // Add any auth headers if needed return config; });

// Response interceptor apiClient.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { // Handle unauthorized window.location.href = '/login'; } return Promise.reject(error); } );

export default apiClient;

Type-Safe API Calls

// src/api/translation.ts import apiClient from './client';

interface TranslationRequest { text: string; direction: 'chk_to_en' | 'en_to_chk'; }

interface TranslationResponse { original: string; translated: string; confidence: number; model_used: string; }

export async function translate(request: TranslationRequest): Promise<TranslationResponse> { const response = await apiClient.post<TranslationResponse>('/translate', request); return response.data; }

export async function translateBatch( texts: string[], direction: 'chk_to_en' | 'en_to_chk' ): Promise<TranslationResponse[]> { const response = await apiClient.post<TranslationResponse[]>('/translate/batch', { texts, direction, }); return response.data; }

Chuukese Text Handling

Font Configuration

/* src/index.css */ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;600;700&#x26;display=swap');

:root { font-family: 'Noto Sans', 'Arial Unicode MS', sans-serif; }

/* Chuukese text styling */ .chuukese-text { font-family: 'Noto Sans', 'Arial Unicode MS', sans-serif; font-feature-settings: 'kern' 1, 'liga' 1; line-height: 1.6; }

Text Component with Accent Support

// src/components/ChuukeseText.tsx import { Text, TextProps } from '@mantine/core';

interface ChuukeseTextProps extends TextProps { children: React.ReactNode; }

export function ChuukeseText({ children, ...props }: ChuukeseTextProps) { return ( <Text {...props} style={{ fontFamily: "'Noto Sans', 'Arial Unicode MS', sans-serif", fontFeatureSettings: "'kern' 1, 'liga' 1", ...props.style, }} > {children} </Text> ); }

Routing

Router Setup

// src/App.tsx import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import { Layout } from './components/Layout'; import { DictionaryPage } from './pages/DictionaryPage'; import { TranslationPage } from './pages/TranslationPage'; import { GrammarPage } from './pages/GrammarPage'; import { LoginPage } from './pages/LoginPage'; import { useAuth } from './contexts/AuthContext';

function ProtectedRoute({ children }: { children: React.ReactNode }) { const { isAuthenticated, loading } = useAuth();

if (loading) return <LoadingScreen />; if (!isAuthenticated) return <Navigate to="/login" />;

return <>{children}</>; }

export function App() { return ( <BrowserRouter> <Routes> <Route path="/login" element={<LoginPage />} /> <Route path="/" element={<Layout />}> <Route index element={<DictionaryPage />} /> <Route path="translate" element={<TranslationPage />} /> <Route path="grammar" element={<GrammarPage />} /> <Route path="admin/*" element={ <ProtectedRoute> <AdminRoutes /> </ProtectedRoute> } /> </Route> </Routes> </BrowserRouter> ); }

Development Commands

Install dependencies

npm install

Start dev server

npm run dev

Build for production

npm run build

Preview production build

npm run preview

Lint code

npm run lint

Type check

npx tsc --noEmit

Best Practices

TypeScript

  • Enable strict mode: Catch more errors at compile time

  • Define interfaces: Type all API responses and component props

  • Avoid any : Use proper types or unknown

  • Use type guards: Narrow types safely

React

  • Use functional components: Hooks for all state management

  • Memoize expensive operations: useMemo , useCallback

  • Split components: Keep components focused and reusable

  • Handle loading and error states: Always show feedback

Mantine

  • Use theme tokens: Don't hardcode colors or spacing

  • Leverage built-in components: Avoid custom implementations

  • Use compound components: Group related components logically

  • Dark mode first: Test in both color schemes

Dependencies

{ "dependencies": { "@mantine/core": "^8.3.10", "@mantine/hooks": "^8.3.10", "@mantine/notifications": "^8.3.10", "@mantine/modals": "^8.3.10", "@tabler/icons-react": "^3.35.0", "axios": "^1.13.2", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.10.1" }, "devDependencies": { "@vitejs/plugin-react": "^5.1.1", "typescript": "~5.9.3", "vite": "^7.2.4" } }

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

python-venv-management

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code-documentation-standards

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

flask-api-development

No summary provided by upstream source.

Repository SourceNeeds Review