nice update

This commit is contained in:
gpatruno
2026-07-16 20:23:58 +02:00
parent a0d7163464
commit 9106c8b873
47 changed files with 5183 additions and 1732 deletions
+199
View File
@@ -0,0 +1,199 @@
import { useEffect, useState } from 'react';
import { Navigate } from 'react-router-dom';
import { api, formatCredits } from '../api';
import { useAuth } from '../AuthContext';
export default function ProfilePage() {
const { user, loading, refresh } = useAuth();
const [profile, setProfile] = useState(null);
const [bio, setBio] = useState('');
const [currentPassword, setCurrentPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [message, setMessage] = useState('');
const [error, setError] = useState('');
const [busy, setBusy] = useState(false);
useEffect(() => {
if (!user || user.role === 'admin') return;
let cancelled = false;
(async () => {
try {
const data = await api.profile();
if (!cancelled) {
setProfile(data.profile);
setBio(data.user.bio || '');
}
} catch (err) {
if (!cancelled) setError(err.message);
}
})();
return () => {
cancelled = true;
};
}, [user]);
const saveBio = async (e) => {
e.preventDefault();
setBusy(true);
setError('');
setMessage('');
try {
await api.updateProfile({ bio });
await refresh();
setMessage('Profile updated.');
} catch (err) {
setError(err.message);
} finally {
setBusy(false);
}
};
const changePassword = async (e) => {
e.preventDefault();
setBusy(true);
setError('');
setMessage('');
try {
await api.changePassword(currentPassword, newPassword);
setCurrentPassword('');
setNewPassword('');
setMessage('Password changed.');
} catch (err) {
setError(err.message);
} finally {
setBusy(false);
}
};
const onAvatar = async (e) => {
const file = e.target.files?.[0];
if (!file) return;
setBusy(true);
setError('');
setMessage('');
try {
await api.uploadAvatar(file);
await refresh();
setMessage('Avatar updated.');
} catch (err) {
setError(err.message);
} finally {
setBusy(false);
e.target.value = '';
}
};
if (loading) return <p className="muted">Loading</p>;
if (!user) return <Navigate to="/login" replace />;
if (user.role === 'admin') return <Navigate to="/admin" replace />;
return (
<div>
<section className="section">
<div className="section-head">
<div>
<h1>Profile</h1>
<p className="muted">@{user.username}</p>
</div>
</div>
{error && <div className="error">{error}</div>}
{message && <div className="ok-msg">{message}</div>}
<div className="profile-layout">
<div className="profile-avatar-block admin-block">
<div className="avatar-preview">
{user.avatarUrl ? (
<img src={user.avatarUrl} alt="" />
) : (
<span>{user.username.slice(0, 2).toUpperCase()}</span>
)}
</div>
<label className="btn secondary file-btn">
Change photo
<input type="file" accept="image/*" hidden onChange={onAvatar} disabled={busy} />
</label>
<div className="muted" style={{ marginTop: '0.75rem' }}>
Balance {formatCredits(user.balance)} cr
</div>
</div>
<div className="profile-stats admin-block">
<h2>Stats</h2>
{profile ? (
<ul className="stat-list">
<li>
Joined{' '}
<strong>{new Date(profile.createdAt).toLocaleDateString()}</strong>
</li>
<li>
Inventory{' '}
<strong>
{profile.inventoryCount} items · {formatCredits(profile.inventoryValue)} cr
</strong>
</li>
<li>
Cases opened <strong>{profile.casesOpened}</strong>
</li>
<li>
Jackpot wins <strong>{profile.jackpotWins}</strong>
</li>
<li>
Duel wins <strong>{profile.duelWins}</strong>
</li>
</ul>
) : (
<p className="muted">Loading stats</p>
)}
</div>
</div>
</section>
<section className="section admin-block">
<h2>Bio</h2>
<form className="form" onSubmit={saveBio}>
<label>
About you
<textarea
rows={3}
maxLength={280}
value={bio}
onChange={(e) => setBio(e.target.value)}
/>
</label>
<button type="submit" className="btn" disabled={busy}>
Save bio
</button>
</form>
</section>
<section className="section admin-block">
<h2>Change password</h2>
<form className="form" onSubmit={changePassword}>
<label>
Current password
<input
type="password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
required
/>
</label>
<label>
New password
<input
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
minLength={6}
required
/>
</label>
<button type="submit" className="btn" disabled={busy}>
Update password
</button>
</form>
</section>
</div>
);
}