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:
@@ -27,7 +27,7 @@ const inputStyle: React.CSSProperties = {
|
||||
padding: "8px 12px",
|
||||
background: "var(--bg-inset)",
|
||||
border: "1px solid var(--border)",
|
||||
borderRadius: 6,
|
||||
borderRadius: 7,
|
||||
color: "var(--text)",
|
||||
fontSize: 14,
|
||||
boxSizing: "border-box",
|
||||
@@ -102,9 +102,6 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
|
||||
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);
|
||||
}, 'image/jpeg', 0.8); // JPEG quality 80%
|
||||
};
|
||||
@@ -144,7 +141,7 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
|
||||
return (
|
||||
<>
|
||||
<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 }}>
|
||||
{avatarUrl && (
|
||||
<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 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 }}>
|
||||
Configure your personal Nextcloud credentials. When set, all file operations (band folders, song uploads, scans) will use these credentials.
|
||||
</p>
|
||||
@@ -190,43 +187,36 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
|
||||
</section>
|
||||
|
||||
<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 }}>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={async (e) => {
|
||||
console.log("File input changed", e.target.files);
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
console.log("Selected file:", file.name, file.type, file.size);
|
||||
setUploading(true);
|
||||
|
||||
|
||||
try {
|
||||
// Check file size and resize if needed
|
||||
const maxSize = 4 * 1024 * 1024; // 4MB (more conservative to account for base64 overhead)
|
||||
let processedFile = file;
|
||||
|
||||
|
||||
if (file.size > maxSize) {
|
||||
console.log("File too large, resizing...");
|
||||
processedFile = await resizeImage(file, 800, 800); // Max 800x800
|
||||
}
|
||||
|
||||
|
||||
const formData = new FormData();
|
||||
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);
|
||||
console.log("Upload response:", response);
|
||||
|
||||
|
||||
setAvatarUrl(response.avatar_url || '');
|
||||
qc.invalidateQueries({ queryKey: ['me'] });
|
||||
qc.invalidateQueries({ queryKey: ['comments'] });
|
||||
} catch (err) {
|
||||
console.error("Upload failed:", err);
|
||||
let errorMessage = 'Failed to upload avatar. Please try again.';
|
||||
|
||||
|
||||
if (err instanceof Error) {
|
||||
errorMessage = err.message;
|
||||
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.).';
|
||||
}
|
||||
} 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 } };
|
||||
if (errorObj.status === 422 && errorObj.data?.detail) {
|
||||
errorMessage = errorObj.data.detail;
|
||||
@@ -252,48 +240,39 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
|
||||
style={{ display: "none" }}
|
||||
id="avatar-upload"
|
||||
/>
|
||||
<label htmlFor="avatar-upload" style={{
|
||||
background: "var(--accent)",
|
||||
border: "none",
|
||||
borderRadius: 6,
|
||||
color: "var(--accent-fg)",
|
||||
cursor: "pointer",
|
||||
padding: "8px 16px",
|
||||
fontWeight: 600,
|
||||
fontSize: 14
|
||||
<label htmlFor="avatar-upload" style={{
|
||||
background: "var(--accent)",
|
||||
border: "none",
|
||||
borderRadius: 6,
|
||||
color: "var(--accent-fg)",
|
||||
cursor: "pointer",
|
||||
padding: "8px 16px",
|
||||
fontWeight: 600,
|
||||
fontSize: 13,
|
||||
}}>
|
||||
{uploading ? "Uploading..." : "Upload Avatar"}
|
||||
</label>
|
||||
<button
|
||||
onClick={async () => {
|
||||
console.log("Generate Random button clicked");
|
||||
try {
|
||||
// Generate a new random avatar using user ID as seed for consistency
|
||||
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`;
|
||||
|
||||
console.log("Generated avatar URL:", newAvatarUrl);
|
||||
console.log("Calling updateSettings with:", { avatar_url: newAvatarUrl });
|
||||
|
||||
await updateSettings({ avatar_url: newAvatarUrl });
|
||||
setAvatarUrl(newAvatarUrl);
|
||||
qc.invalidateQueries({ queryKey: ["me"] });
|
||||
qc.invalidateQueries({ queryKey: ["comments"] });
|
||||
|
||||
console.log("Avatar updated successfully");
|
||||
} catch (err) {
|
||||
console.error("Failed to update avatar:", err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to update avatar');
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
background: "none",
|
||||
border: "1px solid var(--border)",
|
||||
borderRadius: 6,
|
||||
color: "var(--text)",
|
||||
cursor: "pointer",
|
||||
padding: "8px 16px",
|
||||
fontSize: 14
|
||||
style={{
|
||||
background: "none",
|
||||
border: "1px solid var(--border)",
|
||||
borderRadius: 6,
|
||||
color: "var(--text)",
|
||||
cursor: "pointer",
|
||||
padding: "8px 16px",
|
||||
fontSize: 13,
|
||||
}}
|
||||
>
|
||||
Generate Random
|
||||
@@ -306,8 +285,6 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
|
||||
alt="Preview"
|
||||
style={{ width: 48, height: 48, borderRadius: "50%", objectFit: "cover" }}
|
||||
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`);
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user