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

# Frontend (Admin + Website)

> İki React 19 + Vite SPA, axios interceptor, Keycloak entegrasyonu, build

Projede **iki bağımsız React SPA** vardır. İkisi de aynı stack üzerine kuruludur ama ayrı Node.js projeleri, ayrı Dockerfile'lar ve ayrı Keycloak realm'leridir.

<Warning>
  Frontend **React**'tır, Vue değil. Her iki SPA da React 19 + Vite 6 + TypeScript 5.8 kullanır.
</Warning>

| SPA                | Klasör                                              | Dev port | Keycloak realm               | Client            |
| ------------------ | --------------------------------------------------- | -------- | ---------------------------- | ----------------- |
| Website (vatandaş) | `src/DiyanetCleanArchitecture.Presentation.Website` | `3000`   | `diyanet-vatandas-dev-realm` | `diyanet-website` |
| Admin (backoffice) | `src/DiyanetCleanArchitecture.Presentation.Admin`   | `3001`   | `diyanet-yonetim-dev-realm`  | `diyanet-admin`   |

## Teknoloji yığını

* **React 19** + **react-dom 19**, **Vite 6**, **TypeScript 5.8**
* **react-router-dom 7** — routing (Admin'de `basename` `import.meta.env.BASE_URL`'den okunur)
* **zustand 5** — global state (`store/`)
* **@tanstack/react-query 5** — server state / cache
* **axios 1.8** — HTTP client (`lib/axios.ts`)
* **keycloak-js 26** — SSO / OAuth2 + PKCE
* **Radix UI** primitive'leri + **Tailwind CSS 3** + **class-variance-authority** + **tailwind-merge**
* **lucide-react** (ikon), **sonner** (toast), **recharts** (grafik, Admin), **@tanstack/react-table** (tablo, Admin)

## Klasör yapısı

```text theme={null}
src/
├── pages/        # route bileşenleri
├── components/   # paylaşılan UI (Radix + Tailwind)
├── lib/          # axios.ts, keycloak.ts, api-client.ts (NSwag üretimi)
├── store/        # zustand store'ları (auth.store.ts, me.store.ts)
├── router/       # react-router yapılandırması (basename)
└── api/          # endpoint sarmalayıcıları
```

## `lib/axios.ts` — merkezi HTTP client

Tek bir Axios instance (`baseURL = import.meta.env.VITE_API_BASE_URL`, `timeout: 30s`) iki interceptor ile gelir.

**Request interceptor** — her isteğe `X-App-Id` header'ı (`VITE_APP_ID`) ve Keycloak Bearer token'ı ekler. Token 30 saniyeden kısa sürede expire olacaksa otomatik yeniler:

```ts theme={null}
if (keycloak.authenticated) {
  await keycloak.updateToken(30)              // <30s kaldıysa yenile
  config.headers.Authorization = `Bearer ${keycloak.token}`
}
```

Refresh başarısız olursa `safeKeycloakLogin()` çağrılır — bu, 10 saniyelik throttle ile **redirect-loop**'u önler (yanlış audience/issuer durumunda `login → SSO → 401 → login` döngüsüne girmeyi engeller).

**Response interceptor** — RFC 7807 (`application/problem+json`) hatalarını yakalar, status'a göre toast gösterir ve `ProblemDetailsError` fırlatır:

* **401** — token reddedildi. **Otomatik redirect YOK** (loop riski); kullanıcıya "Giriş Yap" action butonlu toast.
* **403** — ProblemDetails'taki `reason` alanı `not_invited` veya `unknown_realm` ise (kullanıcı Keycloak'a girdi ama bu app'te davetli/tanımlı değil) "Çıkış Yap" önerili özel mesaj gösterilir. Diğer 403'ler "yetkiniz yok" toast'ı.
* **422** — `errors` alanı varsa toast atılmaz (component inline gösterir).
* **400 / 404 / 409 / 429 / 5xx** — duruma özel toast'lar.

Tüm hatalar bir diagnostic buffer'a (`supportContext`) kaydedilir — destek modalı snapshot'ında `traceId` ile görünür.

## `store/auth.store.ts` — Keycloak oturum yönetimi

zustand store, uygulama açılırken bir kez `initialize()` çalıştırır:

```ts theme={null}
const authenticated = await keycloak.init({
  onLoad: 'check-sso',                                       // mevcut SSO oturumunu kontrol et
  silentCheckSsoRedirectUri: `${APP_BASE_URL}silent-check-sso.html`,
  pkceMethod: 'S256',
  checkLoginIframe: false,                                   // token'lar memory'de
})
```

Authenticated ise:

1. `provisionKeycloakSession(keycloak.token)` — backend'e token gönderilir; kullanıcı local DB'ye eşlenir (davetli ise Draft/Pending → Active). **Bu çağrı olmadan korumalı her endpoint 403 döner** (`ActiveAccountAuthorizationHandler`).
2. `useMeStore.getState().bootstrap()` — `/me` çekilir, tüm sayfalar (avatar, profil, role-bazlı menü) buradan okur.

Provision 403 dönerse `accessDeniedReason` ayarlanır: `not_invited` / `account_blocked` / `unknown_realm` / `provision_error` / `unknown`. Roller, hem `realm_access.roles` hem `resource_access[clientId].roles`'ten birleştirilir (`SuperAdmin`/`Admin`/`Staff` client-level gelir).

## `VITE_*` ortam değişkenleri

Vite, `VITE_` öneki ile başlayan env'leri build sırasında bundle'a gömer:

| Değişken                  | Açıklama                                                                         |
| ------------------------- | -------------------------------------------------------------------------------- |
| `VITE_API_BASE_URL`       | API adresi (dev: `http://localhost:5005`; stage/prod: boş — aynı origin `/api/`) |
| `VITE_KEYCLOAK_URL`       | Tarayıcının ulaşacağı Keycloak URL'i (external)                                  |
| `VITE_KEYCLOAK_REALM`     | Realm adı                                                                        |
| `VITE_KEYCLOAK_CLIENT_ID` | Client ID                                                                        |
| `VITE_APP_ID`             | `X-App-Id` header'ı (audit/proje izleme)                                         |
| `VITE_ADMIN_ROLES`        | Admin erişimi için izinli roller (Admin SPA, örn. `Admin,SuperAdmin`)            |
| `VITE_BASE_PATH`          | SPA base path (Admin) — dev `/`, stage/prod `/admin/`                            |

## Dockerfile (iki aşamalı)

`node:22-alpine` ile build, `nginx:1.27-alpine` ile serve:

```dockerfile theme={null}
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ARG VITE_API_BASE_URL
ARG VITE_BASE_PATH=/          # Admin: stage/prod compose '/admin/' geçer
RUN npm run build

FROM nginx:1.27-alpine AS runtime
COPY --from=builder /app/dist /usr/share/nginx/html
# Admin: nginx.conf.template runtime'da BASE_PATH ile envsubst'lanır
EXPOSE 80
```

<Note>
  Admin SPA prod'da `/admin/` altında serve edilir. `VITE_BASE_PATH=/admin/` ile build edilir; `vite.config.ts` bunu `base` olarak okur ve nginx `BASE_PATH` env'iyle template'i render eder. Tüm Keycloak redirect URI'ları origin + base path kullanır — aksi halde logout sonrası kullanıcı website'a düşer ve `silent-check-sso.html` 404 olur.
</Note>

## npm script'leri

```bash theme={null}
npm run dev           # Vite dev server (Website 3000 / Admin 3001), /api proxy → localhost:5005
npm run build         # tsc -b && vite build → dist/
npm run preview       # build çıktısını serve et
npm run generate-api  # NSwag ile typed Axios client üret (API çalışırken)
```

## Sonraki adımlar

<CardGroup cols={2}>
  <Card title="OpenAPI / client üretimi" href="/api/openapi">
    npm run generate-api detayları.
  </Card>

  <Card title="Controller'lar" href="/api/controllers">
    SPA'ların çağırdığı endpoint aileleri.
  </Card>

  <Card title="Keycloak ortamları" href="/keycloak/environments">
    Realm ve client yapılandırması.
  </Card>

  <Card title="Dev Docker" href="/operations/docker-dev">
    SPA'ları container'da çalıştırma.
  </Card>
</CardGroup>
