Skip to content

Commit d706cd9

Browse files
committed
feat(auth): naprawa rozłączania SSO i wersjonowanie aplikacji
- Wymuszenie wylogowania i czyszczenia sesji po rozłączeniu SSO - Stałe wyświetlanie opcji rekonfiguracji na ekranie logowania - Dodanie mechanizmu czyszczenia cache po aktualizacji wersji aplikacji - Poprawki lintera
1 parent 5915309 commit d706cd9

6 files changed

Lines changed: 158 additions & 37 deletions

File tree

app/(auth)/login/page.tsx

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ function LoginContent() {
2929
const [isLoading, setIsLoading] = useState(false);
3030
const [error, setError] = useState("");
3131
const [ssoHealthy, setSsoHealthy] = useState<boolean | null>(null);
32-
const [showReconfigure, setShowReconfigure] = useState(false);
32+
3333
const [ssoConfig, setSsoConfig] = useState<{
3434
centerUrl?: string;
3535
projectSlug?: string;
@@ -61,13 +61,8 @@ function LoginContent() {
6161
projectSlug: data.projectSlug,
6262
});
6363
}
64-
// Pokaż przycisk rekonfiguracji jeśli SSO nie skonfigurowane lub nie działa
65-
if (!data.configured || !data.healthy) {
66-
setShowReconfigure(true);
67-
}
6864
} catch {
6965
setSsoHealthy(false);
70-
setShowReconfigure(true);
7166
}
7267
}
7368
checkSSOHealth();
@@ -85,8 +80,6 @@ function LoginContent() {
8580
project_not_found: "Projekt nie istnieje w centrum logowania.",
8681
};
8782
setError(errorMessages[errorParam] || "Wystąpił nieznany błąd.");
88-
// Pokaż przycisk rekonfiguracji przy błędzie
89-
setShowReconfigure(true);
9083
}
9184
}, [searchParams]);
9285

@@ -193,31 +186,26 @@ function LoginContent() {
193186
</div>
194187
</div>
195188

196-
{/* Przycisk rekonfiguracji SSO - widoczny gdy problemy z połączeniem */}
197-
{showReconfigure && (
198-
<div className="flex flex-col items-center gap-3 p-4 rounded-lg bg-amber-50 dark:bg-amber-950/30 border border-amber-200 dark:border-amber-800">
199-
<div className="flex items-center gap-2 text-amber-700 dark:text-amber-400">
200-
<AlertCircle className="h-4 w-4" />
201-
<span className="text-sm font-medium">
202-
Problemy z logowaniem?
203-
</span>
204-
</div>
205-
<p className="text-xs text-amber-600 dark:text-amber-500 text-center">
206-
{ssoHealthy === false
207-
? "Nie można połączyć się z centrum logowania. Możesz skonfigurować połączenie ponownie."
208-
: "Jeśli logowanie nie działa, możesz zrekonfigurować połączenie SSO."}
209-
</p>
210-
<Link href="/setup" className="w-full">
211-
<Button
212-
variant="outline"
213-
className="w-full border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-400 hover:bg-amber-100 dark:hover:bg-amber-900/30"
214-
>
215-
<Settings2 className="mr-2 h-4 w-4" />
216-
Rekonfiguruj SSO
217-
</Button>
218-
</Link>
189+
{/* Przycisk rekonfiguracji SSO - zawsze widoczny */}
190+
<div className="flex flex-col items-center gap-3 p-4 rounded-lg bg-muted/50 border border-muted">
191+
<div className="flex items-center gap-2 text-muted-foreground">
192+
<Settings2 className="h-4 w-4" />
193+
<span className="text-sm font-medium">
194+
Konfiguracja połączenia
195+
</span>
219196
</div>
220-
)}
197+
<p className="text-xs text-muted-foreground text-center">
198+
{ssoHealthy === false
199+
? "Nie można połączyć się z centrum logowania."
200+
: "Jeśli masz problemy z logowaniem, możesz zrekonfigurować połączenie SSO."}
201+
</p>
202+
<Link href="/setup" className="w-full">
203+
<Button variant="outline" className="w-full">
204+
<Settings2 className="mr-2 h-4 w-4" />
205+
Rekonfiguruj SSO
206+
</Button>
207+
</Link>
208+
</div>
221209
</CardContent>
222210
</Card>
223211

app/(dashboard)/admin/sso-setup/_components/disconnect-button.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import { useState } from "react";
44
import { Button } from "@/components/ui/button";
55
import { Loader2, Trash2 } from "lucide-react";
6-
import { useRouter } from "next/navigation";
76
import {
87
AlertDialog,
98
AlertDialogAction,
@@ -18,7 +17,6 @@ import {
1817

1918
export function DisconnectButton() {
2019
const [loading, setLoading] = useState(false);
21-
const router = useRouter();
2220

2321
const handleDisconnect = async () => {
2422
setLoading(true);
@@ -28,14 +26,24 @@ export function DisconnectButton() {
2826
});
2927

3028
if (response.ok) {
31-
// Force refresh to update UI
32-
router.refresh();
29+
// Po usunięciu konfiguracji SSO musimy wylogować użytkownika
30+
// Jego sesja odnosi się do nieistniejącego już projektu
31+
await fetch("/api/auth/sso-logout", { method: "POST" });
32+
33+
// Wyczyść wszystkie lokalne dane aplikacji
34+
if (typeof window !== "undefined") {
35+
localStorage.clear();
36+
sessionStorage.clear();
37+
}
38+
39+
// Przekieruj na stronę logowania (hard redirect by wyczyścić stan)
40+
window.location.href = "/login";
3341
} else {
3442
console.error("Failed to disconnect");
43+
setLoading(false);
3544
}
3645
} catch (error) {
3746
console.error("Error disconnecting:", error);
38-
} finally {
3947
setLoading(false);
4048
}
4149
};

app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Geist, Geist_Mono } from "next/font/google";
33
import "./globals.css";
44
import { ThemeProvider } from "@/components/ThemeProvider";
55
import { Toaster } from "@/components/ui/sonner";
6+
import { AppVersionProvider } from "@/components/AppVersionProvider";
67

78
const geistSans = Geist({
89
variable: "--font-sans",
@@ -35,6 +36,7 @@ export default function RootLayout({
3536
enableSystem
3637
disableTransitionOnChange
3738
>
39+
<AppVersionProvider />
3840
{children}
3941
<Toaster />
4042
</ThemeProvider>

components/AppVersionProvider.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"use client";
2+
3+
import { AppVersionCheck } from "@/lib/use-app-version";
4+
5+
/**
6+
* Wrapper dla AppVersionCheck do użycia w Server Component layout
7+
*/
8+
export function AppVersionProvider() {
9+
return <AppVersionCheck />;
10+
}

lib/use-app-version.tsx

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* Hook do zarządzania wersją aplikacji
3+
*
4+
* Sprawdza czy wersja aplikacji się zmieniła i czyści lokalne dane
5+
* jeśli potrzeba (po deploy nowej wersji).
6+
*/
7+
8+
import { useEffect, useState } from "react";
9+
10+
// Wersja z package.json (build time)
11+
const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || "unknown";
12+
const STORAGE_KEY = "app-version";
13+
14+
interface VersionCheckResult {
15+
currentVersion: string;
16+
previousVersion: string | null;
17+
wasUpdated: boolean;
18+
}
19+
20+
/**
21+
* Hook sprawdzający wersję aplikacji.
22+
* Przy zmianie wersji automatycznie czyści localStorage i sessionStorage.
23+
*/
24+
export function useAppVersion(): VersionCheckResult {
25+
const [result, setResult] = useState<VersionCheckResult>({
26+
currentVersion: APP_VERSION,
27+
previousVersion: null,
28+
wasUpdated: false,
29+
});
30+
31+
useEffect(() => {
32+
if (typeof window === "undefined") return;
33+
34+
const previousVersion = localStorage.getItem(STORAGE_KEY);
35+
36+
// Wersja się zmieniła lub pierwsza wizyta
37+
if (previousVersion && previousVersion !== APP_VERSION) {
38+
console.log(
39+
`[App Version] Wykryto aktualizację: ${previousVersion} -> ${APP_VERSION}`,
40+
);
41+
42+
// Wyczyść lokalne dane (ale nie sso-session - to ciasteczko)
43+
const keysToPreserve = ["theme", "sidebar-state", "cookie-consent"];
44+
const preservedData: Record<string, string | null> = {};
45+
46+
// Zachowaj ważne ustawienia
47+
keysToPreserve.forEach((key) => {
48+
preservedData[key] = localStorage.getItem(key);
49+
});
50+
51+
// Wyczyść wszystko
52+
localStorage.clear();
53+
sessionStorage.clear();
54+
55+
// Przywróć zachowane ustawienia
56+
keysToPreserve.forEach((key) => {
57+
const value = preservedData[key];
58+
if (value) {
59+
localStorage.setItem(key, value);
60+
}
61+
});
62+
63+
// Zapisz nową wersję
64+
localStorage.setItem(STORAGE_KEY, APP_VERSION);
65+
66+
setResult({
67+
currentVersion: APP_VERSION,
68+
previousVersion,
69+
wasUpdated: true,
70+
});
71+
} else if (!previousVersion) {
72+
// Pierwsza wizyta - zapisz wersję
73+
localStorage.setItem(STORAGE_KEY, APP_VERSION);
74+
setResult({
75+
currentVersion: APP_VERSION,
76+
previousVersion: null,
77+
wasUpdated: false,
78+
});
79+
}
80+
}, []);
81+
82+
return result;
83+
}
84+
85+
/**
86+
* Komponent do inicjalizacji sprawdzania wersji w root layout
87+
*/
88+
export function AppVersionCheck() {
89+
const { wasUpdated, previousVersion, currentVersion } = useAppVersion();
90+
91+
useEffect(() => {
92+
if (wasUpdated) {
93+
console.log(
94+
`[App Version] Dane lokalne zostały wyczyszczone po aktualizacji z ${previousVersion} do ${currentVersion}`,
95+
);
96+
}
97+
}, [wasUpdated, previousVersion, currentVersion]);
98+
99+
return null;
100+
}

next.config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
import type { NextConfig } from "next";
2+
import { readFileSync } from "fs";
3+
import { join } from "path";
4+
5+
// Pobierz wersję z package.json
6+
const packageJson = JSON.parse(
7+
readFileSync(join(process.cwd(), "package.json"), "utf-8"),
8+
);
9+
const appVersion = packageJson.version || "unknown";
210

311
// Wykrywanie środowiska
412
const isProduction = process.env.NODE_ENV === "production";
513

614
const nextConfig: NextConfig = {
15+
// Eksportuj wersję aplikacji do klienta
16+
env: {
17+
NEXT_PUBLIC_APP_VERSION: appVersion,
18+
},
19+
720
images: {
821
remotePatterns: [
922
{

0 commit comments

Comments
 (0)