style: align all pages with CLAUDE.md design system

- Inputs: uniform padding (8px 12px), borderRadius 7, bg-inset background
- List rows/cards: bg-subtle background, border-subtle border (bg-inset was input-only)
- Invite/admin badge borders: use accent-border var instead of raw accent
- Section headers: 11px, weight 500, uppercase, 0.7px letter-spacing
- Notification/status banners: borderRadius 8
- Remove debug console.log statements from SettingsPage avatar upload flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mistral Vibe
2026-04-01 09:52:06 +02:00
parent 7677677e7b
commit 6dc191585c
6 changed files with 55 additions and 78 deletions

View File

@@ -254,7 +254,7 @@ export function BandPage() {
value={folderInput} value={folderInput}
onChange={(e) => setFolderInput(e.target.value)} onChange={(e) => setFolderInput(e.target.value)}
placeholder={`bands/${band.slug}/`} placeholder={`bands/${band.slug}/`}
style={{ width: "100%", padding: "7px 10px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 6, color: "var(--text)", fontSize: 13, fontFamily: "monospace", boxSizing: "border-box" }} style={{ width: "100%", padding: "8px 12px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 7, color: "var(--text)", fontSize: 13, fontFamily: "monospace", boxSizing: "border-box" }}
/> />
<div style={{ display: "flex", gap: 8, marginTop: 8 }}> <div style={{ display: "flex", gap: 8, marginTop: 8 }}>
<button <button
@@ -289,7 +289,7 @@ export function BandPage() {
</div> </div>
{inviteLink && ( {inviteLink && (
<div style={{ background: "var(--accent-bg)", border: "1px solid var(--accent)", borderRadius: 6, padding: "10px 14px", marginBottom: 12 }}> <div style={{ background: "var(--accent-bg)", border: "1px solid var(--accent-border)", borderRadius: 8, padding: "10px 14px", marginBottom: 12 }}>
<p style={{ color: "var(--text-muted)", fontSize: 11, margin: "0 0 6px" }}>Invite link (copied to clipboard, valid 72h):</p> <p style={{ color: "var(--text-muted)", fontSize: 11, margin: "0 0 6px" }}>Invite link (copied to clipboard, valid 72h):</p>
<code style={{ color: "var(--accent)", fontSize: 12, wordBreak: "break-all" }}>{inviteLink}</code> <code style={{ color: "var(--accent)", fontSize: 12, wordBreak: "break-all" }}>{inviteLink}</code>
<button <button
@@ -305,7 +305,7 @@ export function BandPage() {
{members?.map((m) => ( {members?.map((m) => (
<div <div
key={m.id} key={m.id}
style={{ background: "var(--bg-subtle)", border: "1px solid var(--border)", borderRadius: 6, padding: "10px 14px", display: "flex", justifyContent: "space-between", alignItems: "center" }} style={{ background: "var(--bg-subtle)", border: "1px solid var(--border-subtle)", borderRadius: 8, padding: "10px 14px", display: "flex", justifyContent: "space-between", alignItems: "center" }}
> >
<div> <div>
<span style={{ fontWeight: 500 }}>{m.display_name}</span> <span style={{ fontWeight: 500 }}>{m.display_name}</span>
@@ -316,7 +316,7 @@ export function BandPage() {
fontSize: 10, fontFamily: "monospace", padding: "2px 6px", borderRadius: 3, fontSize: 10, fontFamily: "monospace", padding: "2px 6px", borderRadius: 3,
background: m.role === "admin" ? "var(--accent-bg)" : "var(--bg-inset)", background: m.role === "admin" ? "var(--accent-bg)" : "var(--bg-inset)",
color: m.role === "admin" ? "var(--accent)" : "var(--text-muted)", color: m.role === "admin" ? "var(--accent)" : "var(--text-muted)",
border: `1px solid ${m.role === "admin" ? "var(--accent)" : "var(--border)"}`, border: `1px solid ${m.role === "admin" ? "var(--accent-border)" : "var(--border)"}`,
}}> }}>
{m.role} {m.role}
</span> </span>
@@ -355,12 +355,12 @@ export function BandPage() {
</div> </div>
{scanning && scanProgress && ( {scanning && scanProgress && (
<div style={{ background: "var(--bg-subtle)", border: "1px solid var(--border)", borderRadius: 6, color: "var(--text-muted)", fontSize: 12, padding: "8px 14px", marginBottom: 8, fontFamily: "monospace" }}> <div style={{ background: "var(--bg-subtle)", border: "1px solid var(--border)", borderRadius: 8, color: "var(--text-muted)", fontSize: 12, padding: "8px 14px", marginBottom: 8, fontFamily: "monospace" }}>
{scanProgress} {scanProgress}
</div> </div>
)} )}
{scanMsg && ( {scanMsg && (
<div style={{ background: "var(--teal-bg)", border: "1px solid var(--teal)", borderRadius: 6, color: "var(--teal)", fontSize: 12, padding: "8px 14px", marginBottom: 12 }}> <div style={{ background: "var(--teal-bg)", border: "1px solid var(--teal)", borderRadius: 8, color: "var(--teal)", fontSize: 12, padding: "8px 14px", marginBottom: 12 }}>
{scanMsg} {scanMsg}
</div> </div>
)} )}
@@ -373,7 +373,7 @@ export function BandPage() {
value={title} value={title}
onChange={(e) => setTitle(e.target.value)} onChange={(e) => setTitle(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && title && createMutation.mutate()} onKeyDown={(e) => e.key === "Enter" && title && createMutation.mutate()}
style={{ width: "100%", padding: "8px 12px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 6, color: "var(--text)", marginBottom: 12, fontSize: 14, boxSizing: "border-box" }} style={{ width: "100%", padding: "8px 12px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 7, color: "var(--text)", marginBottom: 12, fontSize: 14, boxSizing: "border-box" }}
autoFocus autoFocus
/> />
<div style={{ display: "flex", gap: 8 }}> <div style={{ display: "flex", gap: 8 }}>
@@ -425,8 +425,8 @@ export function BandPage() {
key={s.id} key={s.id}
to={`/bands/${bandId}/sessions/${s.id}`} to={`/bands/${bandId}/sessions/${s.id}`}
style={{ style={{
background: "var(--bg-inset)", background: "var(--bg-subtle)",
border: "1px solid var(--border)", border: "1px solid var(--border-subtle)",
borderRadius: 8, borderRadius: 8,
padding: "14px 18px", padding: "14px 18px",
textDecoration: "none", textDecoration: "none",
@@ -467,8 +467,8 @@ export function BandPage() {
key={song.id} key={song.id}
to={`/bands/${bandId}/songs/${song.id}`} to={`/bands/${bandId}/songs/${song.id}`}
style={{ style={{
background: "var(--bg-inset)", background: "var(--bg-subtle)",
border: "1px solid var(--border)", border: "1px solid var(--border-subtle)",
borderRadius: 8, borderRadius: 8,
padding: "14px 18px", padding: "14px 18px",
textDecoration: "none", textDecoration: "none",
@@ -512,7 +512,7 @@ export function BandPage() {
onChange={(e) => setSearchQ(e.target.value)} onChange={(e) => setSearchQ(e.target.value)}
onKeyDown={(e) => { if (e.key === "Enter") { setSearchDirty(true); qc.invalidateQueries({ queryKey: ["songs-search", bandId] }); } }} onKeyDown={(e) => { if (e.key === "Enter") { setSearchDirty(true); qc.invalidateQueries({ queryKey: ["songs-search", bandId] }); } }}
placeholder="Search by name…" placeholder="Search by name…"
style={{ width: "100%", padding: "7px 10px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 6, color: "var(--text)", fontSize: 13, boxSizing: "border-box" }} style={{ width: "100%", padding: "8px 12px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 7, color: "var(--text)", fontSize: 13, boxSizing: "border-box" }}
/> />
</div> </div>
<div> <div>
@@ -521,7 +521,7 @@ export function BandPage() {
value={searchKey} value={searchKey}
onChange={(e) => setSearchKey(e.target.value)} onChange={(e) => setSearchKey(e.target.value)}
placeholder="e.g. Am, C, F#" placeholder="e.g. Am, C, F#"
style={{ width: "100%", padding: "7px 10px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 6, color: "var(--text)", fontSize: 13, boxSizing: "border-box" }} style={{ width: "100%", padding: "8px 12px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 7, color: "var(--text)", fontSize: 13, boxSizing: "border-box" }}
/> />
</div> </div>
<div> <div>
@@ -532,7 +532,7 @@ export function BandPage() {
type="number" type="number"
min={0} min={0}
placeholder="e.g. 80" placeholder="e.g. 80"
style={{ width: "100%", padding: "7px 10px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 6, color: "var(--text)", fontSize: 13, boxSizing: "border-box" }} style={{ width: "100%", padding: "8px 12px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 7, color: "var(--text)", fontSize: 13, boxSizing: "border-box" }}
/> />
</div> </div>
<div> <div>
@@ -543,7 +543,7 @@ export function BandPage() {
type="number" type="number"
min={0} min={0}
placeholder="e.g. 140" placeholder="e.g. 140"
style={{ width: "100%", padding: "7px 10px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 6, color: "var(--text)", fontSize: 13, boxSizing: "border-box" }} style={{ width: "100%", padding: "8px 12px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 7, color: "var(--text)", fontSize: 13, boxSizing: "border-box" }}
/> />
</div> </div>
</div> </div>
@@ -571,7 +571,7 @@ export function BandPage() {
onChange={(e) => setSearchTagInput(e.target.value)} onChange={(e) => setSearchTagInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && addTag()} onKeyDown={(e) => e.key === "Enter" && addTag()}
placeholder="Add tag…" placeholder="Add tag…"
style={{ flex: 1, padding: "6px 10px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 6, color: "var(--text)", fontSize: 12 }} style={{ flex: 1, padding: "6px 10px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 7, color: "var(--text)", fontSize: 12 }}
/> />
<button <button
onClick={addTag} onClick={addTag}
@@ -599,8 +599,8 @@ export function BandPage() {
key={song.id} key={song.id}
to={`/bands/${bandId}/songs/${song.id}`} to={`/bands/${bandId}/songs/${song.id}`}
style={{ style={{
background: "var(--bg-inset)", background: "var(--bg-subtle)",
border: "1px solid var(--border)", border: "1px solid var(--border-subtle)",
borderRadius: 8, borderRadius: 8,
padding: "14px 18px", padding: "14px 18px",
textDecoration: "none", textDecoration: "none",

View File

@@ -35,7 +35,7 @@ export function HomePage() {
padding: "8px 12px", padding: "8px 12px",
background: "var(--bg-inset)", background: "var(--bg-inset)",
border: "1px solid var(--border)", border: "1px solid var(--border)",
borderRadius: 6, borderRadius: 7,
color: "var(--text)", color: "var(--text)",
marginBottom: 12, marginBottom: 12,
fontSize: 14, fontSize: 14,

View File

@@ -28,10 +28,10 @@ export function LoginPage() {
} }
const inputStyle: React.CSSProperties = { const inputStyle: React.CSSProperties = {
width: "100%", padding: "10px 12px", width: "100%", padding: "8px 12px",
background: "var(--bg-inset)", background: "var(--bg-inset)",
border: "1px solid var(--border)", border: "1px solid var(--border)",
borderRadius: 6, borderRadius: 7,
color: "var(--text)", color: "var(--text)",
marginBottom: 16, fontSize: 14, boxSizing: "border-box", marginBottom: 16, fontSize: 14, boxSizing: "border-box",
}; };
@@ -68,7 +68,7 @@ export function LoginPage() {
style={{ ...inputStyle, marginBottom: 24 }} style={{ ...inputStyle, marginBottom: 24 }}
/> />
<button type="submit" style={{ width: "100%", padding: "12px", background: "var(--accent)", border: "none", borderRadius: 6, color: "var(--accent-fg)", fontWeight: 600, cursor: "pointer", fontSize: 14 }}> <button type="submit" style={{ width: "100%", padding: "10px", background: "var(--accent)", border: "none", borderRadius: 7, color: "var(--accent-fg)", fontWeight: 600, cursor: "pointer", fontSize: 14 }}>
{mode === "login" ? "Sign In" : "Create Account"} {mode === "login" ? "Sign In" : "Create Account"}
</button> </button>

View File

@@ -104,14 +104,14 @@ export function SessionPage() {
value={labelInput} value={labelInput}
onChange={(e) => setLabelInput(e.target.value)} onChange={(e) => setLabelInput(e.target.value)}
placeholder="e.g. pre-gig warmup" placeholder="e.g. pre-gig warmup"
style={{ width: "100%", padding: "7px 10px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 6, color: "var(--text)", fontSize: 13, boxSizing: "border-box", marginBottom: 10 }} style={{ width: "100%", padding: "8px 12px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 7, color: "var(--text)", fontSize: 13, boxSizing: "border-box", marginBottom: 10 }}
/> />
<label style={{ display: "block", color: "var(--text-muted)", fontSize: 11, marginBottom: 4 }}>NOTES</label> <label style={{ display: "block", color: "var(--text-muted)", fontSize: 11, marginBottom: 4 }}>NOTES</label>
<textarea <textarea
value={notesInput} value={notesInput}
onChange={(e) => setNotesInput(e.target.value)} onChange={(e) => setNotesInput(e.target.value)}
rows={3} rows={3}
style={{ width: "100%", padding: "7px 10px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 6, color: "var(--text)", fontSize: 13, boxSizing: "border-box", resize: "vertical", fontFamily: "inherit" }} style={{ width: "100%", padding: "8px 12px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 7, color: "var(--text)", fontSize: 13, boxSizing: "border-box", resize: "vertical", fontFamily: "inherit" }}
/> />
<div style={{ display: "flex", gap: 8, marginTop: 10 }}> <div style={{ display: "flex", gap: 8, marginTop: 10 }}>
<button <button
@@ -139,8 +139,8 @@ export function SessionPage() {
key={song.id} key={song.id}
to={`/bands/${bandId}/songs/${song.id}`} to={`/bands/${bandId}/songs/${song.id}`}
style={{ style={{
background: "var(--bg-inset)", background: "var(--bg-subtle)",
border: "1px solid var(--border)", border: "1px solid var(--border-subtle)",
borderRadius: 8, borderRadius: 8,
padding: "14px 18px", padding: "14px 18px",
textDecoration: "none", textDecoration: "none",

View File

@@ -27,7 +27,7 @@ const inputStyle: React.CSSProperties = {
padding: "8px 12px", padding: "8px 12px",
background: "var(--bg-inset)", background: "var(--bg-inset)",
border: "1px solid var(--border)", border: "1px solid var(--border)",
borderRadius: 6, borderRadius: 7,
color: "var(--text)", color: "var(--text)",
fontSize: 14, fontSize: 14,
boxSizing: "border-box", boxSizing: "border-box",
@@ -102,9 +102,6 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
lastModified: Date.now() lastModified: Date.now()
}); });
console.log(`Resized image from ${img.width}x${img.height} to ${width}x${height}`);
console.log(`File size reduced from ${file.size} to ${resizedFile.size} bytes`);
resolve(resizedFile); resolve(resizedFile);
}, 'image/jpeg', 0.8); // JPEG quality 80% }, 'image/jpeg', 0.8); // JPEG quality 80%
}; };
@@ -144,7 +141,7 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
return ( return (
<> <>
<section style={{ marginBottom: 32 }}> <section style={{ marginBottom: 32 }}>
<h2 style={{ fontSize: 13, color: "var(--text-muted)", fontFamily: "monospace", letterSpacing: 1, marginBottom: 16 }}>PROFILE</h2> <h2 style={{ fontSize: 11, fontWeight: 500, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.7px", marginBottom: 16 }}>PROFILE</h2>
<div style={{ display: "flex", alignItems: "center", gap: 16, marginBottom: 16 }}> <div style={{ display: "flex", alignItems: "center", gap: 16, marginBottom: 16 }}>
{avatarUrl && ( {avatarUrl && (
<img src={avatarUrl} alt="Profile" style={{ width: 64, height: 64, borderRadius: "50%", objectFit: "cover" }} /> <img src={avatarUrl} alt="Profile" style={{ width: 64, height: 64, borderRadius: "50%", objectFit: "cover" }} />
@@ -158,7 +155,7 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
</section> </section>
<section style={{ marginBottom: 32 }}> <section style={{ marginBottom: 32 }}>
<h2 style={{ fontSize: 13, color: "var(--text-muted)", fontFamily: "monospace", letterSpacing: 1, marginBottom: 8 }}>NEXTCLOUD CONNECTION</h2> <h2 style={{ fontSize: 11, fontWeight: 500, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.7px", marginBottom: 8 }}>Nextcloud Connection</h2>
<p style={{ color: "var(--text-subtle)", fontSize: 12, marginBottom: 16 }}> <p style={{ color: "var(--text-subtle)", fontSize: 12, marginBottom: 16 }}>
Configure your personal Nextcloud credentials. When set, all file operations (band folders, song uploads, scans) will use these credentials. Configure your personal Nextcloud credentials. When set, all file operations (band folders, song uploads, scans) will use these credentials.
</p> </p>
@@ -190,43 +187,36 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
</section> </section>
<section style={{ marginBottom: 32 }}> <section style={{ marginBottom: 32 }}>
<h2 style={{ fontSize: 13, color: "var(--text-muted)", fontFamily: "monospace", letterSpacing: 1, marginBottom: 16 }}>AVATAR</h2> <h2 style={{ fontSize: 11, fontWeight: 500, color: "var(--text-muted)", textTransform: "uppercase", letterSpacing: "0.7px", marginBottom: 16 }}>AVATAR</h2>
<div style={{ display: "flex", gap: 12, alignItems: "center", marginBottom: 16 }}> <div style={{ display: "flex", gap: 12, alignItems: "center", marginBottom: 16 }}>
<input <input
type="file" type="file"
accept="image/*" accept="image/*"
onChange={async (e) => { onChange={async (e) => {
console.log("File input changed", e.target.files);
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (file) { if (file) {
console.log("Selected file:", file.name, file.type, file.size);
setUploading(true); setUploading(true);
try { try {
// Check file size and resize if needed // Check file size and resize if needed
const maxSize = 4 * 1024 * 1024; // 4MB (more conservative to account for base64 overhead) const maxSize = 4 * 1024 * 1024; // 4MB (more conservative to account for base64 overhead)
let processedFile = file; let processedFile = file;
if (file.size > maxSize) { if (file.size > maxSize) {
console.log("File too large, resizing...");
processedFile = await resizeImage(file, 800, 800); // Max 800x800 processedFile = await resizeImage(file, 800, 800); // Max 800x800
} }
const formData = new FormData(); const formData = new FormData();
formData.append('file', processedFile, processedFile.name || file.name); formData.append('file', processedFile, processedFile.name || file.name);
console.log("Uploading file to /auth/me/avatar");
console.log("Final file size:", processedFile.size);
const response = await api.upload<MemberRead>('/auth/me/avatar', formData); const response = await api.upload<MemberRead>('/auth/me/avatar', formData);
console.log("Upload response:", response);
setAvatarUrl(response.avatar_url || ''); setAvatarUrl(response.avatar_url || '');
qc.invalidateQueries({ queryKey: ['me'] }); qc.invalidateQueries({ queryKey: ['me'] });
qc.invalidateQueries({ queryKey: ['comments'] }); qc.invalidateQueries({ queryKey: ['comments'] });
} catch (err) { } catch (err) {
console.error("Upload failed:", err);
let errorMessage = 'Failed to upload avatar. Please try again.'; let errorMessage = 'Failed to upload avatar. Please try again.';
if (err instanceof Error) { if (err instanceof Error) {
errorMessage = err.message; errorMessage = err.message;
if (err.message.includes('413')) { if (err.message.includes('413')) {
@@ -235,8 +225,6 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
errorMessage = 'Invalid image file. Please upload a valid image (JPG, PNG, etc.).'; errorMessage = 'Invalid image file. Please upload a valid image (JPG, PNG, etc.).';
} }
} else if (typeof err === 'object' && err !== null) { } else if (typeof err === 'object' && err !== null) {
// Try to extract more details from the error object
console.error("Error details:", JSON.stringify(err));
const errorObj = err as { status?: number; data?: { detail?: string } }; const errorObj = err as { status?: number; data?: { detail?: string } };
if (errorObj.status === 422 && errorObj.data?.detail) { if (errorObj.status === 422 && errorObj.data?.detail) {
errorMessage = errorObj.data.detail; errorMessage = errorObj.data.detail;
@@ -252,48 +240,39 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
style={{ display: "none" }} style={{ display: "none" }}
id="avatar-upload" id="avatar-upload"
/> />
<label htmlFor="avatar-upload" style={{ <label htmlFor="avatar-upload" style={{
background: "var(--accent)", background: "var(--accent)",
border: "none", border: "none",
borderRadius: 6, borderRadius: 6,
color: "var(--accent-fg)", color: "var(--accent-fg)",
cursor: "pointer", cursor: "pointer",
padding: "8px 16px", padding: "8px 16px",
fontWeight: 600, fontWeight: 600,
fontSize: 14 fontSize: 13,
}}> }}>
{uploading ? "Uploading..." : "Upload Avatar"} {uploading ? "Uploading..." : "Upload Avatar"}
</label> </label>
<button <button
onClick={async () => { onClick={async () => {
console.log("Generate Random button clicked");
try { try {
// Generate a new random avatar using user ID as seed for consistency
const seed = Math.random().toString(36).substring(2, 15); const seed = Math.random().toString(36).substring(2, 15);
const newAvatarUrl = `https://api.dicebear.com/9.x/identicon/svg?seed=${seed}&backgroundType=gradientLinear&size=128`; const newAvatarUrl = `https://api.dicebear.com/9.x/identicon/svg?seed=${seed}&backgroundType=gradientLinear&size=128`;
console.log("Generated avatar URL:", newAvatarUrl);
console.log("Calling updateSettings with:", { avatar_url: newAvatarUrl });
await updateSettings({ avatar_url: newAvatarUrl }); await updateSettings({ avatar_url: newAvatarUrl });
setAvatarUrl(newAvatarUrl); setAvatarUrl(newAvatarUrl);
qc.invalidateQueries({ queryKey: ["me"] }); qc.invalidateQueries({ queryKey: ["me"] });
qc.invalidateQueries({ queryKey: ["comments"] }); qc.invalidateQueries({ queryKey: ["comments"] });
console.log("Avatar updated successfully");
} catch (err) { } catch (err) {
console.error("Failed to update avatar:", err);
setError(err instanceof Error ? err.message : 'Failed to update avatar'); setError(err instanceof Error ? err.message : 'Failed to update avatar');
} }
}} }}
style={{ style={{
background: "none", background: "none",
border: "1px solid var(--border)", border: "1px solid var(--border)",
borderRadius: 6, borderRadius: 6,
color: "var(--text)", color: "var(--text)",
cursor: "pointer", cursor: "pointer",
padding: "8px 16px", padding: "8px 16px",
fontSize: 14 fontSize: 13,
}} }}
> >
Generate Random Generate Random
@@ -306,8 +285,6 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
alt="Preview" alt="Preview"
style={{ width: 48, height: 48, borderRadius: "50%", objectFit: "cover" }} style={{ width: 48, height: 48, borderRadius: "50%", objectFit: "cover" }}
onError={() => { onError={() => {
console.error("Failed to load avatar:", avatarUrl);
// Set to default avatar on error
setAvatarUrl(`https://api.dicebear.com/9.x/identicon/svg?seed=${me.id}&backgroundType=gradientLinear&size=128`); setAvatarUrl(`https://api.dicebear.com/9.x/identicon/svg?seed=${me.id}&backgroundType=gradientLinear&size=128`);
}} }}
/> />

View File

@@ -219,7 +219,7 @@ export function SongPage() {
onChange={(e) => setCommentBody(e.target.value)} onChange={(e) => setCommentBody(e.target.value)}
placeholder="Add a comment…" placeholder="Add a comment…"
rows={2} rows={2}
style={{ flex: 1, padding: "8px 12px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 6, color: "var(--text)", fontSize: 13, resize: "vertical", fontFamily: "inherit" }} style={{ flex: 1, padding: "8px 12px", background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 7, color: "var(--text)", fontSize: 13, resize: "vertical", fontFamily: "inherit" }}
/> />
<button <button
onClick={() => commentBody.trim() && addCommentMutation.mutate({ body: commentBody.trim(), timestamp: currentTime })} onClick={() => commentBody.trim() && addCommentMutation.mutate({ body: commentBody.trim(), timestamp: currentTime })}
@@ -242,7 +242,7 @@ function AnnotationCard({ annotation: a, onSeek, versionId }: { annotation: Anno
}); });
return ( return (
<div style={{ background: "var(--bg-inset)", border: "1px solid var(--border)", borderRadius: 8, padding: 14 }}> <div style={{ background: "var(--bg-subtle)", border: "1px solid var(--border-subtle)", borderRadius: 8, padding: 14 }}>
<div style={{ display: "flex", gap: 8, marginBottom: 6 }}> <div style={{ display: "flex", gap: 8, marginBottom: 6 }}>
<button <button
onClick={() => onSeek(a.timestamp_ms / 1000)} onClick={() => onSeek(a.timestamp_ms / 1000)}