11<script lang =" ts" >
2- // Pet panel for pet information and quick actions
2+ import { onMount } from ' svelte' ;
3+ import { Plus , Heart , ArrowLeft , Upload , X } from ' lucide-svelte' ;
4+ import { petStore , selectedPetStore , petHelpers , selectedPetHelpers } from ' $lib/stores/pets.js' ;
5+ import { fade } from ' svelte/transition' ;
6+ import type { PetPanelData } from ' $lib/types/Pet.js' ;
7+
8+ let pets: PetPanelData [] = [];
9+ let selectedPetId: string | null = null ;
10+ let showCreateForm = false ;
11+ let imageInput: HTMLInputElement ;
12+
13+ let newPet = {
14+ name: ' ' ,
15+ breed: ' ' ,
16+ age: ' ' ,
17+ gender: ' ' ,
18+ profileImageUrl: ' '
19+ };
20+
21+ let formErrors: Record <string , string > = {};
22+
23+ onMount (() => {
24+ petStore .subscribe (value => { pets = value ; });
25+ selectedPetStore .subscribe (value => { selectedPetId = value ; });
26+ petHelpers .load ();
27+ selectedPetHelpers .load ();
28+ });
29+
30+ function toggleCreateForm() {
31+ showCreateForm = ! showCreateForm ;
32+ if (! showCreateForm ) {
33+ resetForm ();
34+ }
35+ }
36+
37+ function resetForm() {
38+ newPet = {
39+ name: ' ' ,
40+ breed: ' ' ,
41+ age: ' ' ,
42+ gender: ' ' ,
43+ profileImageUrl: ' '
44+ };
45+ formErrors = {};
46+ }
47+
48+ function handleImageUpload(event : Event ) {
49+ const target = event .target as HTMLInputElement ;
50+ const file = target .files ?.[0 ];
51+ if (file ) {
52+ if (file .size > 5 * 1024 * 1024 ) { // 5MB limit
53+ formErrors .image = ' Image must be less than 5MB' ;
54+ return ;
55+ }
56+
57+ const reader = new FileReader ();
58+ reader .onload = (e ) => {
59+ newPet .profileImageUrl = e .target ?.result as string ;
60+ };
61+ reader .readAsDataURL (file );
62+
63+ formErrors .image = ' ' ;
64+ }
65+ }
66+
67+ function validateForm() {
68+ formErrors = {};
69+
70+ if (! newPet .name .trim ()) {
71+ formErrors .name = ' Pet name is required' ;
72+ }
73+
74+ if (! newPet .breed .trim ()) {
75+ formErrors .breed = ' Breed is required' ;
76+ }
77+
78+ const age = parseInt (newPet .age );
79+ if (! newPet .age || age < 0 || age > 30 ) {
80+ formErrors .age = ' Please enter a valid age (0-30 years)' ;
81+ }
82+
83+ if (! newPet .gender ) {
84+ formErrors .gender = ' Please select gender' ;
85+ }
86+
87+ return Object .keys (formErrors ).length === 0 ;
88+ }
89+
90+ async function createPet() {
91+ if (! validateForm ()) return ;
92+
93+ const pet: PetPanelData = {
94+ id: Date .now ().toString (),
95+ name: newPet .name .trim (),
96+ breed: newPet .breed .trim (),
97+ age: parseInt (newPet .age ),
98+ gender: newPet .gender as ' male' | ' female' ,
99+ profileImageUrl: newPet .profileImageUrl || ' /images/default-pet.png' ,
100+ createdAt: new Date ().toISOString (),
101+ journalEntries: []
102+ };
103+
104+ petHelpers .add (pet );
105+ toggleCreateForm ();
106+ }
107+
108+ function selectPet(petId : string ) {
109+ selectedPetHelpers .select (petId );
110+ }
3111 </script >
4112
5- <div class =" h-full p-6 flex flex-col" >
6- <div class =" panel-header mb-4" >
7- <h2 class =" text-lg font-semibold" style =" color: var(--petalytics-text);" >
8- Pet Profile
9- </h2 >
10- <p class =" text-sm mt-1" style =" color: var(--petalytics-subtle);" >
11- Your pet's information and status
12- </p >
13- </div >
14-
15- <div class =" flex-1 flex items-center justify-center" >
16- <div class =" text-center" >
17- <div class =" w-16 h-16 rounded-full mx-auto mb-3 flex items-center justify-center"
18- style =" background: var(--petalytics-highlight-med);" >
19- <span class =" text-2xl" >🐾</span >
20- </div >
21- <p class =" text-sm" style =" color: var(--petalytics-subtle);" >
22- Pet panel placeholder
23- </p >
24- </div >
25- </div >
26- </div >
113+ <div class =" panel-container h-full flex flex-col" >
114+ <div class =" panel-header" >
115+ <div class =" flex items-center justify-between" >
116+ <div class =" flex items-center space-x-2" >
117+ {#if showCreateForm }
118+ <button on:click ={toggleCreateForm } class =" p-1 hover:opacity-70 transition-opacity" >
119+ <ArrowLeft size ={16 } style =" color: var(--petalytics-accent);" />
120+ </button >
121+ {:else }
122+ <Heart size ={18 } style =" color: var(--petalytics-accent);" />
123+ {/if }
124+ <h2 class =" text-lg font-semibold" >
125+ {showCreateForm ? ' Add New Pet' : ' Your Pets' }
126+ </h2 >
127+ </div >
128+ {#if ! showCreateForm }
129+ <button on:click ={toggleCreateForm } class =" flex items-center space-x-1 px-2 py-1 rounded-md button-secondary" >
130+ <Plus size ={16 } />
131+ <span class =" text-sm" >Add</span >
132+ </button >
133+ {/if }
134+ </div >
135+ </div >
136+
137+ <div class =" panel-content flex-1 p-4 overflow-y-auto" >
138+ {#if showCreateForm }
139+ <!-- Pet Creation Form -->
140+ <div class ="create-form space-y-4" transition:fade ={{ duration : 200 }}>
141+ <!-- Profile Image Upload -->
142+ <div class =" section" >
143+ <label class =" block text-sm font-medium mb-2" style =" color: var(--petalytics-subtle);" >
144+ Profile Photo
145+ </label >
146+ <div class =" flex flex-col items-center space-y-3" >
147+ {#if newPet .profileImageUrl }
148+ <img
149+ src ={newPet .profileImageUrl }
150+ alt =" Pet preview"
151+ class =" w-24 h-24 rounded-full object-cover border-2"
152+ style =" border-color: var(--petalytics-border);"
153+ />
154+ {:else }
155+ <div
156+ class =" w-24 h-24 rounded-full border-2 border-dashed flex items-center justify-center cursor-pointer hover:opacity-70 transition-opacity"
157+ style =" border-color: var(--petalytics-border);"
158+ on:click ={() => imageInput .click ()}
159+ on:keydown ={(e ) => e .key === ' Enter' && imageInput .click ()}
160+ role =" button"
161+ tabindex =" 0"
162+ >
163+ <Upload size ={24 } style =" color: var(--petalytics-subtle);" />
164+ </div >
165+ {/if }
166+ <input
167+ bind:this ={imageInput }
168+ type =" file"
169+ accept =" image/*"
170+ on:change ={handleImageUpload }
171+ class =" hidden"
172+ />
173+ {#if formErrors .image }
174+ <p class ="text-sm text-red-400" >{formErrors .image }</p >
175+ {/if }
176+ </div >
177+ </div >
178+
179+ <!-- Pet Name -->
180+ <div class =" section" >
181+ <input
182+ type =" text"
183+ bind:value ={newPet .name }
184+ class =" input w-full"
185+ placeholder =" Pet Name"
186+ />
187+ {#if formErrors .name }
188+ <p class ="text-sm text-red-400 mt-1" >{formErrors .name }</p >
189+ {/if }
190+ </div >
191+
192+ <!-- Breed -->
193+ <div class =" section" >
194+ <input
195+ type =" text"
196+ bind:value ={newPet .breed }
197+ class =" input w-full"
198+ placeholder =" Breed"
199+ />
200+ {#if formErrors .breed }
201+ <p class ="text-sm text-red-400 mt-1" >{formErrors .breed }</p >
202+ {/if }
203+ </div >
204+
205+ <!-- Age and Gender -->
206+ <div class =" grid grid-cols-2 gap-3" >
207+ <div >
208+ <input
209+ type =" number"
210+ bind:value ={newPet .age }
211+ class =" input w-full"
212+ placeholder =" Age"
213+ min =" 0"
214+ max =" 30"
215+ />
216+ {#if formErrors .age }
217+ <p class ="text-xs text-red-400 mt-1" >{formErrors .age }</p >
218+ {/if }
219+ </div >
220+
221+ <div >
222+ <select bind:value ={newPet .gender } class =" input w-full" >
223+ <option value =" " >Gender</option >
224+ <option value =" male" >Male</option >
225+ <option value =" female" >Female</option >
226+ </select >
227+ {#if formErrors .gender }
228+ <p class ="text-xs text-red-400 mt-1" >{formErrors .gender }</p >
229+ {/if }
230+ </div >
231+ </div >
232+
233+ <!-- Form Actions -->
234+ <div class =" flex space-x-3 pt-4" >
235+ <button on:click ={createPet } class =" button flex-1" >Create Pet</button >
236+ <button on:click ={toggleCreateForm } class =" button-secondary flex-1" >Cancel</button >
237+ </div >
238+ </div >
239+ {:else }
240+ <!-- Pet Grid -->
241+ <div class =" pets-grid" >
242+ {#if pets .length === 0 }
243+ <div class =" empty-state text-center py-8" >
244+ <Heart size ={48 } style =" color: var(--petalytics-subtle); margin: 0 auto 1rem;" />
245+ <p class =" text-lg font-medium mb-2" style =" color: var(--petalytics-text);" >
246+ No pets yet
247+ </p >
248+ <p class =" text-sm mb-4" style =" color: var(--petalytics-subtle);" >
249+ Add your first pet to get started with tracking their journal
250+ </p >
251+ <button on:click ={toggleCreateForm } class =" button" >Add Your First Pet</button >
252+ </div >
253+ {:else }
254+ <div class =" grid grid-cols-2 gap-3" >
255+ {#each pets as pet }
256+ <div
257+ class =" pet-card p-3 rounded-lg border cursor-pointer transition-all hover:opacity-80"
258+ class:selected ={selectedPetId === pet .id }
259+ style ="
260+ background: var(--petalytics-surface);
261+ border-color: {selectedPetId === pet.id ? 'var(--petalytics-accent)' : 'var(--petalytics-border)'};
262+ "
263+ on:click ={() => selectPet (pet .id )}
264+ on:keydown ={(e ) => e .key === ' Enter' && selectPet (pet .id )}
265+ role =" button"
266+ tabindex =" 0"
267+ >
268+ <div class =" flex flex-col items-center space-y-2" >
269+ <img
270+ src ={pet .profileImageUrl || ' /images/default-pet.png' }
271+ alt ={pet .name }
272+ class =" w-16 h-16 rounded-full object-cover"
273+ />
274+ <div class =" text-center" >
275+ <p class =" font-medium text-sm truncate" style =" color: var(--petalytics-text);" >
276+ {pet .name }
277+ </p >
278+ <p class =" text-xs truncate" style =" color: var(--petalytics-subtle);" >
279+ {pet .breed }
280+ </p >
281+ <p class =" text-xs" style =" color: var(--petalytics-subtle);" >
282+ {pet .age } {pet .age === 1 ? ' year' : ' years' } old
283+ </p >
284+ </div >
285+ </div >
286+ </div >
287+ {/each }
288+ </div >
289+ {/if }
290+ </div >
291+ {/if }
292+ </div >
293+ </div >
294+
295+ <style >
296+ .pet-card.selected {
297+ box-shadow : 0 0 0 2px var (--petalytics-accent );
298+ }
299+
300+ .panel-header {
301+ background : var (--petalytics-overlay );
302+ border-bottom : 1px solid var (--petalytics-border );
303+ padding : 0.75rem 1rem ;
304+ font-weight : 500 ;
305+ color : var (--petalytics-text );
306+ }
307+
308+ .panel-content {
309+ background : var (--petalytics-surface );
310+ }
311+
312+ .section {
313+ margin-bottom : 1rem ;
314+ }
315+ </style >
0 commit comments