(null);
const [usernameFocused, setUsernameFocused] = useState(false);
- const [passwordFocused, setPasswordFocused] = useState(false);
- const [confirmPasswordFocused, setConfirmPasswordFocused] = useState(false);
const [emailFocused, setEmailFocused] = useState(false);
const [fullNameFocused, setFullNameFocused] = useState(false);
const [usernameHighlight, setUsernameHighlight] = useState(false);
@@ -55,16 +50,11 @@ function Register() {
};
const validateForm = (): boolean => {
- if (!formData.username || !formData.password || !formData.confirmPassword || !formData.email || !formData.fullName) {
+ if (!formData.username || !formData.email || !formData.fullName) {
setValidationError('Bitte füllen Sie alle Pflichtfelder aus.');
return false;
}
- if (formData.password !== formData.confirmPassword) {
- setValidationError('Die Passwörter stimmen nicht überein.');
- return false;
- }
-
if (!formData.email.includes('@')) {
setValidationError('Bitte geben Sie eine gültige E-Mail-Adresse ein.');
return false;
@@ -96,15 +86,21 @@ function Register() {
return;
}
- // Username is available, proceed with registration
- const { confirmPassword, ...registrationData } = formData;
- await register(registrationData);
- navigate('/login', {
- state: {
- registered: true,
- message: 'Registration erfolgreich. Bitte melden Sie sich an.'
- }
- });
+ // Username is available, proceed with registration (no password - magic link flow)
+ await register(formData);
+
+ // Show success message instead of immediate redirect
+ setSuccessMessage('Registrierung erfolgreich! Bitte prüfen Sie Ihre E-Mail (auch den Spam-Ordner) für den Link zum Setzen Ihres Passworts.');
+
+ // Redirect to login page after delay
+ setTimeout(() => {
+ navigate('/login', {
+ state: {
+ registered: true,
+ message: 'Registrierung erfolgreich. Bitte prüfen Sie Ihre E-Mail für den Passwort-Link.'
+ }
+ });
+ }, 5000);
} catch (err) {
console.error('Registration failed:', err);
}
@@ -135,89 +131,73 @@ function Register() {
{getErrorMessage()}
)}
-
- setUsernameFocused(true)}
- onBlur={() => setUsernameFocused(false)}
- className={`${styles.input} ${usernameFocused || formData.username ? styles.focused : ''} ${usernameHighlight ? styles.usernameError : ''}`}
- />
- Benutzername
-
+ {successMessage && (
+ {successMessage}
+ )}
+
+ {!successMessage && (
+ <>
+
+ setUsernameFocused(true)}
+ onBlur={() => setUsernameFocused(false)}
+ className={`${styles.input} ${usernameFocused || formData.username ? styles.focused : ''} ${usernameHighlight ? styles.usernameError : ''}`}
+ />
+ Benutzername
+
-
- setEmailFocused(true)}
- onBlur={() => setEmailFocused(false)}
- className={`${styles.input} ${emailFocused || formData.email ? styles.focused : ''}`}
- />
- E-Mail
-
+
+ setEmailFocused(true)}
+ onBlur={() => setEmailFocused(false)}
+ className={`${styles.input} ${emailFocused || formData.email ? styles.focused : ''}`}
+ />
+ E-Mail
+
-
- setFullNameFocused(true)}
- onBlur={() => setFullNameFocused(false)}
- className={`${styles.input} ${fullNameFocused || formData.fullName ? styles.focused : ''}`}
- />
- Vollständiger Name
-
+
+ setFullNameFocused(true)}
+ onBlur={() => setFullNameFocused(false)}
+ className={`${styles.input} ${fullNameFocused || formData.fullName ? styles.focused : ''}`}
+ />
+ Vollständiger Name
+
-
- setPasswordFocused(true)}
- onBlur={() => setPasswordFocused(false)}
- className={`${styles.input} ${passwordFocused || formData.password ? styles.focused : ''}`}
- />
- Passwort
-
+
+
Nach der Registrierung erhalten Sie eine E-Mail mit einem Link zum Setzen Ihres Passworts.
+
-
- setConfirmPasswordFocused(true)}
- onBlur={() => setConfirmPasswordFocused(false)}
- className={`${styles.input} ${confirmPasswordFocused || formData.confirmPassword ? styles.focused : ''}`}
- />
- Passwort bestätigen
-
+
+
+ Mit der Registrierung stimmen Sie unseren Datenschutzbestimmungen zur KI-Nutzung zu.
+
+
-
-
- Mit der Registrierung stimmen Sie unseren Datenschutzbestimmungen zur KI-Nutzung zu.
-
-
-
-
- {isLoading ? "Registrierung läuft..." : isChecking ? "Benutzername wird geprüft..." : "Registrieren"}
-
+
+ {isLoading ? "Registrierung läuft..." : isChecking ? "Benutzername wird geprüft..." : "Registrieren"}
+
+ >
+ )}
Bereits registriert?
diff --git a/src/pages/Reset.module.css b/src/pages/Reset.module.css
new file mode 100644
index 0000000..4a01484
--- /dev/null
+++ b/src/pages/Reset.module.css
@@ -0,0 +1,234 @@
+.container {
+ display: flex;
+ min-height: 100vh;
+
+ font-family: "DM Sans", sans-serif;
+ color: #181818;
+}
+
+.mainContent {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ padding: 3rem;
+ background-color: #181818;
+}
+
+.loginSection {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1;
+}
+
+.logoText {
+
+ font-size: 35px;
+ display: flex;
+ align-items: center;
+ letter-spacing: -0.5px;
+ font-weight: 200;
+}
+
+.logoPower {
+ color: #E5E7EB;
+}
+
+.logoOn {
+ color: #F25843;
+ font-weight: 700;
+}
+
+.logo img {
+ height: 40px;
+}
+
+.loginBox {
+
+
+ background-color: #181818;
+ width: 25%;
+ height: auto;
+
+ margin-top: 5%;
+ padding: 2rem;
+
+ border-radius: 25px;
+ border: 1px solid rgba(199, 197, 178, 0.15); /* washed-out color */
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02),
+ 0 0 10px rgba(0, 0, 0, 0.1);
+}
+
+.title {
+ font-family: "DM Sans", sans-serif;
+ color: #E5E7EB;
+ font-size: 1.5rem;
+ font-weight: 500;
+ margin-bottom: 1.5rem;
+ text-align: center;
+}
+
+.loginForm {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.floatingLabelInput {
+ position: relative;
+}
+
+.label {
+ position: absolute;
+ left: 16px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: #C7C5B2;
+ font-size: 1rem;
+ pointer-events: none;
+ transition: all 0.3s ease;
+ background-color: transparent;
+ font-family: var(--font-family);
+}
+
+.focusedLabel {
+ position: absolute;
+ left: 12px;
+ top: -8px;
+ transform: translateY(0);
+ color: #F25843;
+ font-size: 0.85rem;
+ pointer-events: none;
+ transition: all 0.3s ease;
+ background-color: #181818;
+ padding: 0 4px;
+ font-family: var(--font-family);
+ font-weight: 500;
+}
+
+.input {
+ width: 100%;
+ height: 50px;
+ padding: 12px 16px;
+ border: 1px solid var(--color-gray-disabled);
+ border-radius: 25px;
+
+ font-size: 1rem;
+ transition: all 0.2s ease;
+ background-color: var(--color-bg);
+ color: var(--color-text);
+ font-family: var(--font-family);
+}
+
+.input:focus {
+ outline: none;
+ border-color: #F25843;
+ box-shadow: 0 0 0 2px rgba(242, 88, 67, 0.1);
+}
+
+.input::placeholder {
+ color: transparent;
+}
+
+/* Fix browser autocomplete styling */
+.input:-webkit-autofill,
+.input:-webkit-autofill:hover,
+.input:-webkit-autofill:focus,
+.input:-webkit-autofill:active {
+ -webkit-box-shadow: 0 0 0 30px #181818 inset !important;
+ -webkit-text-fill-color: #E5E7EB !important;
+ background-color: #181818 !important;
+ transition: background-color 5000s ease-in-out 0s;
+}
+
+/* Ensure label background matches when autofilled */
+.input:-webkit-autofill + .label,
+.input:-webkit-autofill + .focusedLabel {
+ background-color: #181818 !important;
+}
+
+.passwordHint {
+ font-size: 0.8rem;
+ color: #9CA3AF;
+ margin-top: -0.5rem;
+ padding-left: 16px;
+}
+
+.button {
+ width: 100%;
+ height: 50px;
+ padding: 12px 20px;
+ border-radius: 25px;
+
+ font-size: 1rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ border: none;
+ text-align: center;
+}
+
+.loginButton {
+ background-color: #F25843;
+ color: #E5E7EB;
+}
+
+.loginButton:hover {
+ background-color: var(--color-secondary-hover);
+}
+
+.registerLink {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+}
+
+.registerLink span {
+ color: #E5E7EB;
+ font-size: 0.8rem;
+}
+
+.textButton {
+ background: none;
+ border: none;
+ color: var(--color-secondary);
+ font-weight: 500;
+ cursor: pointer;
+ padding: 0;
+ font-size: 0.9rem;
+ font-family: var(--font-family);
+}
+
+.textButton:hover {
+ text-decoration: underline;
+}
+
+button:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+.error {
+ color: var(--color-secondary);
+ background-color: var(--color-secondary-disabled);
+ border: 1px solid var(--color-secondary);
+ border-radius: 25px;
+ padding: 12px;
+ font-size: 0.9rem;
+ text-align: center;
+ font-family: var(--font-family);
+ margin-bottom: 10px;
+}
+
+.success {
+ color: #10b981;
+ background-color: rgba(16, 185, 129, 0.1);
+ border: 1px solid #10b981;
+ border-radius: 25px;
+ padding: 12px;
+ font-size: 0.9rem;
+ text-align: center;
+ font-family: var(--font-family);
+ margin-bottom: 10px;
+}
diff --git a/src/pages/Reset.tsx b/src/pages/Reset.tsx
new file mode 100644
index 0000000..b3a0066
--- /dev/null
+++ b/src/pages/Reset.tsx
@@ -0,0 +1,219 @@
+import { useState, useEffect } from 'react';
+import { useNavigate, useSearchParams } from 'react-router-dom';
+
+import styles from './Reset.module.css';
+import { usePasswordReset } from '../hooks/useAuthentication';
+import { generateAndStoreCSRFToken } from '../utils/csrfUtils';
+
+function Reset() {
+ const navigate = useNavigate();
+ const [searchParams] = useSearchParams();
+ const { resetPassword, isLoading, error } = usePasswordReset();
+
+ const [password, setPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [passwordFocused, setPasswordFocused] = useState(false);
+ const [confirmPasswordFocused, setConfirmPasswordFocused] = useState(false);
+ const [validationError, setValidationError] = useState
(null);
+ const [successMessage, setSuccessMessage] = useState(null);
+ const [tokenError, setTokenError] = useState(null);
+
+ // Get token from URL
+ const token = searchParams.get('token');
+
+ // Set page title and generate CSRF token
+ useEffect(() => {
+ document.title = "PowerOn AI Platform - Neues Passwort setzen";
+ generateAndStoreCSRFToken();
+
+ // Validate token exists and format
+ if (!token) {
+ setTokenError('Ungültiger Reset-Link. Bitte fordern Sie einen neuen Link an.');
+ } else if (!_isValidUUID(token)) {
+ setTokenError('Ungültiger Reset-Link. Bitte fordern Sie einen neuen Link an.');
+ }
+ }, [token]);
+
+ const _isValidUUID = (str: string): boolean => {
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
+ return uuidRegex.test(str);
+ };
+
+ const validateForm = (): boolean => {
+ if (!password || password.length < 8) {
+ setValidationError('Passwort muss mindestens 8 Zeichen lang sein.');
+ return false;
+ }
+
+ if (password !== confirmPassword) {
+ setValidationError('Die Passwörter stimmen nicht überein.');
+ return false;
+ }
+
+ return true;
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setValidationError(null);
+
+ if (!validateForm()) {
+ return;
+ }
+
+ if (!token) {
+ setValidationError('Token fehlt. Bitte fordern Sie einen neuen Reset-Link an.');
+ return;
+ }
+
+ try {
+ await resetPassword(token, password);
+ setSuccessMessage('Passwort erfolgreich gesetzt! Sie werden zum Login weitergeleitet...');
+
+ // Redirect to login after delay
+ setTimeout(() => {
+ navigate('/login', {
+ state: {
+ passwordReset: true,
+ message: 'Passwort erfolgreich geändert. Bitte melden Sie sich an.'
+ }
+ });
+ }, 3000);
+ } catch (err: any) {
+ // Error is already set by the hook
+ const errorMessage = err?.response?.data?.detail || err?.message || 'Passwort-Zurücksetzung fehlgeschlagen.';
+ if (errorMessage.includes('abgelaufen') || errorMessage.includes('expired') || errorMessage.includes('Ungültig') || errorMessage.includes('invalid')) {
+ setValidationError('Der Reset-Link ist ungültig oder abgelaufen. Bitte fordern Sie einen neuen Link an.');
+ } else {
+ setValidationError(errorMessage);
+ }
+ }
+ };
+
+ // Show token error if invalid
+ if (tokenError) {
+ return (
+
+
+
+
+
+
Neues Passwort setzen
+
+
{tokenError}
+
+ navigate("/password-reset-request")}
+ >
+ Neuen Reset-Link anfordern
+
+
+
+ oder zurück zum
+ navigate("/login")}
+ >
+ Login
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
Neues Passwort setzen
+
+ {(validationError || error) && (
+
{validationError || error}
+ )}
+
+ {successMessage && (
+
{successMessage}
+ )}
+
+ {!successMessage && (
+
+ )}
+
+
+ Zurück zum
+ navigate("/login")}
+ >
+ Login
+
+
+
+
+
+
+
+ );
+}
+
+export default Reset;