Skip to content

Commit 78b2d89

Browse files
Merge pull request #10 from gitcoder89431/copilot/fix-5
🐾 Implement Pet Panel with creation, management, and selection functionality
2 parents a0c6df8 + 01f6431 commit 78b2d89

File tree

5 files changed

+479
-28
lines changed

5 files changed

+479
-28
lines changed
Lines changed: 312 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,315 @@
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>

src/lib/components/ui/Input.svelte

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
label?: string;
1010
id?: string;
1111
name?: string;
12-
autocomplete?: string;
1312
oninput?: (event: Event) => void;
1413
onchange?: (event: Event) => void;
1514
onblur?: (event: Event) => void;
@@ -25,7 +24,6 @@
2524
label,
2625
id,
2726
name,
28-
autocomplete,
2927
oninput,
3028
onchange,
3129
onblur,
@@ -58,7 +56,6 @@
5856
{disabled}
5957
{required}
6058
{name}
61-
{autocomplete}
6259
id={inputId}
6360
class="{baseClasses} {hasError ? errorClasses : normalClasses} {disabled ? disabledClasses : ''}"
6461
oninput={oninput}

0 commit comments

Comments
 (0)