umangchaudhry commited on
Commit
6b97fc3
·
verified ·
1 Parent(s): 0f5e9b2

Upload 11 files

Browse files
Files changed (3) hide show
  1. app.py +196 -307
  2. config_manager.py +43 -7
  3. requirements.txt +2 -2
app.py CHANGED
@@ -1,8 +1,11 @@
1
  import streamlit as st
2
  import json
3
  import os
 
4
  from datetime import datetime, date, timedelta
5
  import pandas as pd
 
 
6
  from config_manager import ConfigManager
7
  from dashboard import Dashboard
8
  from tasks import TasksManager
@@ -190,22 +193,120 @@ st.markdown("""
190
  </style>
191
  """, unsafe_allow_html=True)
192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  def main():
194
  # Initialize session state
195
  if 'config_manager' not in st.session_state:
196
  st.session_state.config_manager = ConfigManager()
197
 
198
- # Check if app is initialized
199
- if 'app_initialized' not in st.session_state:
200
- st.session_state.app_initialized = False
201
-
202
- if not st.session_state.app_initialized:
203
- show_landing_page()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  else:
205
- show_main_app()
 
206
 
207
- def show_landing_page():
208
- """Show the new landing page with website-like design and authentication"""
209
 
210
  # Hero Section
211
  st.markdown("""
@@ -307,12 +408,12 @@ def show_landing_page():
307
  margin-bottom: 2rem;
308
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
309
  ">
310
- <h3 style="color: #2d5016; margin-bottom: 1rem;">📅 Event Planning</h3>
311
  <ul style="color: #666; line-height: 1.8;">
312
- <li>Plan multiple events</li>
313
- <li>Create detailed timelines</li>
314
- <li>Manage locations</li>
315
- <li>Track meal choices</li>
316
  </ul>
317
  </div>
318
  """, unsafe_allow_html=True)
@@ -327,12 +428,12 @@ def show_landing_page():
327
  margin-bottom: 2rem;
328
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
329
  ">
330
- <h3 style="color: #2d5016; margin-bottom: 1rem;">👰 Wedding Party</h3>
331
  <ul style="color: #666; line-height: 1.8;">
332
- <li>Manage wedding party roles</li>
333
- <li>Track responsibilities</li>
334
- <li>Store contact information</li>
335
- <li>Assign special tasks</li>
336
  </ul>
337
  </div>
338
  """, unsafe_allow_html=True)
@@ -350,67 +451,32 @@ def show_landing_page():
350
  <h3 style="color: #2d5016; margin-bottom: 1rem;">☁️ Cloud Sync</h3>
351
  <ul style="color: #666; line-height: 1.8;">
352
  <li>Google Drive integration</li>
353
- <li>Access from anywhere</li>
354
  <li>Automatic backups</li>
355
- <li>Multi-device sync</li>
 
356
  </ul>
357
  </div>
358
  """, unsafe_allow_html=True)
359
 
360
  st.markdown("---")
361
 
362
- # Get Started Section
363
- st.markdown("## 🚀 Get Started")
364
-
365
- st.markdown("""
366
- Ready to start planning your wedding? Choose one of the options below to get started.
367
- """)
368
-
369
- # Create two columns for the buttons
370
- col1, col2 = st.columns(2)
371
-
372
- with col1:
373
- st.markdown("### 🎭 Try Demo Mode")
374
- st.markdown("""
375
- Experience the app with sample data including:
376
- • Sample wedding (Emma & James)
377
- • Demo guests with RSVPs
378
- • Vendors with complex payment schedules
379
- • Tasks in various stages
380
- • Wedding party information
381
- """)
382
-
383
- if st.button("🎭 Start with Demo Data", type="primary", use_container_width=True):
384
- with st.spinner("Loading demo data from Google Drive..."):
385
- config_manager = st.session_state.config_manager
386
- if config_manager.load_demo_data_from_drive():
387
- st.session_state.app_initialized = True
388
- st.success("✅ Demo data loaded successfully!")
389
- st.rerun()
390
- else:
391
- st.error("❌ Failed to load demo data from Google Drive. Please check your connection and try again.")
392
 
393
- with col2:
394
- st.markdown("### 📝 Load Your Wedding")
395
- st.markdown("""
396
- Load your existing wedding data from Google Drive:
397
- Your guest list and RSVPs
398
- • Your vendors and payments
399
- Your tasks and timeline
400
- Your wedding party details
401
- """)
402
-
403
- st.markdown("**Note:** This will load data from the `laraandumang` folder in your Google Drive.")
404
-
405
- if st.button("🎉 Get Started", type="secondary", use_container_width=True):
406
- with st.spinner("Loading your wedding data from Google Drive..."):
407
- config_manager = st.session_state.config_manager
408
- if config_manager.load_existing_data_from_drive():
409
- st.session_state.app_initialized = True
410
- st.success("✅ Wedding data loaded successfully!")
411
- st.rerun()
412
- else:
413
- st.error("❌ Failed to load data from Google Drive. Please check your connection and try again.")
414
 
415
 
416
 
@@ -611,244 +677,48 @@ def show_wedding_setup_form():
611
  else:
612
  st.error("Please fill in at least the partner names and wedding date range in the form above.")
613
 
614
- def show_setup_page():
615
- st.markdown('<div class="main-header"><h1>💒 Wedding Planner Setup</h1></div>', unsafe_allow_html=True)
616
-
617
- st.markdown("### Welcome! Let's set up your wedding planning app.")
618
-
619
- # Show Google Drive status on setup page
620
- show_google_drive_status_setup()
621
-
622
- # Demo mode option
623
- st.markdown("#### Choose Your Experience")
624
- col1, col2 = st.columns(2)
625
-
626
- with col1:
627
- st.markdown("**🎭 Try Demo Mode**")
628
- st.markdown("Experience the app with sample data including:")
629
- st.markdown("• Sample wedding (Emma & James)")
630
- st.markdown("• Demo guests with RSVPs")
631
- st.markdown("• Vendors with complex payment schedules")
632
- st.markdown("• Tasks in various stages")
633
- st.markdown("• Wedding party information")
634
-
635
- if st.button("Start with Demo Data", type="primary"):
636
- if st.session_state.config_manager.set_demo_mode(True):
637
- st.success("Demo mode enabled! Loading sample data...")
638
- st.rerun()
639
- else:
640
- st.error("Failed to enable demo mode.")
641
-
642
- with col2:
643
- st.markdown("**📝 Start Fresh**")
644
- st.markdown("Create your own wedding from scratch:")
645
- st.markdown("• Enter your wedding details")
646
- st.markdown("• Add your own events")
647
- st.markdown("• Build your guest list")
648
- st.markdown("• Manage your vendors")
649
- st.markdown("• Track your tasks")
650
-
651
- if st.button("Create My Wedding", type="secondary"):
652
- if st.session_state.config_manager.set_demo_mode(False):
653
- st.success("Demo mode disabled. Ready to create your wedding!")
654
- st.rerun()
655
- else:
656
- st.error("Failed to disable demo mode.")
657
-
658
- st.markdown("---")
659
 
660
- # Initialize session state for events and form data if not exists
661
- if 'setup_events' not in st.session_state:
662
- st.session_state.setup_events = []
663
- if 'setup_form_data' not in st.session_state:
664
- st.session_state.setup_form_data = {
665
- 'partner1_name': '',
666
- 'partner2_name': '',
667
- 'venue_city': '',
668
- 'wedding_start_date': date.today(),
669
- 'wedding_end_date': date.today(),
670
- 'custom_tags': '',
671
- 'task_assignees': ''
672
- }
673
 
674
- # Basic wedding information form
675
- with st.form("wedding_setup"):
676
- st.markdown("#### Basic Wedding Information")
677
-
678
- col1, col2 = st.columns(2)
679
- with col1:
680
- partner1_name = st.text_input("Partner 1 Name", value=st.session_state.setup_form_data['partner1_name'], placeholder="Enter first partner's name")
681
- partner2_name = st.text_input("Partner 2 Name", value=st.session_state.setup_form_data['partner2_name'], placeholder="Enter second partner's name")
682
- venue_city = st.text_input("City", value=st.session_state.setup_form_data['venue_city'], placeholder="Enter city")
683
-
684
- with col2:
685
- st.markdown("**Wedding Date Range**")
686
- wedding_start_date = st.date_input("Start Date", value=st.session_state.setup_form_data['wedding_start_date'])
687
- wedding_end_date = st.date_input("End Date", value=st.session_state.setup_form_data['wedding_end_date'])
688
-
689
- if wedding_end_date < wedding_start_date:
690
- st.error("End date must be after start date")
691
- wedding_end_date = wedding_start_date
692
-
693
- st.markdown("#### Task Organization")
694
- st.info("Tasks will be automatically grouped by your wedding events.")
695
-
696
- st.markdown("#### Custom Tags")
697
- st.markdown("Enter custom tags (one per line):")
698
- custom_tags = st.text_area("Custom Tags", value=st.session_state.setup_form_data['custom_tags'], placeholder="e.g.,\nUrgent\nHigh Priority\nDeposit Required\nResearch Needed")
699
-
700
- st.markdown("#### Task Assignees")
701
- st.markdown("Enter people who will regularly be assigned tasks (one per line):")
702
- task_assignees = st.text_area("Task Assignees", value=st.session_state.setup_form_data['task_assignees'], placeholder="e.g.,\nMom\nDad\nWedding Planner\nBest Friend\nCoordinator")
703
-
704
- form_submitted = st.form_submit_button("Update Wedding Information")
705
-
706
- if form_submitted:
707
- # Update session state with form data
708
- st.session_state.setup_form_data = {
709
- 'partner1_name': partner1_name,
710
- 'partner2_name': partner2_name,
711
- 'venue_city': venue_city,
712
- 'wedding_start_date': wedding_start_date,
713
- 'wedding_end_date': wedding_end_date,
714
- 'custom_tags': custom_tags,
715
- 'task_assignees': task_assignees
716
- }
717
- st.success("Wedding information updated!")
718
- st.rerun()
719
 
720
- # Event management section (outside form)
721
- st.markdown("#### Wedding Events")
722
- st.markdown("Define all your wedding events with their details:")
723
 
724
- # Add/Remove event buttons
725
- col1, col2 = st.columns(2)
726
- with col1:
727
- if st.button("➕ Add Event"):
728
- # Set default date to wedding start date
729
- wedding_start = st.session_state.setup_form_data['wedding_start_date']
730
- st.session_state.setup_events.append({
731
- "name": "New Event",
732
- "description": "",
733
- "date_offset": 0,
734
- "requires_meal_choice": False,
735
- "meal_options": [],
736
- "location": "",
737
- "address": ""
738
- })
739
- st.rerun()
740
 
741
- with col2:
742
- if len(st.session_state.setup_events) > 0 and st.button("➖ Remove Last Event"):
743
- st.session_state.setup_events.pop()
744
- st.rerun()
745
 
746
- # Display events
747
- if st.session_state.setup_events:
748
- for i, event in enumerate(st.session_state.setup_events):
749
- with st.expander(f"Event {i+1}: {event['name']}", expanded=True):
750
- col1, col2 = st.columns(2)
751
-
752
- with col1:
753
- event_name = st.text_input("Event Name", value=event['name'], key=f"event_name_{i}")
754
- event_description = st.text_input("Time", value=event['description'], placeholder="e.g., 2:00 PM, 6:30 PM", key=f"event_desc_{i}")
755
- event_location = st.text_input("Location Name", value=event.get('location', ''), placeholder="e.g., Central Park, Grand Ballroom", key=f"event_location_{i}")
756
-
757
- with col2:
758
- # Get wedding date range
759
- wedding_start = st.session_state.setup_form_data['wedding_start_date']
760
- wedding_end = st.session_state.setup_form_data['wedding_end_date']
761
-
762
- # Calculate current event date from date_offset
763
- current_event_date = wedding_start + timedelta(days=event['date_offset'])
764
-
765
- # Use date input without constraints - allow any date
766
- event_date = st.date_input(
767
- "Event Date",
768
- value=current_event_date,
769
- key=f"event_date_{i}",
770
- help="Select any date for this event"
771
- )
772
-
773
- # Show warning if date is outside wedding range
774
- if event_date < wedding_start or event_date > wedding_end:
775
- st.warning(f"⚠️ Selected date is outside your wedding date range ({wedding_start.strftime('%B %d, %Y')} - {wedding_end.strftime('%B %d, %Y')})")
776
-
777
- requires_meal_choice = st.checkbox("Requires Meal Choice", value=event['requires_meal_choice'], key=f"event_meal_{i}")
778
-
779
- # Meal options section (only show if meal choice is required)
780
- if requires_meal_choice:
781
- st.markdown("**Meal Options**")
782
- st.markdown("Enter meal options (one per line):")
783
- current_meal_options = event.get('meal_options', [])
784
- meal_options_text = '\n'.join(current_meal_options) if current_meal_options else ''
785
- meal_options = st.text_area("Meal Options", value=meal_options_text, placeholder="e.g.,\nDuck\nSurf & Turf\nRisotto (vegetarian)\nStuffed Squash (vegetarian)", key=f"event_meal_options_{i}", height=100)
786
- else:
787
- meal_options = ""
788
-
789
- event_address = st.text_area("Address", value=event.get('address', ''), placeholder="Enter full address (street, city, state, zip code)", key=f"event_address_{i}", height=80)
790
-
791
- # Calculate date_offset from the selected date
792
- date_offset = (event_date - wedding_start).days
793
-
794
- # Parse meal options
795
- meal_options_list = []
796
- if requires_meal_choice and meal_options:
797
- meal_options_list = [option.strip() for option in meal_options.split('\n') if option.strip()]
798
-
799
- # Update session state
800
- st.session_state.setup_events[i] = {
801
- "name": event_name,
802
- "description": event_description,
803
- "date_offset": date_offset,
804
- "requires_meal_choice": requires_meal_choice,
805
- "meal_options": meal_options_list,
806
- "location": event_location,
807
- "address": event_address
808
- }
809
- else:
810
- st.info("No events added yet. Click 'Add Event' to get started!")
811
 
812
- # Save configuration button (after event management)
813
- st.markdown("---")
814
- if st.button("Save Configuration", type="primary"):
815
- # Get form values from session state
816
- form_data = st.session_state.setup_form_data
817
-
818
- if form_data['partner1_name'] and form_data['partner2_name'] and form_data['wedding_start_date'] and form_data['wedding_end_date']:
819
- # Parse tags (task groups will be auto-generated from events)
820
- custom_tags_list = [tag.strip() for tag in form_data['custom_tags'].split('\n') if tag.strip()]
821
- task_assignees_list = [assignee.strip() for assignee in form_data['task_assignees'].split('\n') if assignee.strip()]
822
-
823
- # Create configuration
824
- config = {
825
- 'wedding_info': {
826
- 'partner1_name': form_data['partner1_name'],
827
- 'partner2_name': form_data['partner2_name'],
828
- 'wedding_start_date': form_data['wedding_start_date'].isoformat(),
829
- 'wedding_end_date': form_data['wedding_end_date'].isoformat(),
830
- 'venue_city': form_data['venue_city']
831
- },
832
- 'custom_settings': {
833
- 'custom_tags': custom_tags_list,
834
- 'task_assignees': task_assignees_list
835
- },
836
- 'wedding_events': st.session_state.setup_events
837
- }
838
-
839
- # Save configuration
840
- st.session_state.config_manager.save_config(config)
841
- # Clear setup session state
842
- if 'setup_events' in st.session_state:
843
- del st.session_state.setup_events
844
- if 'setup_form_data' in st.session_state:
845
- del st.session_state.setup_form_data
846
- st.success("Configuration saved successfully!")
847
- st.rerun()
848
- else:
849
- st.error("Please fill in at least the partner names and wedding date range in the form above.")
850
-
851
- def show_main_app():
852
  # Load config
853
  config = st.session_state.config_manager.load_config()
854
  wedding_info = config.get('wedding_info', {})
@@ -869,6 +739,11 @@ def show_main_app():
869
  else:
870
  header_text = f"{partner1} & {partner2}'s Wedding Planner \n"
871
 
 
 
 
 
 
872
  # Add demo mode indicator
873
  if is_demo_mode:
874
  header_text += "🎭 DEMO MODE - Sample Data"
@@ -897,11 +772,25 @@ def show_main_app():
897
 
898
  # Sidebar navigation
899
  with st.sidebar:
900
- # Reset app button
901
- if st.button("🔄 Reset App", type="secondary"):
 
 
 
 
 
 
 
 
 
902
  st.session_state.app_initialized = False
903
- # Clear all cached data and reset config manager state
904
- st.session_state.config_manager.reset_app_state()
 
 
 
 
 
905
  st.rerun()
906
 
907
  st.markdown("---")
 
1
  import streamlit as st
2
  import json
3
  import os
4
+ import yaml
5
  from datetime import datetime, date, timedelta
6
  import pandas as pd
7
+ from yaml.loader import SafeLoader
8
+ import streamlit_authenticator as stauth
9
  from config_manager import ConfigManager
10
  from dashboard import Dashboard
11
  from tasks import TasksManager
 
193
  </style>
194
  """, unsafe_allow_html=True)
195
 
196
+ def load_auth_config():
197
+ """Load authentication configuration from Google Drive"""
198
+ try:
199
+ config_manager = st.session_state.config_manager
200
+
201
+ # Try to load config.yaml from Google Drive root
202
+ if config_manager.google_drive_enabled:
203
+ config_content = config_manager.drive_manager.download_file('config.yaml')
204
+ if config_content:
205
+ # Parse YAML content
206
+ config = yaml.load(config_content, Loader=SafeLoader)
207
+ return config
208
+
209
+ # Fallback to local config.yaml if Google Drive fails
210
+ if os.path.exists('config.yaml'):
211
+ with open('config.yaml') as file:
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 get_user_folder_from_username(username):
222
+ """Get the user folder based on username using wedding mappings"""
223
+ try:
224
+ # Load auth config to get wedding mappings
225
+ auth_config = st.session_state.get('auth_config')
226
+ if not auth_config:
227
+ # Fallback to loading config directly
228
+ auth_config = load_auth_config()
229
+
230
+ if auth_config and 'wedding_mappings' in auth_config:
231
+ wedding_mappings = auth_config['wedding_mappings']
232
+
233
+ # Search through all wedding mappings to find the user
234
+ for wedding_name, wedding_info in wedding_mappings.items():
235
+ if 'users' in wedding_info and username in wedding_info['users']:
236
+ return wedding_info['folder']
237
+
238
+ # Fallback to old hardcoded logic for backward compatibility
239
+ if username == 'demo':
240
+ return 'demo_data'
241
+ elif username == 'laraandumang':
242
+ return 'laraandumang'
243
+ else:
244
+ return 'demo_data' # Default fallback
245
+
246
+ except Exception as e:
247
+ st.error(f"Error getting user folder for {username}: {e}")
248
+ # Fallback to demo_data on error
249
+ return 'demo_data'
250
+
251
+ def get_wedding_info_for_user(username):
252
+ """Get wedding information for a specific user"""
253
+ try:
254
+ auth_config = st.session_state.get('auth_config')
255
+ if not auth_config:
256
+ auth_config = load_auth_config()
257
+
258
+ if auth_config and 'wedding_mappings' in auth_config:
259
+ wedding_mappings = auth_config['wedding_mappings']
260
+
261
+ for wedding_name, wedding_info in wedding_mappings.items():
262
+ if 'users' in wedding_info and username in wedding_info['users']:
263
+ return {
264
+ 'wedding_name': wedding_name,
265
+ 'folder': wedding_info['folder'],
266
+ 'users': wedding_info['users'],
267
+ 'display_name': wedding_info.get('wedding_name', wedding_name)
268
+ }
269
+
270
+ return None
271
+ except Exception as e:
272
+ st.error(f"Error getting wedding info for {username}: {e}")
273
+ return None
274
+
275
  def main():
276
  # Initialize session state
277
  if 'config_manager' not in st.session_state:
278
  st.session_state.config_manager = ConfigManager()
279
 
280
+ # Load authentication configuration
281
+ if 'auth_config' not in st.session_state:
282
+ st.session_state.auth_config = load_auth_config()
283
+
284
+ if st.session_state.auth_config is None:
285
+ st.error("Failed to load authentication configuration. Please check your Google Drive setup.")
286
+ st.stop()
287
+
288
+ # Create authenticator (auto_hash=True by default, so passwords will be hashed automatically)
289
+ authenticator = stauth.Authenticate(
290
+ st.session_state.auth_config['credentials'],
291
+ st.session_state.auth_config['cookie']['name'],
292
+ st.session_state.auth_config['cookie']['key'],
293
+ st.session_state.auth_config['cookie']['expiry_days']
294
+ )
295
+
296
+ # Check authentication status
297
+ if 'authentication_status' not in st.session_state:
298
+ st.session_state.authentication_status = None
299
+
300
+ # Check if user is already authenticated
301
+ if st.session_state.get('authentication_status'):
302
+ # User is authenticated, show main app
303
+ show_main_app(authenticator)
304
  else:
305
+ # Show login page and handle authentication
306
+ show_login_page(authenticator)
307
 
308
+ def show_login_page(authenticator):
309
+ """Show the login page"""
310
 
311
  # Hero Section
312
  st.markdown("""
 
408
  margin-bottom: 2rem;
409
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
410
  ">
411
+ <h3 style="color: #2d5016; margin-bottom: 1rem;">👰 Wedding Party</h3>
412
  <ul style="color: #666; line-height: 1.8;">
413
+ <li>Manage bridal party</li>
414
+ <li>Track responsibilities</li>
415
+ <li>Coordinate schedules</li>
416
+ <li>Store contact info</li>
417
  </ul>
418
  </div>
419
  """, unsafe_allow_html=True)
 
428
  margin-bottom: 2rem;
429
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
430
  ">
431
+ <h3 style="color: #2d5016; margin-bottom: 1rem;">📊 Dashboard</h3>
432
  <ul style="color: #666; line-height: 1.8;">
433
+ <li>Visual progress tracking</li>
434
+ <li>Key metrics overview</li>
435
+ <li>Timeline management</li>
436
+ <li>Quick insights</li>
437
  </ul>
438
  </div>
439
  """, unsafe_allow_html=True)
 
451
  <h3 style="color: #2d5016; margin-bottom: 1rem;">☁️ Cloud Sync</h3>
452
  <ul style="color: #666; line-height: 1.8;">
453
  <li>Google Drive integration</li>
 
454
  <li>Automatic backups</li>
455
+ <li>Multi-device access</li>
456
+ <li>Real-time updates</li>
457
  </ul>
458
  </div>
459
  """, unsafe_allow_html=True)
460
 
461
  st.markdown("---")
462
 
463
+ # Login Section
464
+ st.markdown("## 🔐 Login to Your Wedding Planner")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
 
466
+ # Login form
467
+ try:
468
+ authenticator.login(location='main')
469
+ except Exception as e:
470
+ st.error(f"Login error: {e}")
471
+
472
+ # Check authentication status and show appropriate message
473
+ if st.session_state.get('authentication_status') is False:
474
+ st.error("❌ Invalid username or password")
475
+ elif st.session_state.get('authentication_status') is None:
476
+ st.info("🔐 Please enter your username and password")
477
+ elif st.session_state.get('authentication_status'):
478
+ st.success(f" Welcome, {st.session_state.get('name', 'User')}!")
479
+ st.rerun() # Refresh to show main app
 
 
 
 
 
 
 
480
 
481
 
482
 
 
677
  else:
678
  st.error("Please fill in at least the partner names and wedding date range in the form above.")
679
 
680
+
681
+ def show_main_app(authenticator):
682
+ # Get current user from session state (set by authenticator.login)
683
+ username = st.session_state.get('username')
684
+ name = st.session_state.get('name')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
685
 
686
+ if not username or not name:
687
+ st.error("Authentication error: Missing user information")
688
+ return
 
 
 
 
 
 
 
 
 
 
689
 
690
+ # Set user folder based on username
691
+ user_folder = get_user_folder_from_username(username)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
 
693
+ # Get wedding info for the user (for future use)
694
+ wedding_info = get_wedding_info_for_user(username)
 
695
 
696
+ # Update config manager to use the correct user folder
697
+ config_manager = st.session_state.config_manager
698
+ config_manager.set_user_folder(user_folder)
 
 
 
 
 
 
 
 
 
 
 
 
 
699
 
700
+ # Check if we need to load data (either not initialized or user changed)
701
+ current_user_folder = config_manager.get_current_user_folder()
702
+ user_changed = st.session_state.get('last_user_folder') != user_folder
 
703
 
704
+ if not st.session_state.get('app_initialized', False) or user_changed:
705
+ with st.spinner(f"Loading {name}'s wedding data..."):
706
+ if user_folder == 'demo_data':
707
+ if config_manager.load_demo_data_from_drive():
708
+ st.session_state.app_initialized = True
709
+ st.session_state.last_user_folder = user_folder
710
+ st.success("✅ Demo data loaded successfully!")
711
+ else:
712
+ st.error("Failed to load demo data.")
713
+ return
714
+ else:
715
+ if config_manager.load_existing_data_from_drive():
716
+ st.session_state.app_initialized = True
717
+ st.session_state.last_user_folder = user_folder
718
+ else:
719
+ st.error("Failed to load wedding data.")
720
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
721
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
722
  # Load config
723
  config = st.session_state.config_manager.load_config()
724
  wedding_info = config.get('wedding_info', {})
 
739
  else:
740
  header_text = f"{partner1} & {partner2}'s Wedding Planner \n"
741
 
742
+ # Add wedding mapping info if available
743
+ user_wedding_info = get_wedding_info_for_user(username)
744
+ if user_wedding_info:
745
+ header_text += f"👥 Wedding: {user_wedding_info['display_name']} \n"
746
+
747
  # Add demo mode indicator
748
  if is_demo_mode:
749
  header_text += "🎭 DEMO MODE - Sample Data"
 
772
 
773
  # Sidebar navigation
774
  with st.sidebar:
775
+ # User info and logout
776
+ # Extract first name for a more friendly greeting
777
+ first_name = name.split()[0] if name else "User"
778
+ st.markdown(f"**Welcome, {first_name}!**")
779
+ if authenticator.logout(location='sidebar', key='logout_button'):
780
+ # Clear session state on logout
781
+ for key in list(st.session_state.keys()):
782
+ if key not in ['config_manager', 'auth_config']:
783
+ del st.session_state[key]
784
+
785
+ # Reset app initialization state
786
  st.session_state.app_initialized = False
787
+ st.session_state.last_user_folder = None
788
+
789
+ # Reset config manager state
790
+ if 'config_manager' in st.session_state:
791
+ st.session_state.config_manager.reset_app_state()
792
+ st.session_state.config_manager.user_folder = None
793
+
794
  st.rerun()
795
 
796
  st.markdown("---")
config_manager.py CHANGED
@@ -48,6 +48,14 @@ class ConfigManager:
48
  return "demo_data"
49
  return "laraandumang"
50
 
 
 
 
 
 
 
 
 
51
  def _initialize_google_drive(self):
52
  """Initialize Google Drive connection"""
53
  try:
@@ -68,7 +76,7 @@ class ConfigManager:
68
 
69
  try:
70
  # Get user-specific folder
71
- user_folder = self.get_user_folder()
72
 
73
  # List of data files to sync
74
  data_files = [
@@ -128,6 +136,7 @@ class ConfigManager:
128
  except Exception as e:
129
  print(f"Error syncing from Google Drive: {e}")
130
  # Don't fail the entire initialization if sync fails
 
131
 
132
  def _sync_to_google_drive(self):
133
  """Sync local data files to Google Drive"""
@@ -162,6 +171,17 @@ class ConfigManager:
162
 
163
  def load_app_config(self):
164
  """Load app configuration from file"""
 
 
 
 
 
 
 
 
 
 
 
165
  if os.path.exists(self.app_config_file):
166
  try:
167
  with open(self.app_config_file, 'r') as f:
@@ -372,7 +392,13 @@ class ConfigManager:
372
  """Toggle demo mode on/off"""
373
  self.app_config["demo_mode"] = not self.app_config.get("demo_mode", False)
374
  try:
375
- with open(self.app_config_file, 'w') as f:
 
 
 
 
 
 
376
  json.dump(self.app_config, f, indent=2)
377
  return True
378
  except Exception as e:
@@ -383,7 +409,13 @@ class ConfigManager:
383
  """Set demo mode to specific value"""
384
  self.app_config["demo_mode"] = enabled
385
  try:
386
- with open(self.app_config_file, 'w') as f:
 
 
 
 
 
 
387
  json.dump(self.app_config, f, indent=2)
388
  return True
389
  except Exception as e:
@@ -487,7 +519,7 @@ class ConfigManager:
487
  self.set_demo_mode(False)
488
 
489
  # Get user-specific folder
490
- user_folder = self.get_user_folder()
491
 
492
  # Check if wedding_config.json exists in Google Drive user folder
493
  config_content = self.drive_manager.download_file(f'{user_folder}/wedding_config.json')
@@ -544,6 +576,8 @@ class ConfigManager:
544
  return False
545
  except Exception as e:
546
  print(f"Error loading existing data from Google Drive: {e}")
 
 
547
  return False
548
 
549
  def load_demo_data_from_drive(self):
@@ -556,7 +590,7 @@ class ConfigManager:
556
  self.set_demo_mode(True)
557
 
558
  # Load demo data from demo folder in Google Drive
559
- demo_folder = self.get_user_folder() # Will return "demo_data" since demo mode is enabled
560
 
561
  # Check if wedding_config.json exists in demo folder
562
  config_content = self.drive_manager.download_file(f'{demo_folder}/wedding_config.json')
@@ -613,6 +647,8 @@ class ConfigManager:
613
  return False
614
  except Exception as e:
615
  print(f"Error loading demo data from Google Drive: {e}")
 
 
616
  return False
617
 
618
  def get_google_drive_status(self):
@@ -691,7 +727,7 @@ class ConfigManager:
691
 
692
  try:
693
  # Get user-specific folder
694
- user_folder = self.get_user_folder()
695
 
696
  modified_files = self.get_modified_files()
697
 
@@ -750,4 +786,4 @@ class ConfigManager:
750
  return True
751
  except Exception as e:
752
  print(f"Error resetting app state: {e}")
753
- return False
 
48
  return "demo_data"
49
  return "laraandumang"
50
 
51
+ def set_user_folder(self, folder_name):
52
+ """Set the user-specific folder name"""
53
+ self.user_folder = folder_name
54
+
55
+ def get_current_user_folder(self):
56
+ """Get the currently set user folder"""
57
+ return getattr(self, 'user_folder', self.get_user_folder())
58
+
59
  def _initialize_google_drive(self):
60
  """Initialize Google Drive connection"""
61
  try:
 
76
 
77
  try:
78
  # Get user-specific folder
79
+ user_folder = self.get_current_user_folder()
80
 
81
  # List of data files to sync
82
  data_files = [
 
136
  except Exception as e:
137
  print(f"Error syncing from Google Drive: {e}")
138
  # Don't fail the entire initialization if sync fails
139
+ # This is expected behavior - user can manually sync later
140
 
141
  def _sync_to_google_drive(self):
142
  """Sync local data files to Google Drive"""
 
171
 
172
  def load_app_config(self):
173
  """Load app configuration from file"""
174
+ # For Hugging Face Spaces, check /tmp directory first
175
+ if self.is_huggingface:
176
+ tmp_config_path = f"/tmp/{self.app_config_file}"
177
+ if os.path.exists(tmp_config_path):
178
+ try:
179
+ with open(tmp_config_path, 'r') as f:
180
+ return json.load(f)
181
+ except (json.JSONDecodeError, FileNotFoundError):
182
+ pass
183
+
184
+ # Check original location
185
  if os.path.exists(self.app_config_file):
186
  try:
187
  with open(self.app_config_file, 'r') as f:
 
392
  """Toggle demo mode on/off"""
393
  self.app_config["demo_mode"] = not self.app_config.get("demo_mode", False)
394
  try:
395
+ # For Hugging Face Spaces, use /tmp directory to avoid permission issues
396
+ if self.is_huggingface:
397
+ config_path = f"/tmp/{self.app_config_file}"
398
+ else:
399
+ config_path = self.app_config_file
400
+
401
+ with open(config_path, 'w') as f:
402
  json.dump(self.app_config, f, indent=2)
403
  return True
404
  except Exception as e:
 
409
  """Set demo mode to specific value"""
410
  self.app_config["demo_mode"] = enabled
411
  try:
412
+ # For Hugging Face Spaces, use /tmp directory to avoid permission issues
413
+ if self.is_huggingface:
414
+ config_path = f"/tmp/{self.app_config_file}"
415
+ else:
416
+ config_path = self.app_config_file
417
+
418
+ with open(config_path, 'w') as f:
419
  json.dump(self.app_config, f, indent=2)
420
  return True
421
  except Exception as e:
 
519
  self.set_demo_mode(False)
520
 
521
  # Get user-specific folder
522
+ user_folder = self.get_current_user_folder()
523
 
524
  # Check if wedding_config.json exists in Google Drive user folder
525
  config_content = self.drive_manager.download_file(f'{user_folder}/wedding_config.json')
 
576
  return False
577
  except Exception as e:
578
  print(f"Error loading existing data from Google Drive: {e}")
579
+ # This is a common issue on Hugging Face Spaces due to network/SSL issues
580
+ # The error is expected and the user can retry manually
581
  return False
582
 
583
  def load_demo_data_from_drive(self):
 
590
  self.set_demo_mode(True)
591
 
592
  # Load demo data from demo folder in Google Drive
593
+ demo_folder = self.get_current_user_folder() # Will return "demo_data" since demo mode is enabled
594
 
595
  # Check if wedding_config.json exists in demo folder
596
  config_content = self.drive_manager.download_file(f'{demo_folder}/wedding_config.json')
 
647
  return False
648
  except Exception as e:
649
  print(f"Error loading demo data from Google Drive: {e}")
650
+ # This is a common issue on Hugging Face Spaces due to network/SSL issues
651
+ # The error is expected and the user can retry manually
652
  return False
653
 
654
  def get_google_drive_status(self):
 
727
 
728
  try:
729
  # Get user-specific folder
730
+ user_folder = self.get_current_user_folder()
731
 
732
  modified_files = self.get_modified_files()
733
 
 
786
  return True
787
  except Exception as e:
788
  print(f"Error resetting app state: {e}")
789
+ return False
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
- streamlit==1.44.0
2
  pandas==2.1.3
3
  plotly==5.17.0
4
  datetime
@@ -8,5 +8,5 @@ google-api-python-client>=2.0.0
8
  google-auth-httplib2>=0.1.0
9
  google-auth-oauthlib>=0.5.0
10
  google-auth>=2.0.0
11
- streamlit-authenticator>=0.4.2
12
  PyYAML>=6.0
 
1
+ streamlit==1.28.1
2
  pandas==2.1.3
3
  plotly==5.17.0
4
  datetime
 
8
  google-auth-httplib2>=0.1.0
9
  google-auth-oauthlib>=0.5.0
10
  google-auth>=2.0.0
11
+ streamlit-authenticator>=0.2.3
12
  PyYAML>=6.0