From aba22e2855ba6c24ce1e466435ec737c7b7eb0b5 Mon Sep 17 00:00:00 2001 From: neodk2004 Date: Thu, 25 Dec 2025 12:45:14 +0100 Subject: [PATCH] feat(feed): add birthday highlight for pets in feed and explore - display subtle birthday marker on posts when pet has birthday - highlight birthday posts with soft accent border - reuse central petAge utilities for consistent logic - preserve all existing post meta information (author, time) - avoid layout shifts by using decorative indicators only --- frontend/app/components/PostCard/PostCard.tsx | 41 +- frontend/app/explore/page.tsx | 1 + frontend/app/feed/page.tsx | 1 + frontend/app/globals.css | 27 ++ frontend/app/pets/[id]/edit/page.tsx | 419 +++++++----------- frontend/app/pets/[id]/page.tsx | 4 + 6 files changed, 226 insertions(+), 267 deletions(-) diff --git a/frontend/app/components/PostCard/PostCard.tsx b/frontend/app/components/PostCard/PostCard.tsx index 5fda081..c9930b1 100644 --- a/frontend/app/components/PostCard/PostCard.tsx +++ b/frontend/app/components/PostCard/PostCard.tsx @@ -1,6 +1,7 @@ 'use client'; import { useRef } from 'react'; +import { formatPetAge, isBirthdayToday } from '@/app/lib/petAge'; export type PostCardData = { id: number; @@ -31,6 +32,7 @@ export default function PostCard({ }: PostCardProps) { const likeRef = useRef(null); const commentRef = useRef(null); + const isBirthday = post.petBirthDate && isBirthdayToday(post.petBirthDate); function animate( ref: React.RefObject, @@ -50,11 +52,13 @@ export default function PostCard({ return (
+ {/* Klickbarer Bereich → Preview */}
)} -
- - {post.petName} - - - · {post.petType} · {post.authorName} · {post.createdAt} +
+ + {post.petName} + + + + · {post.petType} + {post.petBirthDate && ` · ${formatPetAge(post.petBirthDate)}`} + · {post.authorName} + · {post.createdAt} + + + {isBirthday && ( + + 🎂 -
+ )} +
+ + +

{post.text}

diff --git a/frontend/app/explore/page.tsx b/frontend/app/explore/page.tsx index f776b8d..f98d1c8 100644 --- a/frontend/app/explore/page.tsx +++ b/frontend/app/explore/page.tsx @@ -4,6 +4,7 @@ import { useRef, useState } from 'react'; import PostCard, { PostCardData, } from '@/app/components/PostCard/PostCard'; +import { formatPetAge, isBirthdayToday } from '@/app/lib/petAge'; /* ======================= MOCK DATA diff --git a/frontend/app/feed/page.tsx b/frontend/app/feed/page.tsx index 183863e..ad34010 100644 --- a/frontend/app/feed/page.tsx +++ b/frontend/app/feed/page.tsx @@ -4,6 +4,7 @@ import { useRef, useState } from 'react'; import PostCard, { PostCardData, } from '@/app/components/PostCard/PostCard'; +import { formatPetAge, isBirthdayToday } from '@/app/lib/petAge'; /* ======================= MOCK: USER KONTEXT diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 2448dbc..ac6c98e 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -721,3 +721,30 @@ select:disabled { color: var(--color-text-muted); cursor: not-allowed; } +.postcard-title-row { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.birthday-dot { + margin-left: auto; + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 12px; + background: var(--color-primary-soft); + border: 1px solid rgba(46, 125, 50, 0.25); + box-shadow: var(--shadow-sm); + font-size: 0.95rem; +} + +/* Birthday Highlight */ +.postcard--birthday { + border-color: rgba(46, 125, 50, 0.35); + box-shadow: + 0 0 0 2px rgba(46, 125, 50, 0.18), + var(--shadow-sm); +} diff --git a/frontend/app/pets/[id]/edit/page.tsx b/frontend/app/pets/[id]/edit/page.tsx index 4258893..8ec5bed 100644 --- a/frontend/app/pets/[id]/edit/page.tsx +++ b/frontend/app/pets/[id]/edit/page.tsx @@ -1,326 +1,231 @@ 'use client'; -import { useState } from 'react'; -import Link from 'next/link'; -import { dogBreeds, catBreeds } from '@/app/data/breeds'; - -type PetType = 'Hund' | 'Katze'; +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { + formatPetAge, +} from '@/app/lib/petAge'; +/* Mock – später API */ type Pet = { id: number; name: string; - type: PetType; + type: 'Hund' | 'Katze'; breed: string; + birthDate?: string; + gender?: 'Männlich' | 'Weiblich' | 'Unbekannt'; bio?: string; + visibility: 'public' | 'followers'; }; -// 🔧 Mock-Daten (später Backend) +/* MOCK-DATEN */ const mockPets: Pet[] = [ { id: 1, name: 'Luna', type: 'Katze', - breed: 'Bengal x Mix', - bio: 'Neugierig, verspielt und liebt Sonne.', - }, - { - id: 2, - name: 'Bello', - type: 'Hund', - breed: 'Labrador Retriever x Mix', - bio: 'Immer gute Laune.', + breed: 'Bengal', + birthDate: '2021-05-14', + gender: 'Weiblich', + bio: 'Neugierig und verspielt', + visibility: 'public', }, ]; -type PageProps = { - params: Promise<{ id: string }>; -}; - -export default async function EditPetPage({ params }: PageProps) { - const { id } = await params; - const petId = Number(id); - +export default function EditPetPage({ + params, +}: { + params: { id: string }; +}) { + const router = useRouter(); + const petId = Number(params.id); const pet = mockPets.find((p) => p.id === petId); - if (!pet) { - return

Haustier nicht gefunden.

; - } - - return ; -} - -/* ---------- FORMULAR (Client Component) ---------- */ - -function EditPetForm({ pet }: { pet: Pet }) { - const [name, setName] = useState(pet.name); - const [type, setType] = useState(pet.type); - const [breed, setBreed] = useState(pet.breed); - const [breedSearch, setBreedSearch] = useState(''); - const [bio, setBio] = useState(pet.bio ?? ''); - const [error, setError] = useState(''); - const [success, setSuccess] = useState(''); - const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); - - - const breeds = type === 'Hund' ? dogBreeds : catBreeds; - const filteredBreeds = breeds.filter((b) => - b.toLowerCase().includes(breedSearch.toLowerCase()) + const [name, setName] = useState(''); + const [birthDate, setBirthDate] = useState(''); + const [ageFallback, setAgeFallback] = useState(''); + const [gender, setGender] = useState<'Männlich' | 'Weiblich' | 'Unbekannt'>( + 'Unbekannt' + ); + const [bio, setBio] = useState(''); + const [visibility, setVisibility] = useState<'public' | 'followers'>( + 'public' ); - function handleSubmit(event: React.FormEvent) { - event.preventDefault(); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(''); + + /* Initialwerte setzen */ + useEffect(() => { + if (!pet) return; + + setName(pet.name); + setBirthDate(pet.birthDate || ''); + setGender(pet.gender || 'Unbekannt'); + setBio(pet.bio || ''); + setVisibility(pet.visibility); + }, [pet]); + + if (!pet) { + return ( +
+ Haustier nicht gefunden + Bitte gehe zurück zur Übersicht. +
+ ); + } + + function handleSubmit(e: React.FormEvent) { + e.preventDefault(); setError(''); setSuccess(''); if (!name.trim()) { - setError('Name darf nicht leer sein.'); + setError('Der Name darf nicht leer sein.'); return; } - if (!breed) { - setError('Bitte wähle eine Rasse aus.'); - return; - } - - const updatedPetPayload = { - id: pet.id, + const updatedPet = { + ...pet, name, - type, - breed, - bio: bio || null, - updatedAt: new Date().toISOString(), + birthDate: birthDate || null, + ageFallback: birthDate ? null : ageFallback || null, + gender, + bio, + visibility, }; - console.log('Haustier aktualisiert (Mock):', updatedPetPayload); + console.log('UPDATED PET:', updatedPet); - setSuccess('Änderungen gespeichert ✅'); + setSuccess('Änderungen wurden gespeichert.'); + setTimeout(() => { + router.push(`/pets/${pet.id}`); + }, 800); } -function handleDelete() { - console.log('Haustier gelöscht (Mock):', pet.id); - // Später: - // await fetch(`/api/pets/${pet.id}`, { method: 'DELETE' }) - // router.push('/pets'); - - setShowDeleteConfirm(false); - alert('Haustier wurde technisch gelöscht (Mock)'); -} + function handleCancel() { + router.back(); + } return ( -
-
+
+

Haustier bearbeiten

-

- Passe die Informationen deines Haustiers an. -

+

Du kannst die Angaben jederzeit korrigieren.

-
+ {/* Name */} -
- Name - setName(e.target.value)} - style={{ - padding: '0.7rem', - borderRadius: '10px', - border: '1px solid #ccc', - }} - /> +
+ + setName(e.target.value)} />
- {/* Art */} -
- Tier-Art - + Kann nachträglich nicht geändert werden. +
+ +
+ + + Kann nachträglich nicht geändert werden. +
+ + {/* Geburtsdatum */} +
+ + { - setType(e.target.value as PetType); - setBreed(''); - setBreedSearch(''); - }} - style={{ - padding: '0.6rem', - borderRadius: '10px', - border: '1px solid #ccc', - maxWidth: '220px', - }} - > - - - -
- - {/* Rasse */} -
- Rasse - - setBreedSearch(e.target.value)} - style={{ - padding: '0.6rem', - borderRadius: '10px', - border: '1px solid #ccc', + setBirthDate(e.target.value); + if (e.target.value) setAgeFallback(''); }} /> + {birthDate && ( + + Aktuelles Alter: {formatPetAge(birthDate)} + + )} +
+ {/* Fallback-Alter */} + {!birthDate && ( +
+ + setAgeFallback(e.target.value)} + /> + Wird verwendet, wenn kein Geburtsdatum bekannt ist. +
+ )} + + {/* Geschlecht */} +
+
{/* Bio */} -
- Kurzbeschreibung +
+