Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions frontend/cmmty/pages/documents/[id]/settings/ChangePassword.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { useState } from 'react';

const ChangePassword = () => {
const [oldPass, setOldPass] = useState('');
const [newPass, setNewPass] = useState('');

const handleChange = async () => {
try {
await fetch('/api/user/change-password', {
method: 'POST',
body: JSON.stringify({ oldPass, newPass }),
});
alert('Password changed successfully!');
} catch {
alert('Error changing password.');
}
};

return (
<div className="settings-section">
<h2>Change Password</h2>
<input
type="password"
placeholder="Old Password"
value={oldPass}
onChange={(e) => setOldPass(e.target.value)}
/>
<input
type="password"
placeholder="New Password"
value={newPass}
onChange={(e) => setNewPass(e.target.value)}
/>
<button onClick={handleChange}>Update Password</button>
</div>
);
};

export default ChangePassword;
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { useState } from 'react';

const NotificationToggles = () => {
const [prefs, setPrefs] = useState({
riskAlerts: true,
verificationEmails: true,
weeklyDigest: false,
});

const handleToggle = (key: keyof typeof prefs) => {
setPrefs({ ...prefs, [key]: !prefs[key] });
};

const handleSave = async () => {
try {
await fetch('/api/user/notifications', {
method: 'POST',
body: JSON.stringify(prefs),
});
alert('Notification preferences saved!');
} catch {
alert('Error saving preferences.');
}
};

return (
<div className="settings-section">
<h2>Notifications</h2>
{Object.keys(prefs).map((key) => (
<label key={key}>
<input
type="checkbox"
checked={prefs[key as keyof typeof prefs]}
onChange={() => handleToggle(key as keyof typeof prefs)}
/>
{key}
</label>
))}
<button onClick={handleSave}>Save Preferences</button>
</div>
);
};

export default NotificationToggles;
39 changes: 39 additions & 0 deletions frontend/cmmty/pages/documents/[id]/settings/SettingsForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { useState } from 'react';

const SettingsForm = () => {
const [fullName, setFullName] = useState('');
const [loading, setLoading] = useState(false);

const handleSave = async () => {
setLoading(true);
try {
// API call to save profile
await fetch('/api/user/update', {
method: 'POST',
body: JSON.stringify({ fullName }),
});
alert('Profile updated successfully!');
} catch (err) {
alert('Error updating profile.');
} finally {
setLoading(false);
}
};

return (
<div className="settings-section">
<h2>Personal Information</h2>
<input
type="text"
placeholder="Full Name"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
/>
<button onClick={handleSave} disabled={loading}>
{loading ? 'Saving...' : 'Save'}
</button>
</div>
);
};

export default SettingsForm;
17 changes: 17 additions & 0 deletions frontend/cmmty/pages/documents/[id]/settings/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import SettingsForm from './SettingsForm';
import NotificationToggles from './NotificationToggles';
import ChangePassword from './ChangePassword';

const SettingsPage = () => {
return (
<div className="settings-container">
<h1>User Profile Settings</h1>
<SettingsForm />
<NotificationToggles />
<ChangePassword />
</div>
);
};

export default SettingsPage;
41 changes: 41 additions & 0 deletions frontend/cmmty/pages/forgot-password/ForgotPasswordForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { useState } from 'react';

const ForgotPasswordForm = () => {
const [email, setEmail] = useState('');
const [submitted, setSubmitted] = useState(false);

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!email || !/\S+@\S+\.\S+/.test(email)) {
alert('Please enter a valid email.');
return;
}
try {
await fetch('/api/auth/forgot-password', {
method: 'POST',
body: JSON.stringify({ email }),
});
} catch (err) {
// swallow errors to avoid enumeration
} finally {
setSubmitted(true);
}
};

return (
<form onSubmit={handleSubmit} className="forgot-form">
<label>Email Address</label>
<input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<button type="submit">Send Reset Link</button>
{submitted && <p className="success-msg">If an account exists, a reset link has been sent.</p>}
</form>
);
};

export default ForgotPasswordForm;
14 changes: 14 additions & 0 deletions frontend/cmmty/pages/forgot-password/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import ForgotPasswordForm from './ForgotPasswordForm';

const ForgotPasswordPage = () => {
return (
<div className="forgot-container">
<h1>Forgot Password</h1>
<ForgotPasswordForm />
<a href="/login" className="back-link">Back to Login</a>
</div>
);
};

export default ForgotPasswordPage;
65 changes: 65 additions & 0 deletions frontend/cmmty/pages/reset-password/ResetPasswordForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React, { useState } from 'react';
import { useRouter } from 'next/router';

interface Props {
token: string;
}

const ResetPasswordForm: React.FC<Props> = ({ token }) => {
const [newPass, setNewPass] = useState('');
const [confirmPass, setConfirmPass] = useState('');
const [error, setError] = useState('');
const [success, setSuccess] = useState(false);
const router = useRouter();

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (newPass.length < 8) {
setError('Password must be at least 8 characters.');
return;
}
if (newPass !== confirmPass) {
setError('Passwords do not match.');
return;
}
setError('');
try {
const res = await fetch('/api/auth/reset-password', {
method: 'POST',
body: JSON.stringify({ token, newPass }),
});
if (res.ok) {
setSuccess(true);
setTimeout(() => router.push('/login'), 3000);
} else {
setError('Invalid or expired token.');
}
} catch {
setError('Something went wrong. Please try again.');
}
};

return (
<form onSubmit={handleSubmit} className="reset-form">
<label>New Password</label>
<input
type="password"
value={newPass}
onChange={(e) => setNewPass(e.target.value)}
required
/>
<label>Confirm Password</label>
<input
type="password"
value={confirmPass}
onChange={(e) => setConfirmPass(e.target.value)}
required
/>
<button type="submit">Reset Password</button>
{error && <p className="error-msg">{error}</p>}
{success && <p className="success-msg">Password reset successful! Redirecting to login...</p>}
</form>
);
};

export default ResetPasswordForm;
21 changes: 21 additions & 0 deletions frontend/cmmty/pages/reset-password/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { useRouter } from 'next/router';
import ResetPasswordForm from './ResetPasswordForm';

const ResetPasswordPage = () => {
const router = useRouter();
const { token } = router.query;

return (
<div className="reset-container">
<h1>Reset Password</h1>
{token ? (
<ResetPasswordForm token={token as string} />
) : (
<p>Invalid or missing reset token.</p>
)}
</div>
);
};

export default ResetPasswordPage;
41 changes: 41 additions & 0 deletions frontend/cmmty/pages/verify-email/Confirmed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';

interface Props {
token: string;
}

const Confirmed: React.FC<Props> = ({ token }) => {
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
const router = useRouter();

useEffect(() => {
const verifyEmail = async () => {
try {
const res = await fetch('/api/auth/verify-email', {
method: 'POST',
body: JSON.stringify({ token }),
});
if (res.ok) {
setStatus('success');
setTimeout(() => router.push('/dashboard'), 3000);
} else {
setStatus('error');
}
} catch {
setStatus('error');
}
};
verifyEmail();
}, [token, router]);

return (
<div className="verify-container">
{status === 'loading' && <p>Verifying your email...</p>}
{status === 'success' && <p>Email verified! Redirecting to dashboard...</p>}
{status === 'error' && <p>Verification failed. Please try again.</p>}
</div>
);
};

export default Confirmed;
29 changes: 29 additions & 0 deletions frontend/cmmty/pages/verify-email/Pending.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { useState } from 'react';

const Pending = () => {
const [loading, setLoading] = useState(false);

const handleResend = async () => {
setLoading(true);
try {
await fetch('/api/auth/resend-verification', { method: 'POST' });
alert('Verification email resent!');
} catch {
alert('Error resending email.');
} finally {
setLoading(false);
}
};

return (
<div className="verify-container">
<h1>Verify Your Email</h1>
<p>Please check your inbox for a verification link.</p>
<button onClick={handleResend} disabled={loading}>
{loading ? 'Resending...' : 'Resend Email'}
</button>
</div>
);
};

export default Pending;
16 changes: 16 additions & 0 deletions frontend/cmmty/pages/verify-email/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import { useRouter } from 'next/router';
import Pending from './Pending';
import Confirmed from './Confirmed';

const VerifyEmailPage = () => {
const router = useRouter();
const { token } = router.query;

if (token) {
return <Confirmed token={token as string} />;
}
return <Pending />;
};

export default VerifyEmailPage;
Loading
Loading