# Mobile Integration - Web + Mobile Shared Architecture

## Vue d'Ensemble

Cette template implémente une architecture **Web + Mobile** où la **business logic est 100% partagée** entre Next.js (web) et React Native (mobile).

## Architecture Partagée

```
apps/
├── web/                    # Next.js 15
│   └── app/                # App Router
│       └── (routes)/       # Pages web
│
└── mobile/                 # React Native + Expo
    └── app/                # Expo Router
        └── (tabs)/         # Navigation mobile

packages/                   # 👇 100% PARTAGÉ
├── domain/                 # Business logic (ZERO deps)
│   ├── entities/           # User, Product
│   ├── value-objects/      # Email, Money
│   └── use-cases/          # CreateUser, GetUserById
│
├── infrastructure/         # Implémentations
│   └── database/           # Prisma repositories
│
├── api/                    # tRPC routers
├── validators/             # Zod schemas
└── api-client/             # tRPC client
```

## Code 100% Partagé

### 1. Domain Layer

Le domain layer fonctionne **identiquement** sur web et mobile:

```typescript
// packages/domain/src/use-cases/CreateUser.ts
// ✅ Fonctionne sur Node.js, Browser, React Native
export class CreateUser {
  constructor(private userRepository: IUserRepository) {}

  async execute(input: CreateUserInput): Promise<Result<User, DomainError>> {
    const emailResult = Email.create(input.email);
    if (isFailure(emailResult)) return emailResult;

    const userResult = User.create({
      id: crypto.randomUUID(),
      email: emailResult.value,
      name: input.name
    });

    if (isFailure(userResult)) return userResult;

    return this.userRepository.save(userResult.value);
  }
}
```

**Utilisation identique**:

```typescript
// Web (Next.js)
const result = await useCase.execute({ email: 'test@example.com', name: 'John' });

// Mobile (React Native)
const result = await useCase.execute({ email: 'test@example.com', name: 'John' });
```

### 2. tRPC API

L'API tRPC est appelée depuis web ET mobile:

```typescript
// packages/api/src/routers/user.ts
export const userRouter = router({
  create: publicProcedure.input(createUserSchema).mutation(async ({ input }) => {
    const useCase = new CreateUser(userRepository);
    const result = await useCase.execute(input);

    if (isFailure(result)) {
      throw new TRPCError({ code: 'BAD_REQUEST', message: result.error.message });
    }

    return result.value.toJSON();
  })
});
```

**Appel depuis web**:
```typescript
// apps/web/app/api/trpc/[trpc]/route.ts
import { appRouter } from '@crafted/api';
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';

export const GET = (req: Request) =>
  fetchRequestHandler({ endpoint: '/api/trpc', req, router: appRouter });
```

**Appel depuis mobile**:
```typescript
// apps/mobile/utils/trpc.ts
import { createApiClient } from '@crafted/api-client';

export const trpc = createApiClient('https://api.myapp.com/trpc');
```

### 3. Validators (Zod)

Les schémas Zod sont partagés:

```typescript
// packages/validators/src/user.ts
export const createUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(2)
});

// ✅ Utilisé sur web et mobile
const result = createUserSchema.safeParse({ email: 'test@example.com', name: 'John' });
```

## Code Platform-Specific

### UI Layer (Séparée)

```
packages/ui/
├── primitives/             # Logique UI partagée (hooks, utils)
├── web/                    # shadcn/ui (React + Tailwind)
└── mobile/                 # React Native components (NativeWind)
```

**Exemple: Button**

```typescript
// packages/ui/web/src/components/button.tsx
import { cn } from '../lib/utils';

export function Button({ children, ...props }) {
  return (
    <button
      className={cn('px-4 py-2 bg-blue-500 text-white rounded')}
      {...props}
    >
      {children}
    </button>
  );
}

// packages/ui/mobile/src/components/button.tsx
import { TouchableOpacity, Text } from 'react-native';
import { styled } from 'nativewind';

const StyledButton = styled(TouchableOpacity);
const StyledText = styled(Text);

export function Button({ children, ...props }) {
  return (
    <StyledButton className="px-4 py-2 bg-blue-500 rounded" {...props}>
      <StyledText className="text-white">{children}</StyledText>
    </StyledButton>
  );
}
```

### Storage Abstraction

```typescript
// packages/storage/src/index.ts
export interface IStorage {
  getItem(key: string): Promise<string | null>;
  setItem(key: string, value: string): Promise<void>;
  removeItem(key: string): Promise<void>;
}

// Web implementation
export class WebStorage implements IStorage {
  async getItem(key: string) {
    return localStorage.getItem(key);
  }
  async setItem(key: string, value: string) {
    localStorage.setItem(key, value);
  }
  async removeItem(key: string) {
    localStorage.removeItem(key);
  }
}

// Mobile implementation (in apps/mobile)
import * as SecureStore from 'expo-secure-store';

export class MobileStorage implements IStorage {
  async getItem(key: string) {
    return await SecureStore.getItemAsync(key);
  }
  async setItem(key: string, value: string) {
    await SecureStore.setItemAsync(key, value);
  }
  async removeItem(key: string) {
    await SecureStore.deleteItemAsync(key);
  }
}
```

## Setup Mobile App

### 1. Installation

```bash
# Install dependencies
pnpm install

# Setup Expo
cd apps/mobile
npx expo install
```

### 2. Configuration

```typescript
// apps/mobile/app.json
{
  "expo": {
    "name": "My SaaS",
    "slug": "my-saas",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "scheme": "mysaas",
    "platforms": ["ios", "android"],
    "plugins": [
      "expo-router",
      "expo-secure-store"
    ]
  }
}
```

### 3. Navigation (Expo Router)

```typescript
// apps/mobile/app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';

export default function TabLayout() {
  return (
    <Tabs>
      <Tabs.Screen name="index" options={{ title: 'Home' }} />
      <Tabs.Screen name="profile" options={{ title: 'Profile' }} />
    </Tabs>
  );
}
```

### 4. tRPC Client

```typescript
// apps/mobile/utils/trpc.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '@crafted/api';
import superjson from 'superjson';

export const trpc = createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: process.env.EXPO_PUBLIC_API_URL + '/api/trpc',
      transformer: superjson
    })
  ]
});
```

### 5. Usage dans un Screen

```typescript
// apps/mobile/app/(tabs)/index.tsx
import { View, Text, Button } from 'react-native';
import { trpc } from '../../utils/trpc';

export default function HomeScreen() {
  const createUser = async () => {
    try {
      const result = await trpc.user.create.mutate({
        email: 'test@example.com',
        name: 'John Doe'
      });
      console.log('User created:', result);
    } catch (error) {
      console.error('Error:', error);
    }
  };

  return (
    <View className="flex-1 items-center justify-center">
      <Text className="text-xl font-bold">Home</Text>
      <Button title="Create User" onPress={createUser} />
    </View>
  );
}
```

## Testing Strategy

### Unit Tests (Shared)

```typescript
// packages/domain/__tests__/use-cases/CreateUser.test.ts
// ✅ Tests partagés entre web et mobile
describe('CreateUser Use-Case', () => {
  it('should create user with valid input', async () => {
    const mockRepository = createMockRepository();
    const useCase = new CreateUser(mockRepository);

    const result = await useCase.execute({
      email: 'test@example.com',
      name: 'John'
    });

    expect(isSuccess(result)).toBe(true);
  });
});
```

### E2E Tests

**Web (Playwright)**:
```typescript
// apps/web/e2e/auth.spec.ts
test('should create account', async ({ page }) => {
  await page.goto('/signup');
  await page.fill('[name="email"]', 'test@example.com');
  await page.fill('[name="name"]', 'John Doe');
  await page.click('button[type="submit"]');
  await expect(page).toHaveURL('/dashboard');
});
```

**Mobile (Detox)**:
```typescript
// apps/mobile/e2e/auth.spec.ts
describe('Auth Flow', () => {
  it('should create account', async () => {
    await element(by.id('email-input')).typeText('test@example.com');
    await element(by.id('name-input')).typeText('John Doe');
    await element(by.id('submit-button')).tap();
    await expect(element(by.id('dashboard'))).toBeVisible();
  });
});
```

## Platform-Specific Features

### Deep Linking (Mobile)

```typescript
// apps/mobile/app.json
{
  "expo": {
    "scheme": "mysaas",
    "android": {
      "intentFilters": [
        {
          "action": "VIEW",
          "data": [{ "scheme": "https", "host": "myapp.com" }]
        }
      ]
    },
    "ios": {
      "associatedDomains": ["applinks:myapp.com"]
    }
  }
}
```

### Push Notifications (Mobile)

```typescript
// apps/mobile/utils/notifications.ts
import * as Notifications from 'expo-notifications';

export async function registerForPushNotifications() {
  const { status } = await Notifications.requestPermissionsAsync();

  if (status === 'granted') {
    const token = await Notifications.getExpoPushTokenAsync();
    // Send token to backend
    await trpc.user.updatePushToken.mutate({ token: token.data });
  }
}
```

### Offline Support (Mobile)

```typescript
// apps/mobile/utils/offline.ts
import NetInfo from '@react-native-community/netinfo';
import { useEffect, useState } from 'react';

export function useOfflineMode() {
  const [isOffline, setIsOffline] = useState(false);

  useEffect(() => {
    const unsubscribe = NetInfo.addEventListener(state => {
      setIsOffline(!state.isConnected);
    });

    return () => unsubscribe();
  }, []);

  return isOffline;
}
```

## Build & Deploy

### Web (Vercel)

```bash
# Build web app
pnpm --filter web build

# Deploy to Vercel
vercel deploy
```

### Mobile (EAS Build)

```bash
# Configure EAS
cd apps/mobile
eas build:configure

# Build for iOS
eas build --platform ios

# Build for Android
eas build --platform android

# Submit to stores
eas submit --platform ios
eas submit --platform android
```

## Best Practices

### ✅ DO

1. **Partager la business logic** dans `packages/domain/`
2. **Utiliser tRPC** pour l'API (type-safe web + mobile)
3. **Abstraire le storage** (localStorage vs SecureStore)
4. **Tester le domain** indépendamment de la plateforme
5. **Utiliser Result pattern** pour gestion d'erreurs

### ❌ DON'T

1. **Dupliquer la business logic** entre web et mobile
2. **Importer des deps platform-specific** dans le domain
3. **Utiliser fetch directement** (utiliser tRPC client)
4. **Ignorer les différences platform** (storage, navigation)
5. **Mélanger UI web et mobile** (garder séparé)

## Avantages de cette Architecture

✅ **DRY**: Business logic écrite une seule fois
✅ **Type-safe**: TypeScript end-to-end
✅ **Testable**: Domain testable sans UI
✅ **Maintenable**: Séparation claire web/mobile
✅ **Évolutive**: Facile d'ajouter features
✅ **Performance**: Shared bundle entre web/mobile

## Debugging

### Web
```bash
pnpm --filter web dev
# Open http://localhost:3000
```

### Mobile (iOS Simulator)
```bash
cd apps/mobile
npx expo start --ios
```

### Mobile (Android Emulator)
```bash
cd apps/mobile
npx expo start --android
```

### Mobile (Physical Device)
```bash
cd apps/mobile
npx expo start
# Scan QR code with Expo Go app
```

## Resources

- [Expo Documentation](https://docs.expo.dev/)
- [React Native](https://reactnative.dev/)
- [NativeWind](https://www.nativewind.dev/)
- [Expo Router](https://docs.expo.dev/router/introduction/)
- [tRPC React Native](https://trpc.io/docs/client/react/setup#3-add-trpc-providers)
