UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: единая шапка и OIDC вход launcher

This commit is contained in:
DCCONSTRUCTIONS
2026-05-05 15:00:23 +03:00
parent 09400f7db8
commit fd921cc400
10 changed files with 365 additions and 55 deletions
+33 -8
View File
@@ -24,6 +24,7 @@ import {
updateAdminInvite,
updateAdminMembership,
updateAdminService,
updateAdminSettings,
updateAdminUserProfile,
type ControlPlaneMutationResult,
} from "../shared/api/adminApi";
@@ -31,8 +32,10 @@ import {
buildLauncherServices,
buildMe,
initialLauncherData,
normalizeLauncherData,
profileOptions,
type LauncherData,
type LauncherSettings,
} from "../shared/api/mockApi";
import {
fetchAuthSession,
@@ -55,6 +58,8 @@ import { ServiceRail } from "../widgets/service-rail/ServiceRail";
import { ServiceStage } from "../widgets/service-stage/ServiceStage";
import { TopBar } from "../widgets/top-bar/TopBar";
let lastAuthRedirect: { url: string; startedAt: number } | null = null;
export function LauncherApp() {
const [data, setData] = useState<LauncherData>(() => syncLauncherServiceLinks(initialLauncherData));
const [activeProfileId, setActiveProfileId] = useState(profileOptions[0].userId);
@@ -188,7 +193,7 @@ export function LauncherApp() {
useEffect(() => {
if (!authSession || authSession.authenticated) return;
window.location.replace(buildLoginRedirectUrl(authSession.loginUrl));
redirectToLogin(authSession.loginUrl);
}, [authSession]);
useEffect(() => {
@@ -198,7 +203,7 @@ export function LauncherApp() {
if (isRedirecting) return;
isRedirecting = true;
window.location.replace(buildLoginRedirectUrl("/auth/login?prompt=login"));
redirectToLogin("/auth/login?prompt=login");
});
}, []);
@@ -213,7 +218,7 @@ export function LauncherApp() {
if (!isMounted) return;
if (!session.authenticated) {
window.location.replace(buildLoginRedirectUrl(session.loginUrl));
redirectToLogin(session.loginUrl);
return;
}
@@ -221,7 +226,7 @@ export function LauncherApp() {
})
.catch(() => {
if (isMounted) {
window.location.replace(buildLoginRedirectUrl("/auth/login"));
redirectToLogin("/auth/login");
}
});
};
@@ -418,6 +423,10 @@ export function LauncherApp() {
applyControlPlaneMutation(retryAdminSync(syncId));
}
function handleUpdateSettings(patch: Partial<LauncherSettings>) {
applyControlPlaneMutation(updateAdminSettings(patch));
}
function handleUpdateService(serviceId: string, patch: Partial<Service>) {
applyControlPlaneMutation(updateAdminService(serviceId, patch));
}
@@ -542,6 +551,7 @@ export function LauncherApp() {
onOpenShowcase={() => setAdminOpen(false)}
onOpenProfileSettings={() => setProfileSettingsOpen(true)}
onLogout={handleLogout}
brandLinkUrl={data.settings.brand.logoLinkUrl}
/>
<main className="launcher-main">
@@ -578,6 +588,7 @@ export function LauncherApp() {
onReorderServices={handleReorderServices}
onCreateService={handleCreateService}
onDeleteService={handleDeleteService}
onUpdateSettings={handleUpdateSettings}
/>
) : null}
{profileSettingsOpen && activeProfileUser ? (
@@ -594,10 +605,12 @@ export function LauncherApp() {
);
}
function syncLauncherServiceLinks(data: LauncherData): LauncherData {
function syncLauncherServiceLinks(data: Partial<LauncherData>): LauncherData {
const normalizedData = normalizeLauncherData(data);
return {
...data,
services: data.services.map(syncServiceLaunchLink),
...normalizedData,
services: normalizedData.services.map(syncServiceLaunchLink),
};
}
@@ -697,7 +710,7 @@ function AuthStateScreen({
<p style={{ margin: 0, color: "var(--text-secondary)", lineHeight: 1.5 }}>{description}</p>
{error ? <p style={{ margin: 0, color: "var(--warning)", lineHeight: 1.45 }}>{error}</p> : null}
{loginUrl ? (
<button className="button button--primary" type="button" onClick={() => window.location.assign(loginUrl)}>
<button className="button button--primary" type="button" onClick={() => redirectToLogin(loginUrl)}>
Войти
</button>
) : null}
@@ -720,3 +733,15 @@ function buildLoginRedirectUrl(loginUrl?: string) {
return url.origin === window.location.origin ? `${url.pathname}${url.search}${url.hash}` : url.toString();
}
function redirectToLogin(loginUrl?: string) {
const redirectUrl = buildLoginRedirectUrl(loginUrl);
const now = Date.now();
if (lastAuthRedirect && now - lastAuthRedirect.startedAt < 1500) {
return;
}
lastAuthRedirect = { url: redirectUrl, startedAt: now };
window.location.replace(redirectUrl);
}