Skip to content

Commit 0c87f07

Browse files
authored
Merge pull request #436 from zitadel/logout-page
feat: Logout page
2 parents c97e60c + ba37fdc commit 0c87f07

File tree

16 files changed

+418
-50
lines changed

16 files changed

+418
-50
lines changed

apps/login/locales/de.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88
"addAnother": "Ein weiteres Konto hinzufügen",
99
"noResults": "Keine Konten gefunden"
1010
},
11+
"logout": {
12+
"title": "Logout",
13+
"description": "Wählen Sie den Account aus, das Sie entfernen möchten",
14+
"noResults": "Keine Konten gefunden",
15+
"clear": "Session beenden",
16+
"verifiedAt": "Zuletzt aktiv: {time}",
17+
"success": {
18+
"title": "Logout erfolgreich",
19+
"description": "Sie haben sich erfolgreich abgemeldet."
20+
}
21+
},
1122
"loginname": {
1223
"title": "Willkommen zurück!",
1324
"description": "Geben Sie Ihre Anmeldedaten ein.",

apps/login/locales/en.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88
"addAnother": "Add another account",
99
"noResults": "No accounts found"
1010
},
11+
"logout": {
12+
"title": "Logout",
13+
"description": "Click an account to end the session",
14+
"noResults": "No accounts found",
15+
"clear": "End Session",
16+
"verifiedAt": "Last active: {time}",
17+
"success": {
18+
"title": "Logout successful",
19+
"description": "You have successfully logged out."
20+
}
21+
},
1122
"loginname": {
1223
"title": "Welcome back!",
1324
"description": "Enter your login data.",

apps/login/locales/es.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88
"addAnother": "Agregar otra cuenta",
99
"noResults": "No se encontraron cuentas"
1010
},
11+
"logout": {
12+
"title": "Cerrar sesión",
13+
"description": "Selecciona la cuenta que deseas eliminar",
14+
"noResults": "No se encontraron cuentas",
15+
"clear": "Eliminar sesión",
16+
"verifiedAt": "Última actividad: {time}",
17+
"success": {
18+
"title": "Cierre de sesión exitoso",
19+
"description": "Has cerrado sesión correctamente."
20+
}
21+
},
1122
"loginname": {
1223
"title": "¡Bienvenido de nuevo!",
1324
"description": "Introduce tus datos de acceso.",

apps/login/locales/it.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88
"addAnother": "Aggiungi un altro account",
99
"noResults": "Nessun account trovato"
1010
},
11+
"logout": {
12+
"title": "Esci",
13+
"description": "Seleziona l'account che desideri uscire",
14+
"noResults": "Nessun account trovato",
15+
"clear": "Elimina sessione",
16+
"verifiedAt": "Ultima attività: {time}",
17+
"success": {
18+
"title": "Uscita riuscita",
19+
"description": "Hai effettuato l'uscita con successo."
20+
}
21+
},
1122
"loginname": {
1223
"title": "Bentornato!",
1324
"description": "Inserisci i tuoi dati di accesso.",

apps/login/locales/pl.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88
"addAnother": "Dodaj kolejne konto",
99
"noResults": "Nie znaleziono kont"
1010
},
11+
"logout": {
12+
"title": "Wyloguj się",
13+
"description": "Wybierz konto, które chcesz usunąć",
14+
"noResults": "Nie znaleziono kont",
15+
"clear": "Usuń sesję",
16+
"verifiedAt": "Ostatnia aktywność: {time}",
17+
"success": {
18+
"title": "Wylogowanie udane",
19+
"description": "Pomyślnie się wylogowałeś."
20+
}
21+
},
1122
"loginname": {
1223
"title": "Witamy ponownie!",
1324
"description": "Wprowadź dane logowania.",

apps/login/locales/ru.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88
"addAnother": "Добавить другой аккаунт",
99
"noResults": "Аккаунты не найдены"
1010
},
11+
"logout": {
12+
"title": "Выход",
13+
"description": "Выберите аккаунт, который хотите удалить",
14+
"noResults": "Аккаунты не найдены",
15+
"clear": "Удалить сессию",
16+
"verifiedAt": "Последняя активность: {time}",
17+
"success": {
18+
"title": "Выход выполнен успешно",
19+
"description": "Вы успешно вышли из системы."
20+
}
21+
},
1122
"loginname": {
1223
"title": "С возвращением!",
1324
"description": "Введите свои данные для входа.",

apps/login/locales/zh.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88
"addAnother": "添加另一个账户",
99
"noResults": "未找到账户"
1010
},
11+
"logout": {
12+
"title": "注销",
13+
"description": "选择您想注销的账户",
14+
"noResults": "未找到账户",
15+
"clear": "注销会话",
16+
"verifiedAt": "最后活动时间:{time}",
17+
"success": {
18+
"title": "注销成功",
19+
"description": "您已成功注销。"
20+
}
21+
},
1122
"loginname": {
1223
"title": "欢迎回来!",
1324
"description": "请输入您的登录信息。",
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { DynamicTheme } from "@/components/dynamic-theme";
2+
import { SessionsClearList } from "@/components/sessions-clear-list";
3+
import { getAllSessionCookieIds } from "@/lib/cookies";
4+
import { getServiceUrlFromHeaders } from "@/lib/service-url";
5+
import {
6+
getBrandingSettings,
7+
getDefaultOrg,
8+
listSessions,
9+
} from "@/lib/zitadel";
10+
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
11+
import { getLocale, getTranslations } from "next-intl/server";
12+
import { headers } from "next/headers";
13+
14+
async function loadSessions({ serviceUrl }: { serviceUrl: string }) {
15+
const ids: (string | undefined)[] = await getAllSessionCookieIds();
16+
17+
if (ids && ids.length) {
18+
const response = await listSessions({
19+
serviceUrl,
20+
ids: ids.filter((id) => !!id) as string[],
21+
});
22+
return response?.sessions ?? [];
23+
} else {
24+
console.info("No session cookie found.");
25+
return [];
26+
}
27+
}
28+
29+
export default async function Page(props: {
30+
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
31+
}) {
32+
const searchParams = await props.searchParams;
33+
const locale = getLocale();
34+
const t = await getTranslations({ locale, namespace: "logout" });
35+
36+
const organization = searchParams?.organization;
37+
const postLogoutRedirectUri = searchParams?.post_logout_redirect_uri;
38+
const logoutHint = searchParams?.logout_hint;
39+
const UILocales = searchParams?.ui_locales; // TODO implement with new translation service
40+
41+
const _headers = await headers();
42+
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
43+
44+
let defaultOrganization;
45+
if (!organization) {
46+
const org: Organization | null = await getDefaultOrg({
47+
serviceUrl,
48+
});
49+
if (org) {
50+
defaultOrganization = org.id;
51+
}
52+
}
53+
54+
let sessions = await loadSessions({ serviceUrl });
55+
56+
const branding = await getBrandingSettings({
57+
serviceUrl,
58+
organization: organization ?? defaultOrganization,
59+
});
60+
61+
const params = new URLSearchParams();
62+
63+
if (organization) {
64+
params.append("organization", organization);
65+
}
66+
67+
return (
68+
<DynamicTheme branding={branding}>
69+
<div className="flex flex-col items-center space-y-4">
70+
<h1>{t("title")}</h1>
71+
<p className="ztdl-p mb-6 block">{t("description")}</p>
72+
73+
<div className="flex flex-col w-full space-y-2">
74+
<SessionsClearList
75+
sessions={sessions}
76+
logoutHint={logoutHint}
77+
postLogoutRedirectUri={postLogoutRedirectUri}
78+
organization={organization ?? defaultOrganization}
79+
/>
80+
</div>
81+
</div>
82+
</DynamicTheme>
83+
);
84+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { DynamicTheme } from "@/components/dynamic-theme";
2+
import { getServiceUrlFromHeaders } from "@/lib/service-url";
3+
import { getBrandingSettings, getDefaultOrg } from "@/lib/zitadel";
4+
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
5+
import { getLocale, getTranslations } from "next-intl/server";
6+
import { headers } from "next/headers";
7+
8+
export default async function Page(props: { searchParams: Promise<any> }) {
9+
const searchParams = await props.searchParams;
10+
const locale = getLocale();
11+
const t = await getTranslations({ locale, namespace: "logout" });
12+
13+
const _headers = await headers();
14+
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
15+
16+
const { login_hint, organization } = searchParams;
17+
18+
let defaultOrganization;
19+
if (!organization) {
20+
const org: Organization | null = await getDefaultOrg({
21+
serviceUrl,
22+
});
23+
if (org) {
24+
defaultOrganization = org.id;
25+
}
26+
}
27+
28+
const branding = await getBrandingSettings({
29+
serviceUrl,
30+
organization,
31+
});
32+
33+
return (
34+
<DynamicTheme branding={branding}>
35+
<div className="flex flex-col items-center space-y-4">
36+
<h1>{t("success.title")}</h1>
37+
<p className="ztdl-p mb-6 block">{t("success.description")}</p>
38+
</div>
39+
</DynamicTheme>
40+
);
41+
}

apps/login/src/app/(login)/verify/success/page.tsx

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,16 @@
11
import { DynamicTheme } from "@/components/dynamic-theme";
22
import { UserAvatar } from "@/components/user-avatar";
3-
import { getSessionCookieById } from "@/lib/cookies";
43
import { getServiceUrlFromHeaders } from "@/lib/service-url";
54
import { loadMostRecentSession } from "@/lib/session";
65
import {
76
getBrandingSettings,
87
getLoginSettings,
9-
getSession,
108
getUserByID,
119
} from "@/lib/zitadel";
1210
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
1311
import { getLocale, getTranslations } from "next-intl/server";
1412
import { headers } from "next/headers";
1513

16-
async function loadSessionById(
17-
serviceUrl: string,
18-
sessionId: string,
19-
organization?: string,
20-
) {
21-
const recent = await getSessionCookieById({ sessionId, organization });
22-
return getSession({
23-
serviceUrl,
24-
sessionId: recent.id,
25-
sessionToken: recent.token,
26-
}).then((response) => {
27-
if (response?.session) {
28-
return response.session;
29-
}
30-
});
31-
}
32-
3314
export default async function Page(props: { searchParams: Promise<any> }) {
3415
const searchParams = await props.searchParams;
3516
const locale = getLocale();

0 commit comments

Comments
 (0)