@micha.bigler/ui-core-micha 1.4.12 → 1.4.13

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.
@@ -12,8 +12,7 @@ const SupportRecoveryRequestsTab = () => {
12
12
  const [statusFilter, setStatusFilter] = useState('pending');
13
13
  const [dialogOpen, setDialogOpen] = useState(false);
14
14
  const [dialogNote, setDialogNote] = useState('');
15
- const [dialogRequestId, setDialogRequestId] = useState(null);
16
- const [dialogMode, setDialogMode] = useState('approve');
15
+ const [selectedRequest, setSelectedRequest] = useState(null);
17
16
  const loadRequests = async () => {
18
17
  setLoading(true);
19
18
  setErrorKey(null);
@@ -32,49 +31,57 @@ const SupportRecoveryRequestsTab = () => {
32
31
  loadRequests();
33
32
  // eslint-disable-next-line react-hooks/exhaustive-deps
34
33
  }, [statusFilter]);
35
- const openDialog = (id, mode) => {
36
- setDialogRequestId(id);
37
- setDialogMode(mode);
34
+ const openDialog = (req) => {
35
+ setSelectedRequest(req);
38
36
  setDialogNote('');
39
37
  setDialogOpen(true);
40
38
  };
41
39
  const closeDialog = () => {
42
40
  setDialogOpen(false);
43
- setDialogRequestId(null);
41
+ setSelectedRequest(null);
44
42
  setDialogNote('');
45
43
  };
46
- const handleConfirm = async () => {
47
- if (!dialogRequestId)
44
+ const handleApprove = async () => {
45
+ if (!selectedRequest)
48
46
  return;
49
47
  setErrorKey(null);
50
48
  try {
51
- if (dialogMode === 'approve') {
52
- await authApi.approveRecoveryRequest(dialogRequestId, dialogNote);
53
- }
54
- else {
55
- await authApi.rejectRecoveryRequest(dialogRequestId, dialogNote);
56
- }
49
+ await authApi.approveRecoveryRequest(selectedRequest.id, dialogNote);
57
50
  await loadRequests();
58
51
  closeDialog();
59
52
  }
60
53
  catch (err) {
61
- setErrorKey(err.code ||
62
- (dialogMode === 'approve'
63
- ? 'Support.RECOVERY_REQUEST_APPROVE_FAILED'
64
- : 'Support.RECOVERY_REQUEST_REJECT_FAILED'));
54
+ setErrorKey(err.code || 'Support.RECOVERY_REQUEST_APPROVE_FAILED');
55
+ }
56
+ };
57
+ const handleReject = async () => {
58
+ if (!selectedRequest)
59
+ return;
60
+ setErrorKey(null);
61
+ try {
62
+ await authApi.rejectRecoveryRequest(selectedRequest.id, dialogNote);
63
+ await loadRequests();
64
+ closeDialog();
65
+ }
66
+ catch (err) {
67
+ setErrorKey(err.code || 'Support.RECOVERY_REQUEST_REJECT_FAILED');
65
68
  }
66
69
  };
67
70
  if (loading) {
68
71
  return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', mt: 4 }, children: _jsx(CircularProgress, {}) }));
69
72
  }
70
- return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Support.RECOVERY_REQUESTS_TITLE', 'Account recovery requests') }), _jsx(Typography, { variant: "body2", sx: { mb: 2 }, children: t('Support.RECOVERY_REQUESTS_DESCRIPTION', 'Users who cannot complete MFA can request support. You can send them a recovery link or reject the request after verification.') }), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), _jsxs(Stack, { direction: "row", spacing: 2, sx: { mb: 2 }, children: [_jsx(Button, { variant: statusFilter === 'pending' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('pending'), children: t('Support.RECOVERY_FILTER_PENDING', 'Open') }), _jsx(Button, { variant: statusFilter === 'approved' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('approved'), children: t('Support.RECOVERY_FILTER_APPROVED', 'Approved') }), _jsx(Button, { variant: statusFilter === 'rejected' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('rejected'), children: t('Support.RECOVERY_FILTER_REJECTED', 'Rejected') })] }), requests.length === 0 ? (_jsx(Typography, { variant: "body2", children: t('Support.RECOVERY_REQUESTS_EMPTY', 'No recovery requests for this filter.') })) : (_jsx(TableContainer, { component: Paper, children: _jsxs(Table, { size: "small", children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: t('Support.RECOVERY_COL_CREATED', 'Created') }), _jsx(TableCell, { children: t('Support.RECOVERY_COL_USER', 'User') }), _jsx(TableCell, { children: t('Support.RECOVERY_COL_STATUS', 'Status') }), _jsx(TableCell, { children: t('Support.RECOVERY_COL_ACTIONS', 'Actions') })] }) }), _jsx(TableBody, { children: requests.map((req) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: req.created_at
73
+ return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Support.RECOVERY_REQUESTS_TITLE', 'Account recovery requests') }), _jsx(Typography, { variant: "body2", sx: { mb: 2 }, children: t('Support.RECOVERY_REQUESTS_DESCRIPTION', 'Users who cannot complete MFA can request support. You can review their request and approve or reject it.') }), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), _jsxs(Stack, { direction: "row", spacing: 2, sx: { mb: 2 }, children: [_jsx(Button, { variant: statusFilter === 'pending' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('pending'), children: t('Support.RECOVERY_FILTER_PENDING', 'Open') }), _jsx(Button, { variant: statusFilter === 'approved' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('approved'), children: t('Support.RECOVERY_FILTER_APPROVED', 'Approved') }), _jsx(Button, { variant: statusFilter === 'rejected' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('rejected'), children: t('Support.RECOVERY_FILTER_REJECTED', 'Rejected') })] }), requests.length === 0 ? (_jsx(Typography, { variant: "body2", children: t('Support.RECOVERY_REQUESTS_EMPTY', 'No recovery requests for this filter.') })) : (_jsx(TableContainer, { component: Paper, children: _jsxs(Table, { size: "small", children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: t('Support.RECOVERY_COL_CREATED', 'Created') }), _jsx(TableCell, { children: t('Support.RECOVERY_COL_USER', 'User') }), _jsx(TableCell, { children: t('Support.RECOVERY_COL_STATUS', 'Status') }), _jsx(TableCell, { children: t('Support.RECOVERY_COL_ACTIONS', 'Actions') })] }) }), _jsx(TableBody, { children: requests.map((req) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: req.created_at
71
74
  ? new Date(req.created_at).toLocaleString()
72
- : '-' }), _jsx(TableCell, { children: req.user_email || req.user }), _jsx(TableCell, { children: req.status }), _jsx(TableCell, { children: _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Button, { variant: "contained", size: "small", onClick: () => openDialog(req.id, 'approve'), disabled: req.status !== 'pending', children: t('Support.RECOVERY_ACTION_SEND_LINK', 'Send recovery link') }), _jsx(Button, { variant: "outlined", size: "small", color: "error", onClick: () => openDialog(req.id, 'reject'), disabled: req.status !== 'pending', children: t('Support.RECOVERY_ACTION_REJECT', 'Reject') })] }) })] }, req.id))) })] }) })), _jsxs(Dialog, { open: dialogOpen, onClose: closeDialog, fullWidth: true, maxWidth: "sm", children: [_jsx(DialogTitle, { children: dialogMode === 'approve'
73
- ? t('Support.RECOVERY_APPROVE_DIALOG_TITLE', 'Confirm account recovery')
74
- : t('Support.RECOVERY_REJECT_DIALOG_TITLE', 'Reject account recovery') }), _jsxs(DialogContent, { dividers: true, children: [_jsx(Typography, { variant: "body2", sx: { mb: 2 }, children: dialogMode === 'approve'
75
- ? t('Support.RECOVERY_APPROVE_CONFIRM_QUESTION', 'Are you sure you want to approve this recovery request?')
76
- : t('Support.RECOVERY_REJECT_CONFIRM_QUESTION', 'Are you sure you want to reject this recovery request?') }), _jsx(TextField, { label: t('Support.RECOVERY_NOTE_LABEL', 'Reason for this decision'), helperText: t('Support.RECOVERY_NOTE_HELP', 'Describe briefly why you are approving or rejecting this request.'), multiline: true, minRows: 3, fullWidth: true, value: dialogNote, onChange: (e) => setDialogNote(e.target.value) })] }), _jsxs(DialogActions, { children: [_jsx(Button, { onClick: closeDialog, children: t('Common.CANCEL', 'Cancel') }), _jsx(Button, { onClick: handleConfirm, variant: "contained", disabled: !dialogNote.trim(), color: dialogMode === 'approve' ? 'primary' : 'error', children: dialogMode === 'approve'
77
- ? t('Support.RECOVERY_APPROVE_SUBMIT', 'Send link')
78
- : t('Support.RECOVERY_REJECT_SUBMIT', 'Reject request') })] })] })] }));
75
+ : '-' }), _jsx(TableCell, { children: req.user_email || req.user }), _jsx(TableCell, { children: req.status }), _jsx(TableCell, { children: _jsx(Button, { variant: "contained", size: "small", onClick: () => openDialog(req), disabled: req.status !== 'pending', children: t('Support.RECOVERY_ACTION_REVIEW', 'Review') }) })] }, req.id))) })] }) })), _jsxs(Dialog, { open: dialogOpen, onClose: closeDialog, fullWidth: true, maxWidth: "sm", children: [_jsx(DialogTitle, { children: t('Support.RECOVERY_REVIEW_DIALOG_TITLE', 'Review recovery request') }), _jsxs(DialogContent, { dividers: true, children: [selectedRequest && (_jsxs(Box, { sx: { mb: 2 }, children: [_jsxs(Typography, { variant: "body2", sx: { mb: 1 }, children: [_jsxs("strong", { children: [t('Support.RECOVERY_REVIEW_USER', 'User'), ":"] }), ' ', selectedRequest.user_email || selectedRequest.user] }), _jsxs(Typography, { variant: "body2", sx: { mb: 1 }, children: [_jsxs("strong", { children: [t('Support.RECOVERY_REVIEW_CREATED', 'Requested at'), ":"] }), ' ', selectedRequest.created_at
76
+ ? new Date(selectedRequest.created_at).toLocaleString()
77
+ : '-'] }), _jsx(Typography, { variant: "body2", sx: { mb: 1 }, children: _jsxs("strong", { children: [t('Support.RECOVERY_REVIEW_MESSAGE', 'User’s explanation'), ":"] }) }), _jsx(Box, { sx: {
78
+ border: 1,
79
+ borderColor: 'divider',
80
+ borderRadius: 1,
81
+ p: 1,
82
+ mb: 2,
83
+ whiteSpace: 'pre-wrap',
84
+ fontSize: '0.875rem',
85
+ }, children: selectedRequest.message || t('Support.RECOVERY_NO_MESSAGE', 'No message provided.') })] })), _jsx(TextField, { label: t('Support.RECOVERY_NOTE_LABEL', 'Reason for your decision'), helperText: t('Support.RECOVERY_NOTE_HELP', 'Explain briefly why you approve or reject this request.'), multiline: true, minRows: 3, fullWidth: true, value: dialogNote, onChange: (e) => setDialogNote(e.target.value) })] }), _jsxs(DialogActions, { children: [_jsx(Button, { onClick: closeDialog, children: t('Common.CANCEL', 'Cancel') }), _jsx(Button, { onClick: handleReject, color: "error", disabled: !dialogNote.trim(), children: t('Support.RECOVERY_REJECT_SUBMIT', 'Reject request') }), _jsx(Button, { onClick: handleApprove, variant: "contained", disabled: !dialogNote.trim(), children: t('Support.RECOVERY_APPROVE_SUBMIT', 'Approve and send link') })] })] })] }));
79
86
  };
80
- export default SupportRecoveryRequestsTab;
87
+ export default SupportRecove;
@@ -905,6 +905,161 @@ export const authTranslations = {
905
905
  "de": "Anfrage ablehnen",
906
906
  "fr": "Refuser la demande",
907
907
  "en": "Reject request"
908
+ },
909
+ "Support.RECOVERY_REQUESTS_TITLE": {
910
+ "de": "Anfragen zur Kontowiederherstellung",
911
+ "fr": "Demandes de récupération de compte",
912
+ "en": "Account recovery requests"
913
+ },
914
+ "Support.RECOVERY_REQUESTS_DESCRIPTION": {
915
+ "de": "Benutzer, die die MFA nicht abschliessen können, können hier eine Anfrage stellen. Sie können die Anfrage prüfen und anschliessend akzeptieren oder ablehnen.",
916
+ "fr": "Les utilisateurs qui ne peuvent pas terminer la MFA peuvent envoyer ici une demande. Vous pouvez examiner la demande puis l’accepter ou la refuser.",
917
+ "en": "Users who cannot complete MFA can submit a request here. You can review the request and then approve or reject it."
918
+ },
919
+ "Support.RECOVERY_REQUESTS_LOAD_FAILED": {
920
+ "de": "Die Anfragen zur Kontowiederherstellung konnten nicht geladen werden.",
921
+ "fr": "Impossible de charger les demandes de récupération de compte.",
922
+ "en": "Failed to load account recovery requests."
923
+ },
924
+ "Support.RECOVERY_FILTER_PENDING": {
925
+ "de": "Offen",
926
+ "fr": "Ouvertes",
927
+ "en": "Open"
928
+ },
929
+ "Support.RECOVERY_FILTER_APPROVED": {
930
+ "de": "Akzeptiert",
931
+ "fr": "Acceptées",
932
+ "en": "Approved"
933
+ },
934
+ "Support.RECOVERY_FILTER_REJECTED": {
935
+ "de": "Abgelehnt",
936
+ "fr": "Refusées",
937
+ "en": "Rejected"
938
+ },
939
+ "Support.RECOVERY_REQUESTS_EMPTY": {
940
+ "de": "Für diesen Filter liegen keine Anfragen vor.",
941
+ "fr": "Aucune demande pour ce filtre.",
942
+ "en": "No recovery requests for this filter."
943
+ },
944
+ "Support.RECOVERY_COL_CREATED": {
945
+ "de": "Erstellt",
946
+ "fr": "Créée le",
947
+ "en": "Created"
948
+ },
949
+ "Support.RECOVERY_COL_USER": {
950
+ "de": "Benutzer",
951
+ "fr": "Utilisateur",
952
+ "en": "User"
953
+ },
954
+ "Support.RECOVERY_COL_STATUS": {
955
+ "de": "Status",
956
+ "fr": "Statut",
957
+ "en": "Status"
958
+ },
959
+ "Support.RECOVERY_COL_ACTIONS": {
960
+ "de": "Aktionen",
961
+ "fr": "Actions",
962
+ "en": "Actions"
963
+ },
964
+ "Support.RECOVERY_ACTION_REVIEW": {
965
+ "de": "Prüfen",
966
+ "fr": "Examiner",
967
+ "en": "Review"
968
+ },
969
+ "Support.RECOVERY_REVIEW_DIALOG_TITLE": {
970
+ "de": "Anfrage zur Kontowiederherstellung prüfen",
971
+ "fr": "Examiner la demande de récupération de compte",
972
+ "en": "Review recovery request"
973
+ },
974
+ "Support.RECOVERY_REVIEW_USER": {
975
+ "de": "Benutzer",
976
+ "fr": "Utilisateur",
977
+ "en": "User"
978
+ },
979
+ "Support.RECOVERY_REVIEW_CREATED": {
980
+ "de": "Angefragt am",
981
+ "fr": "Demandée le",
982
+ "en": "Requested at"
983
+ },
984
+ "Support.RECOVERY_REVIEW_MESSAGE": {
985
+ "de": "Begründung des Benutzers",
986
+ "fr": "Explication de l’utilisateur",
987
+ "en": "User’s explanation"
988
+ },
989
+ "Support.RECOVERY_NO_MESSAGE": {
990
+ "de": "Keine Begründung angegeben.",
991
+ "fr": "Aucune explication fournie.",
992
+ "en": "No message provided."
993
+ },
994
+ "Support.RECOVERY_NOTE_LABEL": {
995
+ "de": "Begründung für Ihre Entscheidung",
996
+ "fr": "Raison de votre décision",
997
+ "en": "Reason for your decision"
998
+ },
999
+ "Support.RECOVERY_NOTE_HELP": {
1000
+ "de": "Beschreiben Sie kurz, warum Sie diese Anfrage akzeptieren oder ablehnen.",
1001
+ "fr": "Décrivez brièvement pourquoi vous acceptez ou refusez cette demande.",
1002
+ "en": "Briefly explain why you approve or reject this request."
1003
+ },
1004
+ "Support.RECOVERY_REJECT_SUBMIT": {
1005
+ "de": "Anfrage ablehnen",
1006
+ "fr": "Refuser la demande",
1007
+ "en": "Reject request"
1008
+ },
1009
+ "Support.RECOVERY_APPROVE_SUBMIT": {
1010
+ "de": "Akzeptieren und Link senden",
1011
+ "fr": "Accepter et envoyer le lien",
1012
+ "en": "Approve and send link"
1013
+ },
1014
+ "Support.RECOVERY_REQUEST_REJECT_FAILED": {
1015
+ "de": "Die Anfrage konnte nicht abgelehnt werden.",
1016
+ "fr": "La demande n’a pas pu être refusée.",
1017
+ "en": "Failed to reject the recovery request."
1018
+ },
1019
+ "Support.RECOVERY_REQUEST_APPROVE_FAILED": {
1020
+ "de": "Die Anfrage konnte nicht akzeptiert werden.",
1021
+ "fr": "La demande n’a pas pu être acceptée.",
1022
+ "en": "Failed to approve the recovery request."
1023
+ },
1024
+ "Auth.MFA_HELP_DIALOG_TITLE": {
1025
+ "de": "Hilfe bei der Anmeldung",
1026
+ "fr": "Aide pour la connexion",
1027
+ "en": "Help with sign-in"
1028
+ },
1029
+ "Auth.MFA_HELP_DIALOG_DESCRIPTION": {
1030
+ "de": "Beschreiben Sie kurz, warum Sie die verfügbaren Methoden nicht nutzen können. Eine Support-Person wird Ihre Anfrage prüfen.",
1031
+ "fr": "Décrivez brièvement pourquoi vous ne pouvez pas utiliser les méthodes disponibles. Une personne du support examinera votre demande.",
1032
+ "en": "Briefly describe why you cannot use the available methods. A support person will review your request."
1033
+ },
1034
+ "Auth.MFA_HELP_MESSAGE_LABEL": {
1035
+ "de": "Ihre Nachricht an den Support",
1036
+ "fr": "Votre message au support",
1037
+ "en": "Your message to support"
1038
+ },
1039
+ "Auth.MFA_HELP_MESSAGE_HELP": {
1040
+ "de": "Optional, hilft dem Support aber bei der Überprüfung Ihrer Identität.",
1041
+ "fr": "Optionnel, mais aide le support à vérifier votre identité.",
1042
+ "en": "Optional, but helps support verify your identity."
1043
+ },
1044
+ "Auth.MFA_HELP_SUBMIT": {
1045
+ "de": "Anfrage senden",
1046
+ "fr": "Envoyer la demande",
1047
+ "en": "Send request"
1048
+ },
1049
+ "Auth.MFA_IDENTIFIER_REQUIRED": {
1050
+ "de": "Bitte geben Sie eine E-Mail-Adresse an.",
1051
+ "fr": "Veuillez indiquer une adresse e-mail.",
1052
+ "en": "Please provide an email address."
1053
+ },
1054
+ "Auth.MFA_HELP_REQUESTED": {
1055
+ "de": "Falls ein Konto mit dieser E-Mail existiert, wurde Ihre Anfrage an den Support weitergeleitet.",
1056
+ "fr": "Si un compte existe avec cette adresse e-mail, votre demande a été transmise au support.",
1057
+ "en": "If an account with this email exists, your request has been forwarded to support."
1058
+ },
1059
+ "Auth.MFA_HELP_REQUEST_FAILED": {
1060
+ "de": "Die Anfrage an den Support konnte nicht gesendet werden.",
1061
+ "fr": "Impossible d’envoyer la demande au support.",
1062
+ "en": "Failed to send the request to support."
908
1063
  }
909
1064
  // ...
910
1065
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micha.bigler/ui-core-micha",
3
- "version": "1.4.12",
3
+ "version": "1.4.13",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "private": false,
@@ -33,8 +33,7 @@ const SupportRecoveryRequestsTab = () => {
33
33
 
34
34
  const [dialogOpen, setDialogOpen] = useState(false);
35
35
  const [dialogNote, setDialogNote] = useState('');
36
- const [dialogRequestId, setDialogRequestId] = useState(null);
37
- const [dialogMode, setDialogMode] = useState('approve');
36
+ const [selectedRequest, setSelectedRequest] = useState(null);
38
37
 
39
38
  const loadRequests = async () => {
40
39
  setLoading(true);
@@ -54,37 +53,39 @@ const SupportRecoveryRequestsTab = () => {
54
53
  // eslint-disable-next-line react-hooks/exhaustive-deps
55
54
  }, [statusFilter]);
56
55
 
57
- const openDialog = (id, mode) => {
58
- setDialogRequestId(id);
59
- setDialogMode(mode);
56
+ const openDialog = (req) => {
57
+ setSelectedRequest(req);
60
58
  setDialogNote('');
61
59
  setDialogOpen(true);
62
60
  };
63
61
 
64
62
  const closeDialog = () => {
65
63
  setDialogOpen(false);
66
- setDialogRequestId(null);
64
+ setSelectedRequest(null);
67
65
  setDialogNote('');
68
66
  };
69
67
 
70
- const handleConfirm = async () => {
71
- if (!dialogRequestId) return;
68
+ const handleApprove = async () => {
69
+ if (!selectedRequest) return;
72
70
  setErrorKey(null);
73
71
  try {
74
- if (dialogMode === 'approve') {
75
- await authApi.approveRecoveryRequest(dialogRequestId, dialogNote);
76
- } else {
77
- await authApi.rejectRecoveryRequest(dialogRequestId, dialogNote);
78
- }
72
+ await authApi.approveRecoveryRequest(selectedRequest.id, dialogNote);
79
73
  await loadRequests();
80
74
  closeDialog();
81
75
  } catch (err) {
82
- setErrorKey(
83
- err.code ||
84
- (dialogMode === 'approve'
85
- ? 'Support.RECOVERY_REQUEST_APPROVE_FAILED'
86
- : 'Support.RECOVERY_REQUEST_REJECT_FAILED'),
87
- );
76
+ setErrorKey(err.code || 'Support.RECOVERY_REQUEST_APPROVE_FAILED');
77
+ }
78
+ };
79
+
80
+ const handleReject = async () => {
81
+ if (!selectedRequest) return;
82
+ setErrorKey(null);
83
+ try {
84
+ await authApi.rejectRecoveryRequest(selectedRequest.id, dialogNote);
85
+ await loadRequests();
86
+ closeDialog();
87
+ } catch (err) {
88
+ setErrorKey(err.code || 'Support.RECOVERY_REQUEST_REJECT_FAILED');
88
89
  }
89
90
  };
90
91
 
@@ -105,7 +106,7 @@ const SupportRecoveryRequestsTab = () => {
105
106
  <Typography variant="body2" sx={{ mb: 2 }}>
106
107
  {t(
107
108
  'Support.RECOVERY_REQUESTS_DESCRIPTION',
108
- 'Users who cannot complete MFA can request support. You can send them a recovery link or reject the request after verification.',
109
+ 'Users who cannot complete MFA can request support. You can review their request and approve or reject it.',
109
110
  )}
110
111
  </Typography>
111
112
 
@@ -176,28 +177,14 @@ const SupportRecoveryRequestsTab = () => {
176
177
  <TableCell>{req.user_email || req.user}</TableCell>
177
178
  <TableCell>{req.status}</TableCell>
178
179
  <TableCell>
179
- <Stack direction="row" spacing={1}>
180
- <Button
181
- variant="contained"
182
- size="small"
183
- onClick={() => openDialog(req.id, 'approve')}
184
- disabled={req.status !== 'pending'}
185
- >
186
- {t(
187
- 'Support.RECOVERY_ACTION_SEND_LINK',
188
- 'Send recovery link',
189
- )}
190
- </Button>
191
- <Button
192
- variant="outlined"
193
- size="small"
194
- color="error"
195
- onClick={() => openDialog(req.id, 'reject')}
196
- disabled={req.status !== 'pending'}
197
- >
198
- {t('Support.RECOVERY_ACTION_REJECT', 'Reject')}
199
- </Button>
200
- </Stack>
180
+ <Button
181
+ variant="contained"
182
+ size="small"
183
+ onClick={() => openDialog(req)}
184
+ disabled={req.status !== 'pending'}
185
+ >
186
+ {t('Support.RECOVERY_ACTION_REVIEW', 'Review')}
187
+ </Button>
201
188
  </TableCell>
202
189
  </TableRow>
203
190
  ))}
@@ -208,37 +195,58 @@ const SupportRecoveryRequestsTab = () => {
208
195
 
209
196
  <Dialog open={dialogOpen} onClose={closeDialog} fullWidth maxWidth="sm">
210
197
  <DialogTitle>
211
- {dialogMode === 'approve'
212
- ? t(
213
- 'Support.RECOVERY_APPROVE_DIALOG_TITLE',
214
- 'Confirm account recovery',
215
- )
216
- : t(
217
- 'Support.RECOVERY_REJECT_DIALOG_TITLE',
218
- 'Reject account recovery',
219
- )}
198
+ {t('Support.RECOVERY_REVIEW_DIALOG_TITLE', 'Review recovery request')}
220
199
  </DialogTitle>
221
200
  <DialogContent dividers>
222
- <Typography variant="body2" sx={{ mb: 2 }}>
223
- {dialogMode === 'approve'
224
- ? t(
225
- 'Support.RECOVERY_APPROVE_CONFIRM_QUESTION',
226
- 'Are you sure you want to approve this recovery request?',
227
- )
228
- : t(
229
- 'Support.RECOVERY_REJECT_CONFIRM_QUESTION',
230
- 'Are you sure you want to reject this recovery request?',
231
- )}
232
- </Typography>
201
+ {selectedRequest && (
202
+ <Box sx={{ mb: 2 }}>
203
+ <Typography variant="body2" sx={{ mb: 1 }}>
204
+ <strong>
205
+ {t('Support.RECOVERY_REVIEW_USER', 'User')}:
206
+ </strong>{' '}
207
+ {selectedRequest.user_email || selectedRequest.user}
208
+ </Typography>
209
+ <Typography variant="body2" sx={{ mb: 1 }}>
210
+ <strong>
211
+ {t('Support.RECOVERY_REVIEW_CREATED', 'Requested at')}:
212
+ </strong>{' '}
213
+ {selectedRequest.created_at
214
+ ? new Date(selectedRequest.created_at).toLocaleString()
215
+ : '-'}
216
+ </Typography>
217
+ <Typography variant="body2" sx={{ mb: 1 }}>
218
+ <strong>
219
+ {t(
220
+ 'Support.RECOVERY_REVIEW_MESSAGE',
221
+ 'User’s explanation',
222
+ )}
223
+ :
224
+ </strong>
225
+ </Typography>
226
+ <Box
227
+ sx={{
228
+ border: 1,
229
+ borderColor: 'divider',
230
+ borderRadius: 1,
231
+ p: 1,
232
+ mb: 2,
233
+ whiteSpace: 'pre-wrap',
234
+ fontSize: '0.875rem',
235
+ }}
236
+ >
237
+ {selectedRequest.message || t('Support.RECOVERY_NO_MESSAGE', 'No message provided.')}
238
+ </Box>
239
+ </Box>
240
+ )}
233
241
 
234
242
  <TextField
235
243
  label={t(
236
244
  'Support.RECOVERY_NOTE_LABEL',
237
- 'Reason for this decision',
245
+ 'Reason for your decision',
238
246
  )}
239
247
  helperText={t(
240
248
  'Support.RECOVERY_NOTE_HELP',
241
- 'Describe briefly why you are approving or rejecting this request.',
249
+ 'Explain briefly why you approve or reject this request.',
242
250
  )}
243
251
  multiline
244
252
  minRows={3}
@@ -252,14 +260,18 @@ const SupportRecoveryRequestsTab = () => {
252
260
  {t('Common.CANCEL', 'Cancel')}
253
261
  </Button>
254
262
  <Button
255
- onClick={handleConfirm}
263
+ onClick={handleReject}
264
+ color="error"
265
+ disabled={!dialogNote.trim()}
266
+ >
267
+ {t('Support.RECOVERY_REJECT_SUBMIT', 'Reject request')}
268
+ </Button>
269
+ <Button
270
+ onClick={handleApprove}
256
271
  variant="contained"
257
272
  disabled={!dialogNote.trim()}
258
- color={dialogMode === 'approve' ? 'primary' : 'error'}
259
273
  >
260
- {dialogMode === 'approve'
261
- ? t('Support.RECOVERY_APPROVE_SUBMIT', 'Send link')
262
- : t('Support.RECOVERY_REJECT_SUBMIT', 'Reject request')}
274
+ {t('Support.RECOVERY_APPROVE_SUBMIT', 'Approve and send link')}
263
275
  </Button>
264
276
  </DialogActions>
265
277
  </Dialog>
@@ -267,4 +279,4 @@ const SupportRecoveryRequestsTab = () => {
267
279
  );
268
280
  };
269
281
 
270
- export default SupportRecoveryRequestsTab;
282
+ export default SupportRecove
@@ -953,7 +953,167 @@ export const authTranslations = {
953
953
  "de": "Anfrage ablehnen",
954
954
  "fr": "Refuser la demande",
955
955
  "en": "Reject request"
956
- }
956
+ },
957
+ "Support.RECOVERY_REQUESTS_TITLE": {
958
+ "de": "Anfragen zur Kontowiederherstellung",
959
+ "fr": "Demandes de récupération de compte",
960
+ "en": "Account recovery requests"
961
+ },
962
+ "Support.RECOVERY_REQUESTS_DESCRIPTION": {
963
+ "de": "Benutzer, die die MFA nicht abschliessen können, können hier eine Anfrage stellen. Sie können die Anfrage prüfen und anschliessend akzeptieren oder ablehnen.",
964
+ "fr": "Les utilisateurs qui ne peuvent pas terminer la MFA peuvent envoyer ici une demande. Vous pouvez examiner la demande puis l’accepter ou la refuser.",
965
+ "en": "Users who cannot complete MFA can submit a request here. You can review the request and then approve or reject it."
966
+ },
967
+ "Support.RECOVERY_REQUESTS_LOAD_FAILED": {
968
+ "de": "Die Anfragen zur Kontowiederherstellung konnten nicht geladen werden.",
969
+ "fr": "Impossible de charger les demandes de récupération de compte.",
970
+ "en": "Failed to load account recovery requests."
971
+ },
972
+ "Support.RECOVERY_FILTER_PENDING": {
973
+ "de": "Offen",
974
+ "fr": "Ouvertes",
975
+ "en": "Open"
976
+ },
977
+ "Support.RECOVERY_FILTER_APPROVED": {
978
+ "de": "Akzeptiert",
979
+ "fr": "Acceptées",
980
+ "en": "Approved"
981
+ },
982
+ "Support.RECOVERY_FILTER_REJECTED": {
983
+ "de": "Abgelehnt",
984
+ "fr": "Refusées",
985
+ "en": "Rejected"
986
+ },
987
+ "Support.RECOVERY_REQUESTS_EMPTY": {
988
+ "de": "Für diesen Filter liegen keine Anfragen vor.",
989
+ "fr": "Aucune demande pour ce filtre.",
990
+ "en": "No recovery requests for this filter."
991
+ },
992
+ "Support.RECOVERY_COL_CREATED": {
993
+ "de": "Erstellt",
994
+ "fr": "Créée le",
995
+ "en": "Created"
996
+ },
997
+ "Support.RECOVERY_COL_USER": {
998
+ "de": "Benutzer",
999
+ "fr": "Utilisateur",
1000
+ "en": "User"
1001
+ },
1002
+ "Support.RECOVERY_COL_STATUS": {
1003
+ "de": "Status",
1004
+ "fr": "Statut",
1005
+ "en": "Status"
1006
+ },
1007
+ "Support.RECOVERY_COL_ACTIONS": {
1008
+ "de": "Aktionen",
1009
+ "fr": "Actions",
1010
+ "en": "Actions"
1011
+ },
1012
+ "Support.RECOVERY_ACTION_REVIEW": {
1013
+ "de": "Prüfen",
1014
+ "fr": "Examiner",
1015
+ "en": "Review"
1016
+ },
1017
+
1018
+ "Support.RECOVERY_REVIEW_DIALOG_TITLE": {
1019
+ "de": "Anfrage zur Kontowiederherstellung prüfen",
1020
+ "fr": "Examiner la demande de récupération de compte",
1021
+ "en": "Review recovery request"
1022
+ },
1023
+ "Support.RECOVERY_REVIEW_USER": {
1024
+ "de": "Benutzer",
1025
+ "fr": "Utilisateur",
1026
+ "en": "User"
1027
+ },
1028
+ "Support.RECOVERY_REVIEW_CREATED": {
1029
+ "de": "Angefragt am",
1030
+ "fr": "Demandée le",
1031
+ "en": "Requested at"
1032
+ },
1033
+ "Support.RECOVERY_REVIEW_MESSAGE": {
1034
+ "de": "Begründung des Benutzers",
1035
+ "fr": "Explication de l’utilisateur",
1036
+ "en": "User’s explanation"
1037
+ },
1038
+ "Support.RECOVERY_NO_MESSAGE": {
1039
+ "de": "Keine Begründung angegeben.",
1040
+ "fr": "Aucune explication fournie.",
1041
+ "en": "No message provided."
1042
+ },
1043
+
1044
+ "Support.RECOVERY_NOTE_LABEL": {
1045
+ "de": "Begründung für Ihre Entscheidung",
1046
+ "fr": "Raison de votre décision",
1047
+ "en": "Reason for your decision"
1048
+ },
1049
+ "Support.RECOVERY_NOTE_HELP": {
1050
+ "de": "Beschreiben Sie kurz, warum Sie diese Anfrage akzeptieren oder ablehnen.",
1051
+ "fr": "Décrivez brièvement pourquoi vous acceptez ou refusez cette demande.",
1052
+ "en": "Briefly explain why you approve or reject this request."
1053
+ },
1054
+
1055
+ "Support.RECOVERY_REJECT_SUBMIT": {
1056
+ "de": "Anfrage ablehnen",
1057
+ "fr": "Refuser la demande",
1058
+ "en": "Reject request"
1059
+ },
1060
+ "Support.RECOVERY_APPROVE_SUBMIT": {
1061
+ "de": "Akzeptieren und Link senden",
1062
+ "fr": "Accepter et envoyer le lien",
1063
+ "en": "Approve and send link"
1064
+ },
1065
+ "Support.RECOVERY_REQUEST_REJECT_FAILED": {
1066
+ "de": "Die Anfrage konnte nicht abgelehnt werden.",
1067
+ "fr": "La demande n’a pas pu être refusée.",
1068
+ "en": "Failed to reject the recovery request."
1069
+ },
1070
+ "Support.RECOVERY_REQUEST_APPROVE_FAILED": {
1071
+ "de": "Die Anfrage konnte nicht akzeptiert werden.",
1072
+ "fr": "La demande n’a pas pu être acceptée.",
1073
+ "en": "Failed to approve the recovery request."
1074
+ },
1075
+
1076
+ "Auth.MFA_HELP_DIALOG_TITLE": {
1077
+ "de": "Hilfe bei der Anmeldung",
1078
+ "fr": "Aide pour la connexion",
1079
+ "en": "Help with sign-in"
1080
+ },
1081
+ "Auth.MFA_HELP_DIALOG_DESCRIPTION": {
1082
+ "de": "Beschreiben Sie kurz, warum Sie die verfügbaren Methoden nicht nutzen können. Eine Support-Person wird Ihre Anfrage prüfen.",
1083
+ "fr": "Décrivez brièvement pourquoi vous ne pouvez pas utiliser les méthodes disponibles. Une personne du support examinera votre demande.",
1084
+ "en": "Briefly describe why you cannot use the available methods. A support person will review your request."
1085
+ },
1086
+ "Auth.MFA_HELP_MESSAGE_LABEL": {
1087
+ "de": "Ihre Nachricht an den Support",
1088
+ "fr": "Votre message au support",
1089
+ "en": "Your message to support"
1090
+ },
1091
+ "Auth.MFA_HELP_MESSAGE_HELP": {
1092
+ "de": "Optional, hilft dem Support aber bei der Überprüfung Ihrer Identität.",
1093
+ "fr": "Optionnel, mais aide le support à vérifier votre identité.",
1094
+ "en": "Optional, but helps support verify your identity."
1095
+ },
1096
+ "Auth.MFA_HELP_SUBMIT": {
1097
+ "de": "Anfrage senden",
1098
+ "fr": "Envoyer la demande",
1099
+ "en": "Send request"
1100
+ },
1101
+
1102
+ "Auth.MFA_IDENTIFIER_REQUIRED": {
1103
+ "de": "Bitte geben Sie eine E-Mail-Adresse an.",
1104
+ "fr": "Veuillez indiquer une adresse e-mail.",
1105
+ "en": "Please provide an email address."
1106
+ },
1107
+ "Auth.MFA_HELP_REQUESTED": {
1108
+ "de": "Falls ein Konto mit dieser E-Mail existiert, wurde Ihre Anfrage an den Support weitergeleitet.",
1109
+ "fr": "Si un compte existe avec cette adresse e-mail, votre demande a été transmise au support.",
1110
+ "en": "If an account with this email exists, your request has been forwarded to support."
1111
+ },
1112
+ "Auth.MFA_HELP_REQUEST_FAILED": {
1113
+ "de": "Die Anfrage an den Support konnte nicht gesendet werden.",
1114
+ "fr": "Impossible d’envoyer la demande au support.",
1115
+ "en": "Failed to send the request to support."
1116
+ }
957
1117
 
958
1118
 
959
1119