@scalio-cloud/ui 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +34 -0
- package/auth.css +114 -0
- package/index.css +1 -0
- package/package.json +5 -3
- package/react/auth.js +158 -0
- package/react/index.js +2 -0
- package/react/labels.js +67 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
- New sign-in building blocks in `@scalio-cloud/ui/react`: `AuthLayout` (app brand on a dark grid on the left, form
|
|
5
|
+
on the right, language and optional theme top right, small brand above the form on phones), `AuthHeader`,
|
|
6
|
+
`MicrosoftButton`, `AuthDivider` ("oder mit E-Mail"), `AuthMessage`.
|
|
7
|
+
- New `ProfileSection` and `PROFILE_SECTIONS` for the profile page (Konto, Darstellung, Sprache und Region,
|
|
8
|
+
Sicherheit, Verknüpfungen), title and hint on the left, content in a card on the right.
|
|
9
|
+
- More shared words in `LABELS` (Anmelden, Passwort vergessen?, Passwort ändern, Zwei-Faktor-Anmeldung, ...).
|
|
10
|
+
- New `auth.css` (imported by `index.css`). No visual change for apps that do not use the new components.
|
|
11
|
+
|
|
3
12
|
## 0.3.0
|
|
4
13
|
- New `@scalio-cloud/ui/react`: shared app header (`AppHeader` with status, language, theme, support and user menu
|
|
5
14
|
in a fixed order), `UserMenu`, `LanguageToggle`, `ThemeToggle`, `SupportButton`, `PageHeader`, `VersionTag`.
|
package/README.md
CHANGED
|
@@ -68,6 +68,40 @@ Texts are built in for `lang` `de` and `en` and can be overridden with `labels`.
|
|
|
68
68
|
`TEST_IDS`. The components are plain CommonJS with `React.createElement`, so CRA, Vite and Jest need no extra config.
|
|
69
69
|
Icons are inline (Lucide, ISC, see `licenses/`).
|
|
70
70
|
|
|
71
|
+
## Sign-in page and profile (React)
|
|
72
|
+
|
|
73
|
+
All sign-in pages use the same frame and order: Microsoft first, then "oder mit E-Mail", the password field with
|
|
74
|
+
"Passwort vergessen?" below it, the submit button. Forgot password, reset, verify email and the two-factor step are
|
|
75
|
+
their own pages in the same frame. Show only what the app can do (register only in Compass, demo accounts only
|
|
76
|
+
in demo mode).
|
|
77
|
+
|
|
78
|
+
```jsx
|
|
79
|
+
import { AuthLayout, AuthHeader, MicrosoftButton, AuthDivider, AuthMessage, ProfileSection } from "@scalio-cloud/ui/react";
|
|
80
|
+
|
|
81
|
+
<AuthLayout
|
|
82
|
+
brand={{ mark: "N", name: "Scalio Nexus", claim: "Alle Konten aus einer Konsole verwalten.",
|
|
83
|
+
features: ["Onboarding", "Ändern", "Offboarding"] }}
|
|
84
|
+
lang={lang} onLangChange={setLang} // top right
|
|
85
|
+
theme={theme} onThemeChange={setTheme} // optional
|
|
86
|
+
footer={<VersionTag version={v} />} // optional
|
|
87
|
+
>
|
|
88
|
+
<AuthHeader eyebrow="Zugang" title="Anmelden" subtitle="..." />
|
|
89
|
+
<MicrosoftButton href="/api/auth/sso/login" lang={lang} disabled={!ssoReady} hint="..." />
|
|
90
|
+
<AuthDivider lang={lang} />
|
|
91
|
+
<form>...</form>
|
|
92
|
+
<AuthMessage kind="error">{error}</AuthMessage>
|
|
93
|
+
</AuthLayout>
|
|
94
|
+
|
|
95
|
+
// Profile page: the same sections in every app, leave out what an app does not need.
|
|
96
|
+
<ProfileSection id="account" lang={lang}>...</ProfileSection> // Konto
|
|
97
|
+
<ProfileSection id="appearance" lang={lang}>...</ProfileSection> // Darstellung
|
|
98
|
+
<ProfileSection id="language" lang={lang}>...</ProfileSection> // Sprache und Region
|
|
99
|
+
<ProfileSection id="security" lang={lang}>...</ProfileSection> // Sicherheit
|
|
100
|
+
<ProfileSection id="connections" lang={lang}>...</ProfileSection> // Verknüpfungen
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Form fields and buttons stay the app's own (shadcn), so they match the rest of the app.
|
|
104
|
+
|
|
71
105
|
## Installing
|
|
72
106
|
|
|
73
107
|
Public on npmjs.com, no token needed: `yarn add @scalio-cloud/ui` (pin the exact version).
|
package/auth.css
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/* Sign-in pages (AuthLayout) and profile sections. Plain CSS on the tokens, classes start with "scalio-".
|
|
2
|
+
* The brand panel is always dark (like Rosetta), in light and dark mode. */
|
|
3
|
+
.scalio-auth {
|
|
4
|
+
min-height: 100vh;
|
|
5
|
+
display: grid;
|
|
6
|
+
grid-template-columns: 1fr;
|
|
7
|
+
background: hsl(var(--background));
|
|
8
|
+
color: hsl(var(--foreground));
|
|
9
|
+
}
|
|
10
|
+
@media (min-width: 1024px) { .scalio-auth { grid-template-columns: 1.2fr 1fr; } }
|
|
11
|
+
|
|
12
|
+
.scalio-auth-brand {
|
|
13
|
+
display: none;
|
|
14
|
+
position: relative;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
justify-content: space-between;
|
|
18
|
+
gap: 2rem;
|
|
19
|
+
padding: 3rem;
|
|
20
|
+
background: #09090b;
|
|
21
|
+
color: #f4f4f5;
|
|
22
|
+
}
|
|
23
|
+
@media (min-width: 1024px) { .scalio-auth-brand { display: flex; } }
|
|
24
|
+
.scalio-auth-grid {
|
|
25
|
+
position: absolute; inset: 0; opacity: 0.07; pointer-events: none;
|
|
26
|
+
background-image: linear-gradient(#fff 1px, transparent 1px), linear-gradient(90deg, #fff 1px, transparent 1px);
|
|
27
|
+
background-size: 48px 48px;
|
|
28
|
+
}
|
|
29
|
+
.scalio-auth-brand > :not(.scalio-auth-grid) { position: relative; }
|
|
30
|
+
.scalio-auth-eyebrow {
|
|
31
|
+
font-family: 'JetBrains Mono', monospace; font-size: 0.75rem; text-transform: uppercase;
|
|
32
|
+
letter-spacing: 0.2em; color: #71717a;
|
|
33
|
+
}
|
|
34
|
+
.scalio-auth-mark {
|
|
35
|
+
width: 2rem; height: 2rem; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
|
|
36
|
+
background: #002FA7; color: #fff; border-radius: 2px;
|
|
37
|
+
font-family: 'Chivo', sans-serif; font-weight: 900; font-size: 0.875rem;
|
|
38
|
+
}
|
|
39
|
+
.scalio-auth-mark-lg { width: 3.5rem; height: 3.5rem; font-size: 1.5rem; margin-bottom: 2rem; }
|
|
40
|
+
.scalio-auth-title {
|
|
41
|
+
margin: 0; font-family: 'Chivo', sans-serif; font-weight: 900; text-transform: uppercase;
|
|
42
|
+
letter-spacing: -0.02em; font-size: 3rem; line-height: 0.95;
|
|
43
|
+
}
|
|
44
|
+
@media (min-width: 1280px) { .scalio-auth-title { font-size: 3.75rem; } }
|
|
45
|
+
.scalio-auth-claim { margin: 1.5rem 0 0; max-width: 28rem; color: #a1a1aa; font-size: 1rem; line-height: 1.6; }
|
|
46
|
+
.scalio-auth-features {
|
|
47
|
+
list-style: none; margin: 0; padding: 0;
|
|
48
|
+
display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px;
|
|
49
|
+
background: #27272a; border: 1px solid #27272a;
|
|
50
|
+
}
|
|
51
|
+
.scalio-auth-features li {
|
|
52
|
+
background: #09090b; padding: 0.75rem 1rem; color: #a1a1aa;
|
|
53
|
+
font-family: 'JetBrains Mono', monospace; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.scalio-auth-main { position: relative; display: flex; flex-direction: column; min-width: 0; }
|
|
57
|
+
.scalio-auth-top { display: flex; justify-content: flex-end; gap: 0.5rem; padding: 1rem; min-height: 4.25rem; }
|
|
58
|
+
.scalio-auth-body { flex: 1 1 auto; display: flex; align-items: center; justify-content: center; padding: 0 1.5rem 2rem; }
|
|
59
|
+
@media (min-width: 640px) { .scalio-auth-body { padding: 0 3rem 3rem; } }
|
|
60
|
+
.scalio-auth-form { width: 100%; max-width: 24rem; display: flex; flex-direction: column; gap: 1.25rem; }
|
|
61
|
+
.scalio-auth-footer {
|
|
62
|
+
padding: 0.75rem 1rem; text-align: right; color: hsl(var(--muted-foreground));
|
|
63
|
+
font-family: 'JetBrains Mono', monospace; font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.scalio-auth-mobile-brand { display: flex; align-items: center; gap: 0.75rem; }
|
|
67
|
+
@media (min-width: 1024px) { .scalio-auth-mobile-brand { display: none; } }
|
|
68
|
+
.scalio-auth-mobile-name { font-family: 'Chivo', sans-serif; font-weight: 900; text-transform: uppercase; font-size: 0.875rem; }
|
|
69
|
+
|
|
70
|
+
.scalio-auth-header .scalio-eyebrow { margin-bottom: 0.5rem; }
|
|
71
|
+
.scalio-auth-heading {
|
|
72
|
+
margin: 0; font-family: 'Chivo', sans-serif; font-weight: 900; text-transform: uppercase;
|
|
73
|
+
letter-spacing: -0.01em; font-size: 1.5rem; line-height: 1.2;
|
|
74
|
+
}
|
|
75
|
+
.scalio-auth-subtitle { margin: 0.5rem 0 0; font-size: 0.875rem; color: hsl(var(--muted-foreground)); }
|
|
76
|
+
|
|
77
|
+
.scalio-auth-divider {
|
|
78
|
+
display: flex; align-items: center; gap: 0.75rem; color: hsl(var(--muted-foreground));
|
|
79
|
+
font-family: 'JetBrains Mono', monospace; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
|
|
80
|
+
}
|
|
81
|
+
.scalio-auth-divider::before, .scalio-auth-divider::after { content: ""; flex: 1 1 auto; height: 1px; background: hsl(var(--border)); }
|
|
82
|
+
|
|
83
|
+
.scalio-auth-ms {
|
|
84
|
+
display: flex; width: 100%; height: 2.75rem; align-items: center; justify-content: center; gap: 0.625rem;
|
|
85
|
+
border: 1px solid hsl(var(--input)); border-radius: 2px; background: hsl(var(--background)); color: hsl(var(--foreground));
|
|
86
|
+
font: inherit; font-size: 0.875rem; font-weight: 600; text-decoration: none; cursor: pointer; transition: background-color 150ms;
|
|
87
|
+
}
|
|
88
|
+
.scalio-auth-ms:hover { background: hsl(var(--secondary)); }
|
|
89
|
+
.scalio-auth-ms:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 1px; }
|
|
90
|
+
.scalio-auth-ms-disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
|
|
91
|
+
.scalio-auth-hint { margin-top: 0.25rem; font-size: 11px; color: hsl(var(--muted-foreground)); }
|
|
92
|
+
|
|
93
|
+
.scalio-auth-msg { border: 1px solid; border-radius: 2px; padding: 0.5rem 0.75rem; font-size: 0.875rem; }
|
|
94
|
+
.scalio-auth-msg-error { color: hsl(var(--destructive)); border-color: hsl(var(--destructive) / 0.35); background: hsl(var(--destructive) / 0.08); }
|
|
95
|
+
.scalio-auth-msg-info { color: hsl(var(--foreground)); border-color: hsl(var(--border)); background: hsl(var(--muted)); }
|
|
96
|
+
.scalio-auth-msg-success { color: hsl(142 72% 26%); border-color: hsl(142 60% 40% / 0.35); background: hsl(142 60% 40% / 0.08); }
|
|
97
|
+
.dark .scalio-auth-msg-success { color: hsl(142 60% 60%); }
|
|
98
|
+
|
|
99
|
+
/* Profile: sections one below the other, title left and content right from 768px. */
|
|
100
|
+
.scalio-profile-section {
|
|
101
|
+
display: grid; grid-template-columns: 1fr; gap: 1rem;
|
|
102
|
+
padding: 1.5rem 0; border-top: 1px solid hsl(var(--border));
|
|
103
|
+
}
|
|
104
|
+
.scalio-profile-section:first-of-type { border-top: 0; padding-top: 0; }
|
|
105
|
+
@media (min-width: 768px) { .scalio-profile-section { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 2rem; } }
|
|
106
|
+
.scalio-profile-section-title {
|
|
107
|
+
margin: 0; font-family: 'Chivo', sans-serif; font-weight: 900; font-size: 0.875rem;
|
|
108
|
+
text-transform: uppercase; letter-spacing: 0.02em;
|
|
109
|
+
}
|
|
110
|
+
.scalio-profile-section-desc { margin: 0.375rem 0 0; font-size: 0.8125rem; color: hsl(var(--muted-foreground)); line-height: 1.5; }
|
|
111
|
+
.scalio-profile-section-body {
|
|
112
|
+
min-width: 0; padding: 1.25rem; border: 1px solid hsl(var(--border)); border-radius: 2px; background: hsl(var(--card));
|
|
113
|
+
color: hsl(var(--card-foreground)); display: flex; flex-direction: column; gap: 1rem;
|
|
114
|
+
}
|
package/index.css
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalio-cloud/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Scalio Control Room design tokens, Tailwind preset, self-hosted fonts
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Scalio Control Room design tokens, Tailwind preset, self-hosted fonts, the shared app header and sign-in layout",
|
|
5
5
|
"license": "SEE LICENSE IN README.md",
|
|
6
6
|
"private": false,
|
|
7
7
|
"repository": {
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"./fonts/*": "./fonts/*",
|
|
21
21
|
"./package.json": "./package.json",
|
|
22
22
|
"./react": "./react/index.js",
|
|
23
|
-
"./shell.css": "./shell.css"
|
|
23
|
+
"./shell.css": "./shell.css",
|
|
24
|
+
"./auth.css": "./auth.css"
|
|
24
25
|
},
|
|
25
26
|
"files": [
|
|
26
27
|
"tokens.js",
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
"fonts.css",
|
|
31
32
|
"base.css",
|
|
32
33
|
"shell.css",
|
|
34
|
+
"auth.css",
|
|
33
35
|
"react/",
|
|
34
36
|
"licenses/",
|
|
35
37
|
"fonts/",
|
package/react/auth.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Sign-in pages and profile sections. Same building blocks in every app:
|
|
3
|
+
// AuthLayout = brand on a dark grid on the left, form on the right, language top right.
|
|
4
|
+
// Order on a sign-in form: AuthHeader, MicrosoftButton, AuthDivider ("or with email"), fields,
|
|
5
|
+
// "Forgot password?" under the password field, submit. Styles: auth.css (in index.css).
|
|
6
|
+
const React = require("react");
|
|
7
|
+
const h = React.createElement;
|
|
8
|
+
const { labelsFor } = require("./labels");
|
|
9
|
+
|
|
10
|
+
function cx() {
|
|
11
|
+
return Array.prototype.slice.call(arguments).filter(Boolean).join(" ");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function BrandMark({ brand, className }) {
|
|
15
|
+
return h(
|
|
16
|
+
"span",
|
|
17
|
+
{ className: cx("scalio-auth-mark", className), style: brand.color ? { background: brand.color } : undefined, "aria-hidden": "true" },
|
|
18
|
+
brand.mark
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// brand: { mark: "N", name: "Scalio Nexus", claim, eyebrow, features: [..], color }
|
|
23
|
+
function AuthLayout(props) {
|
|
24
|
+
const brand = props.brand || {};
|
|
25
|
+
const L = labelsFor(props.lang, props.labels);
|
|
26
|
+
const words = String(brand.name || "").split(/\s+/).filter(Boolean);
|
|
27
|
+
const { LanguageToggle, ThemeToggle } = require("./index");
|
|
28
|
+
return h(
|
|
29
|
+
"div",
|
|
30
|
+
{ className: cx("scalio-auth", props.className), "data-testid": props.testId || "auth-layout" },
|
|
31
|
+
h(
|
|
32
|
+
"aside",
|
|
33
|
+
{ className: "scalio-auth-brand", "data-testid": "auth-brand" },
|
|
34
|
+
h("div", { className: "scalio-auth-grid", "aria-hidden": "true" }),
|
|
35
|
+
h("div", { className: "scalio-auth-eyebrow" }, brand.eyebrow || "Scalio · Control Room"),
|
|
36
|
+
h(
|
|
37
|
+
"div",
|
|
38
|
+
{ className: "scalio-auth-hero" },
|
|
39
|
+
h(BrandMark, { brand, className: "scalio-auth-mark-lg" }),
|
|
40
|
+
h("h1", { className: "scalio-auth-title" }, words.map((w, i) => h(React.Fragment, { key: i }, i ? h("br") : null, w))),
|
|
41
|
+
brand.claim ? h("p", { className: "scalio-auth-claim" }, brand.claim) : null
|
|
42
|
+
),
|
|
43
|
+
brand.features && brand.features.length
|
|
44
|
+
? h(
|
|
45
|
+
"ul",
|
|
46
|
+
{ className: "scalio-auth-features" },
|
|
47
|
+
brand.features.map((f, i) => h("li", { key: i }, f))
|
|
48
|
+
)
|
|
49
|
+
: h("div")
|
|
50
|
+
),
|
|
51
|
+
h(
|
|
52
|
+
"main",
|
|
53
|
+
{ className: "scalio-auth-main" },
|
|
54
|
+
h(
|
|
55
|
+
"div",
|
|
56
|
+
{ className: "scalio-auth-top" },
|
|
57
|
+
h(ThemeToggle, { theme: props.theme, onChange: props.onThemeChange, modes: props.themeModes, lang: props.lang, labels: props.labels, testId: "auth-theme-btn" }),
|
|
58
|
+
h(LanguageToggle, { lang: props.lang, onChange: props.onLangChange, labels: props.labels, testId: (props.testIds && props.testIds.language) || "auth-language-btn" })
|
|
59
|
+
),
|
|
60
|
+
h(
|
|
61
|
+
"div",
|
|
62
|
+
{ className: "scalio-auth-body" },
|
|
63
|
+
h(
|
|
64
|
+
"div",
|
|
65
|
+
{ className: "scalio-auth-form" },
|
|
66
|
+
h(
|
|
67
|
+
"div",
|
|
68
|
+
{ className: "scalio-auth-mobile-brand" },
|
|
69
|
+
h(BrandMark, { brand }),
|
|
70
|
+
h("span", { className: "scalio-auth-mobile-name" }, brand.name)
|
|
71
|
+
),
|
|
72
|
+
props.children
|
|
73
|
+
)
|
|
74
|
+
),
|
|
75
|
+
props.footer ? h("div", { className: "scalio-auth-footer", "aria-label": L.version }, props.footer) : null
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function AuthHeader({ eyebrow, title, subtitle, testId }) {
|
|
81
|
+
return h(
|
|
82
|
+
"div",
|
|
83
|
+
{ className: "scalio-auth-header", "data-testid": testId },
|
|
84
|
+
eyebrow ? h("div", { className: "scalio-eyebrow" }, eyebrow) : null,
|
|
85
|
+
h("h2", { className: "scalio-auth-heading" }, title),
|
|
86
|
+
subtitle ? h("p", { className: "scalio-auth-subtitle" }, subtitle) : null
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function AuthDivider({ label, lang, labels, testId }) {
|
|
91
|
+
const L = labelsFor(lang, labels);
|
|
92
|
+
return h(
|
|
93
|
+
"div",
|
|
94
|
+
{ className: "scalio-auth-divider", role: "separator", "data-testid": testId || "auth-divider" },
|
|
95
|
+
h("span", null, label || L.orEmail)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Microsoft logo: four squares (Microsoft brand guidelines for "Sign in with Microsoft").
|
|
100
|
+
function MicrosoftLogo() {
|
|
101
|
+
const sq = (x, y, fill) => h("rect", { key: fill, x, y, width: 10, height: 10, fill });
|
|
102
|
+
return h(
|
|
103
|
+
"svg",
|
|
104
|
+
{ xmlns: "http://www.w3.org/2000/svg", width: 16, height: 16, viewBox: "0 0 21 21", "aria-hidden": "true" },
|
|
105
|
+
[sq(0, 0, "#F25022"), sq(11, 0, "#7FBA00"), sq(0, 11, "#00A4EF"), sq(11, 11, "#FFB900")]
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// A link when href is given (server redirect), else a button. disabled shows it greyed with the hint.
|
|
110
|
+
function MicrosoftButton({ href, onClick, disabled, busy, label, hint, lang, labels, testId }) {
|
|
111
|
+
const L = labelsFor(lang, labels);
|
|
112
|
+
const id = testId || "login-microsoft-btn";
|
|
113
|
+
const content = [h(MicrosoftLogo, { key: "l" }), h("span", { key: "t" }, busy ? L.redirecting : label || L.microsoft)];
|
|
114
|
+
const cls = cx("scalio-auth-ms", (disabled || busy) && "scalio-auth-ms-disabled");
|
|
115
|
+
const btn = href && !disabled && !busy
|
|
116
|
+
? h("a", { className: cls, href, onClick, "data-testid": id }, content)
|
|
117
|
+
: h("button", { type: "button", className: cls, onClick, disabled: !!(disabled || busy), "aria-disabled": !!(disabled || busy), "data-testid": id }, content);
|
|
118
|
+
return h(
|
|
119
|
+
"div",
|
|
120
|
+
{ className: "scalio-auth-ms-wrap" },
|
|
121
|
+
btn,
|
|
122
|
+
hint ? h("div", { className: "scalio-auth-hint", "data-testid": `${id}-hint` }, hint) : null
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// kind: error | info | success
|
|
127
|
+
function AuthMessage({ kind, children, testId }) {
|
|
128
|
+
if (!children) return null;
|
|
129
|
+
return h(
|
|
130
|
+
"div",
|
|
131
|
+
{ className: cx("scalio-auth-msg", `scalio-auth-msg-${kind || "info"}`), role: kind === "error" ? "alert" : "status", "data-testid": testId },
|
|
132
|
+
children
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// One section of the profile page: title and description on the left, content on the right.
|
|
137
|
+
// The same five sections in every app (only those an app needs): account, appearance,
|
|
138
|
+
// language and region, security, connections. PROFILE_SECTIONS holds their ids and order.
|
|
139
|
+
const PROFILE_SECTIONS = ["account", "appearance", "language", "security", "connections"];
|
|
140
|
+
|
|
141
|
+
function ProfileSection({ id, title, description, lang, labels, children, testId }) {
|
|
142
|
+
const L = labelsFor(lang, labels);
|
|
143
|
+
const heading = title || (id && L.sections[id]) || "";
|
|
144
|
+
const desc = description !== undefined ? description : id && L.sectionHints[id];
|
|
145
|
+
return h(
|
|
146
|
+
"section",
|
|
147
|
+
{ className: "scalio-profile-section", id: id ? `profile-${id}` : undefined, "data-testid": testId || (id ? `profile-section-${id}` : undefined) },
|
|
148
|
+
h(
|
|
149
|
+
"div",
|
|
150
|
+
{ className: "scalio-profile-section-text" },
|
|
151
|
+
h("h2", { className: "scalio-profile-section-title" }, heading),
|
|
152
|
+
desc ? h("p", { className: "scalio-profile-section-desc" }, desc) : null
|
|
153
|
+
),
|
|
154
|
+
h("div", { className: "scalio-profile-section-body" }, children)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = { AuthLayout, AuthHeader, AuthDivider, AuthMessage, MicrosoftButton, MicrosoftLogo, ProfileSection, PROFILE_SECTIONS };
|
package/react/index.js
CHANGED
package/react/labels.js
CHANGED
|
@@ -12,6 +12,37 @@ const LABELS = {
|
|
|
12
12
|
userMenu: "Benutzermenü",
|
|
13
13
|
menu: "Menü",
|
|
14
14
|
version: "Version",
|
|
15
|
+
// Sign-in and profile (same words in every app)
|
|
16
|
+
signIn: "Anmelden",
|
|
17
|
+
microsoft: "Mit Microsoft anmelden",
|
|
18
|
+
redirecting: "Weiterleitung …",
|
|
19
|
+
orEmail: "oder mit E-Mail",
|
|
20
|
+
email: "E-Mail",
|
|
21
|
+
password: "Passwort",
|
|
22
|
+
forgotPassword: "Passwort vergessen?",
|
|
23
|
+
backToSignIn: "Zurück zur Anmeldung",
|
|
24
|
+
profile: "Profil",
|
|
25
|
+
save: "Speichern",
|
|
26
|
+
changePassword: "Passwort ändern",
|
|
27
|
+
currentPassword: "Aktuelles Passwort",
|
|
28
|
+
newPassword: "Neues Passwort",
|
|
29
|
+
confirmPassword: "Neues Passwort bestätigen",
|
|
30
|
+
twoFactor: "Zwei-Faktor-Anmeldung",
|
|
31
|
+
mfaCode: "Code aus der Authenticator-App",
|
|
32
|
+
sections: {
|
|
33
|
+
account: "Konto",
|
|
34
|
+
appearance: "Darstellung",
|
|
35
|
+
language: "Sprache und Region",
|
|
36
|
+
security: "Sicherheit",
|
|
37
|
+
connections: "Verknüpfungen",
|
|
38
|
+
},
|
|
39
|
+
sectionHints: {
|
|
40
|
+
account: "Ihre Angaben. Die E-Mail-Adresse ist Ihr Anmeldename.",
|
|
41
|
+
appearance: "Hell, dunkel oder wie Ihr System.",
|
|
42
|
+
language: "Sprache der Oberfläche und Zeitzone für Zeitangaben.",
|
|
43
|
+
security: "Passwort und Zwei-Faktor-Anmeldung.",
|
|
44
|
+
connections: "Verbundene Konten für die Anmeldung.",
|
|
45
|
+
},
|
|
15
46
|
},
|
|
16
47
|
en: {
|
|
17
48
|
language: "Language / Sprache",
|
|
@@ -24,11 +55,46 @@ const LABELS = {
|
|
|
24
55
|
userMenu: "User menu",
|
|
25
56
|
menu: "Menu",
|
|
26
57
|
version: "Version",
|
|
58
|
+
signIn: "Sign in",
|
|
59
|
+
microsoft: "Sign in with Microsoft",
|
|
60
|
+
redirecting: "Redirecting …",
|
|
61
|
+
orEmail: "or with email",
|
|
62
|
+
email: "Email",
|
|
63
|
+
password: "Password",
|
|
64
|
+
forgotPassword: "Forgot password?",
|
|
65
|
+
backToSignIn: "Back to sign in",
|
|
66
|
+
profile: "Profile",
|
|
67
|
+
save: "Save",
|
|
68
|
+
changePassword: "Change password",
|
|
69
|
+
currentPassword: "Current password",
|
|
70
|
+
newPassword: "New password",
|
|
71
|
+
confirmPassword: "Confirm new password",
|
|
72
|
+
twoFactor: "Two-factor sign-in",
|
|
73
|
+
mfaCode: "Code from the authenticator app",
|
|
74
|
+
sections: {
|
|
75
|
+
account: "Account",
|
|
76
|
+
appearance: "Appearance",
|
|
77
|
+
language: "Language and region",
|
|
78
|
+
security: "Security",
|
|
79
|
+
connections: "Connections",
|
|
80
|
+
},
|
|
81
|
+
sectionHints: {
|
|
82
|
+
account: "Your details. Your email address is your sign-in name.",
|
|
83
|
+
appearance: "Light, dark or like your system.",
|
|
84
|
+
language: "Language of the interface and time zone for times.",
|
|
85
|
+
security: "Password and two-factor sign-in.",
|
|
86
|
+
connections: "Linked accounts for signing in.",
|
|
87
|
+
},
|
|
27
88
|
},
|
|
28
89
|
};
|
|
29
90
|
|
|
30
91
|
function labelsFor(lang, overrides) {
|
|
31
|
-
|
|
92
|
+
const base = LABELS[lang === "en" ? "en" : "de"];
|
|
93
|
+
const o = overrides || {};
|
|
94
|
+
return Object.assign({}, base, o, {
|
|
95
|
+
sections: Object.assign({}, base.sections, o.sections),
|
|
96
|
+
sectionHints: Object.assign({}, base.sectionHints, o.sectionHints),
|
|
97
|
+
});
|
|
32
98
|
}
|
|
33
99
|
|
|
34
100
|
module.exports = { LABELS, labelsFor };
|