@micha.bigler/ui-core-micha 2.1.19 → 2.2.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.
@@ -1,27 +1,74 @@
1
- // src/pages/SignUpPage.jsx
2
- import React, { useState } from 'react';
3
- import { useNavigate } from 'react-router-dom';
1
+ import React, { useContext, useEffect, useMemo, useState } from 'react';
2
+ import { useNavigate, useLocation } from 'react-router-dom';
4
3
  import {
4
+ Alert,
5
5
  Box,
6
- TextField,
7
6
  Button,
7
+ Stack,
8
+ Tab,
9
+ Tabs,
10
+ TextField,
8
11
  Typography,
9
- Alert,
10
12
  } from '@mui/material';
11
13
  import { Helmet } from 'react-helmet';
12
14
  import { useTranslation } from 'react-i18next';
13
15
  import { NarrowPage } from '../layout/PageLayout';
14
- import { validateAccessCode, requestInviteWithCode } from '../auth/authApi';
16
+ import { AuthContext } from '../auth/AuthContext';
17
+ import { submitRegistrationRequest } from '../auth/authApi';
18
+
19
+ const MODE_LABELS = {
20
+ self_signup_access_code: 'Auth.SIGNUP_ACCESS_CODE_TAB',
21
+ self_signup_open: 'Auth.SIGNUP_OPEN_TAB',
22
+ self_signup_email_domain: 'Auth.SIGNUP_EMAIL_DOMAIN_TAB',
23
+ self_signup_qr: 'Auth.SIGNUP_QR_TAB',
24
+ };
15
25
 
16
26
  export function SignUpPage() {
17
27
  const navigate = useNavigate();
28
+ const location = useLocation();
18
29
  const { t } = useTranslation();
30
+ const { authMethods } = useContext(AuthContext);
31
+
32
+ const signupModes = useMemo(() => {
33
+ const configured = Array.isArray(authMethods?.signup_modes)
34
+ ? authMethods.signup_modes.filter(Boolean)
35
+ : [];
36
+ if (configured.length > 0) {
37
+ return configured;
38
+ }
39
+ return authMethods?.signup ? ['self_signup_access_code'] : [];
40
+ }, [authMethods]);
41
+
42
+ const query = new URLSearchParams(location.search);
43
+ const tokenFromUrl = query.get('rt') || '';
19
44
 
45
+ const initialMode = useMemo(() => {
46
+ if (tokenFromUrl && signupModes.includes('self_signup_qr')) {
47
+ return 'self_signup_qr';
48
+ }
49
+ return signupModes[0] || 'self_signup_access_code';
50
+ }, [signupModes, tokenFromUrl]);
51
+
52
+ const [mode, setMode] = useState(initialMode);
20
53
  const [email, setEmail] = useState('');
21
54
  const [accessCode, setAccessCode] = useState('');
22
55
  const [submitting, setSubmitting] = useState(false);
23
56
  const [successKey, setSuccessKey] = useState(null);
24
57
  const [errorKey, setErrorKey] = useState(null);
58
+ const [qrHint, setQrHint] = useState('');
59
+
60
+ useEffect(() => {
61
+ setMode(initialMode);
62
+ }, [initialMode]);
63
+
64
+ useEffect(() => {
65
+ if (!tokenFromUrl || mode !== 'self_signup_qr') {
66
+ setQrHint('');
67
+ return undefined;
68
+ }
69
+ setQrHint(t('Auth.SIGNUP_QR_READY', 'QR signup token detected. You can complete your signup now.'));
70
+ return undefined;
71
+ }, [mode, tokenFromUrl, t]);
25
72
 
26
73
  const handleSubmit = async (event) => {
27
74
  event.preventDefault();
@@ -32,27 +79,27 @@ export function SignUpPage() {
32
79
  setErrorKey('Auth.EMAIL_REQUIRED');
33
80
  return;
34
81
  }
35
- if (!accessCode) {
82
+
83
+ if (mode === 'self_signup_access_code' && !accessCode) {
36
84
  setErrorKey('Auth.SIGNUP_ACCESS_CODE_REQUIRED');
37
85
  return;
38
86
  }
39
87
 
40
- setSubmitting(true);
88
+ if (mode === 'self_signup_qr' && !tokenFromUrl) {
89
+ setErrorKey('Auth.SIGNUP_QR_INVALID');
90
+ return;
91
+ }
41
92
 
93
+ setSubmitting(true);
42
94
  try {
43
- // 1) Access-Code prüfen
44
- const res = await validateAccessCode(accessCode);
45
- if (!res?.valid) {
46
- setErrorKey('Auth.ACCESS_CODE_INVALID_OR_INACTIVE');
47
- return;
48
- }
49
-
50
- // 2) Invite anfordern
51
- await requestInviteWithCode(email, accessCode);
52
-
95
+ await submitRegistrationRequest({
96
+ email,
97
+ mode,
98
+ accessCode,
99
+ registrationContextToken: mode === 'self_signup_qr' ? tokenFromUrl : null,
100
+ });
53
101
  setSuccessKey('Auth.INVITE_REQUEST_SUCCESS');
54
102
  } catch (err) {
55
- // validateAccessCode / requestInviteWithCode liefern normalisierte Errors
56
103
  setErrorKey(err.code || 'Auth.INVITE_FAILED');
57
104
  } finally {
58
105
  setSubmitting(false);
@@ -82,10 +129,28 @@ export function SignUpPage() {
82
129
 
83
130
  {errorKey && (
84
131
  <Alert severity="error" sx={{ mb: 2 }}>
85
- {t(errorKey)}
132
+ {t(errorKey, t('Auth.INVITE_FAILED', 'Could not complete signup.'))}
86
133
  </Alert>
87
134
  )}
88
135
 
136
+ {signupModes.length > 1 && (
137
+ <Tabs
138
+ value={mode}
139
+ onChange={(_event, next) => setMode(next)}
140
+ variant="scrollable"
141
+ scrollButtons="auto"
142
+ sx={{ mb: 2 }}
143
+ >
144
+ {signupModes.map((entry) => (
145
+ <Tab
146
+ key={entry}
147
+ value={entry}
148
+ label={t(MODE_LABELS[entry] || entry, entry)}
149
+ />
150
+ ))}
151
+ </Tabs>
152
+ )}
153
+
89
154
  <Box
90
155
  component="form"
91
156
  onSubmit={handleSubmit}
@@ -101,20 +166,45 @@ export function SignUpPage() {
101
166
  disabled={submitting}
102
167
  />
103
168
 
104
- <TextField
105
- label={t('Auth.ACCESS_CODE_LABEL')}
106
- type="text"
107
- required
108
- fullWidth
109
- value={accessCode}
110
- onChange={(e) => setAccessCode(e.target.value)}
111
- disabled={submitting}
112
- />
169
+ {mode === 'self_signup_access_code' && (
170
+ <TextField
171
+ label={t('Auth.ACCESS_CODE_LABEL')}
172
+ type="text"
173
+ required
174
+ fullWidth
175
+ value={accessCode}
176
+ onChange={(e) => setAccessCode(e.target.value)}
177
+ disabled={submitting}
178
+ />
179
+ )}
180
+
181
+ {mode === 'self_signup_email_domain' && (
182
+ <Alert severity="info">
183
+ {t(
184
+ 'Auth.SIGNUP_EMAIL_DOMAIN_HINT',
185
+ 'Only addresses from configured email domains are allowed for this signup flow.',
186
+ )}
187
+ </Alert>
188
+ )}
189
+
190
+ {mode === 'self_signup_qr' && (
191
+ <Stack spacing={1}>
192
+ <Alert severity="info">
193
+ {qrHint || t('Auth.SIGNUP_QR_HINT', 'Use a valid QR signup link to continue.')}
194
+ </Alert>
195
+ <TextField
196
+ label={t('Auth.SIGNUP_QR_TOKEN_LABEL', 'QR token')}
197
+ value={tokenFromUrl}
198
+ fullWidth
199
+ InputProps={{ readOnly: true }}
200
+ />
201
+ </Stack>
202
+ )}
113
203
 
114
204
  <Button
115
205
  type="submit"
116
206
  variant="contained"
117
- disabled={submitting}
207
+ disabled={submitting || signupModes.length === 0}
118
208
  >
119
209
  {submitting
120
210
  ? t('Auth.SIGNUP_SUBMITTING')