@micha.bigler/ui-core-micha 1.0.0 → 1.1.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/dist/auth/authApi.js +24 -1
- package/dist/auth/authConfig.js +2 -0
- package/dist/components/AccessCodeManager.js +113 -0
- package/dist/components/LoginForm.js +4 -2
- package/dist/index.js +2 -0
- package/dist/pages/LoginPage.js +6 -3
- package/dist/pages/PasswordResetRequestPage.js +1 -1
- package/dist/pages/SignUpPage.js +56 -0
- package/package.json +23 -23
- package/src/auth/AuthContext.jsx +108 -108
- package/src/auth/authApi.jsx +243 -210
- package/src/auth/authConfig.jsx +34 -31
- package/src/components/AccessCodeManager.jsx +236 -0
- package/src/components/LoginForm.jsx +108 -93
- package/src/components/PasswordChangeForm.jsx +66 -66
- package/src/components/PasswordResetRequestForm.jsx +41 -41
- package/src/components/PasswordSetForm.jsx +76 -76
- package/src/components/ProfileComponent.jsx +230 -230
- package/src/components/SecurityComponent.jsx +95 -95
- package/src/components/SocialLoginButtons.jsx +72 -72
- package/src/index.js +18 -16
- package/src/layout/PageLayout.jsx +34 -34
- package/src/pages/AccountPage.jsx +64 -64
- package/src/pages/LoginPage.jsx +78 -72
- package/src/pages/PasswordChangePage.jsx +58 -58
- package/src/pages/PasswordInvitePage.jsx +108 -108
- package/src/pages/PasswordResetRequestPage.jsx +61 -61
- package/src/pages/SignUpPage.jsx +136 -0
- package/tsconfig.build.json +16 -16
package/dist/auth/authApi.js
CHANGED
|
@@ -13,7 +13,7 @@ function extractErrorMessage(error) {
|
|
|
13
13
|
if (Array.isArray(data.non_field_errors) && data.non_field_errors.length > 0) {
|
|
14
14
|
return data.non_field_errors[0];
|
|
15
15
|
}
|
|
16
|
-
return
|
|
16
|
+
return 'An error occurred. Please try again.';
|
|
17
17
|
}
|
|
18
18
|
// Helper to get CSRF token from cookies manually
|
|
19
19
|
function getCsrfToken() {
|
|
@@ -133,6 +133,27 @@ export async function logoutSession() {
|
|
|
133
133
|
export function startSocialLogin(provider) {
|
|
134
134
|
window.location.href = `/accounts/${provider}/login/?process=login`;
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Prüft, ob ein Access-Code gültig ist.
|
|
138
|
+
* Erwartet: POST /api/access-codes/validate/ { code }
|
|
139
|
+
* Antwort: { valid: true/false } oder 400 mit detail-Fehler.
|
|
140
|
+
*/
|
|
141
|
+
export async function validateAccessCode(code) {
|
|
142
|
+
const res = await axios.post(`${ACCESS_CODES_BASE}/validate/`, { code }, { withCredentials: true });
|
|
143
|
+
return res.data; // { valid: bool } oder Error
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Fordert eine Einladung mit optionalem Access-Code an.
|
|
147
|
+
* Backend prüft den Code noch einmal serverseitig.
|
|
148
|
+
*/
|
|
149
|
+
export async function requestInviteWithCode(email, accessCode) {
|
|
150
|
+
const payload = { email };
|
|
151
|
+
if (accessCode) {
|
|
152
|
+
payload.access_code = accessCode;
|
|
153
|
+
}
|
|
154
|
+
const res = await axios.post(`${USERS_BASE}/invite/`, payload, { withCredentials: true });
|
|
155
|
+
return res.data;
|
|
156
|
+
}
|
|
136
157
|
/**
|
|
137
158
|
* Loads the current session information directly from allauth headless.
|
|
138
159
|
*/
|
|
@@ -169,4 +190,6 @@ export const authApi = {
|
|
|
169
190
|
setNewPassword,
|
|
170
191
|
loginWithPasskey,
|
|
171
192
|
registerPasskey,
|
|
193
|
+
validateAccessCode,
|
|
194
|
+
requestInviteWithCode,
|
|
172
195
|
};
|
package/dist/auth/authConfig.js
CHANGED
|
@@ -9,6 +9,8 @@ export const HEADLESS_VERSION = 'v1';
|
|
|
9
9
|
export const HEADLESS_BASE = `${AUTH_BASE}/${HEADLESS_VARIANT}/${HEADLESS_VERSION}`;
|
|
10
10
|
// Eigene User-API
|
|
11
11
|
export const USERS_BASE = '/api/users';
|
|
12
|
+
// NEU: Access-Code-API (kommt aus der Lib)
|
|
13
|
+
export const ACCESS_CODES_BASE = '/api/access-codes';
|
|
12
14
|
// CSRF-Endpoint (Django-View csrf_token_view)
|
|
13
15
|
export const CSRF_URL = '/api/csrf/';
|
|
14
16
|
// Konfiguration der Social-Provider
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// src/auth/components/AccessCodeManager.jsx
|
|
3
|
+
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import { Box, Stack, Typography, Slider, Button, TextField, IconButton, Chip, Alert, CircularProgress, } from '@mui/material';
|
|
6
|
+
import CloseIcon from '@mui/icons-material/Close';
|
|
7
|
+
import { ACCESS_CODES_BASE } from '../auth/authConfig';
|
|
8
|
+
/**
|
|
9
|
+
* AccessCodeManager
|
|
10
|
+
*
|
|
11
|
+
* - Zeigt alle Access-Codes
|
|
12
|
+
* - Löschen von Codes
|
|
13
|
+
* - Generieren neuer Codes mit wählbarer Länge
|
|
14
|
+
* - Manuelles Hinzufügen eines Codes
|
|
15
|
+
*
|
|
16
|
+
* Backend-API (DRF ViewSet):
|
|
17
|
+
* GET /api/access-codes/ → Liste [{id, code, is_active, created_at}]
|
|
18
|
+
* POST /api/access-codes/ → {code: "..."} → 201
|
|
19
|
+
* DELETE /api/access-codes/{id}/ → 204
|
|
20
|
+
*/
|
|
21
|
+
export function AccessCodeManager() {
|
|
22
|
+
const [codes, setCodes] = useState([]);
|
|
23
|
+
const [loading, setLoading] = useState(true);
|
|
24
|
+
const [submitting, setSubmitting] = useState(false);
|
|
25
|
+
const [length, setLength] = useState(12);
|
|
26
|
+
const [manualCode, setManualCode] = useState('');
|
|
27
|
+
const [error, setError] = useState('');
|
|
28
|
+
const [success, setSuccess] = useState('');
|
|
29
|
+
// Loads all access codes
|
|
30
|
+
const loadCodes = async () => {
|
|
31
|
+
var _a, _b;
|
|
32
|
+
setLoading(true);
|
|
33
|
+
setError('');
|
|
34
|
+
try {
|
|
35
|
+
const res = await axios.get(ACCESS_CODES_BASE + '/', {
|
|
36
|
+
withCredentials: true,
|
|
37
|
+
});
|
|
38
|
+
setCodes(res.data || []);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
setError(((_b = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.detail) || (err === null || err === void 0 ? void 0 : err.message) || 'Could not load access codes.');
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
setLoading(false);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
loadCodes();
|
|
49
|
+
}, []);
|
|
50
|
+
// Generates a random code with the selected length
|
|
51
|
+
const generateRandomCode = (len) => {
|
|
52
|
+
const alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
|
53
|
+
let out = '';
|
|
54
|
+
for (let i = 0; i < len; i += 1) {
|
|
55
|
+
const idx = Math.floor(Math.random() * alphabet.length);
|
|
56
|
+
out += alphabet[idx];
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
};
|
|
60
|
+
const handleCreateCode = async (code) => {
|
|
61
|
+
var _a, _b;
|
|
62
|
+
setSubmitting(true);
|
|
63
|
+
setError('');
|
|
64
|
+
setSuccess('');
|
|
65
|
+
try {
|
|
66
|
+
const res = await axios.post(ACCESS_CODES_BASE + '/', { code }, { withCredentials: true });
|
|
67
|
+
setCodes((prev) => [...prev, res.data]);
|
|
68
|
+
setSuccess('Access code saved.');
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
setError(((_b = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.detail) ||
|
|
72
|
+
(err === null || err === void 0 ? void 0 : err.message) ||
|
|
73
|
+
'Could not save access code.');
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
setSubmitting(false);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const handleGenerateClick = async () => {
|
|
80
|
+
const code = generateRandomCode(length);
|
|
81
|
+
await handleCreateCode(code);
|
|
82
|
+
};
|
|
83
|
+
const handleAddManual = async () => {
|
|
84
|
+
if (!manualCode.trim()) {
|
|
85
|
+
setError('Please enter a code.');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
await handleCreateCode(manualCode.trim());
|
|
89
|
+
setManualCode('');
|
|
90
|
+
};
|
|
91
|
+
const handleDelete = async (id) => {
|
|
92
|
+
var _a, _b;
|
|
93
|
+
setError('');
|
|
94
|
+
setSuccess('');
|
|
95
|
+
try {
|
|
96
|
+
await axios.delete(`${ACCESS_CODES_BASE}/${id}/`, {
|
|
97
|
+
withCredentials: true,
|
|
98
|
+
});
|
|
99
|
+
setCodes((prev) => prev.filter((c) => c.id !== id));
|
|
100
|
+
setSuccess('Access code deleted.');
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
setError(((_b = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.detail) ||
|
|
104
|
+
(err === null || err === void 0 ? void 0 : err.message) ||
|
|
105
|
+
'Could not delete access code.');
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
if (loading) {
|
|
109
|
+
return (_jsx(Box, { sx: { py: 3, display: 'flex', justifyContent: 'center' }, children: _jsx(CircularProgress, {}) }));
|
|
110
|
+
}
|
|
111
|
+
return (_jsxs(Box, { sx: { mt: 2 }, children: [error && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: error })), success && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: success })), _jsxs(Box, { sx: { mb: 3 }, children: [_jsx(Typography, { variant: "subtitle1", gutterBottom: true, children: "Active access codes" }), codes.length === 0 ? (_jsx(Typography, { variant: "body2", children: "No access codes defined." })) : (_jsx(Stack, { direction: "row", flexWrap: "wrap", gap: 1, children: codes.map((code) => (_jsx(Chip, { label: code.code, onDelete: () => handleDelete(code.id), deleteIcon: _jsx(CloseIcon, {}) }, code.id))) }))] }), _jsxs(Box, { sx: { mb: 3 }, children: [_jsx(Typography, { variant: "subtitle1", gutterBottom: true, children: "Generate new code" }), _jsxs(Box, { sx: { maxWidth: 360 }, children: [_jsxs(Typography, { variant: "body2", gutterBottom: true, children: ["Length: ", length] }), _jsx(Slider, { min: 6, max: 32, step: 1, value: length, onChange: (_, val) => setLength(val), valueLabelDisplay: "auto", disabled: submitting })] }), _jsx(Button, { variant: "contained", sx: { mt: 1 }, onClick: handleGenerateClick, disabled: submitting, children: submitting ? 'Saving…' : 'Generate code' })] }), _jsxs(Box, { sx: { mb: 2, maxWidth: 360 }, children: [_jsx(Typography, { variant: "subtitle1", gutterBottom: true, children: "Add code manually" }), _jsxs(Box, { sx: { display: 'flex', gap: 1 }, children: [_jsx(TextField, { label: "Access code", fullWidth: true, value: manualCode, onChange: (e) => setManualCode(e.target.value), disabled: submitting }), _jsx(Button, { variant: "outlined", onClick: handleAddManual, disabled: submitting, children: "Add" })] })] })] }));
|
|
112
|
+
}
|
|
113
|
+
export default AccessCodeManager;
|
|
@@ -3,7 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import React, { useState } from 'react';
|
|
4
4
|
import { Box, TextField, Button, Typography, Divider, } from '@mui/material';
|
|
5
5
|
import SocialLoginButtons from './SocialLoginButtons';
|
|
6
|
-
const LoginForm = ({ onSubmit, onForgotPassword, onSocialLogin,
|
|
6
|
+
const LoginForm = ({ onSubmit, onForgotPassword, onSocialLogin, onSignUp, // <--- NEU
|
|
7
|
+
error, disabled = false, }) => {
|
|
7
8
|
const [identifier, setIdentifier] = useState('');
|
|
8
9
|
const [password, setPassword] = useState('');
|
|
9
10
|
const handleSubmit = (event) => {
|
|
@@ -17,6 +18,7 @@ const LoginForm = ({ onSubmit, onForgotPassword, onSocialLogin, error, disabled
|
|
|
17
18
|
justifyContent: 'space-between',
|
|
18
19
|
alignItems: 'center',
|
|
19
20
|
mt: 1,
|
|
20
|
-
|
|
21
|
+
gap: 1,
|
|
22
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', gap: 1 }, children: [_jsx(Button, { type: "submit", variant: "contained", disabled: disabled, children: "Login" }), onSignUp && (_jsx(Button, { type: "button", variant: "outlined", onClick: onSignUp, disabled: disabled, children: "Sign up" }))] }), _jsx(Button, { type: "button", variant: "outlined", onClick: onForgotPassword, disabled: disabled, children: "Forgot password?" })] }), _jsx(Divider, { sx: { my: 2 }, children: "or" }), _jsx(SocialLoginButtons, { onProviderClick: onSocialLogin })] }));
|
|
21
23
|
};
|
|
22
24
|
export default LoginForm;
|
package/dist/index.js
CHANGED
|
@@ -10,5 +10,7 @@ export { PasswordChangePage } from './pages/PasswordChangePage';
|
|
|
10
10
|
export { PasswordInvitePage } from './pages/PasswordInvitePage';
|
|
11
11
|
export { AccountPage } from './pages/AccountPage';
|
|
12
12
|
export { ProfileComponent } from './components/ProfileComponent';
|
|
13
|
+
export { SignUpPage } from './pages/SignUpPage';
|
|
14
|
+
export { AccessCodeManager } from './components/AccessCodeManager';
|
|
13
15
|
// Falls du noch pure UI-Komponenten hast (Formulare)
|
|
14
16
|
// export { default as LoginForm } from './components/forms/LoginForm';
|
package/dist/pages/LoginPage.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// src/pages/LoginPage.jsx (oder wo deine LoginPage liegt)
|
|
2
3
|
import React, { useState, useContext } from 'react';
|
|
3
4
|
import { useNavigate } from 'react-router-dom';
|
|
4
5
|
import { Helmet } from 'react-helmet';
|
|
@@ -39,12 +40,14 @@ export function LoginPage() {
|
|
|
39
40
|
authApi.startSocialLogin(providerKey);
|
|
40
41
|
}
|
|
41
42
|
catch (err) {
|
|
42
|
-
//
|
|
43
|
+
// eslint-disable-next-line no-console
|
|
43
44
|
console.error('Social login init failed', err);
|
|
44
45
|
setError('Could not start social login.');
|
|
45
46
|
}
|
|
46
47
|
};
|
|
47
|
-
|
|
48
|
+
const handleSignUp = () => {
|
|
49
|
+
navigate('/signup');
|
|
50
|
+
};
|
|
51
|
+
return (_jsxs(NarrowPage, { title: "Login", children: [_jsx(Helmet, { children: _jsx("title", { children: "PROJECT_NAME \u2013 Login" }) }), error && (_jsx(Typography, { color: "error", gutterBottom: true, children: error })), _jsx(LoginForm, { onSubmit: handleSubmit, onForgotPassword: handleForgotPassword, onSocialLogin: handleSocialLogin, onSignUp: handleSignUp, error: undefined, disabled: submitting })] }));
|
|
48
52
|
}
|
|
49
|
-
;
|
|
50
53
|
export default LoginPage;
|
|
@@ -29,7 +29,7 @@ export function PasswordResetRequestPage() {
|
|
|
29
29
|
setSubmitting(false);
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
|
-
return (_jsxs(NarrowPage, { title: "Reset password", children: [_jsx(Helmet, { children: _jsx("title", { children: "PROJECT_NAME \u2013 Reset password" }) }), successMessage && (_jsx(Typography, {
|
|
32
|
+
return (_jsxs(NarrowPage, { title: "Reset password", children: [_jsx(Helmet, { children: _jsx("title", { children: "PROJECT_NAME \u2013 Reset password" }) }), successMessage && (_jsx(Typography, { color: "primary", gutterBottom: true, children: successMessage })), error && (_jsx(Typography, { color: "error", gutterBottom: true, children: error })), _jsx(PasswordResetRequestForm, { onSubmit: handleSubmit, submitting: submitting })] }));
|
|
33
33
|
}
|
|
34
34
|
;
|
|
35
35
|
export default PasswordResetRequestPage;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// src/pages/SignUpPage.jsx
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { useNavigate, Link as RouterLink } from 'react-router-dom';
|
|
5
|
+
import { Box, TextField, Button, Typography, Alert, } from '@mui/material';
|
|
6
|
+
import { Helmet } from 'react-helmet';
|
|
7
|
+
import { NarrowPage } from '../layout/PageLayout';
|
|
8
|
+
import { authApi } from '../auth/authApi';
|
|
9
|
+
export function SignUpPage() {
|
|
10
|
+
const navigate = useNavigate();
|
|
11
|
+
const [email, setEmail] = useState('');
|
|
12
|
+
const [accessCode, setAccessCode] = useState('');
|
|
13
|
+
const [submitting, setSubmitting] = useState(false);
|
|
14
|
+
const [success, setSuccess] = useState('');
|
|
15
|
+
const [error, setError] = useState('');
|
|
16
|
+
const handleSubmit = async (event) => {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
setSuccess('');
|
|
20
|
+
setError('');
|
|
21
|
+
if (!email) {
|
|
22
|
+
setError('Please enter an email address.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!accessCode) {
|
|
26
|
+
setError('Please enter an access code.');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
setSubmitting(true);
|
|
30
|
+
try {
|
|
31
|
+
// 1) Access-Code prüfen (API /api/access-codes/validate/)
|
|
32
|
+
const res = await authApi.validateAccessCode(accessCode);
|
|
33
|
+
if (!res.valid) {
|
|
34
|
+
setError('Access code is invalid.');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// 2) Invite anfordern (API /api/users/invite/)
|
|
38
|
+
await authApi.requestInviteWithCode(email, accessCode);
|
|
39
|
+
setSuccess(`If the access code is valid for this project, an invitation link has been sent to ${email}.`);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
const detail = ((_b = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.detail) ||
|
|
43
|
+
(err === null || err === void 0 ? void 0 : err.message) ||
|
|
44
|
+
'Could not request invitation.';
|
|
45
|
+
setError(detail);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
setSubmitting(false);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const handleGoToLogin = () => {
|
|
52
|
+
navigate('/login');
|
|
53
|
+
};
|
|
54
|
+
return (_jsxs(NarrowPage, { title: "Sign up", children: [_jsx(Helmet, { children: _jsx("title", { children: "PROJECT_NAME \u2013 Sign up" }) }), success && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: success })), error && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: error })), _jsxs(Box, { component: "form", onSubmit: handleSubmit, sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(TextField, { label: "Email address", type: "email", required: true, fullWidth: true, value: email, onChange: (e) => setEmail(e.target.value), disabled: submitting }), _jsx(TextField, { label: "Access code", type: "text", required: true, fullWidth: true, value: accessCode, onChange: (e) => setAccessCode(e.target.value), disabled: submitting }), _jsx(Button, { type: "submit", variant: "contained", disabled: submitting, children: submitting ? 'Submitting…' : 'Request invitation' })] }), _jsx(Box, { sx: { mt: 3 }, children: _jsxs(Typography, { variant: "body2", children: ["Already have an account?", ' ', _jsx(Button, { onClick: handleGoToLogin, variant: "text", size: "small", children: "Go to login" })] }) })] }));
|
|
55
|
+
}
|
|
56
|
+
export default SignUpPage;
|
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@micha.bigler/ui-core-micha",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"main": "dist/index.js",
|
|
5
|
-
"module": "dist/index.js",
|
|
6
|
-
"private": false,
|
|
7
|
-
"peerDependencies": {
|
|
8
|
-
"@emotion/react": "^11.0.0",
|
|
9
|
-
"@emotion/styled": "^11.0.0",
|
|
10
|
-
"@mui/material": "^
|
|
11
|
-
"axios": "^1.0.0",
|
|
12
|
-
"react": "^18.0.0",
|
|
13
|
-
"react-dom": "^18.0.0",
|
|
14
|
-
"react-helmet": "^6.0.0",
|
|
15
|
-
"react-router-dom": "^6.0.0"
|
|
16
|
-
},
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build": "tsc -p tsconfig.build.json"
|
|
19
|
-
},
|
|
20
|
-
"devDependencies": {
|
|
21
|
-
"typescript": "^5.9.3"
|
|
22
|
-
}
|
|
23
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@micha.bigler/ui-core-micha",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"module": "dist/index.js",
|
|
6
|
+
"private": false,
|
|
7
|
+
"peerDependencies": {
|
|
8
|
+
"@emotion/react": "^11.0.0",
|
|
9
|
+
"@emotion/styled": "^11.0.0",
|
|
10
|
+
"@mui/material": "^7.3.5",
|
|
11
|
+
"axios": "^1.0.0",
|
|
12
|
+
"react": "^18.0.0",
|
|
13
|
+
"react-dom": "^18.0.0",
|
|
14
|
+
"react-helmet": "^6.0.0",
|
|
15
|
+
"react-router-dom": "^6.0.0"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc -p tsconfig.build.json"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"typescript": "^5.9.3"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/auth/AuthContext.jsx
CHANGED
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
// src/auth/AuthContext.jsx
|
|
2
|
-
import React, {
|
|
3
|
-
createContext,
|
|
4
|
-
useState,
|
|
5
|
-
useEffect,
|
|
6
|
-
} from 'react';
|
|
7
|
-
import axios from 'axios';
|
|
8
|
-
import { CSRF_URL } from './authConfig';
|
|
9
|
-
import {
|
|
10
|
-
fetchCurrentUser,
|
|
11
|
-
logoutSession,
|
|
12
|
-
} from './authApi';
|
|
13
|
-
|
|
14
|
-
export const AuthContext = createContext(null);
|
|
15
|
-
|
|
16
|
-
export const AuthProvider = ({ children }) => {
|
|
17
|
-
const [user, setUser] = useState(null);
|
|
18
|
-
const [loading, setLoading] = useState(true);
|
|
19
|
-
|
|
20
|
-
// Einmalige Axios-Basis-Konfiguration
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
axios.defaults.withCredentials = true;
|
|
23
|
-
axios.defaults.xsrfCookieName = 'csrftoken';
|
|
24
|
-
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
|
|
25
|
-
}, []);
|
|
26
|
-
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
let isMounted = true;
|
|
29
|
-
|
|
30
|
-
const initAuth = async () => {
|
|
31
|
-
try {
|
|
32
|
-
// 1) CSRF-Cookie setzen (Django-View /api/csrf/)
|
|
33
|
-
try {
|
|
34
|
-
await axios.get(CSRF_URL, { withCredentials: true });
|
|
35
|
-
// console.log('CSRF cookie set');
|
|
36
|
-
} catch (err) {
|
|
37
|
-
// eslint-disable-next-line no-console
|
|
38
|
-
console.error('Error setting CSRF cookie:', err);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// 2) aktuellen User laden (falls Session vorhanden)
|
|
42
|
-
try {
|
|
43
|
-
const data = await fetchCurrentUser();
|
|
44
|
-
if (!isMounted) return;
|
|
45
|
-
setUser({
|
|
46
|
-
id: data.id,
|
|
47
|
-
username: data.username,
|
|
48
|
-
email: data.email,
|
|
49
|
-
first_name: data.first_name,
|
|
50
|
-
last_name: data.last_name,
|
|
51
|
-
role: data.role,
|
|
52
|
-
is_superuser: data.is_superuser,
|
|
53
|
-
});
|
|
54
|
-
} catch (err) {
|
|
55
|
-
// Kein eingeloggter User ist ein normaler Fall
|
|
56
|
-
// eslint-disable-next-line no-console
|
|
57
|
-
console.log('No logged-in user:', err?.message || err);
|
|
58
|
-
if (!isMounted) return;
|
|
59
|
-
setUser(null);
|
|
60
|
-
}
|
|
61
|
-
} finally {
|
|
62
|
-
if (isMounted) {
|
|
63
|
-
setLoading(false);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
initAuth();
|
|
69
|
-
|
|
70
|
-
return () => {
|
|
71
|
-
isMounted = false;
|
|
72
|
-
};
|
|
73
|
-
}, []);
|
|
74
|
-
|
|
75
|
-
// Nach erfolgreichem Login das User-Objekt setzen
|
|
76
|
-
// (z. B. aus loginWithPassword in authApi)
|
|
77
|
-
const login = (userData) => {
|
|
78
|
-
setUser((prev) => ({
|
|
79
|
-
...prev,
|
|
80
|
-
...userData,
|
|
81
|
-
}));
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
// Logout im Backend + lokalen State leeren
|
|
85
|
-
const logout = async () => {
|
|
86
|
-
try {
|
|
87
|
-
await logoutSession();
|
|
88
|
-
} catch (error) {
|
|
89
|
-
// eslint-disable-next-line no-console
|
|
90
|
-
console.error('Error during logout:', error);
|
|
91
|
-
} finally {
|
|
92
|
-
setUser(null);
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<AuthContext.Provider
|
|
98
|
-
value={{
|
|
99
|
-
user,
|
|
100
|
-
loading,
|
|
101
|
-
login,
|
|
102
|
-
logout,
|
|
103
|
-
}}
|
|
104
|
-
>
|
|
105
|
-
{children}
|
|
106
|
-
</AuthContext.Provider>
|
|
107
|
-
);
|
|
108
|
-
};
|
|
1
|
+
// src/auth/AuthContext.jsx
|
|
2
|
+
import React, {
|
|
3
|
+
createContext,
|
|
4
|
+
useState,
|
|
5
|
+
useEffect,
|
|
6
|
+
} from 'react';
|
|
7
|
+
import axios from 'axios';
|
|
8
|
+
import { CSRF_URL } from './authConfig';
|
|
9
|
+
import {
|
|
10
|
+
fetchCurrentUser,
|
|
11
|
+
logoutSession,
|
|
12
|
+
} from './authApi';
|
|
13
|
+
|
|
14
|
+
export const AuthContext = createContext(null);
|
|
15
|
+
|
|
16
|
+
export const AuthProvider = ({ children }) => {
|
|
17
|
+
const [user, setUser] = useState(null);
|
|
18
|
+
const [loading, setLoading] = useState(true);
|
|
19
|
+
|
|
20
|
+
// Einmalige Axios-Basis-Konfiguration
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
axios.defaults.withCredentials = true;
|
|
23
|
+
axios.defaults.xsrfCookieName = 'csrftoken';
|
|
24
|
+
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
let isMounted = true;
|
|
29
|
+
|
|
30
|
+
const initAuth = async () => {
|
|
31
|
+
try {
|
|
32
|
+
// 1) CSRF-Cookie setzen (Django-View /api/csrf/)
|
|
33
|
+
try {
|
|
34
|
+
await axios.get(CSRF_URL, { withCredentials: true });
|
|
35
|
+
// console.log('CSRF cookie set');
|
|
36
|
+
} catch (err) {
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.error('Error setting CSRF cookie:', err);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 2) aktuellen User laden (falls Session vorhanden)
|
|
42
|
+
try {
|
|
43
|
+
const data = await fetchCurrentUser();
|
|
44
|
+
if (!isMounted) return;
|
|
45
|
+
setUser({
|
|
46
|
+
id: data.id,
|
|
47
|
+
username: data.username,
|
|
48
|
+
email: data.email,
|
|
49
|
+
first_name: data.first_name,
|
|
50
|
+
last_name: data.last_name,
|
|
51
|
+
role: data.role,
|
|
52
|
+
is_superuser: data.is_superuser,
|
|
53
|
+
});
|
|
54
|
+
} catch (err) {
|
|
55
|
+
// Kein eingeloggter User ist ein normaler Fall
|
|
56
|
+
// eslint-disable-next-line no-console
|
|
57
|
+
console.log('No logged-in user:', err?.message || err);
|
|
58
|
+
if (!isMounted) return;
|
|
59
|
+
setUser(null);
|
|
60
|
+
}
|
|
61
|
+
} finally {
|
|
62
|
+
if (isMounted) {
|
|
63
|
+
setLoading(false);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
initAuth();
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
isMounted = false;
|
|
72
|
+
};
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
// Nach erfolgreichem Login das User-Objekt setzen
|
|
76
|
+
// (z. B. aus loginWithPassword in authApi)
|
|
77
|
+
const login = (userData) => {
|
|
78
|
+
setUser((prev) => ({
|
|
79
|
+
...prev,
|
|
80
|
+
...userData,
|
|
81
|
+
}));
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Logout im Backend + lokalen State leeren
|
|
85
|
+
const logout = async () => {
|
|
86
|
+
try {
|
|
87
|
+
await logoutSession();
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
console.error('Error during logout:', error);
|
|
91
|
+
} finally {
|
|
92
|
+
setUser(null);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<AuthContext.Provider
|
|
98
|
+
value={{
|
|
99
|
+
user,
|
|
100
|
+
loading,
|
|
101
|
+
login,
|
|
102
|
+
logout,
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
{children}
|
|
106
|
+
</AuthContext.Provider>
|
|
107
|
+
);
|
|
108
|
+
};
|