> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Frontend Setup

> Initial frontend setup with SonamuProvider

Learn how to configure `SonamuProvider`, the starting point for Sonamu frontend integration. Configure authentication, file uploads, and internationalization in one place.

## Setup Overview

<CardGroup cols={2}>
  <Card title="Auth Integration" icon="key">
    Connect UserService.useMe

    Login/logout flow
  </Card>

  <Card title="File Upload" icon="upload">
    FileService integration

    Auto-connect to useTypeForm
  </Card>

  <Card title="Internationalization" icon="language">
    SD function provider

    Type-safe translations
  </Card>

  <Card title="Global State" icon="globe">
    Context API based

    Access from all components
  </Card>
</CardGroup>

## Basic Setup

### 1. Create SonamuProvider Config File

Create `src/contexts/sonamu-provider.tsx` in your project.

```typescript theme={null}
// src/contexts/sonamu-provider.tsx
import {
  SonamuProvider as BaseSonamuProvider,
  useSonamuBaseContext,
} from "@sonamu-kit/react-components";
import { type SonamuFile } from "@sonamu-kit/react-components";
import { type BetterAuthClientOptions } from "better-auth/client";
import { inferAdditionalFields } from "better-auth/client/plugins";
import { type ReactNode } from "react";

import { type MergedDictionary } from "@/i18n/sd.generated";
import { SD } from "@/i18n/sd.generated";
import { FileService } from "@/services/services.generated";

// Define better-auth client options
const authOptions = {
  plugins: [
    inferAdditionalFields({
      user: {
        role: { type: "string" },
      },
    }),
  ],
} satisfies BetterAuthClientOptions;

// Typed useSonamuContext
export function useSonamuContext() {
  return useSonamuBaseContext<MergedDictionary, typeof authOptions>();
}

export function SonamuProvider({ children }: { children: ReactNode }) {
  const uploadMutation = FileService.useUploadMutation();

  const uploader = async (files: File[]): Promise<SonamuFile[]> => {
    if (files.length === 0) {
      return [];
    }

    const result = await uploadMutation.mutateAsync({ files });
    return result.files;
  };

  return (
    <BaseSonamuProvider<MergedDictionary> authOptions={authOptions} uploader={uploader} SD={SD}>
      {children}
    </BaseSonamuProvider>
  );
}
```

### 2. Apply to App

Configure `SonamuProvider` in `__root.tsx`.

```typescript theme={null}
// src/routes/__root.tsx
import { QueryClientProvider } from "@tanstack/react-query";
import { createRootRouteWithContext, Outlet } from "@tanstack/react-router";
import { SonamuProvider } from "@/contexts/sonamu-provider";

export const Route = createRootRouteWithContext()({
  component: RootComponent,
});

function RootComponent() {
  const { queryClient } = Route.useRouteContext();

  return (
    <QueryClientProvider client={queryClient}>
      <SonamuProvider>
        <Outlet />
      </SonamuProvider>
    </QueryClientProvider>
  );
}
```

<Info>
  **Why create a separate SonamuProvider file?**

  `SonamuProvider` uses React hooks like `FileService.useUploadMutation()`, so it must be placed under `QueryClientProvider`.
  Separating it into its own file allows you to co-locate `authOptions`, `useSonamuContext`, and type definitions.
</Info>

## Auth Configuration

Sonamu uses [better-auth](https://www.better-auth.com/) for authentication. Pass `authOptions` to `SonamuProvider` and a better-auth client is created internally.

### Defining authOptions

Define better-auth client options. You can extend user fields through plugins.

```typescript theme={null}
import { type BetterAuthClientOptions } from "better-auth/client";
import { inferAdditionalFields } from "better-auth/client/plugins";

const authOptions = {
  plugins: [
    inferAdditionalFields({
      user: {
        role: { type: "string" },
        // Add additional fields as needed
      },
    }),
  ],
} satisfies BetterAuthClientOptions;
```

<Info>
  **What is `inferAdditionalFields`?**

  It adds project-specific fields (like `role`) to the client-side User type beyond better-auth defaults (`id`, `name`, `email`, etc.).
  These should match the user schema defined on the server.
</Info>

### Login Flow

Use the better-auth client's `signIn` method.

```typescript theme={null}
import { useSonamuContext } from "@/contexts/sonamu-provider";

export function LoginPage() {
  const { auth } = useSonamuContext();
  const [email, setEmail] = React.useState("");
  const [password, setPassword] = React.useState("");

  const handleSubmit = async () => {
    const result = await auth.signIn.email({ email, password });

    if (result.error) {
      alert(result.error.message);
      return;
    }
  };

  return (
    <form onSubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
      <button type="submit">Login</button>
    </form>
  );
}
```

### Logout Flow

```typescript theme={null}
const handleLogout = () => {
  auth.signOut();
};
```

### Using Session

Access the auth client via `useSonamuContext` in components, and use `auth.useSession()` to query the current session.

```typescript theme={null}
import { useSonamuContext } from "@/contexts/sonamu-provider";

export function Header() {
  const { auth } = useSonamuContext();
  const session = auth.useSession();
  const user = session.data?.user ?? null;

  if (session.isPending) {
    return <div>Loading...</div>;
  }

  if (!user) {
    return <a href="/login">Login</a>;
  }

  return (
    <div>
      <span>Welcome, {user.name}</span>
      <button onClick={() => auth.signOut()}>Logout</button>
    </div>
  );
}
```

## File Upload Configuration

### Uploader Interface

```typescript theme={null}
type Uploader = (files: File[], params?: UploadParams) => Promise<SonamuFile[]>;

interface SonamuFileExtend {}

type SonamuFileBase = {
  name: string;
  url: string;
  mime_type: string;
  size: number;
};

interface SonamuFile extends SonamuFileBase, SonamuFileExtend {}

interface UploadParams {}
```

<Info>
  **Extending SonamuFile and UploadParams**

  Both `SonamuFile` and `UploadParams` can be extended with project-specific fields via TypeScript declaration merging.

  ```typescript theme={null}
  // src/types/sonamu-file-extend.d.ts
  import "@sonamu-kit/react-components";

  declare module "@sonamu-kit/react-components" {
    interface SonamuFileExtend {
      thumbnailUrl?: string;
      category?: string;
    }

    interface UploadParams {
      directory?: string;
      maxWidth?: number;
    }
  }
  ```

  Since the frontend imports `SonamuFile` and `UploadParams` from `@sonamu-kit/react-components`, the declaration merging target must be the same module.
</Info>

### FileService Integration

```typescript theme={null}
const uploadMutation = FileService.useUploadMutation();

const uploader_config = async (files: File[]): Promise<SonamuFile[]> => {
  if (files.length === 0) {
    return [];
  }

  const result = await uploadMutation.mutateAsync({ files });
  return result.files;
};
```

**How it works**:

1. Upload files to backend with `mutateAsync`
2. Return uploaded file info (`SonamuFile[]`)
3. `useTypeForm` automatically uses this uploader

### Auto-integration with useTypeForm

When `uploader` is configured, `useTypeForm`'s `submit` automatically uploads files.

```typescript theme={null}
import { useTypeForm } from "@sonamu-kit/react-components";
import { FileInput } from "@sonamu-kit/react-components/components";

export function UserForm() {
  const { form, setForm, register, submit } = useTypeForm(UserSaveParams, {
    username: "",
    avatar: null,  // SonamuFile | null
  });

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        submit(async (formData) => {
          // formData.avatar is already uploaded as SonamuFile
          await UserService.save({ params: formData });
        });
      }}
    >
      <Input {...register("username")} />

      {/* FileInput: File → SonamuFile conversion handled by submit */}
      <FileInput {...register("avatar")} />

      <button type="submit">Save</button>
    </form>
  );
}
```

<Info>
  **Auto-upload Mechanism**

  The `submit` function internally calls `traverseAndUploadFiles` to find and upload all `File` objects.
  Files in nested objects or arrays are automatically handled.
</Info>

### Custom Upload Logic

You can implement your own if using a different upload service.

```typescript theme={null}
// AWS S3 direct upload example
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

const s3Client = new S3Client({ region: "ap-northeast-2" });

const uploader_config = async (files: File[]): Promise<SonamuFile[]> => {
  const uploadPromises = files.map(async (file) => {
    const key = `uploads/${Date.now()}-${file.name}`;

    await s3Client.send(
      new PutObjectCommand({
        Bucket: "my-bucket",
        Key: key,
        Body: file,
        ContentType: file.type,
      }),
    );

    return {
      name: file.name,
      url: `https://my-bucket.s3.amazonaws.com/${key}`,
      mime_type: file.type,
      size: file.size,
    };
  });

  return Promise.all(uploadPromises);
};
```

## Internationalization (SD)

### SD Function

The `SD` (Sonamu Dictionary) function provides type-safe translations.

```typescript theme={null}
const sd_config = <K extends DictKey>(key: K): ReturnType<typeof SD<K>> => SD(key);
```

### Usage Example

```typescript theme={null}
import { useSonamuContext } from "@sonamu-kit/react-components";

export function WelcomeMessage() {
  const { SD } = useSonamuContext();

  return (
    <div>
      <h1>{SD("common.welcome")}</h1>
      <p>{SD("user.login.prompt")}</p>
    </div>
  );
}
```

<Info>
  **Why provide SD through Context?**

  While you can import and use it directly, using Context makes it easy to add runtime locale switching or dynamic dictionary loading in the future.
</Info>

## Type Safety

### Specify Generic Type

Specifying the Dictionary type in `BaseSonamuProvider` makes the `SD` function type-safe.

```typescript theme={null}
import type { MergedDictionary } from "@/i18n/sd.generated";

<BaseSonamuProvider<MergedDictionary> authOptions={authOptions} uploader={uploader} SD={SD}>
  {children}
</BaseSonamuProvider>
```

### Auto-completion

```typescript theme={null}
// Specify Dictionary and authOptions types in useSonamuBaseContext.
const { SD } = useSonamuBaseContext<MergedDictionary, typeof authOptions>();

SD("
    ↓
    common.welcome
    common.save
    common.cancel
    user.login.failed
    user.logout.failed
    entity.User.email
    ...
```

## Advanced Configuration

### Minimal Setup (Auth Only)

You can omit `uploader` if file uploads are not needed.

```typescript theme={null}
export function SonamuProvider({ children }: { children: ReactNode }) {
  return (
    <BaseSonamuProvider<MergedDictionary> authOptions={authOptions} SD={SD}>
      {children}
    </BaseSonamuProvider>
  );
  // uploader omitted (fallback function auto-configured)
}
```

<Warning>
  Omitting `uploader` will cause errors when using `FileInput`. Always configure it if file upload
  functionality is needed.
</Warning>

### Using Without Auth

If authentication isn't needed, `authOptions` can be omitted.

```typescript theme={null}
export function SonamuProvider({ children }: { children: ReactNode }) {
  return (
    <BaseSonamuProvider<MergedDictionary> SD={SD}>
      {children}
    </BaseSonamuProvider>
  );
}
```

### Display Loading State

```typescript theme={null}
import { useSonamuContext } from "@/contexts/sonamu-provider";

export function App({ children }: { children: React.ReactNode }) {
  const { auth } = useSonamuContext();
  const session = auth.useSession();
  const user = session.data?.user ?? null;

  // Initial loading
  if (session.isPending) {
    return (
      <div className="flex items-center justify-center h-screen">
        <Spinner />
      </div>
    );
  }

  // Authentication required
  if (!user) {
    return <LoginPage />;
  }

  // Authenticated
  return children;
}
```

### Customize Redirect

You can dynamically determine the redirect path after successful login.

```typescript theme={null}
const handleLogin = async (email: string, password: string) => {
  const result = await auth.signIn.email({ email, password });

  if (result.error) {
    alert(result.error.message);
    return;
  }

  // Navigate to different pages based on role
  const session = await auth.getSession();
  if (session.data?.user.role === "admin") {
    navigate({ to: "/admin", replace: true });
  } else {
    navigate({ to: "/dashboard", replace: true });
  }
};
```

## Troubleshooting

### "uploader is not configured" Error

```
Error: [SonamuProvider] uploader is not configured.
Please provide uploader configuration to SonamuProvider.
```

**Solution**: Pass `uploader` to `SonamuProvider`.

### "auth is not configured" Error

```
Error: [SonamuProvider] auth is not configured.
Please provide auth configuration to SonamuProvider.
```

**Solution**: Pass `authOptions` to `SonamuProvider`.

### Cannot Find QueryClient

```
Error: useQueryClient must be used within a QueryClientProvider
```

**Solution**: Place `SonamuProvider` inside `QueryClientProvider`.

```typescript theme={null}
<QueryClientProvider client={queryClient}>
  <SonamuProvider {...config}>
    {children}
  </SonamuProvider>
</QueryClientProvider>
```

## Next Steps

* [useTypeForm](/en/frontend-integration/generated-services/using-services#usetypeform) - Form management and file uploads
* [useListParams](/en/frontend-integration/core-hooks/use-list-params) - List filtering
* [Auth API Development](/en/api-development/authentication/setup) - Backend auth setup
* [File Upload API](/en/advanced-features/storage/file-operations) - Backend storage setup
