CCockrum commited on
Commit
badcf09
·
verified ·
1 Parent(s): 46c7af1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +599 -0
app.py CHANGED
@@ -57,6 +57,605 @@ st.markdown("""
57
  </style>
58
  """, unsafe_allow_html=True)
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  # Initialize session state variables
61
  if 'access_token' not in st.session_state:
62
  st.session_state.access_token = None
 
57
  </style>
58
  """, unsafe_allow_html=True)
59
 
60
+ # Initialize session state variables
61
+ if 'access_token' not in st.session_state:
62
+ st.session_state.access_token = None
63
+ if 'token_expires' not in st.session_state:
64
+ st.session_state.token_expires = 0
65
+ if 'search_results' not in st.session_state:
66
+ st.session_state.search_results = None
67
+ if 'selected_pet' not in st.session_state:
68
+ st.session_state.selected_pet = None
69
+ if 'page' not in st.session_state:
70
+ st.session_state.page = 1
71
+ if 'favorites' not in st.session_state:
72
+ st.session_state.favorites = []
73
+
74
+ # Function to get access token
75
+ def get_access_token():
76
+ # Check if token is still valid
77
+ if st.session_state.access_token and time.time() < st.session_state.token_expires:
78
+ return st.session_state.access_token
79
+
80
+ # Get API credentials from environment variables or secrets
81
+ api_key = os.environ.get('PETFINDER_API_KEY') or st.secrets.get('PETFINDER_API_KEY')
82
+ api_secret = os.environ.get('PETFINDER_API_SECRET') or st.secrets.get('PETFINDER_API_SECRET')
83
+
84
+ if not api_key or not api_secret:
85
+ st.error("⚠️ Petfinder API credentials are missing. Please set them in your environment variables or Streamlit secrets.")
86
+ return None
87
+
88
+ # Get new token
89
+ url = "https://api.petfinder.com/v2/oauth2/token"
90
+ data = {
91
+ "grant_type": "client_credentials",
92
+ "client_id": api_key,
93
+ "client_secret": api_secret
94
+ }
95
+
96
+ try:
97
+ response = requests.post(url, data=data)
98
+ response.raise_for_status()
99
+ token_data = response.json()
100
+ st.session_state.access_token = token_data['access_token']
101
+ st.session_state.token_expires = time.time() + token_data['expires_in'] - 60 # Buffer of 60 seconds
102
+ return st.session_state.access_token
103
+ except requests.exceptions.RequestException as e:
104
+ st.error(f"⚠️ Error getting access token: {str(e)}")
105
+ return None
106
+
107
+ # Function to search pets
108
+ def search_pets(params):
109
+ token = get_access_token()
110
+ if not token:
111
+ return None
112
+
113
+ url = "https://api.petfinder.com/v2/animals"
114
+ headers = {"Authorization": f"Bearer {token}"}
115
+
116
+ try:
117
+ response = requests.get(url, headers=headers, params=params)
118
+ response.raise_for_status()
119
+ return response.json()
120
+ except requests.exceptions.RequestException as e:
121
+ st.error(f"⚠️ Error searching pets: {str(e)}")
122
+ return None
123
+
124
+ # Function to get breeds
125
+ def get_breeds(animal_type):
126
+ token = get_access_token()
127
+ if not token:
128
+ return []
129
+
130
+ url = f"https://api.petfinder.com/v2/types/{animal_type}/breeds"
131
+ headers = {"Authorization": f"Bearer {token}"}
132
+
133
+ try:
134
+ response = requests.get(url, headers=headers)
135
+ response.raise_for_status()
136
+ return [breed['name'] for breed in response.json()['breeds']]
137
+ except requests.exceptions.RequestException as e:
138
+ st.error(f"⚠️ Error getting breeds: {str(e)}")
139
+ return []
140
+
141
+ # Function to get organizations
142
+ def get_organizations(location):
143
+ token = get_access_token()
144
+ if not token:
145
+ return []
146
+
147
+ url = "https://api.petfinder.com/v2/organizations"
148
+ headers = {"Authorization": f"Bearer {token}"}
149
+ params = {"location": location, "distance": 100, "limit": 100}
150
+
151
+ try:
152
+ response = requests.get(url, headers=headers, params=params)
153
+ response.raise_for_status()
154
+ return [(org['id'], org['name']) for org in response.json()['organizations']]
155
+ except requests.exceptions.RequestException as e:
156
+ st.error(f"⚠️ Error getting organizations: {str(e)}")
157
+ return []
158
+
159
+ # Function to get pet details
160
+ def get_pet_details(pet_id):
161
+ token = get_access_token()
162
+ if not token:
163
+ return None
164
+
165
+ url = f"https://api.petfinder.com/v2/animals/{pet_id}"
166
+ headers = {"Authorization": f"Bearer {token}"}
167
+
168
+ try:
169
+ response = requests.get(url, headers=headers)
170
+ response.raise_for_status()
171
+ return response.json()['animal']
172
+ except requests.exceptions.RequestException as e:
173
+ st.error(f"⚠️ Error getting pet details: {str(e)}")
174
+ return None
175
+
176
+ # Function to format pet card
177
+ def display_pet_card(pet, is_favorite=False, context="search"):
178
+ col1, col2 = st.columns([1, 2])
179
+
180
+ with col1:
181
+ if pet['photos'] and len(pet['photos']) > 0:
182
+ st.image(pet['photos'][0]['medium'], use_container_width=True)
183
+ else:
184
+ st.image("https://via.placeholder.com/300x300?text=No+Image", use_container_width=True)
185
+
186
+ with col2:
187
+ st.markdown(f"<div class='pet-name'>{pet['name']}</div>", unsafe_allow_html=True)
188
+
189
+ # Tags
190
+ tags_html = ""
191
+ if pet['status'] == 'adoptable':
192
+ tags_html += "<span class='tag' style='background-color: #808080;'>Adoptable</span> "
193
+ else:
194
+ tags_html += f"<span class='tag' style='background-color: #808080;'>{pet['status'].title()}</span> "
195
+
196
+ if pet['age']:
197
+ tags_html += f"<span class='tag'>{pet['age']}</span> "
198
+ if pet['gender']:
199
+ tags_html += f"<span class='tag'>{pet['gender']}</span> "
200
+ if pet['size']:
201
+ tags_html += f"<span class='tag'>{pet['size']}</span> "
202
+
203
+ st.markdown(f"<div>{tags_html}</div>", unsafe_allow_html=True)
204
+
205
+ st.markdown("<div class='pet-details'>", unsafe_allow_html=True)
206
+ if pet['breeds']['primary']:
207
+ breed_text = pet['breeds']['primary']
208
+ if pet['breeds']['secondary']:
209
+ breed_text += f" & {pet['breeds']['secondary']}"
210
+ if pet['breeds']['mixed']:
211
+ breed_text += " (Mixed)"
212
+ st.markdown(f"<strong>Breed:</strong> {breed_text}", unsafe_allow_html=True)
213
+
214
+ if pet['colors']['primary'] or pet['colors']['secondary'] or pet['colors']['tertiary']:
215
+ colors = [c for c in [pet['colors']['primary'], pet['colors']['secondary'], pet['colors']['tertiary']] if c]
216
+ st.markdown(f"<strong>Colors:</strong> {', '.join(colors)}", unsafe_allow_html=True)
217
+
218
+ if 'location' in pet and pet['contact']['address']['city'] and pet['contact']['address']['state']:
219
+ st.markdown(f"<strong>Location:</strong> {pet['contact']['address']['city']}, {pet['contact']['address']['state']}", unsafe_allow_html=True)
220
+
221
+ st.markdown("</div>", unsafe_allow_html=True)
222
+
223
+ if pet['description']:
224
+ st.markdown(f"<div class='pet-description'>{pet['description'][:500]}{'...' if len(pet['description']) > 500 else ''}</div>", unsafe_allow_html=True)
225
+
226
+ col1, col2 = st.columns(2)
227
+ with col1:
228
+ if st.button("View Details", key=f"details_{context}_{pet['id']}"):
229
+ st.session_state.selected_pet = pet['id']
230
+ st.rerun()
231
+ with col2:
232
+ if not is_favorite:
233
+ if st.button("Add to Favorites", key=f"fav_{context}_{pet['id']}"):
234
+ if pet['id'] not in [p['id'] for p in st.session_state.favorites]:
235
+ st.session_state.favorites.append(pet)
236
+ st.success(f"Added {pet['name']} to favorites!")
237
+ st.rerun()
238
+ else:
239
+ if st.button("Remove from Favorites", key=f"unfav_{context}_{pet['id']}"):
240
+ st.session_state.favorites = [p for p in st.session_state.favorites if p['id'] != pet['id']]
241
+ st.success(f"Removed {pet['name']} from favorites!")
242
+ st.rerun()
243
+
244
+ # Function to generate pet compatibility message
245
+ def get_compatibility_message(pet):
246
+ messages = []
247
+
248
+ # Check for kids
249
+ if 'children' in pet['environment'] and pet['environment']['children'] is not None:
250
+ if pet['environment']['children']:
251
+ messages.append("✅ Good with children")
252
+ else:
253
+ messages.append("❌ Not recommended for homes with children")
254
+
255
+ # Check for dogs
256
+ if 'dogs' in pet['environment'] and pet['environment']['dogs'] is not None:
257
+ if pet['environment']['dogs']:
258
+ messages.append("✅ Good with dogs")
259
+ else:
260
+ messages.append("❌ Not recommended for homes with dogs")
261
+
262
+ # Check for cats
263
+ if 'cats' in pet['environment'] and pet['environment']['cats'] is not None:
264
+ if pet['environment']['cats']:
265
+ messages.append("✅ Good with cats")
266
+ else:
267
+ messages.append("❌ Not recommended for homes with cats")
268
+
269
+ # Handling care needs
270
+ if pet['attributes']:
271
+ if 'special_needs' in pet['attributes'] and pet['attributes']['special_needs']:
272
+ messages.append("⚠️ Has special needs")
273
+
274
+ if 'house_trained' in pet['attributes'] and pet['attributes']['house_trained']:
275
+ messages.append("✅ House-trained")
276
+ elif 'house_trained' in pet['attributes']:
277
+ messages.append("❌ Not house-trained")
278
+
279
+ if 'shots_current' in pet['attributes'] and pet['attributes']['shots_current']:
280
+ messages.append("✅ Vaccinations up to date")
281
+
282
+ if 'spayed_neutered' in pet['attributes'] and pet['attributes']['spayed_neutered']:
283
+ messages.append("✅ Spayed/neutered")
284
+
285
+ return messages
286
+
287
+ # Function to display pet details page
288
+ # Changes to make keys unique across different tabs
289
+
290
+ # Function to display pet details page with unique tab identifier
291
+ def display_pet_details(pet_id, context="search", tab_id="tab1"):
292
+ pet = get_pet_details(pet_id)
293
+ if not pet:
294
+ st.error("Unable to retrieve pet details. Please try again.")
295
+ return
296
+
297
+ # Back button with unique key that includes tab identifier
298
+ if st.button("← Back to Search Results", key=f"back_{tab_id}_{context}_{pet_id}"):
299
+ st.session_state.selected_pet = None
300
+ st.rerun() # Force immediate rerun
301
+
302
+ # Pet name and status
303
+ st.markdown(f"<h1 class='main-header'>{pet['name']}</h1>", unsafe_allow_html=True)
304
+
305
+ status_color = "#c8e6c9" if pet['status'] == 'adoptable' else "#ffcdd2"
306
+ st.markdown(f"<div style='text-align: center;'><span class='tag' style='background-color: {status_color}; font-size: 1rem;'>{pet['status'].title()}</span></div>", unsafe_allow_html=True)
307
+
308
+ # Pet photos
309
+ if pet['photos'] and len(pet['photos']) > 0:
310
+ photo_cols = st.columns(min(3, len(pet['photos'])))
311
+ for i, col in enumerate(photo_cols):
312
+ if i < len(pet['photos']):
313
+ col.image(pet['photos'][i]['large'], use_container_width=True)
314
+ else:
315
+ st.image("https://via.placeholder.com/500x300?text=No+Image", use_container_width=True)
316
+
317
+ # Pet details
318
+ col1, col2 = st.columns(2)
319
+
320
+ with col1:
321
+ st.markdown("### Details")
322
+ # Fix the breed line
323
+ breed_text = pet['breeds']['primary']
324
+ if pet['breeds']['secondary']:
325
+ breed_text += f" & {pet['breeds']['secondary']}"
326
+ if pet['breeds']['mixed']:
327
+ breed_text += " (Mixed)"
328
+
329
+ details = [
330
+ f"**Type:** {pet['type']}",
331
+ f"**Breed:** {breed_text}",
332
+ f"**Age:** {pet['age']}",
333
+ f"**Gender:** {pet['gender']}",
334
+ f"**Size:** {pet['size']}"
335
+ ]
336
+
337
+ # Fix the colors line as well, to be safe
338
+ colors = [c for c in [pet['colors']['primary'], pet['colors']['secondary'], pet['colors']['tertiary']] if c]
339
+ if colors:
340
+ details.append(f"**Colors:** {', '.join(colors)}")
341
+
342
+ for detail in details:
343
+ st.markdown(detail)
344
+
345
+ with col2:
346
+ st.markdown("### Compatibility")
347
+ compatibility = get_compatibility_message(pet)
348
+ for msg in compatibility:
349
+ st.markdown(msg)
350
+
351
+ # Description
352
+ if pet['description']:
353
+ if pet['description']:
354
+ st.markdown("### About")
355
+ #st.markdown(pet['description'])
356
+ st.markdown(f"<div class='pet-description'>{pet['description'][:500]}{'...' if len(pet['description']) > 500 else ''}</div>", unsafe_allow_html=True)
357
+
358
+ # Contact information
359
+ st.markdown("### Adoption Information")
360
+
361
+ # Organization info
362
+ if pet['organization_id']:
363
+ st.markdown(f"**Organization:** {pet['organization_id']}")
364
+
365
+ # Contact details
366
+ contact_info = []
367
+ if pet['contact']['email']:
368
+ contact_info.append(f"**Email:** {pet['contact']['email']}")
369
+ if pet['contact']['phone']:
370
+ contact_info.append(f"**Phone:** {pet['contact']['phone']}")
371
+ if pet['contact']['address']['city'] and pet['contact']['address']['state']:
372
+ contact_info.append(f"**Location:** {pet['contact']['address']['city']}, {pet['contact']['address']['state']} {pet['contact']['address']['postcode'] or ''}")
373
+
374
+ for info in contact_info:
375
+ st.markdown(info)
376
+
377
+ # URL to pet on Petfinder
378
+ if pet['url']:
379
+ st.markdown(f"[View on Petfinder]({pet['url']})")
380
+
381
+ # Add to favorites with unique key
382
+ is_favorite = pet['id'] in [p['id'] for p in st.session_state.favorites]
383
+ if not is_favorite:
384
+ if st.button("Add to Favorites", key=f"add_fav_{tab_id}_{context}_{pet_id}"):
385
+ st.session_state.favorites.append(pet)
386
+ st.success(f"Added {pet['name']} to favorites!")
387
+ st.rerun()
388
+ else:
389
+ if st.button("Remove from Favorites", key=f"rem_fav_{tab_id}_{context}_{pet_id}"):
390
+ st.session_state.favorites = [p for p in st.session_state.favorites if p['id'] != pet['id']]
391
+ st.success(f"Removed {pet['name']} from favorites!")
392
+ st.rerun()
393
+
394
+ # Function to format pet card with unique tab identifier
395
+ def display_pet_card(pet, is_favorite=False, context="search", tab_id="tab1"):
396
+ col1, col2 = st.columns([1, 2])
397
+
398
+ with col1:
399
+ if pet['photos'] and len(pet['photos']) > 0:
400
+ st.image(pet['photos'][0]['medium'], use_container_width=True)
401
+ else:
402
+ st.image("https://via.placeholder.com/300x300?text=No+Image", use_container_width=True)
403
+
404
+ with col2:
405
+ st.markdown(f"<div class='pet-name'>{pet['name']}</div>", unsafe_allow_html=True)
406
+
407
+ # Tags
408
+ tags_html = ""
409
+ if pet['status'] == 'adoptable':
410
+ tags_html += "<span class='tag' style='background-color: #808080;'>Adoptable</span> "
411
+ else:
412
+ tags_html += f"<span class='tag' style='background-color: #808080;'>{pet['status'].title()}</span> "
413
+
414
+ if pet['age']:
415
+ tags_html += f"<span class='tag'>{pet['age']}</span> "
416
+ if pet['gender']:
417
+ tags_html += f"<span class='tag'>{pet['gender']}</span> "
418
+ if pet['size']:
419
+ tags_html += f"<span class='tag'>{pet['size']}</span> "
420
+
421
+ st.markdown(f"<div>{tags_html}</div>", unsafe_allow_html=True)
422
+
423
+ st.markdown("<div class='pet-details'>", unsafe_allow_html=True)
424
+ if pet['breeds']['primary']:
425
+ breed_text = pet['breeds']['primary']
426
+ if pet['breeds']['secondary']:
427
+ breed_text += f" & {pet['breeds']['secondary']}"
428
+ if pet['breeds']['mixed']:
429
+ breed_text += " (Mixed)"
430
+ st.markdown(f"<strong>Breed:</strong> {breed_text}", unsafe_allow_html=True)
431
+
432
+ if pet['colors']['primary'] or pet['colors']['secondary'] or pet['colors']['tertiary']:
433
+ colors = [c for c in [pet['colors']['primary'], pet['colors']['secondary'], pet['colors']['tertiary']] if c]
434
+ st.markdown(f"<strong>Colors:</strong> {', '.join(colors)}", unsafe_allow_html=True)
435
+
436
+ if 'location' in pet and pet['contact']['address']['city'] and pet['contact']['address']['state']:
437
+ st.markdown(f"<strong>Location:</strong> {pet['contact']['address']['city']}, {pet['contact']['address']['state']}", unsafe_allow_html=True)
438
+
439
+ st.markdown("</div>", unsafe_allow_html=True)
440
+
441
+ if pet['description']:
442
+ st.markdown(f"<div class='pet-description'>{pet['description'][:300]}{'...' if len(pet['description']) > 300 else ''}</div>", unsafe_allow_html=True)
443
+
444
+ col1, col2 = st.columns(2)
445
+ with col1:
446
+ if st.button("View Details", key=f"details_{tab_id}_{context}_{pet['id']}"):
447
+ st.session_state.selected_pet = pet['id']
448
+ st.rerun()
449
+ with col2:
450
+ if not is_favorite:
451
+ if st.button("Add to Favorites", key=f"fav_{tab_id}_{context}_{pet['id']}"):
452
+ if pet['id'] not in [p['id'] for p in st.session_state.favorites]:
453
+ st.session_state.favorites.append(pet)
454
+ st.success(f"Added {pet['name']} to favorites!")
455
+ st.rerun()
456
+ else:
457
+ if st.button("Remove from Favorites", key=f"unfav_{tab_id}_{context}_{pet['id']}"):
458
+ st.session_state.favorites = [p for p in st.session_state.favorites if p['id'] != pet['id']]
459
+ st.success(f"Removed {pet['name']} from favorites!")
460
+ st.rerun()
461
+
462
+ # Main app with updated function calls
463
+ def main():
464
+ # Title
465
+ st.markdown("<h1 class='main-header'>🐾 PetMatch</h1>", unsafe_allow_html=True)
466
+ st.markdown("<p class='sub-header'>Find your perfect pet companion</p>", unsafe_allow_html=True)
467
+
468
+ # Create tabs
469
+ tab1, tab2, tab3 = st.tabs(["Search", "Favorites", "About"])
470
+
471
+ with tab1:
472
+ # If a pet is selected, show details
473
+ if st.session_state.selected_pet:
474
+ display_pet_details(st.session_state.selected_pet, context="search", tab_id="tab1")
475
+ else:
476
+ # Search form
477
+ with st.expander("Search Options", expanded=True):
478
+ with st.form("pet_search_form"):
479
+ col1, col2 = st.columns(2)
480
+
481
+ with col1:
482
+ animal_type = st.selectbox(
483
+ "Animal Type",
484
+ ["Dog", "Cat", "Rabbit", "Small & Furry", "Horse", "Bird", "Scales, Fins & Other", "Barnyard"]
485
+ )
486
+
487
+ location = st.text_input("Location (ZIP code or City, State)", "")
488
+
489
+ distance = st.slider("Distance (miles)", min_value=10, max_value=500, value=50, step=10)
490
+
491
+ with col2:
492
+ age_options = ["", "Baby", "Young", "Adult", "Senior"]
493
+ age = st.selectbox("Age", age_options)
494
+
495
+ size_options = ["", "Small", "Medium", "Large", "XLarge"]
496
+ size = st.selectbox("Size", size_options)
497
+
498
+ gender_options = ["", "Male", "Female"]
499
+ gender = st.selectbox("Gender", gender_options)
500
+ good_with_children = st.checkbox("Good with children")
501
+ good_with_dogs = st.checkbox("Good with dogs")
502
+ good_with_cats = st.checkbox("Good with cats")
503
+ house_trained = st.checkbox("House-trained")
504
+ special_needs = st.checkbox("Special needs")
505
+
506
+ submitted = st.form_submit_button("Search")
507
+
508
+ if submitted:
509
+ # Build search parameters
510
+ params = {
511
+ "type": animal_type.split(" ")[0], # Take first word for types like "Small & Furry"
512
+ "location": location,
513
+ "distance": distance,
514
+ "status": "adoptable",
515
+ "sort": "distance",
516
+ "limit": 100
517
+ }
518
+
519
+ if age and age != "":
520
+ params["age"] = age
521
+ if size and size != "":
522
+ params["size"] = size
523
+ if gender and gender != "":
524
+ params["gender"] = gender
525
+
526
+ # Add advanced filters
527
+ if good_with_children:
528
+ params["good_with_children"] = 1
529
+ if good_with_dogs:
530
+ params["good_with_dogs"] = 1
531
+ if good_with_cats:
532
+ params["good_with_cats"] = 1
533
+ if house_trained:
534
+ params["house_trained"] = 1
535
+ if special_needs:
536
+ params["special_needs"] = 1
537
+
538
+ # Perform search
539
+ results = search_pets(params)
540
+ if results and 'animals' in results:
541
+ st.session_state.search_results = results
542
+ st.session_state.page = 1
543
+ st.success(f"Found {len(results['animals'])} pets!")
544
+ else:
545
+ st.error("No pets found with those criteria. Try expanding your search.")
546
+
547
+ # Display search results
548
+ if st.session_state.search_results and 'animals' in st.session_state.search_results:
549
+ st.markdown("### Search Results")
550
+
551
+ # Pagination
552
+ results = st.session_state.search_results['animals']
553
+ total_pages = (len(results) + 9) // 10 # 10 items per page
554
+
555
+ # Display page selector
556
+ if total_pages > 1:
557
+ col1, col2, col3 = st.columns([1, 3, 1])
558
+ with col2:
559
+ page = st.slider("Page", 1, total_pages, st.session_state.page)
560
+ if page != st.session_state.page:
561
+ st.session_state.page = page
562
+
563
+ # Display pets for current page
564
+ start_idx = (st.session_state.page - 1) * 10
565
+ end_idx = min(start_idx + 10, len(results))
566
+
567
+ for pet in results[start_idx:end_idx]:
568
+ st.markdown("---")
569
+ display_pet_card(pet, tab_id="tab1")
570
+
571
+ with tab2:
572
+ st.markdown("### Your Favorite Pets")
573
+
574
+ if not st.session_state.favorites:
575
+ st.info("You haven't added any pets to your favorites yet. Start searching to find your perfect match!")
576
+ else:
577
+ # Check if a pet is selected from favorites
578
+ if st.session_state.selected_pet:
579
+ display_pet_details(st.session_state.selected_pet, context="favorites", tab_id="tab2")
580
+ else:
581
+ for pet in st.session_state.favorites:
582
+ st.markdown("---")
583
+ display_pet_card(pet, is_favorite=True, context="favorites", tab_id="tab2")
584
+
585
+ with tab3:
586
+ st.markdown("### About PetMatch")
587
+ st.markdown("""
588
+ PetMatch helps you find your perfect pet companion from thousands of adoptable animals across the country.
589
+
590
+ **How to use PetMatch:**
591
+ 1. Search for pets based on your preferences and location
592
+ 2. Browse through the results and click "View Details" to learn more about each pet
593
+ 3. Add pets to your favorites to keep track of the ones you're interested in
594
+ 4. Contact the shelter or rescue organization directly using the provided information
595
+
596
+ **Data Source:**
597
+ PetMatch uses the Petfinder API to provide up-to-date information on adoptable pets. Petfinder is North America's largest adoption website with hundreds of thousands of adoptable pets listed by more than 11,500 animal shelters and rescue organizations.
598
+
599
+ **Privacy:**
600
+ PetMatch does not store any personal information or search history. Your favorites are stored locally in your browser and are not shared with any third parties.
601
+ """)
602
+
603
+
604
+ if __name__ == "__main__":
605
+ main()
606
+ import os
607
+ import functools
608
+
609
+ # Set page configuration
610
+ st.set_page_config(
611
+ page_title="PetMatch - Find Your Perfect Pet",
612
+ page_icon="🐾",
613
+ layout="centered"
614
+ )
615
+
616
+ # Custom CSS
617
+ st.markdown("""
618
+ <style>
619
+ .main-header {
620
+ font-size: 2.5rem;
621
+ color: #ff6b6c;
622
+ text-align: center;
623
+ margin-bottom: 1rem;
624
+ }
625
+ .sub-header {
626
+ font-size: 1.5rem;
627
+ color: #4a4a4a;
628
+ text-align: center;
629
+ margin-bottom: 2rem;
630
+ }
631
+ .pet-card {
632
+ border-radius: 10px;
633
+ border: 1px solid #e0e0e0;
634
+ padding: 1rem;
635
+ margin-bottom: 1rem;
636
+ }
637
+ .pet-name {
638
+ font-size: 1.3rem;
639
+ font-weight: bold;
640
+ color: #ff6b6b;
641
+ }
642
+ .pet-details {
643
+ margin-top: 0.5rem;
644
+ }
645
+ .pet-description {
646
+ margin-top: 1rem;
647
+ font-style: italic;
648
+ }
649
+ .tag {
650
+ background-color: #808080;
651
+ border-radius: 20px;
652
+ padding: 0.2rem 0.6rem;
653
+ margin-right: 0.3rem;
654
+ font-size: 0.8rem;
655
+ }
656
+ </style>
657
+ """, unsafe_allow_html=True)
658
+
659
  # Initialize session state variables
660
  if 'access_token' not in st.session_state:
661
  st.session_state.access_token = None