Skip to content

Commit ea2f827

Browse files
committed
feat(ui): restyle AccountSelect and ProfileImage
- AccountSelect: card layout, accessible row buttons, avatar initials, per-row spinner + disabled state while a selection is in flight - ProfileImage: cohesive avatar pill, fix broken zitadel* color classes, hide account list when only one session, disable current session row - Add missing CREATING_SESSION + FINALIZING_AUTH_REQUEST locale keys - Tidy account_select copy
1 parent 77b4323 commit ea2f827

6 files changed

Lines changed: 217 additions & 134 deletions

File tree

locales/de/account_select.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"accountSelectTitle": "Select account",
3-
"addAccount": "+ Add other account",
4-
"empty": "<empty>"
2+
"accountSelectTitle": "Konto auswählen",
3+
"addAccount": "Anderes Konto verwenden",
4+
"empty": ""
55
}

locales/de/common.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,7 @@
4747
"LOGGING_IN_TO_APP": "Du meldest dich bei {{appName}} an",
4848
"LOGIN_ERROR": "Anmeldung fehlgeschlagen. Bitte Anmeldedaten prüfen.",
4949
"REGISTER_ERROR": "Konto konnte nicht erstellt werden. Bitte erneut versuchen.",
50-
"SOMETHING_WENT_WRONG": "Etwas ist schiefgelaufen"
50+
"SOMETHING_WENT_WRONG": "Etwas ist schiefgelaufen",
51+
"CREATING_SESSION": "Sitzung wird erstellt…",
52+
"FINALIZING_AUTH_REQUEST": "Anmeldung wird abgeschlossen…"
5153
}

locales/en/account_select.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"accountSelectTitle": "Select account",
3-
"addAccount": "+ Add other account",
4-
"empty": "<empty>"
2+
"accountSelectTitle": "Choose an account",
3+
"addAccount": "Use another account",
4+
"empty": ""
55
}

locales/en/common.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,7 @@
4747
"LOGGING_IN_TO_APP": "You are logging in to {{appName}}",
4848
"LOGIN_ERROR": "Couldn't sign you in. Check your credentials and try again.",
4949
"REGISTER_ERROR": "Couldn't create your account. Please try again.",
50-
"SOMETHING_WENT_WRONG": "Something went wrong"
50+
"SOMETHING_WENT_WRONG": "Something went wrong",
51+
"CREATING_SESSION": "Creating session…",
52+
"FINALIZING_AUTH_REQUEST": "Finalizing sign-in…"
5153
}

ui/AccountSelect/AccountSelect.tsx

Lines changed: 112 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
/* eslint-disable max-len */
22
'use client';
3-
import React from 'react';
4-
import ApiService from '#/services/frontend/api.service';
3+
import LoadingState from '#/components/Loading';
4+
import { objectToQueryString } from '#/lib/api-caller';
55
import { ROUTING } from '#/lib/router';
6+
import ApiService from '#/services/frontend/api.service';
67
import type { AuthRequest, Session } from '#/types/zitadel';
78
import useTranslations from 'next-translate/useTranslation';
8-
import Image from 'next/image';
99
import { useRouter } from 'next/navigation';
10-
import { objectToQueryString } from '#/lib/api-caller';
10+
import React, { useState } from 'react';
1111

1212
export default (props: {
1313
appUrl: string;
@@ -18,38 +18,43 @@ export default (props: {
1818
const router = useRouter();
1919
const { t } = useTranslations('account_select');
2020
const apiService = ApiService({ appUrl });
21+
const [pending, setPending] = useState<string | null>(null);
22+
const isLoading = pending !== null;
2123

22-
const formatDisplayText = (text?: string, length = 25) => {
24+
const formatDisplayText = (text?: string, length = 32) => {
2325
if (!text) return t('empty');
24-
return text?.length ? text.substring(0, length) : text;
26+
return text.length > length ? `${text.substring(0, length)}…` : text;
2527
};
2628

29+
const initialOf = (name?: string) =>
30+
name && name.length ? name.substring(0, 1).toUpperCase() : 'A';
31+
32+
const activeSessions = sessions.filter((e) => !!e.factors?.user?.id);
33+
2734
return (
28-
<div className="flex flex-col bg-white w-screen h-screen">
29-
<div className="min-h-screen bg-gray-100 flex justify-center items-center">
30-
<div className="bg-white w-[416px] py-4 rounded-md shadow-md">
31-
<div className="flex flex-col items-center justify-center text-center p-2">
32-
<Image
33-
src="/images/company.png"
34-
alt="logo"
35-
width={100}
36-
height="75"
37-
/>
38-
<div>
39-
<h2 className="text-lg font-semibold">
40-
{t('accountSelectTitle')}
41-
</h2>
42-
</div>
43-
</div>
35+
<div className="flex h-full w-full flex-col items-center justify-center bg-gray-50 px-4 py-8 sm:py-12">
36+
<LoadingState loading={isLoading} />
4437

45-
<div>
46-
{[...sessions]
47-
.filter((e) => !!e.factors?.user?.id)
48-
.map((session) => (
49-
<div
50-
key={session.id}
51-
className="flex items-center p-2 mb-4 hover:bg-gray-100 hover:cursor-pointer"
38+
<div className="w-full max-w-[440px] overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm">
39+
<div className="px-6 pb-2 pt-6 sm:px-8 sm:pt-8">
40+
<h1 className="text-center text-2xl font-bold text-gray-900 sm:text-3xl">
41+
{t('accountSelectTitle')}
42+
</h1>
43+
</div>
44+
45+
<ul className="divide-y divide-gray-100" role="list">
46+
{activeSessions.map((session) => {
47+
const displayName = session.factors?.user?.displayName;
48+
const loginName = session.factors?.user?.loginName;
49+
50+
return (
51+
<li key={session.id}>
52+
<button
53+
type="button"
54+
disabled={isLoading}
55+
className="flex w-full items-center gap-3 px-6 py-3 text-left transition-colors hover:bg-gray-50 focus:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 disabled:cursor-not-allowed disabled:opacity-60 sm:px-8"
5256
onClick={async () => {
57+
setPending(session.id);
5358
try {
5459
if (authRequest && session?.factors?.user?.id) {
5560
const result = await apiService.finalizeAuthRequest({
@@ -69,42 +74,97 @@ export default (props: {
6974
router.push(`${ROUTING.ACCOUNT}/${index}`);
7075
} catch (error) {
7176
console.error(error);
77+
setPending(null);
7278
}
7379
}}
7480
>
75-
<div className="flex items-center justify-center h-10 w-10 rounded-full mr-4 ring-2">
76-
<div className="text-center">
77-
{session
78-
? session.factors?.user?.displayName?.substring(0, 1)
79-
: 'A'}
80-
</div>
81-
</div>
81+
<span
82+
aria-hidden="true"
83+
className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-indigo-100 text-sm font-semibold text-indigo-700"
84+
>
85+
{initialOf(displayName || loginName)}
86+
</span>
8287

83-
<div>
84-
<h3 className="text-lg font-semibold">
85-
{formatDisplayText(session.factors?.user?.displayName)}
86-
</h3>
87-
<p className="text-gray-600">
88-
{formatDisplayText(session.factors?.user?.loginName)}
89-
</p>
90-
</div>
91-
</div>
92-
))}
88+
<span className="flex min-w-0 flex-1 flex-col">
89+
<span className="truncate text-sm font-medium text-gray-900">
90+
{formatDisplayText(displayName)}
91+
</span>
92+
<span className="truncate text-xs text-gray-500">
93+
{formatDisplayText(loginName)}
94+
</span>
95+
</span>
9396

94-
<div
95-
className="flex items-center p-4 hover:bg-gray-100 hover:cursor-pointer"
97+
{pending === session.id ? (
98+
<span
99+
aria-hidden="true"
100+
className="h-4 w-4 flex-shrink-0 animate-spin rounded-full border-2 border-gray-300 border-t-indigo-600"
101+
/>
102+
) : (
103+
<svg
104+
aria-hidden="true"
105+
width="16"
106+
height="16"
107+
viewBox="0 0 24 24"
108+
fill="none"
109+
stroke="currentColor"
110+
strokeWidth="2"
111+
strokeLinecap="round"
112+
strokeLinejoin="round"
113+
className="flex-shrink-0 text-gray-400"
114+
>
115+
<polyline points="9 18 15 12 9 6" />
116+
</svg>
117+
)}
118+
</button>
119+
</li>
120+
);
121+
})}
122+
123+
<li>
124+
<button
125+
type="button"
126+
disabled={isLoading}
127+
className="flex w-full items-center gap-3 px-6 py-3 text-left transition-colors hover:bg-gray-50 focus:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 disabled:cursor-not-allowed disabled:opacity-60 sm:px-8"
96128
onClick={() => {
129+
setPending('add');
97130
router.push(
98131
objectToQueryString(ROUTING.LOGIN, {
99132
authRequest: authRequest?.id,
100133
}),
101134
);
102135
}}
103136
>
104-
<p>{t('addAccount')}</p>
105-
</div>
106-
</div>
107-
</div>
137+
<span
138+
aria-hidden="true"
139+
className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full border border-dashed border-gray-300 text-gray-500"
140+
>
141+
{pending === 'add' ? (
142+
<span
143+
aria-hidden="true"
144+
className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-indigo-600"
145+
/>
146+
) : (
147+
<svg
148+
width="18"
149+
height="18"
150+
viewBox="0 0 24 24"
151+
fill="none"
152+
stroke="currentColor"
153+
strokeWidth="2"
154+
strokeLinecap="round"
155+
strokeLinejoin="round"
156+
>
157+
<line x1="12" y1="5" x2="12" y2="19" />
158+
<line x1="5" y1="12" x2="19" y2="12" />
159+
</svg>
160+
)}
161+
</span>
162+
<span className="text-sm font-medium text-gray-700">
163+
{t('addAccount')}
164+
</span>
165+
</button>
166+
</li>
167+
</ul>
108168
</div>
109169
</div>
110170
);

0 commit comments

Comments
 (0)