Fix FAB z-index/position, broken image fallbacks, and upload progress UI
- Bug 2: raise all FABs (activity, memories, circle) to z-50 and bottom-24 so they sit above the bottom nav (z-40, ~56px tall) - Bug 1: add onError handlers to baby photo (home) and parent avatar (profile) so a broken R2 URL shows the 👶 / initials placeholder instead of a broken browser icon; reset error flag after a successful upload - Bug 3: replace ⏳ emoji spinner with a proper CSS spinner on home page baby photo; add a fixed upload-progress toast to the memories page that appears at the top of the screen for the full duration of the upload Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
309fd5aa29
commit
ef30f27e9c
6 changed files with 25 additions and 9 deletions
File diff suppressed because one or more lines are too long
|
|
@ -371,7 +371,7 @@ export default function ActivityPage() {
|
|||
</div>
|
||||
|
||||
{/* FAB */}
|
||||
<div className="fixed bottom-20 right-5 flex flex-col items-end gap-2 z-40">
|
||||
<div className="fixed bottom-24 right-5 flex flex-col items-end gap-2 z-50">
|
||||
{fabOpen && (["feed", "sleep", "diaper"] as ModalLogType[]).map(t => (
|
||||
<button
|
||||
key={t}
|
||||
|
|
|
|||
|
|
@ -615,7 +615,7 @@ export default function CircleFeedPage({ params }: { params: Promise<{ id: strin
|
|||
{/* FAB — create post */}
|
||||
<button
|
||||
onClick={() => setShowCreate(true)}
|
||||
className="fixed bottom-20 right-5 w-14 h-14 bg-rose-400 text-white rounded-full shadow-lg flex items-center justify-center text-2xl z-40"
|
||||
className="fixed bottom-24 right-5 w-14 h-14 bg-rose-400 text-white rounded-full shadow-lg flex items-center justify-center text-2xl z-50"
|
||||
>+</button>
|
||||
|
||||
{showCreate && circle && familyId && (
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ export default function HomePage() {
|
|||
const [vaccineReminders, setVaccineReminders] = useState<any[]>([]);
|
||||
const [aiChips, setAiChips] = useState<string[]>([]);
|
||||
const [uploadingPhoto, setUploadingPhoto] = useState(false);
|
||||
const [photoError, setPhotoError] = useState(false);
|
||||
const [showPhotoMenu, setShowPhotoMenu] = useState(false);
|
||||
const photoInputRef = useRef<HTMLInputElement>(null);
|
||||
const { theme, toggle: toggleTheme } = useTheme();
|
||||
|
|
@ -215,6 +216,7 @@ export default function HomePage() {
|
|||
});
|
||||
|
||||
// 4. Update in-memory state immediately — no full reload needed
|
||||
setPhotoError(false);
|
||||
updateChildImage(childId, publicUrl);
|
||||
} catch (err) {
|
||||
console.error("Photo upload failed:", err);
|
||||
|
|
@ -318,15 +320,18 @@ export default function HomePage() {
|
|||
className="relative group"
|
||||
title={child?.imageUrl ? "Photo options" : "Add photo"}
|
||||
>
|
||||
{child?.imageUrl
|
||||
? <img src={child.imageUrl} alt={child?.name} className="w-16 h-16 rounded-full object-cover" />
|
||||
{child?.imageUrl && !photoError
|
||||
? <img src={child.imageUrl} alt={child?.name} className="w-16 h-16 rounded-full object-cover" onError={() => setPhotoError(true)} />
|
||||
: <div className="w-16 h-16 bg-rose-100 dark:bg-rose-900 rounded-full flex items-center justify-center text-2xl">👶</div>
|
||||
}
|
||||
{/* Camera overlay */}
|
||||
{/* Camera / upload overlay */}
|
||||
<div className={`absolute inset-0 rounded-full flex items-center justify-center transition-opacity ${
|
||||
uploadingPhoto ? "bg-black/40 opacity-100" : "bg-black/0 opacity-0 group-hover:opacity-100 group-active:opacity-100"
|
||||
}`}>
|
||||
<span className="text-white text-lg">{uploadingPhoto ? "⏳" : "📷"}</span>
|
||||
{uploadingPhoto
|
||||
? <div className="w-6 h-6 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
: <span className="text-white text-lg">📷</span>
|
||||
}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
|
|
|||
|
|
@ -351,8 +351,16 @@ export default function MemoriesPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upload progress toast */}
|
||||
{uploading && (
|
||||
<div className="fixed top-4 inset-x-4 z-50 bg-gray-900/90 text-white text-sm rounded-2xl px-4 py-3 flex items-center gap-3 shadow-xl pointer-events-none">
|
||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin flex-shrink-0" />
|
||||
<span>Uploading photo…</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Upload FAB — disabled when storage quota is exceeded */}
|
||||
<div className="fixed bottom-6 right-6 z-20">
|
||||
<div className="fixed bottom-24 right-6 z-50">
|
||||
<button
|
||||
onClick={quotaExceeded ? undefined : handleUploadClick}
|
||||
disabled={uploading || quotaExceeded}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export default function ProfilePage() {
|
|||
const [saving, setSaving] = useState(false);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [saveMsg, setSaveMsg] = useState("");
|
||||
const [avatarError, setAvatarError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/auth/profile")
|
||||
|
|
@ -67,6 +68,7 @@ export default function ProfilePage() {
|
|||
if (!patchRes.ok) throw new Error(patchData.error || "Failed to save photo");
|
||||
|
||||
setAvatarUrl(newPublicUrl);
|
||||
setAvatarError(false);
|
||||
setSaveMsg("Photo updated!");
|
||||
} catch (err) {
|
||||
setSaveMsg(err instanceof Error ? err.message : "Upload failed");
|
||||
|
|
@ -125,11 +127,12 @@ export default function ProfilePage() {
|
|||
{/* Avatar */}
|
||||
<div className="flex flex-col items-center pt-8 pb-4">
|
||||
<div className="relative mb-3">
|
||||
{avatarUrl ? (
|
||||
{avatarUrl && !avatarError ? (
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt={name}
|
||||
className="w-24 h-24 rounded-full object-cover ring-4 ring-white dark:ring-gray-800 shadow-md"
|
||||
onError={() => setAvatarError(true)}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-24 h-24 rounded-full bg-gradient-to-br from-rose-300 to-amber-300 flex items-center justify-center text-white text-2xl font-bold shadow-md ring-4 ring-white dark:ring-gray-800">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue