refactor: replace hand-rolled OIDC with @logto/react SDK
All checks were successful
CI / build (push) Successful in 38s
CI / docker (push) Successful in 48s

The hand-rolled OIDC flow (manual PKCE, token exchange, URL
construction) was fragile and accumulated multiple bugs. Replaced
with the official @logto/react SDK which handles PKCE, token
exchange, storage, and refresh automatically.

- Add @logto/react SDK dependency
- Add LogtoProvider with runtime config in main.tsx
- Add TokenSync component bridging SDK tokens to API client
- Add useAuth hook replacing Zustand auth store
- Simplify LoginPage to signIn(), CallbackPage to useHandleSignInCallback()
- Delete pkce.ts and auth-store.ts (replaced by SDK)
- Fix react-router-dom → react-router imports in page files
- All 17 React Query hooks unchanged (token provider pattern)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-05 01:17:47 +02:00
parent 84667170f1
commit 0843a33383
19 changed files with 320 additions and 224 deletions

View File

@@ -1,5 +1,5 @@
import React, { useRef, useState } from 'react';
import { useNavigate, useParams, Link } from 'react-router-dom';
import { useNavigate, useParams, Link } from 'react-router';
import {
Badge,
Button,
@@ -17,7 +17,7 @@ import {
useToast,
} from '@cameleer/design-system';
import type { Column, LogEntry as DSLogEntry } from '@cameleer/design-system';
import { useAuthStore } from '../auth/auth-store';
import { useAuth } from '../auth/useAuth';
import {
useApp,
useDeployment,
@@ -118,7 +118,7 @@ export function AppDetailPage() {
const { envId = '', appId = '' } = useParams<{ envId: string; appId: string }>();
const navigate = useNavigate();
const { toast } = useToast();
const tenantId = useAuthStore((s) => s.tenantId);
const { tenantId } = useAuth();
const { canManageApps, canDeploy } = usePermissions();
// Active tab

View File

@@ -1,5 +1,5 @@
import React, { useState, useCallback, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import {
Badge,
Button,
@@ -8,7 +8,7 @@ import {
KpiStrip,
Spinner,
} from '@cameleer/design-system';
import { useAuthStore } from '../auth/auth-store';
import { useAuth } from '../auth/useAuth';
import { useTenant, useEnvironments, useApps } from '../api/hooks';
import { RequirePermission } from '../components/RequirePermission';
import type { EnvironmentResponse, AppResponse } from '../types/api';
@@ -41,7 +41,7 @@ function tierColor(tier: string): 'primary' | 'success' | 'warning' | 'error' {
export function DashboardPage() {
const navigate = useNavigate();
const tenantId = useAuthStore((s) => s.tenantId);
const { tenantId } = useAuth();
const { data: tenant, isLoading: tenantLoading } = useTenant(tenantId ?? '');
const { data: environments, isLoading: envsLoading } = useEnvironments(tenantId ?? '');

View File

@@ -1,5 +1,5 @@
import React, { useRef, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router';
import {
Badge,
Button,
@@ -15,7 +15,7 @@ import {
useToast,
} from '@cameleer/design-system';
import type { Column } from '@cameleer/design-system';
import { useAuthStore } from '../auth/auth-store';
import { useAuth } from '../auth/useAuth';
import {
useEnvironments,
useUpdateEnvironment,
@@ -77,7 +77,7 @@ export function EnvironmentDetailPage() {
const navigate = useNavigate();
const { envId } = useParams<{ envId: string }>();
const { toast } = useToast();
const tenantId = useAuthStore((s) => s.tenantId);
const { tenantId } = useAuth();
const { data: environments, isLoading: envsLoading } = useEnvironments(tenantId ?? '');
const environment = environments?.find((e) => e.id === envId);

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import {
Badge,
Button,
@@ -13,7 +13,7 @@ import {
useToast,
} from '@cameleer/design-system';
import type { Column } from '@cameleer/design-system';
import { useAuthStore } from '../auth/auth-store';
import { useAuth } from '../auth/useAuth';
import { useEnvironments, useCreateEnvironment } from '../api/hooks';
import { RequirePermission } from '../components/RequirePermission';
import type { EnvironmentResponse } from '../types/api';
@@ -67,7 +67,7 @@ const columns: Column<TableRow>[] = [
export function EnvironmentsPage() {
const navigate = useNavigate();
const { toast } = useToast();
const tenantId = useAuthStore((s) => s.tenantId);
const { tenantId } = useAuth();
const { data: environments, isLoading } = useEnvironments(tenantId ?? '');
const createMutation = useCreateEnvironment(tenantId ?? '');

View File

@@ -5,7 +5,7 @@ import {
EmptyState,
Spinner,
} from '@cameleer/design-system';
import { useAuthStore } from '../auth/auth-store';
import { useAuth } from '../auth/useAuth';
import { useLicense } from '../api/hooks';
const FEATURE_LABELS: Record<string, string> = {
@@ -39,7 +39,7 @@ function daysRemaining(expiresAt: string): number {
}
export function LicensePage() {
const tenantId = useAuthStore((s) => s.tenantId);
const { tenantId } = useAuth();
const { data: license, isLoading, isError } = useLicense(tenantId ?? '');
const [tokenExpanded, setTokenExpanded] = useState(false);