umangchaudhry commited on
Commit
5f9023d
·
verified ·
1 Parent(s): d828bc9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +327 -905
app.py CHANGED
@@ -16,7 +16,7 @@ from vendors import VendorManager
16
  # Page configuration
17
  st.set_page_config(
18
  page_title="Wedding Planner",
19
- page_icon="💒",
20
  layout="wide",
21
  initial_sidebar_state="expanded"
22
  )
@@ -190,19 +190,6 @@ st.markdown("""
190
  margin: 0.5rem 0;
191
  border-radius: 5px;
192
  }
193
-
194
- .signup-form {
195
- background: #f8f9fa;
196
- padding: 2rem;
197
- border-radius: 10px;
198
- border: 2px solid #4a7c59;
199
- margin: 1rem 0;
200
- }
201
-
202
- .auth-container {
203
- max-width: 600px;
204
- margin: 0 auto;
205
- }
206
  </style>
207
  """, unsafe_allow_html=True)
208
 
@@ -225,40 +212,108 @@ def load_auth_config():
225
  config = yaml.load(file, Loader=SafeLoader)
226
  return config
227
 
228
- st.error(" No auth config found")
229
  return None
230
  except Exception as e:
231
  st.error(f"Error loading authentication config: {e}")
232
  return None
233
 
234
- def save_auth_config(config):
235
- """Save authentication configuration to Google Drive and local file"""
236
  try:
237
  config_manager = st.session_state.config_manager
238
 
239
- # Convert config to YAML string
240
- import yaml
241
- yaml_content = yaml.dump(config, default_flow_style=False, sort_keys=False)
242
 
243
  # Save to Google Drive if enabled
244
  if config_manager.google_drive_enabled:
245
- if config_manager.drive_manager.upload_file('config.yaml', yaml_content):
246
- print("✅ Auth config saved to Google Drive")
247
- else:
248
- print("❌ Failed to save auth config to Google Drive")
249
 
250
  # Also save locally as backup
251
- with open('config.yaml', 'w') as file:
252
- yaml.dump(config, file, default_flow_style=False, sort_keys=False)
 
 
 
253
 
254
- # Update session state
255
- st.session_state.auth_config = config
256
  return True
257
-
258
  except Exception as e:
259
  st.error(f"Error saving authentication config: {e}")
260
  return False
261
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  def get_user_folder_from_username(username):
263
  """Get the user folder based on username using wedding mappings"""
264
  try:
@@ -313,63 +368,37 @@ def get_wedding_info_for_user(username):
313
  st.error(f"Error getting wedding info for {username}: {e}")
314
  return None
315
 
316
- def create_new_wedding_folder(folder_name):
317
- """Create a new wedding folder in Google Drive with initial data files"""
318
- try:
319
- config_manager = st.session_state.config_manager
320
-
321
- if not config_manager.google_drive_enabled:
322
- return False
323
-
324
- # Create empty data files for the new wedding
325
- initial_files = {
326
- 'wedding_config.json': {
327
- 'wedding_info': {
328
- 'partner1_name': '',
329
- 'partner2_name': '',
330
- 'wedding_start_date': '',
331
- 'wedding_end_date': '',
332
- 'venue_city': ''
333
- },
334
- 'custom_settings': {
335
- 'custom_tags': [
336
- "Wedding Party", "Urgent", "Rehearsal", "Timeline",
337
- "Maid of Honor", "Best Man", "Bridesmaid", "Groomsman",
338
- "Flower Girl", "Ring Bearer", "Usher", "Reader",
339
- "Venue", "Catering", "Photography", "Videography", "Music/DJ",
340
- "Flowers", "Decor", "Attire", "Hair & Makeup", "Transportation",
341
- "Invitations", "Cake", "Officiant", "Other",
342
- "Decorations", "Centerpieces", "Favors", "Signage", "Linens",
343
- "Tableware", "Lighting", "Accessories", "Stationery", "Gifts",
344
- "Vendor", "Item", "Deposit Required", "Final Payment", "Installment",
345
- "Food & Beverage", "Music", "Entertainment", "Lodging"
346
- ],
347
- 'task_assignees': []
348
- },
349
- 'wedding_events': []
350
- },
351
- 'guest_list_data.json': [],
352
- 'rsvp_data.json': {},
353
- 'tasks.json': [],
354
- 'vendors.json': [],
355
- 'wedding_party.json': []
356
- }
357
-
358
- # Upload each file to the new folder
359
- for filename, content in initial_files.items():
360
- full_path = f"{folder_name}/{filename}"
361
- if not config_manager.drive_manager.upload_file(full_path, content):
362
- st.error(f"Failed to create {filename} in {folder_name}")
363
- return False
364
-
365
- return True
366
-
367
- except Exception as e:
368
- st.error(f"Error creating wedding folder: {e}")
369
- return False
370
 
371
- def show_signup_page(authenticator):
372
- """Show the signup page with user registration"""
373
 
374
  # Hero Section
375
  st.markdown("""
@@ -382,103 +411,19 @@ def show_signup_page(authenticator):
382
  margin-bottom: 3rem;
383
  box-shadow: 0 10px 30px rgba(0,0,0,0.2);
384
  ">
385
- <h1 style="font-size: 3.5rem; margin-bottom: 1rem; font-weight: 700;">💒 Create Your Wedding Account</h1>
386
  <h2 style="font-size: 1.8rem; margin-bottom: 2rem; font-weight: 300; opacity: 0.9;">
387
- Join thousands of couples planning their perfect day
388
  </h2>
389
  <p style="font-size: 1.2rem; max-width: 600px; margin: 0 auto; line-height: 1.6;">
390
- Create your personalized wedding planning workspace with guest management,
391
- task tracking, vendor coordination, and so much more.
392
  </p>
393
  </div>
394
  """, unsafe_allow_html=True)
395
 
396
- # Registration Section
397
- st.markdown('<div class="auth-container">', unsafe_allow_html=True)
398
-
399
- # Create tabs for signup and login
400
- tab1, tab2 = st.tabs(["Create Account", "Login"])
401
-
402
- with tab1:
403
- st.markdown("## ✨ Create Your Wedding Planning Account")
404
-
405
- # User Registration Form
406
- try:
407
- email, username, name = authenticator.register_user(
408
- location='main',
409
- pre_authorized=None, # Allow any user to register
410
- captcha=False, # Disable captcha for simplicity
411
- clear_on_submit=False,
412
- key='register_user'
413
- )
414
-
415
- if email:
416
- st.success(f"✅ Account created successfully for {name}!")
417
- st.success("🎉 Now let's set up your wedding details...")
418
-
419
- # Update auth config and create wedding folder
420
- auth_config = st.session_state.auth_config
421
-
422
- if auth_config:
423
- # Create wedding folder name (using username)
424
- wedding_folder = username.lower().replace(' ', '_')
425
-
426
- # Add new wedding mapping
427
- if 'wedding_mappings' not in auth_config:
428
- auth_config['wedding_mappings'] = {}
429
-
430
- auth_config['wedding_mappings'][wedding_folder] = {
431
- 'wedding_name': f"{name}'s Wedding",
432
- 'folder': wedding_folder,
433
- 'users': [username]
434
- }
435
-
436
- # Save updated auth config
437
- if save_auth_config(auth_config):
438
- # Create wedding folder with initial files
439
- if create_new_wedding_folder(wedding_folder):
440
- st.success("📁 Wedding workspace created in Google Drive!")
441
-
442
- # Show wedding setup form
443
- st.session_state['show_wedding_setup'] = True
444
- st.session_state['new_user_folder'] = wedding_folder
445
- st.rerun()
446
- else:
447
- st.warning("Account created but failed to create wedding workspace. You can set up your wedding details after login.")
448
- else:
449
- st.warning("Account created but failed to save wedding mapping. Please contact support.")
450
-
451
- except Exception as e:
452
- if "Username already taken" in str(e):
453
- st.error("❌ Username already exists. Please choose a different username.")
454
- elif "Email already taken" in str(e):
455
- st.error("❌ Email already registered. Please use a different email or login instead.")
456
- else:
457
- st.error(f"Registration error: {e}")
458
-
459
- with tab2:
460
- st.markdown("## 🔐 Login to Your Account")
461
-
462
- # Login form
463
- try:
464
- authenticator.login(location='main')
465
- except Exception as e:
466
- st.error(f"Login error: {e}")
467
-
468
- # Check authentication status and show appropriate message
469
- if st.session_state.get('authentication_status') is False:
470
- st.error("❌ Invalid username or password")
471
- elif st.session_state.get('authentication_status') is None:
472
- st.info("📝 Please enter your username and password")
473
- elif st.session_state.get('authentication_status'):
474
- st.success(f"✅ Welcome back, {st.session_state.get('name', 'User')}!")
475
- st.rerun() # Refresh to show main app
476
-
477
- st.markdown('</div>', unsafe_allow_html=True)
478
-
479
- # Features showcase (same as before)
480
- st.markdown("---")
481
- st.markdown("## ✨ What You Get With Your Wedding Account")
482
 
483
  col1, col2, col3 = st.columns(3)
484
 
@@ -541,16 +486,179 @@ def show_signup_page(authenticator):
541
  </ul>
542
  </div>
543
  """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
 
545
  def show_wedding_setup_form():
546
- """Show the wedding setup form for creating a new wedding"""
547
- st.markdown("### 📝 Set Up Your Wedding Details")
548
-
549
- # Get the new user folder from session state
550
- new_user_folder = st.session_state.get('new_user_folder')
551
- if not new_user_folder:
552
- st.error("No wedding folder found. Please try registering again.")
553
- return
554
 
555
  # Initialize session state for events and form data if not exists
556
  if 'setup_events' not in st.session_state:
@@ -571,203 +679,6 @@ def show_wedding_setup_form():
571
  st.markdown("#### Basic Wedding Information")
572
 
573
  col1, col2 = st.columns(2)
574
-
575
- with col1:
576
- if st.button("📥 Sync from Google Drive", help="Download latest data from Google Drive"):
577
- with st.spinner("Syncing from Google Drive..."):
578
- if config_manager.manual_sync_from_drive():
579
- st.success("Successfully synced from Google Drive!")
580
- st.rerun()
581
- else:
582
- st.error("Failed to sync from Google Drive")
583
-
584
- with col2:
585
- # Show different button text based on whether there are modified files
586
- modified_files = config_manager.get_modified_files()
587
- if modified_files:
588
- button_text = f"📤 Sync {len(modified_files)} Modified Files to Drive"
589
- button_help = f"Upload {len(modified_files)} modified files to Google Drive"
590
- else:
591
- button_text = "📤 Sync to Google Drive"
592
- button_help = "Upload current data to Google Drive (no changes to sync)"
593
-
594
- if st.button(button_text, help=button_help, disabled=not modified_files):
595
- with st.spinner("Syncing to Google Drive..."):
596
- if config_manager.manual_sync_to_drive():
597
- st.success("Successfully synced to Google Drive!")
598
- st.rerun()
599
- else:
600
- st.error("Failed to sync to Google Drive")
601
-
602
- # Configuration info
603
- st.markdown("#### Configuration")
604
- st.markdown("To enable Google Drive integration, set the following environment variables:")
605
- st.code("""
606
- GOOGLE_DRIVE_FOLDER_ID=your_folder_id
607
- GOOGLE_PROJECT_ID=your_project_id
608
- GOOGLE_PRIVATE_KEY_ID=your_private_key_id
609
- GOOGLE_PRIVATE_KEY=your_private_key
610
- GOOGLE_CLIENT_EMAIL=your_client_email
611
- GOOGLE_CLIENT_ID=your_client_id
612
- """)
613
-
614
- st.markdown("**Note:** Google Drive integration is automatically enabled when running on Hugging Face Spaces with proper credentials configured.")
615
-
616
- def show_event_management_section(config):
617
- st.markdown("#### Manage Wedding Events")
618
-
619
- # Initialize session state for event editing if not exists
620
- if 'edit_events' not in st.session_state:
621
- st.session_state.edit_events = config.get('wedding_events', []).copy()
622
-
623
- wedding_events = st.session_state.edit_events
624
- wedding_info = config.get('wedding_info', {})
625
-
626
- # Get wedding dates for date calculations
627
- wedding_start_str = wedding_info.get('wedding_start_date', '')
628
- wedding_end_str = wedding_info.get('wedding_end_date', '')
629
-
630
- if not wedding_start_str or not wedding_end_str:
631
- st.warning("Please set your wedding date range in the 'Edit Configuration' tab first.")
632
- return
633
-
634
- try:
635
- wedding_start = datetime.fromisoformat(wedding_start_str).date()
636
- wedding_end = datetime.fromisoformat(wedding_end_str).date()
637
- except:
638
- st.error("Invalid wedding date format. Please check your configuration.")
639
- return
640
-
641
- # Add event button
642
- if st.button("➕ Add New Event"):
643
- st.session_state.edit_events.append({
644
- "name": "New Event",
645
- "description": "",
646
- "date_offset": 0,
647
- "requires_meal_choice": False,
648
- "meal_options": [],
649
- "location": "",
650
- "address": ""
651
- })
652
- st.rerun()
653
-
654
- # Display events for editing
655
- if st.session_state.edit_events:
656
- st.markdown("##### Edit Events")
657
-
658
- for i, event in enumerate(st.session_state.edit_events):
659
- with st.expander(f"Event {i+1}: {event['name']}", expanded=True):
660
- # Add delete button at the top right of each event
661
- col_header1, col_header2 = st.columns([4, 1])
662
- with col_header2:
663
- if st.button("🗑️ Delete", key=f"delete_event_{i}", help="Delete this event"):
664
- st.session_state.edit_events.pop(i)
665
- st.rerun()
666
-
667
- col1, col2 = st.columns(2)
668
-
669
- with col1:
670
- event_name = st.text_input("Event Name", value=event['name'], key=f"settings_event_name_{i}")
671
- event_description = st.text_input("Time", value=event['description'], placeholder="e.g., 2:00 PM, 6:30 PM", key=f"settings_event_desc_{i}")
672
- event_location = st.text_input("Location Name", value=event.get('location', ''), placeholder="e.g., Central Park, Grand Ballroom", key=f"settings_event_location_{i}")
673
-
674
- with col2:
675
- # Calculate current event date from date_offset
676
- current_event_date = wedding_start + timedelta(days=event['date_offset'])
677
-
678
- # Use date input without constraints - allow any date
679
- event_date = st.date_input(
680
- "Event Date",
681
- value=current_event_date,
682
- key=f"settings_event_date_{i}",
683
- help="Select any date for this event"
684
- )
685
-
686
- # Show warning if date is outside wedding range
687
- if event_date < wedding_start or event_date > wedding_end:
688
- st.warning(f"⚠️ Selected date is outside your wedding date range ({wedding_start.strftime('%B %d, %Y')} - {wedding_end.strftime('%B %d, %Y')})")
689
-
690
- requires_meal_choice = st.checkbox("Requires Meal Choice", value=event['requires_meal_choice'], key=f"settings_event_meal_{i}")
691
-
692
- # Meal options section (only show if meal choice is required)
693
- if requires_meal_choice:
694
- st.markdown("**Meal Options**")
695
- st.markdown("Enter meal options (one per line):")
696
- current_meal_options = event.get('meal_options', [])
697
- meal_options_text = '\n'.join(current_meal_options) if current_meal_options else ''
698
- meal_options = st.text_area("Meal Options", value=meal_options_text, placeholder="e.g.,\nDuck\nSurf & Turf\nRisotto (vegetarian)\nStuffed Squash (vegetarian)", key=f"settings_event_meal_options_{i}", height=100)
699
- else:
700
- meal_options = ""
701
-
702
- event_address = st.text_area("Address", value=event.get('address', ''), placeholder="Enter full address (street, city, state, zip code)", key=f"settings_event_address_{i}", height=80)
703
-
704
- # Calculate date_offset from the selected date
705
- date_offset = (event_date - wedding_start).days
706
-
707
- # Parse meal options
708
- meal_options_list = []
709
- if requires_meal_choice and meal_options:
710
- meal_options_list = [option.strip() for option in meal_options.split('\n') if option.strip()]
711
-
712
- # Update session state
713
- st.session_state.edit_events[i] = {
714
- "name": event_name,
715
- "description": event_description,
716
- "date_offset": date_offset,
717
- "requires_meal_choice": requires_meal_choice,
718
- "meal_options": meal_options_list,
719
- "location": event_location,
720
- "address": event_address
721
- }
722
-
723
- # Save events button
724
- st.markdown("---")
725
- col1, col2, col3 = st.columns([1, 1, 1])
726
- with col2:
727
- if st.button("💾 Save Event Changes", type="primary"):
728
- # Update the configuration with edited events
729
- updated_config = config.copy()
730
- updated_config['wedding_events'] = st.session_state.edit_events
731
-
732
- # Save the updated configuration
733
- st.session_state.config_manager.save_config(updated_config)
734
- st.success("Event changes saved successfully!")
735
- st.rerun()
736
- else:
737
- st.info("No events added yet. Click 'Add New Event' to get started!")
738
-
739
- # Event summary
740
- if st.session_state.edit_events:
741
- st.markdown("##### Event Summary")
742
-
743
- col1, col2, col3 = st.columns(3)
744
-
745
- with col1:
746
- total_events = len(st.session_state.edit_events)
747
- st.metric("Total Events", total_events)
748
-
749
- with col2:
750
- meal_events = len([e for e in st.session_state.edit_events if e.get('requires_meal_choice', False)])
751
- st.metric("Events with Meals", meal_events)
752
-
753
- with col3:
754
- days_span = (wedding_end - wedding_start).days + 1
755
- st.metric("Celebration Days", days_span)
756
-
757
- def get_friendly_file_name(filename):
758
- """Convert technical file names to user-friendly names"""
759
- friendly_names = {
760
- 'wedding_config.json': 'Wedding Configuration',
761
- 'guest_list_data.json': 'Guest List',
762
- 'rsvp_data.json': 'RSVP Responses',
763
- 'tasks.json': 'Tasks',
764
- 'vendors.json': 'Vendors',
765
- 'wedding_party.json': 'Wedding Party'
766
- }
767
- return friendly_names.get(filename, filename)
768
-
769
- if __name__ == "__main__":
770
- main() = st.columns(2)
771
  with col1:
772
  partner1_name = st.text_input("Partner 1 Name", value=st.session_state.setup_form_data['partner1_name'], placeholder="Enter first partner's name")
773
  partner2_name = st.text_input("Partner 2 Name", value=st.session_state.setup_form_data['partner2_name'], placeholder="Enter second partner's name")
@@ -903,7 +814,7 @@ if __name__ == "__main__":
903
 
904
  # Save configuration button (after event management)
905
  st.markdown("---")
906
- if st.button("Save Configuration & Start Planning", type="primary"):
907
  # Get form values from session state
908
  form_data = st.session_state.setup_form_data
909
 
@@ -928,65 +839,20 @@ if __name__ == "__main__":
928
  'wedding_events': st.session_state.setup_events
929
  }
930
 
931
- # Save configuration to the new wedding folder
932
- config_manager = st.session_state.config_manager
933
- if config_manager.google_drive_enabled:
934
- full_config_path = f"{new_user_folder}/wedding_config.json"
935
- if config_manager.drive_manager.upload_file(full_config_path, config):
936
- # Update user folder in config manager
937
- config_manager.set_user_folder(new_user_folder)
938
-
939
- # Clear setup session state
940
- for key in ['setup_events', 'setup_form_data', 'show_wedding_setup', 'new_user_folder']:
941
- if key in st.session_state:
942
- del st.session_state[key]
943
-
944
- st.success("✅ Wedding configuration saved successfully!")
945
- st.success("🎉 Welcome to your personalized wedding planner!")
946
- st.rerun()
947
- else:
948
- st.error("Failed to save wedding configuration. Please try again.")
949
- else:
950
- st.error("Google Drive not available. Cannot save configuration.")
951
  else:
952
  st.error("Please fill in at least the partner names and wedding date range in the form above.")
953
 
954
- def main():
955
- # Initialize session state
956
- if 'config_manager' not in st.session_state:
957
- st.session_state.config_manager = ConfigManager()
958
-
959
- # Load authentication configuration
960
- if 'auth_config' not in st.session_state:
961
- st.session_state.auth_config = load_auth_config()
962
-
963
- if st.session_state.auth_config is None:
964
- st.error("Failed to load authentication configuration. Please check your Google Drive setup.")
965
- st.stop()
966
-
967
- # Create authenticator using the new v0.4.2 syntax
968
- authenticator = stauth.Authenticate(
969
- st.session_state.auth_config['credentials'],
970
- st.session_state.auth_config['cookie']['name'],
971
- st.session_state.auth_config['cookie']['key'],
972
- st.session_state.auth_config['cookie']['expiry_days']
973
- )
974
-
975
- # Check if user wants to see wedding setup
976
- if st.session_state.get('show_wedding_setup', False):
977
- show_wedding_setup_form()
978
- return
979
-
980
- # Check authentication status from session state (v0.4.2 uses session state)
981
- authentication_status = st.session_state.get('authentication_status')
982
-
983
- if authentication_status:
984
- # User is authenticated, show main app
985
- show_main_app(authenticator)
986
- else:
987
- # Show signup/login page
988
- show_signup_page(authenticator)
989
-
990
  def show_main_app(authenticator):
991
  # Get current user from session state (set by authenticator.login)
992
  username = st.session_state.get('username')
@@ -1025,8 +891,20 @@ def show_main_app(authenticator):
1025
  st.session_state.app_initialized = True
1026
  st.session_state.last_user_folder = user_folder
1027
  else:
1028
- st.error("Failed to load wedding data.")
1029
- return
 
 
 
 
 
 
 
 
 
 
 
 
1030
 
1031
  # Load config
1032
  config = st.session_state.config_manager.load_config()
@@ -1119,14 +997,14 @@ def show_main_app(authenticator):
1119
 
1120
  # Always show push button
1121
  if modified_files:
1122
- st.warning(f"📝 {len(modified_files)} unsaved changes")
1123
  if st.button("📤 Push Changes", type="primary", help="Save changes to Google Drive", key="sidebar_push"):
1124
  with st.spinner("Pushing changes..."):
1125
  if config_manager.manual_sync_to_drive():
1126
  st.success("✅ Changes saved!")
1127
  st.rerun()
1128
  else:
1129
- st.error(" Failed to save changes")
1130
  else:
1131
  st.success("✅ All changes saved")
1132
  if st.button("📤 Push to Drive", help="Save current data to Google Drive", key="sidebar_push_all"):
@@ -1135,16 +1013,16 @@ def show_main_app(authenticator):
1135
  st.success("✅ Data saved!")
1136
  st.rerun()
1137
  else:
1138
- st.error(" Failed to save")
1139
 
1140
  # Always show pull button
1141
- if st.button("📥 Pull Latest", help="Get latest from Google Drive", key="sidebar_pull"):
1142
  with st.spinner("Pulling latest..."):
1143
  if config_manager.manual_sync_from_drive():
1144
  st.success("✅ Latest loaded!")
1145
  st.rerun()
1146
  else:
1147
- st.error(" Failed to load changes")
1148
 
1149
  # Route to appropriate page
1150
  if page == "Dashboard":
@@ -1162,465 +1040,9 @@ def show_main_app(authenticator):
1162
  elif page == "Settings":
1163
  show_settings_page(config)
1164
 
1165
- def show_wedding_timeline_page(config):
1166
- st.markdown("## 📅 Wedding Overview")
1167
-
1168
- # Show wedding events directly without tabs
1169
- show_wedding_events_section(config)
1170
 
1171
- def get_vendors_for_event(event_name, vendors_data):
1172
- """Get vendors associated with a specific event, handling multiple categories per vendor"""
1173
- event_vendors = []
1174
- for vendor in vendors_data:
1175
- vendor_events = vendor.get('events', [])
1176
- if event_name in vendor_events:
1177
- # Get all categories for this vendor
1178
- categories = vendor.get('categories', [])
1179
- primary_category = vendor.get('category', '')
1180
-
1181
- # If no categories array, use the primary category
1182
- if not categories and primary_category:
1183
- categories = [primary_category]
1184
-
1185
- # If still no categories, use a default
1186
- if not categories:
1187
- categories = ['Vendor/Service']
1188
-
1189
- # Create an entry for each category this vendor serves
1190
- for category in categories:
1191
- event_vendors.append({
1192
- 'name': vendor.get('name', ''),
1193
- 'category': category,
1194
- 'status': vendor.get('status', ''),
1195
- 'vendor_id': vendor.get('id', '') # Add ID to help identify duplicates
1196
- })
1197
-
1198
- return event_vendors
1199
-
1200
- def get_meal_choices_for_event(event_name, rsvp_data, meal_options):
1201
- """Get meal choice counts for a specific event from RSVP data"""
1202
- meal_counts = {}
1203
-
1204
- # Initialize counts for all meal options
1205
- for option in meal_options:
1206
- meal_counts[option] = 0
1207
-
1208
- # Count meal choices from RSVP data
1209
- for group_code, group_data in rsvp_data.items():
1210
- event_responses = group_data.get('event_responses', {})
1211
- if event_name in event_responses:
1212
- event_data = event_responses[event_name]
1213
- meal_choices = event_data.get('meal_choice', {})
1214
-
1215
- for attendee, choice in meal_choices.items():
1216
- if choice in meal_counts:
1217
- meal_counts[choice] += 1
1218
-
1219
- return meal_counts
1220
-
1221
- def show_wedding_events_section(config):
1222
- st.markdown("## 📅 Wedding Events")
1223
-
1224
- wedding_events = config.get('wedding_events', [])
1225
- wedding_info = config.get('wedding_info', {})
1226
-
1227
- if not wedding_events:
1228
- st.info("No events configured yet. Please complete the setup to define your wedding events.")
1229
- return
1230
-
1231
- # Get wedding dates
1232
- wedding_start_str = wedding_info.get('wedding_start_date', '')
1233
- wedding_end_str = wedding_info.get('wedding_end_date', '')
1234
-
1235
- if wedding_start_str and wedding_end_str:
1236
- try:
1237
- wedding_start = datetime.fromisoformat(wedding_start_str).date()
1238
- wedding_end = datetime.fromisoformat(wedding_end_str).date()
1239
- except:
1240
- st.error("Invalid wedding date format. Please check your settings.")
1241
- return
1242
- else:
1243
- st.warning("Wedding dates not set. Please complete the setup.")
1244
- return
1245
-
1246
- # Load vendors and RSVP data
1247
- config_manager = ConfigManager()
1248
- vendors_data = config_manager.load_json_data('vendors.json')
1249
- rsvp_data = config_manager.load_json_data('rsvp_data.json')
1250
-
1251
- # Display events
1252
- st.markdown(f"### Your Wedding Events ({len(wedding_events)} total)")
1253
-
1254
- # Group events by day
1255
- events_by_day = {}
1256
- for event in wedding_events:
1257
- event_date = wedding_start + timedelta(days=event.get('date_offset', 0))
1258
- day_key = event_date.strftime('%Y-%m-%d')
1259
- if day_key not in events_by_day:
1260
- events_by_day[day_key] = []
1261
- events_by_day[day_key].append(event)
1262
-
1263
- # Sort days
1264
- sorted_days = sorted(events_by_day.keys())
1265
-
1266
- for day in sorted_days:
1267
- day_date = datetime.fromisoformat(day).date()
1268
- day_events = events_by_day[day]
1269
-
1270
- # Determine day label
1271
- if day_date == wedding_start:
1272
- day_label = "Day 1 - Wedding Start"
1273
- elif day_date == wedding_end:
1274
- day_label = "Final Day - Wedding End"
1275
- else:
1276
- days_from_start = (day_date - wedding_start).days
1277
- day_label = f"Day {days_from_start + 1}"
1278
-
1279
- st.markdown(f"#### {day_label} - {day_date.strftime('%B %d, %Y')}")
1280
-
1281
- for event in day_events:
1282
- # Simple event display
1283
- time_info = event.get('description', '') or 'Time TBD'
1284
- location = event.get('location', '') or 'Location TBD'
1285
- address = event.get('address', '')
1286
- meal_required = event.get('requires_meal_choice', False)
1287
- event_name = event.get('name', 'Untitled Event')
1288
-
1289
- st.markdown(f"**{event_name}**")
1290
- st.markdown(f"🕐 **Time:** {time_info}")
1291
- st.markdown(f"📍 **Location:** {location}")
1292
- if address:
1293
- st.markdown(f"🏠 **Address:** {address}")
1294
- st.markdown(f"🍽️ **Meal Choice:** {'Required' if meal_required else 'Not Required'}")
1295
-
1296
- # Display meal choices and counts if meal choice is required
1297
- if meal_required:
1298
- meal_options = event.get('meal_options', [])
1299
- if meal_options:
1300
- meal_counts = get_meal_choices_for_event(event_name, rsvp_data, meal_options)
1301
- st.markdown("🍽️ **Meal Choices:**")
1302
- for option in meal_options:
1303
- count = meal_counts.get(option, 0)
1304
- st.markdown(f" • **{option}:** {count} orders")
1305
- else:
1306
- st.markdown("🍽️ **Meal Choices:** No options configured")
1307
-
1308
- # Display vendors for this event
1309
- event_vendors = get_vendors_for_event(event_name, vendors_data)
1310
- if event_vendors:
1311
- st.markdown("🏢 **Vendors:**")
1312
-
1313
- # Group vendors by name to handle multiple categories
1314
- vendors_by_name = {}
1315
- for vendor in event_vendors:
1316
- vendor_name = vendor['name']
1317
- if vendor_name not in vendors_by_name:
1318
- vendors_by_name[vendor_name] = {
1319
- 'categories': [],
1320
- 'status': vendor['status']
1321
- }
1322
- vendors_by_name[vendor_name]['categories'].append(vendor['category'])
1323
-
1324
- # Display grouped vendors
1325
- for vendor_name, vendor_info in vendors_by_name.items():
1326
- status_emoji = "✅" if vendor_info['status'] == "Booked" else "⏳" if vendor_info['status'] == "Researching" else "📋"
1327
- categories_text = ", ".join(vendor_info['categories'])
1328
- st.markdown(f" • {status_emoji} **{categories_text}:** {vendor_name}")
1329
- else:
1330
- st.markdown("🏢 **Vendors:** None assigned")
1331
-
1332
- st.markdown("---")
1333
-
1334
- # Event summary
1335
- st.markdown("### Event Summary")
1336
-
1337
- col1, col2, col3 = st.columns(3)
1338
-
1339
- with col1:
1340
- total_events = len(wedding_events)
1341
- st.metric("Total Events", total_events)
1342
-
1343
- with col2:
1344
- meal_events = len([e for e in wedding_events if e.get('requires_meal_choice', False)])
1345
- st.metric("Events with Meals", meal_events)
1346
-
1347
- with col3:
1348
- days_span = (wedding_end - wedding_start).days + 1
1349
- st.metric("Celebration Days", days_span)
1350
-
1351
- def show_settings_page(config):
1352
- st.markdown("### Settings")
1353
-
1354
- # Check demo mode status
1355
- is_demo_mode = st.session_state.config_manager.is_demo_mode()
1356
-
1357
- # Demo mode toggle at the top
1358
- st.markdown("#### Demo Mode")
1359
- col1, col2 = st.columns([3, 1])
1360
- with col1:
1361
- if is_demo_mode:
1362
- st.info("🎭 **Demo Mode is ON** - You are currently viewing sample data. This includes demo guests, vendors with complex payment schedules, tasks, and wedding party information.")
1363
- else:
1364
- st.info("📝 **Demo Mode is OFF** - You are viewing your actual wedding data.")
1365
-
1366
- with col2:
1367
- if st.button("Toggle Demo Mode", type="secondary"):
1368
- if st.session_state.config_manager.toggle_demo_mode():
1369
- st.success("Demo mode toggled! Please refresh the page.")
1370
- st.rerun()
1371
- else:
1372
- st.error("Failed to toggle demo mode.")
1373
-
1374
- # Create tabs for different settings
1375
- tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs(["Edit Configuration", "Manage Events", "Google Drive", "Cache Management", "Current Configuration", "Reset"])
1376
-
1377
- with tab1:
1378
- st.markdown("#### Edit Wedding Configuration")
1379
-
1380
- # Initialize session state for editing if not exists
1381
- if 'edit_config' not in st.session_state:
1382
- st.session_state.edit_config = config.copy()
1383
-
1384
- with st.form("edit_wedding_config"):
1385
- st.markdown("##### Basic Wedding Information")
1386
-
1387
- col1, col2 = st.columns(2)
1388
- with col1:
1389
- partner1_name = st.text_input("Partner 1 Name", value=st.session_state.edit_config.get('wedding_info', {}).get('partner1_name', ''))
1390
- partner2_name = st.text_input("Partner 2 Name", value=st.session_state.edit_config.get('wedding_info', {}).get('partner2_name', ''))
1391
- venue_city = st.text_input("City", value=st.session_state.edit_config.get('wedding_info', {}).get('venue_city', ''))
1392
-
1393
- with col2:
1394
- # Get current dates
1395
- wedding_start_str = st.session_state.edit_config.get('wedding_info', {}).get('wedding_start_date', '')
1396
- wedding_end_str = st.session_state.edit_config.get('wedding_info', {}).get('wedding_end_date', '')
1397
-
1398
- try:
1399
- if wedding_start_str:
1400
- wedding_start = datetime.fromisoformat(wedding_start_str).date()
1401
- else:
1402
- wedding_start = date.today()
1403
-
1404
- if wedding_end_str:
1405
- wedding_end = datetime.fromisoformat(wedding_end_str).date()
1406
- else:
1407
- wedding_end = date.today()
1408
- except:
1409
- wedding_start = date.today()
1410
- wedding_end = date.today()
1411
-
1412
- wedding_start_date = st.date_input("Wedding Start Date", value=wedding_start)
1413
- wedding_end_date = st.date_input("Wedding End Date", value=wedding_end)
1414
-
1415
- if wedding_end_date < wedding_start_date:
1416
- st.error("End date must be after start date")
1417
- wedding_end_date = wedding_start_date
1418
-
1419
- st.markdown("##### Custom Tags")
1420
- current_tags = st.session_state.edit_config.get('custom_settings', {}).get('custom_tags', [])
1421
- custom_tags_text = '\n'.join(current_tags) if current_tags else ''
1422
- custom_tags = st.text_area("Custom Tags (one per line)", value=custom_tags_text, placeholder="e.g.,\nUrgent\nHigh Priority\nDeposit Required")
1423
-
1424
- st.markdown("##### Task Assignees")
1425
- current_assignees = st.session_state.edit_config.get('custom_settings', {}).get('task_assignees', [])
1426
- task_assignees_text = '\n'.join(current_assignees) if current_assignees else ''
1427
- task_assignees = st.text_area("Task Assignees (one per line)", value=task_assignees_text, placeholder="e.g.,\nMom\nDad\nWedding Planner\nBest Friend\nCoordinator")
1428
-
1429
- submitted = st.form_submit_button("Save Changes", type="primary")
1430
-
1431
- if submitted:
1432
- # Update the configuration
1433
- updated_config = st.session_state.edit_config.copy()
1434
- updated_config['wedding_info'] = {
1435
- 'partner1_name': partner1_name,
1436
- 'partner2_name': partner2_name,
1437
- 'venue_city': venue_city,
1438
- 'wedding_start_date': wedding_start_date.isoformat(),
1439
- 'wedding_end_date': wedding_end_date.isoformat()
1440
- }
1441
-
1442
- # Parse custom tags and task assignees
1443
- custom_tags_list = [tag.strip() for tag in custom_tags.split('\n') if tag.strip()]
1444
- task_assignees_list = [assignee.strip() for assignee in task_assignees.split('\n') if assignee.strip()]
1445
- updated_config['custom_settings'] = {
1446
- 'custom_tags': custom_tags_list,
1447
- 'task_assignees': task_assignees_list
1448
- }
1449
-
1450
- # Save the updated configuration
1451
- st.session_state.config_manager.save_config(updated_config)
1452
- st.success("Configuration updated successfully!")
1453
- st.rerun()
1454
-
1455
- with tab2:
1456
- show_event_management_section(config)
1457
-
1458
- with tab3:
1459
- show_google_drive_section()
1460
-
1461
- with tab4:
1462
- show_cache_management_section()
1463
-
1464
- with tab5:
1465
- st.markdown("#### Current Configuration")
1466
- st.json(config)
1467
-
1468
- with tab6:
1469
- st.markdown("#### Reset Configuration")
1470
- st.warning("⚠️ This will permanently delete all your wedding configuration data. This action cannot be undone.")
1471
-
1472
- if st.button("Reset Configuration", type="secondary"):
1473
- if st.session_state.config_manager.reset_config():
1474
- st.success("Configuration reset! Please refresh the page.")
1475
- st.rerun()
1476
-
1477
- def show_cache_management_section():
1478
- """Show cache management section in settings"""
1479
- st.markdown("#### Cache Management")
1480
-
1481
- config_manager = st.session_state.config_manager
1482
-
1483
- # Get cache status
1484
- cache_status = config_manager.get_cache_status()
1485
-
1486
- st.markdown("**Current Cache Status:**")
1487
-
1488
- col1, col2 = st.columns(2)
1489
-
1490
- with col1:
1491
- st.metric("Total Cached Items", cache_status['total_cached'])
1492
- if cache_status['config_cached']:
1493
- st.success("✅ Wedding configuration cached")
1494
- else:
1495
- st.info("ℹ️ Wedding configuration not cached")
1496
-
1497
- with col2:
1498
- if cache_status['cached_data_files']:
1499
- st.success(f"✅ {len(cache_status['cached_data_files'])} data files cached")
1500
- with st.expander("View cached files"):
1501
- for filename in cache_status['cached_data_files']:
1502
- st.markdown(f"• {filename}")
1503
- else:
1504
- st.info("ℹ️ No data files cached")
1505
-
1506
- st.markdown("---")
1507
-
1508
- # Cache management buttons
1509
- st.markdown("**Cache Actions:**")
1510
-
1511
- col1, col2, col3 = st.columns(3)
1512
-
1513
- with col1:
1514
- if st.button("🗑️ Clear All Cache", help="Clear all cached data to force reload from source"):
1515
- config_manager.clear_cache()
1516
- st.success("Cache cleared! Data will be reloaded from source on next access.")
1517
- st.rerun()
1518
-
1519
- with col2:
1520
- if st.button("🔄 Refresh Cache", help="Reload all data from Google Drive and update cache"):
1521
- if config_manager.google_drive_enabled:
1522
- with st.spinner("Refreshing cache from Google Drive..."):
1523
- if config_manager.manual_sync_from_drive():
1524
- st.success("Cache refreshed from Google Drive!")
1525
- st.rerun()
1526
- else:
1527
- st.error("Failed to refresh cache from Google Drive")
1528
- else:
1529
- st.warning("Google Drive not enabled. Cannot refresh from Drive.")
1530
-
1531
- with col3:
1532
- if st.button("ℹ️ Cache Info", help="Show detailed cache information"):
1533
- st.info("Cache helps improve performance by storing data in memory. Data is automatically cached when first loaded and updated when modified.")
1534
-
1535
- def show_google_drive_sync_status():
1536
- """Show Google Drive sync status and push button prominently in main app"""
1537
- config_manager = st.session_state.config_manager
1538
- drive_status = config_manager.get_google_drive_status()
1539
-
1540
- # Only show if Google Drive is enabled
1541
- if not drive_status['enabled']:
1542
- return
1543
-
1544
- # Get modified files
1545
- modified_files = config_manager.get_modified_files()
1546
-
1547
- # Create a prominent status bar with always-visible push button
1548
- col1, col2, col3 = st.columns([2, 1, 1])
1549
-
1550
- with col1:
1551
- if modified_files:
1552
- friendly_names = [get_friendly_file_name(f) for f in modified_files]
1553
- st.warning(f"📝 **{len(modified_files)} unsaved changes** - {', '.join(friendly_names)}")
1554
- else:
1555
- st.success("✅ **All changes saved** - Your data is up to date with Google Drive")
1556
-
1557
- with col2:
1558
- # Always show push button - it will sync all current data
1559
- if modified_files:
1560
- button_text = f"📤 Push {len(modified_files)} Changes"
1561
- button_help = f"Save {len(modified_files)} modified files to Google Drive"
1562
- else:
1563
- button_text = "📤 Push to Drive"
1564
- button_help = "Save current data to Google Drive"
1565
-
1566
- if st.button(button_text, type="primary", help=button_help):
1567
- with st.spinner("Pushing changes to Google Drive..."):
1568
- if config_manager.manual_sync_to_drive():
1569
- st.success("✅ Changes saved to Google Drive!")
1570
- st.rerun()
1571
- else:
1572
- st.error("❌ Failed to save changes to Google Drive")
1573
-
1574
- with col3:
1575
- if st.button("📥 Pull Latest", help="Get latest changes from Google Drive"):
1576
- with st.spinner("Pulling latest changes from Google Drive..."):
1577
- if config_manager.manual_sync_from_drive():
1578
- st.success("✅ Latest changes loaded from Google Drive!")
1579
- st.rerun()
1580
- else:
1581
- st.error("❌ Failed to load changes from Google Drive")
1582
-
1583
- # Add a small separator
1584
- st.markdown("---")
1585
-
1586
- def show_google_drive_section():
1587
- st.markdown("#### Google Drive Integration")
1588
-
1589
- config_manager = st.session_state.config_manager
1590
- drive_status = config_manager.get_google_drive_status()
1591
-
1592
- # Status display
1593
- if drive_status['enabled']:
1594
- if drive_status['status'] == 'Online':
1595
- st.success(f"✅ {drive_status['message']}")
1596
- elif drive_status['status'] == 'Offline':
1597
- st.warning(f"⚠️ {drive_status['message']}")
1598
- else:
1599
- st.error(f"❌ {drive_status['message']}")
1600
- else:
1601
- st.info(f"ℹ️ {drive_status['message']}")
1602
-
1603
- # Show files if available
1604
- if drive_status['enabled'] and 'files' in drive_status:
1605
- st.markdown("**Files in Google Drive:**")
1606
- for file_name in drive_status['files']:
1607
- friendly_name = get_friendly_file_name(file_name)
1608
- st.markdown(f"• {friendly_name}")
1609
-
1610
- # Show modified files status
1611
- if drive_status['enabled']:
1612
- modified_files = config_manager.get_modified_files()
1613
- if modified_files:
1614
- st.markdown("#### 📝 Modified Files (Not Synced)")
1615
- st.warning(f"The following files have been modified and need to be synced to Google Drive:")
1616
- for file_name in modified_files:
1617
- friendly_name = get_friendly_file_name(file_name)
1618
- st.markdown(f"• {friendly_name}")
1619
- else:
1620
- st.markdown("#### ✅ All Files Synced")
1621
- st.success("All your data is up to date with Google Drive.")
1622
-
1623
- # Manual sync buttons
1624
- if drive_status['enabled']:
1625
- st.markdown("#### Manual Sync")
1626
- col1, col2
 
16
  # Page configuration
17
  st.set_page_config(
18
  page_title="Wedding Planner",
19
+ page_icon="👑",
20
  layout="wide",
21
  initial_sidebar_state="expanded"
22
  )
 
190
  margin: 0.5rem 0;
191
  border-radius: 5px;
192
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  </style>
194
  """, unsafe_allow_html=True)
195
 
 
212
  config = yaml.load(file, Loader=SafeLoader)
213
  return config
214
 
215
+ st.error(" No auth config found")
216
  return None
217
  except Exception as e:
218
  st.error(f"Error loading authentication config: {e}")
219
  return None
220
 
221
+ def save_auth_config(auth_config):
222
+ """Save authentication configuration to Google Drive and local storage"""
223
  try:
224
  config_manager = st.session_state.config_manager
225
 
226
+ # Convert to YAML string
227
+ config_yaml = yaml.dump(auth_config, default_flow_style=False, sort_keys=False)
 
228
 
229
  # Save to Google Drive if enabled
230
  if config_manager.google_drive_enabled:
231
+ success = config_manager.drive_manager.upload_file('config.yaml', config_yaml)
232
+ if not success:
233
+ st.error("Failed to save authentication config to Google Drive")
234
+ return False
235
 
236
  # Also save locally as backup
237
+ try:
238
+ with open('config.yaml', 'w') as file:
239
+ yaml.dump(auth_config, file, default_flow_style=False, sort_keys=False)
240
+ except Exception as e:
241
+ print(f"Warning: Could not save local config backup: {e}")
242
 
 
 
243
  return True
 
244
  except Exception as e:
245
  st.error(f"Error saving authentication config: {e}")
246
  return False
247
 
248
+ def create_wedding_folder_structure(username, wedding_name, partner1, partner2):
249
+ """Create the folder structure and initial files for a new wedding"""
250
+ try:
251
+ config_manager = st.session_state.config_manager
252
+
253
+ if not config_manager.google_drive_enabled:
254
+ st.error("Google Drive is required for creating new weddings")
255
+ return False
256
+
257
+ # Create wedding folder name (sanitize it)
258
+ folder_name = username.lower().replace(' ', '').replace('@', '').replace('.', '')
259
+
260
+ # Create default wedding configuration
261
+ default_wedding_config = {
262
+ 'wedding_info': {
263
+ 'partner1_name': partner1,
264
+ 'partner2_name': partner2,
265
+ 'wedding_start_date': '',
266
+ 'wedding_end_date': '',
267
+ 'venue_city': ''
268
+ },
269
+ 'custom_settings': {
270
+ 'custom_tags': [
271
+ "Wedding Party", "Urgent", "Rehearsal", "Timeline",
272
+ "Maid of Honor", "Best Man", "Bridesmaid", "Groomsman",
273
+ "Flower Girl", "Ring Bearer", "Usher", "Reader",
274
+ "Venue", "Catering", "Photography", "Videography", "Music/DJ",
275
+ "Flowers", "Decor", "Attire", "Hair & Makeup", "Transportation",
276
+ "Invitations", "Cake", "Officiant", "Other",
277
+ "Decorations", "Centerpieces", "Favors", "Signage", "Linens",
278
+ "Tableware", "Lighting", "Accessories", "Stationery", "Gifts",
279
+ "Vendor", "Item", "Deposit Required", "Final Payment", "Installment",
280
+ "Food & Beverage", "Music", "Entertainment", "Lodging"
281
+ ],
282
+ 'task_assignees': []
283
+ },
284
+ 'wedding_events': [
285
+ {"name": "Welcome Dinner", "date_offset": -1, "description": "Welcome dinner for out-of-town guests", "requires_meal_choice": True, "meal_options": ["Duck", "Surf & Turf", "Risotto (vegetarian)", "Stuffed Squash (vegetarian)"], "location": "", "address": ""},
286
+ {"name": "Church Ceremony", "date_offset": 0, "description": "Main wedding ceremony", "requires_meal_choice": False, "meal_options": [], "location": "", "address": ""},
287
+ {"name": "Reception", "date_offset": 0, "description": "Wedding reception with dinner", "requires_meal_choice": True, "meal_options": ["Duck", "Surf & Turf", "Risotto (vegetarian)", "Stuffed Squash (vegetarian)"], "location": "", "address": ""},
288
+ {"name": "Mehndi Afterparty", "date_offset": 1, "description": "Mehndi celebration and afterparty", "requires_meal_choice": False, "meal_options": [], "location": "", "address": ""},
289
+ {"name": "Indian Ceremony", "date_offset": 1, "description": "Traditional Indian wedding ceremony", "requires_meal_choice": False, "meal_options": [], "location": "", "address": ""},
290
+ {"name": "Indian Reception", "date_offset": 1, "description": "Indian reception celebration", "requires_meal_choice": True, "meal_options": ["Duck", "Surf & Turf", "Risotto (vegetarian)", "Stuffed Squash (vegetarian)"], "location": "", "address": ""}
291
+ ]
292
+ }
293
+
294
+ # Create empty data files
295
+ empty_data_files = {
296
+ 'guest_list_data.json': [],
297
+ 'rsvp_data.json': {},
298
+ 'tasks.json': [],
299
+ 'vendors.json': [],
300
+ 'wedding_party.json': []
301
+ }
302
+
303
+ # Upload wedding config to user's folder
304
+ if not config_manager.drive_manager.upload_file(f'{folder_name}/wedding_config.json', default_wedding_config):
305
+ return False
306
+
307
+ # Upload empty data files to user's folder
308
+ for filename, data in empty_data_files.items():
309
+ if not config_manager.drive_manager.upload_file(f'{folder_name}/{filename}', data):
310
+ return False
311
+
312
+ return folder_name
313
+ except Exception as e:
314
+ st.error(f"Error creating wedding folder structure: {e}")
315
+ return False
316
+
317
  def get_user_folder_from_username(username):
318
  """Get the user folder based on username using wedding mappings"""
319
  try:
 
368
  st.error(f"Error getting wedding info for {username}: {e}")
369
  return None
370
 
371
+ def main():
372
+ # Initialize session state
373
+ if 'config_manager' not in st.session_state:
374
+ st.session_state.config_manager = ConfigManager()
375
+
376
+ # Load authentication configuration
377
+ if 'auth_config' not in st.session_state:
378
+ st.session_state.auth_config = load_auth_config()
379
+
380
+ if st.session_state.auth_config is None:
381
+ st.error("Failed to load authentication configuration. Please check your Google Drive setup.")
382
+ st.stop()
383
+
384
+ # Create authenticator with updated API for 0.4.2
385
+ authenticator = stauth.Authenticate(
386
+ st.session_state.auth_config['credentials'],
387
+ st.session_state.auth_config['cookie']['name'],
388
+ st.session_state.auth_config['cookie']['key'],
389
+ st.session_state.auth_config['cookie']['expiry_days']
390
+ )
391
+
392
+ # Check if user is already authenticated
393
+ if st.session_state.get('authentication_status'):
394
+ # User is authenticated, show main app
395
+ show_main_app(authenticator)
396
+ else:
397
+ # Show login/signup page and handle authentication
398
+ show_login_page(authenticator)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
 
400
+ def show_login_page(authenticator):
401
+ """Show the login page with signup option"""
402
 
403
  # Hero Section
404
  st.markdown("""
 
411
  margin-bottom: 3rem;
412
  box-shadow: 0 10px 30px rgba(0,0,0,0.2);
413
  ">
414
+ <h1 style="font-size: 3.5rem; margin-bottom: 1rem; font-weight: 700;">👑 Wedding Planner</h1>
415
  <h2 style="font-size: 1.8rem; margin-bottom: 2rem; font-weight: 300; opacity: 0.9;">
416
+ Your Complete Wedding Planning Solution
417
  </h2>
418
  <p style="font-size: 1.2rem; max-width: 600px; margin: 0 auto; line-height: 1.6;">
419
+ Organize your special day with our comprehensive wedding planning tool.
420
+ Manage guests, track tasks, coordinate vendors, and create unforgettable memories.
421
  </p>
422
  </div>
423
  """, unsafe_allow_html=True)
424
 
425
+ # Features Section (existing code remains the same)
426
+ st.markdown("## ✨ What You Can Do")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
 
428
  col1, col2, col3 = st.columns(3)
429
 
 
486
  </ul>
487
  </div>
488
  """, unsafe_allow_html=True)
489
+
490
+ # Additional Features
491
+ col4, col5, col6 = st.columns(3)
492
+
493
+ with col4:
494
+ st.markdown("""
495
+ <div style="
496
+ background: #f8f9fa;
497
+ padding: 2rem;
498
+ border-radius: 10px;
499
+ border-left: 4px solid #4a7c59;
500
+ margin-bottom: 2rem;
501
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
502
+ ">
503
+ <h3 style="color: #2d5016; margin-bottom: 1rem;">💐 Wedding Party</h3>
504
+ <ul style="color: #666; line-height: 1.8;">
505
+ <li>Manage bridal party</li>
506
+ <li>Track responsibilities</li>
507
+ <li>Coordinate schedules</li>
508
+ <li>Store contact info</li>
509
+ </ul>
510
+ </div>
511
+ """, unsafe_allow_html=True)
512
+
513
+ with col5:
514
+ st.markdown("""
515
+ <div style="
516
+ background: #f8f9fa;
517
+ padding: 2rem;
518
+ border-radius: 10px;
519
+ border-left: 4px solid #4a7c59;
520
+ margin-bottom: 2rem;
521
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
522
+ ">
523
+ <h3 style="color: #2d5016; margin-bottom: 1rem;">📊 Dashboard</h3>
524
+ <ul style="color: #666; line-height: 1.8;">
525
+ <li>Visual progress tracking</li>
526
+ <li>Key metrics overview</li>
527
+ <li>Timeline management</li>
528
+ <li>Quick insights</li>
529
+ </ul>
530
+ </div>
531
+ """, unsafe_allow_html=True)
532
+
533
+ with col6:
534
+ st.markdown("""
535
+ <div style="
536
+ background: #f8f9fa;
537
+ padding: 2rem;
538
+ border-radius: 10px;
539
+ border-left: 4px solid #4a7c59;
540
+ margin-bottom: 2rem;
541
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
542
+ ">
543
+ <h3 style="color: #2d5016; margin-bottom: 1rem;">☁️ Cloud Sync</h3>
544
+ <ul style="color: #666; line-height: 1.8;">
545
+ <li>Google Drive integration</li>
546
+ <li>Automatic backups</li>
547
+ <li>Multi-device access</li>
548
+ <li>Real-time updates</li>
549
+ </ul>
550
+ </div>
551
+ """, unsafe_allow_html=True)
552
+
553
+ st.markdown("---")
554
+
555
+ # Login/Signup Section
556
+ tab1, tab2 = st.tabs(["🔐 Login", "📝 Create New Wedding"])
557
+
558
+ with tab1:
559
+ st.markdown("## 🔐 Login to Your Wedding Planner")
560
+
561
+ # Login form - Updated for 0.4.2 API
562
+ try:
563
+ authenticator.login(location='main')
564
+ except Exception as e:
565
+ st.error(f"Login error: {e}")
566
+
567
+ # Check authentication status and show appropriate message
568
+ if st.session_state.get('authentication_status') is False:
569
+ st.error("⚠ Invalid username or password")
570
+ elif st.session_state.get('authentication_status') is None:
571
+ st.info("🔐 Please enter your username and password")
572
+ elif st.session_state.get('authentication_status'):
573
+ st.success(f"✅ Welcome, {st.session_state.get('name', 'User')}!")
574
+ st.rerun() # Refresh to show main app
575
+
576
+ with tab2:
577
+ st.markdown("## 📝 Create Your Wedding Planner")
578
+ st.markdown("Sign up to create your personalized wedding planning workspace!")
579
+
580
+ # Signup form using register_user widget - Updated for 0.4.2 API
581
+ try:
582
+ email, username, name = authenticator.register_user(
583
+ location='main',
584
+ pre_authorized=None, # Allow anyone to register
585
+ fields={'Form name': 'Create Your Wedding Account',
586
+ 'Email': 'Email Address',
587
+ 'Username': 'Username',
588
+ 'Password': 'Password',
589
+ 'Repeat password': 'Confirm Password',
590
+ 'First name': 'First Partner Name',
591
+ 'Last name': 'Second Partner Name',
592
+ 'Register': 'Create Wedding Account'},
593
+ captcha=False, # Disable captcha for simplicity
594
+ merge_username_email=False, # Keep username and email separate
595
+ password_hint=False, # Disable password hint
596
+ key='register_user'
597
+ )
598
+
599
+ if email:
600
+ st.success(f"🎉 Welcome to Wedding Planner! Account created for {name}")
601
+
602
+ # Extract partner names from the registration
603
+ # For the updated API, 'name' might contain "FirstName LastName"
604
+ name_parts = name.split(' ', 1) if name else ['', '']
605
+ partner1 = name_parts[0] if len(name_parts) > 0 else 'Partner 1'
606
+ partner2 = name_parts[1] if len(name_parts) > 1 else 'Partner 2'
607
+
608
+ # Create wedding folder structure
609
+ with st.spinner("Setting up your wedding workspace..."):
610
+ folder_name = create_wedding_folder_structure(username, f"{partner1} & {partner2}", partner1, partner2)
611
+
612
+ if folder_name:
613
+ # Update the wedding mappings in auth config
614
+ auth_config = st.session_state.auth_config
615
+
616
+ # Initialize wedding_mappings if it doesn't exist
617
+ if 'wedding_mappings' not in auth_config:
618
+ auth_config['wedding_mappings'] = {}
619
+
620
+ # Add new wedding mapping
621
+ wedding_display_name = f"{partner1} & {partner2}"
622
+ auth_config['wedding_mappings'][wedding_display_name] = {
623
+ 'folder': folder_name,
624
+ 'users': [username],
625
+ 'wedding_name': wedding_display_name
626
+ }
627
+
628
+ # Save updated auth config
629
+ if save_auth_config(auth_config):
630
+ st.session_state.auth_config = auth_config
631
+ st.success("✅ Wedding workspace created successfully!")
632
+ st.info("🔄 Please refresh the page and log in with your new account to complete the setup.")
633
+
634
+ # Show next steps
635
+ st.markdown("### 🎯 Next Steps:")
636
+ st.markdown(f"""
637
+ 1. **Log in** using your credentials:
638
+ - Username: `{username}`
639
+ - Password: The password you just created
640
+
641
+ 2. **Complete your wedding setup** by adding:
642
+ - Wedding dates and venue
643
+ - Wedding events and timeline
644
+ - Guest lists and vendors
645
+
646
+ 3. **Start planning** your perfect day!
647
+ """)
648
+ else:
649
+ st.error("❌ Failed to save wedding configuration. Please try again.")
650
+ else:
651
+ st.error("❌ Failed to create wedding workspace. Please try again.")
652
+
653
+ except Exception as e:
654
+ st.error(f"Registration error: {e}")
655
+ # Additional helpful information
656
+ st.info("💡 Registration requirements: unique username, valid email, and matching passwords")
657
 
658
  def show_wedding_setup_form():
659
+ """Show the wedding setup form for creating a new wedding (updated for new users)"""
660
+ st.markdown("### 🔧 Complete Your Wedding Setup")
661
+ st.info("Welcome! Let's finish setting up your wedding planner with your specific details.")
 
 
 
 
 
662
 
663
  # Initialize session state for events and form data if not exists
664
  if 'setup_events' not in st.session_state:
 
679
  st.markdown("#### Basic Wedding Information")
680
 
681
  col1, col2 = st.columns(2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
682
  with col1:
683
  partner1_name = st.text_input("Partner 1 Name", value=st.session_state.setup_form_data['partner1_name'], placeholder="Enter first partner's name")
684
  partner2_name = st.text_input("Partner 2 Name", value=st.session_state.setup_form_data['partner2_name'], placeholder="Enter second partner's name")
 
814
 
815
  # Save configuration button (after event management)
816
  st.markdown("---")
817
+ if st.button("Save Configuration", type="primary"):
818
  # Get form values from session state
819
  form_data = st.session_state.setup_form_data
820
 
 
839
  'wedding_events': st.session_state.setup_events
840
  }
841
 
842
+ # Save configuration
843
+ st.session_state.config_manager.save_config(config)
844
+ # Clear setup session state
845
+ if 'setup_events' in st.session_state:
846
+ del st.session_state.setup_events
847
+ if 'setup_form_data' in st.session_state:
848
+ del st.session_state.setup_form_data
849
+ if 'show_setup_form' in st.session_state:
850
+ del st.session_state.show_setup_form
851
+ st.success("Configuration saved successfully!")
852
+ st.rerun()
 
 
 
 
 
 
 
 
 
853
  else:
854
  st.error("Please fill in at least the partner names and wedding date range in the form above.")
855
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
856
  def show_main_app(authenticator):
857
  # Get current user from session state (set by authenticator.login)
858
  username = st.session_state.get('username')
 
891
  st.session_state.app_initialized = True
892
  st.session_state.last_user_folder = user_folder
893
  else:
894
+ # For new users, check if we need to show setup form
895
+ config = config_manager.load_config()
896
+ wedding_info_data = config.get('wedding_info', {})
897
+
898
+ # If basic wedding info is missing, show setup form
899
+ if (not wedding_info_data.get('partner1_name') or
900
+ not wedding_info_data.get('partner2_name') or
901
+ not wedding_info_data.get('wedding_start_date')):
902
+
903
+ show_wedding_setup_form()
904
+ return
905
+
906
+ st.session_state.app_initialized = True
907
+ st.session_state.last_user_folder = user_folder
908
 
909
  # Load config
910
  config = st.session_state.config_manager.load_config()
 
997
 
998
  # Always show push button
999
  if modified_files:
1000
+ st.warning(f"🔄 {len(modified_files)} unsaved changes")
1001
  if st.button("📤 Push Changes", type="primary", help="Save changes to Google Drive", key="sidebar_push"):
1002
  with st.spinner("Pushing changes..."):
1003
  if config_manager.manual_sync_to_drive():
1004
  st.success("✅ Changes saved!")
1005
  st.rerun()
1006
  else:
1007
+ st.error(" Failed to save changes")
1008
  else:
1009
  st.success("✅ All changes saved")
1010
  if st.button("📤 Push to Drive", help="Save current data to Google Drive", key="sidebar_push_all"):
 
1013
  st.success("✅ Data saved!")
1014
  st.rerun()
1015
  else:
1016
+ st.error(" Failed to save")
1017
 
1018
  # Always show pull button
1019
+ if st.button("📄 Pull Latest", help="Get latest from Google Drive", key="sidebar_pull"):
1020
  with st.spinner("Pulling latest..."):
1021
  if config_manager.manual_sync_from_drive():
1022
  st.success("✅ Latest loaded!")
1023
  st.rerun()
1024
  else:
1025
+ st.error(" Failed to load changes")
1026
 
1027
  # Route to appropriate page
1028
  if page == "Dashboard":
 
1040
  elif page == "Settings":
1041
  show_settings_page(config)
1042
 
1043
+ # [Rest of the functions remain the same - show_wedding_timeline_page, get_vendors_for_event,
1044
+ # get_meal_choices_for_event, show_wedding_events_section, show_settings_page, etc.]
1045
+ # ... (keeping all existing functions for brevity)
 
 
1046
 
1047
+ if __name__ == "__main__":
1048
+ main()