SikhAid Data & State Management
State Management Architecture
Svelte Stores
Pattern: Reactive stores for client-side state management
Store Types Used:
writable()- Read/write stores for component state- No derived stores currently
- No custom stores (plain writable stores)
Store Locations:
src/lib/stores/
├── donation.ts # Donation amount state
├── contact.ts # Contact form submissions
├── volunteering.ts # Volunteer form submissions
└── csr.ts # CSR partnership submissions
Store Patterns
1. Donation Store
File: src/lib/stores/donation.ts
Purpose: Manage selected donation amount across components
typescript1import { writable } from 'svelte/store'; 2 3export const selectedAmount = writable<number>(0); 4 5export function setDonationAmount(amount: number) { 6 selectedAmount.set(amount); 7 8 // Side effect: scroll to payment form 9 setTimeout(() => { 10 const paymentSection = document.getElementById('payment-form'); 11 paymentSection?.scrollIntoView({ behavior: 'smooth' }); 12 }, 100); 13}
Usage:
svelte1<script lang="ts"> 2 import { selectedAmount, setDonationAmount } from '$lib/stores/donation'; 3 4 // Read value with $ prefix 5 $: amount = $selectedAmount; 6 7 // Write value 8 function selectAmount(value: number) { 9 setDonationAmount(value); 10 } 11</script> 12 13<div>Selected: ₹{$selectedAmount}</div> 14<button on:click={() => selectAmount(1000)}>₹1,000</button>
2. Contact Submissions Store
File: src/lib/stores/contact.ts
Purpose: Store contact form submissions (in-memory and Firestore)
typescript1import { writable } from 'svelte/store'; 2 3export interface ContactSubmission { 4 name: string; 5 email: string; 6 phone: string; 7 subject: string; 8 message: string; 9 timestamp: number; 10} 11 12export const contactSubmissions = writable<ContactSubmission[]>([]); 13 14export function addContactSubmission(submission: ContactSubmission) { 15 contactSubmissions.update(submissions => { 16 return [...submissions, submission]; 17 }); 18}
3. Volunteering Store
File: src/lib/stores/volunteering.ts
Purpose: Store volunteer applications
typescript1export interface VolunteerSubmission { 2 fullName: string; 3 email: string; 4 mobile: string; 5 gender: string; 6 address: string; 7 opportunity: string; 8 durationMonths: number; 9 durationYears: number; 10 startDate: string; 11 about: string; 12 timestamp: number; 13} 14 15export const volunteerSubmissions = writable<VolunteerSubmission[]>([]);
4. CSR Store
File: src/lib/stores/csr.ts
Purpose: Store CSR partnership inquiries
typescript1export interface CSRSubmission { 2 companyName: string; 3 contactPerson: string; 4 email: string; 5 phone: string; 6 companySize: string; 7 interests: string[]; 8 message: string; 9 timestamp: number; 10} 11 12export const csrSubmissions = writable<CSRSubmission[]>([]);
Data Flow Pattern
Standard Form Submission Flow
1. User fills form
↓
2. Form validation
↓
3. Create submission object with timestamp
↓
4. Update Svelte store (addSubmission)
↓
5. Write to Firestore (addToFirestore)
↓
6. Show success/error feedback
↓
7. Reset form (optional)
Example Flow Implementation
svelte1<script lang="ts"> 2 import { addContactSubmission } from '$lib/stores/contact'; 3 import { addContactToFirestore } from '$lib/firestore'; 4 5 let formData = { name: '', email: '', phone: '', subject: '', message: '' }; 6 let isSubmitting = false; 7 let successMessage = ''; 8 let errorMessage = ''; 9 10 async function handleSubmit() { 11 // 1. Validate 12 if (!validateForm()) return; 13 14 isSubmitting = true; 15 errorMessage = ''; 16 17 try { 18 // 2. Create submission object 19 const submission = { 20 ...formData, 21 timestamp: Date.now() 22 }; 23 24 // 3. Update store 25 addContactSubmission(submission); 26 27 // 4. Write to Firestore 28 await addContactToFirestore(submission); 29 30 // 5. Success feedback 31 successMessage = 'Submission received!'; 32 formData = { name: '', email: '', phone: '', subject: '', message: '' }; 33 } catch (error) { 34 // 6. Error feedback 35 errorMessage = 'Submission failed. Please try again.'; 36 console.error('❌ Error:', error); 37 } finally { 38 isSubmitting = false; 39 } 40 } 41</script>
Firebase Integration
Firebase Initialization
File: src/lib/firebase.ts
typescript1import { initializeApp, type FirebaseApp } from 'firebase/app'; 2import { getFirestore, type Firestore } from 'firebase/firestore'; 3import { getAuth, type Auth } from 'firebase/auth'; 4 5const firebaseConfig = { 6 apiKey: import.meta.env.VITE_FIREBASE_API_KEY, 7 authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN, 8 projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID, 9 storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET, 10 messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID, 11 appId: import.meta.env.VITE_FIREBASE_APP_ID 12}; 13 14let app: FirebaseApp | null = null; 15let db: Firestore | null = null; 16let auth: Auth | null = null; 17 18// Browser-only initialization 19if (typeof window !== 'undefined') { 20 try { 21 app = initializeApp(firebaseConfig); 22 db = getFirestore(app); 23 auth = getAuth(app); 24 console.log('✅ Firebase initialized successfully'); 25 } catch (error) { 26 console.error('❌ Firebase initialization error:', error); 27 } 28} 29 30export { app, db, auth };
Key Points:
- Browser-only initialization (
typeof window !== 'undefined') - Environment variables for configuration
- Error handling with console logging
- Exports app, db, and auth instances
Firestore Operations
Collections Structure
Firestore Database
├── contact_submissions/
│ └── [auto-generated-id]/
│ ├── name: string
│ ├── email: string
│ ├── phone: string
│ ├── subject: string
│ ├── message: string
│ ├── status: 'new' | 'read' | 'resolved'
│ └── timestamp: Firestore Timestamp
│
├── volunteer_submissions/
│ └── [auto-generated-id]/
│ ├── fullName: string
│ ├── email: string
│ ├── mobile: string
│ ├── opportunity: string
│ ├── status: 'new' | 'read' | 'resolved'
│ └── ...
│
└── csr_submissions/
└── [auto-generated-id]/
├── companyName: string
├── email: string
├── status: 'new' | 'read' | 'resolved'
└── ...
Firestore Functions
File: src/lib/firestore.ts
Add Contact Submission
typescript1import { collection, addDoc, Timestamp } from 'firebase/firestore'; 2import { db } from './firebase'; 3import type { ContactSubmission } from './stores/contact'; 4 5export async function addContactToFirestore( 6 submission: ContactSubmission 7): Promise<string> { 8 if (!db) throw new Error('Firestore not initialized'); 9 10 try { 11 const docRef = await addDoc(collection(db, 'contact_submissions'), { 12 ...submission, 13 status: 'new', 14 timestamp: Timestamp.fromMillis(submission.timestamp) 15 }); 16 17 console.log('✅ Contact added to Firestore:', docRef.id); 18 return docRef.id; 19 } catch (error) { 20 console.error('❌ Error adding contact:', error); 21 throw error; 22 } 23}
Get All Submissions
typescript1import { collection, getDocs, query, orderBy } from 'firebase/firestore'; 2 3export interface FirestoreContactSubmission extends ContactSubmission { 4 id?: string; 5 status: 'new' | 'read' | 'resolved'; 6 firestoreTimestamp?: Timestamp; 7} 8 9export async function getContactSubmissions(): Promise<FirestoreContactSubmission[]> { 10 if (!db) throw new Error('Firestore not initialized'); 11 12 try { 13 const q = query( 14 collection(db, 'contact_submissions'), 15 orderBy('timestamp', 'desc') 16 ); 17 18 const querySnapshot = await getDocs(q); 19 const submissions: FirestoreContactSubmission[] = []; 20 21 querySnapshot.forEach((doc) => { 22 submissions.push({ 23 id: doc.id, 24 ...doc.data() as FirestoreContactSubmission 25 }); 26 }); 27 28 console.log(`📊 Fetched ${submissions.length} contact submissions`); 29 return submissions; 30 } catch (error) { 31 console.error('❌ Error fetching submissions:', error); 32 throw error; 33 } 34}
Update Submission Status
typescript1import { doc, updateDoc } from 'firebase/firestore'; 2 3export async function updateSubmissionStatus( 4 collectionName: string, 5 documentId: string, 6 status: 'new' | 'read' | 'resolved' 7): Promise<void> { 8 if (!db) throw new Error('Firestore not initialized'); 9 10 try { 11 const docRef = doc(db, collectionName, documentId); 12 await updateDoc(docRef, { status }); 13 14 console.log(`✅ Updated ${documentId} status to ${status}`); 15 } catch (error) { 16 console.error('❌ Error updating status:', error); 17 throw error; 18 } 19}
Add Volunteer Submission
typescript1export async function addVolunteerToFirestore( 2 submission: VolunteerSubmission 3): Promise<string> { 4 if (!db) throw new Error('Firestore not initialized'); 5 6 try { 7 const docRef = await addDoc(collection(db, 'volunteer_submissions'), { 8 ...submission, 9 status: 'new', 10 timestamp: Timestamp.fromMillis(submission.timestamp) 11 }); 12 13 console.log('✅ Volunteer added:', docRef.id); 14 return docRef.id; 15 } catch (error) { 16 console.error('❌ Error adding volunteer:', error); 17 throw error; 18 } 19}
Get Volunteer Submissions
typescript1export async function getVolunteerSubmissions(): Promise<FirestoreVolunteerSubmission[]> { 2 if (!db) throw new Error('Firestore not initialized'); 3 4 try { 5 const q = query( 6 collection(db, 'volunteer_submissions'), 7 orderBy('timestamp', 'desc') 8 ); 9 10 const querySnapshot = await getDocs(q); 11 const submissions: FirestoreVolunteerSubmission[] = []; 12 13 querySnapshot.forEach((doc) => { 14 submissions.push({ 15 id: doc.id, 16 ...doc.data() as FirestoreVolunteerSubmission 17 }); 18 }); 19 20 return submissions; 21 } catch (error) { 22 console.error('❌ Error fetching volunteers:', error); 23 throw error; 24 } 25}
CSR Functions (Similar Pattern)
addCSRToFirestore()- Add CSR submissiongetCSRSubmissions()- Get all CSR submissions- Both follow same pattern as contact/volunteer
Static Data Management
Campaigns Data
File: src/lib/data/campaigns.js
Structure:
javascript1export const campaigns = [ 2 { 3 slug: 'langar-aid', 4 title: 'Langar Aid', 5 subtitle: 'Free Meals for All', 6 shortDescription: 'Serving nutritious meals...', 7 fullDescription: 'Detailed description...', 8 image: '/images/campaign.jpg', 9 category: 'hunger-relief', 10 status: 'ongoing', // 'ongoing' | 'completed' | 'seasonal' 11 impactStats: [ 12 { label: 'Meals Served', value: '100,000+', icon: 'mdi:food' } 13 ], 14 howItWorks: [ 15 { step: 1, title: 'Step Title', description: 'Step description' } 16 ], 17 gallery: [ 18 { src: '/images/gallery1.jpg', alt: 'Description' } 19 ] 20 }, 21 // More campaigns... 22];
Usage:
svelte1<script lang="ts"> 2 import { campaigns } from '$lib/data/campaigns'; 3 import type { Campaign } from '$lib/types/campaign'; 4 5 // Filter campaigns 6 $: ongoingCampaigns = campaigns.filter(c => c.status === 'ongoing'); 7 8 // Find by slug 9 $: campaign = campaigns.find(c => c.slug === 'langar-aid'); 10</script> 11 12{#each ongoingCampaigns as campaign} 13 <div>{campaign.title}</div> 14{/each}
Blog Data
File: src/lib/data/blogs.js
Structure:
javascript1export const blogs = [ 2 { 3 slug: 'post-slug', 4 title: 'Blog Post Title', 5 excerpt: 'Short excerpt...', 6 content: 'Full blog content...', 7 author: 'Author Name', 8 date: '2024-01-15', 9 image: '/images/blog.jpg', 10 category: 'community' 11 }, 12 // More blog posts... 13];
TypeScript Types & Interfaces
Campaign Types
File: src/lib/types/campaign.ts
typescript1export interface Campaign { 2 slug: string; 3 title: string; 4 subtitle: string; 5 shortDescription: string; 6 fullDescription: string; 7 image: string; 8 category: string; 9 status: 'ongoing' | 'completed' | 'seasonal'; 10 impactStats: ImpactStat[]; 11 howItWorks: HowItWorksStep[]; 12 gallery: GalleryImage[]; 13} 14 15export interface ImpactStat { 16 label: string; 17 value: string; 18 icon: string; 19} 20 21export interface HowItWorksStep { 22 step: number; 23 title: string; 24 description: string; 25} 26 27export interface GalleryImage { 28 src: string; 29 alt: string; 30}
Store Types (Defined in store files)
typescript1// Contact 2export interface ContactSubmission { 3 name: string; 4 email: string; 5 phone: string; 6 subject: string; 7 message: string; 8 timestamp: number; 9} 10 11// Volunteer 12export interface VolunteerSubmission { 13 fullName: string; 14 email: string; 15 mobile: string; 16 gender: string; 17 address: string; 18 opportunity: string; 19 durationMonths: number; 20 durationYears: number; 21 startDate: string; 22 about: string; 23 timestamp: number; 24} 25 26// CSR 27export interface CSRSubmission { 28 companyName: string; 29 contactPerson: string; 30 email: string; 31 phone: string; 32 companySize: string; 33 interests: string[]; 34 message: string; 35 timestamp: number; 36}
Firestore Extended Types
typescript1import type { Timestamp } from 'firebase/firestore'; 2 3export interface FirestoreContactSubmission extends ContactSubmission { 4 id?: string; 5 status: 'new' | 'read' | 'resolved'; 6 firestoreTimestamp?: Timestamp; 7} 8 9export interface FirestoreVolunteerSubmission extends VolunteerSubmission { 10 id?: string; 11 status: 'new' | 'read' | 'resolved'; 12 firestoreTimestamp?: Timestamp; 13} 14 15export interface FirestoreCSRSubmission extends CSRSubmission { 16 id?: string; 17 status: 'new' | 'read' | 'resolved'; 18 firestoreTimestamp?: Timestamp; 19}
Data Loading in Components
Loading Firestore Data (Admin Panel)
svelte1<script lang="ts"> 2 import { onMount } from 'svelte'; 3 import { 4 getContactSubmissions, 5 type FirestoreContactSubmission 6 } from '$lib/firestore'; 7 8 let submissions: FirestoreContactSubmission[] = []; 9 let isLoading = true; 10 let error = ''; 11 12 onMount(async () => { 13 try { 14 submissions = await getContactSubmissions(); 15 isLoading = false; 16 } catch (err) { 17 error = 'Failed to load submissions'; 18 isLoading = false; 19 } 20 }); 21</script> 22 23{#if isLoading} 24 <div>Loading...</div> 25{:else if error} 26 <div class="text-red-500">{error}</div> 27{:else} 28 {#each submissions as submission} 29 <div>{submission.name}</div> 30 {/each} 31{/if}
Using Static Data
svelte1<script lang="ts"> 2 import { campaigns } from '$lib/data/campaigns'; 3 4 // No onMount needed - data is already available 5 $: featuredCampaigns = campaigns.filter(c => c.featured); 6</script> 7 8{#each featuredCampaigns as campaign} 9 <div>{campaign.title}</div> 10{/each}
Store Subscription Patterns
Auto-subscribing with $ Prefix
svelte1<script lang="ts"> 2 import { selectedAmount } from '$lib/stores/donation'; 3 4 // Automatic subscription 5 $: amount = $selectedAmount; 6</script> 7 8<div>Amount: ₹{$selectedAmount}</div>
Manual Subscribe/Unsubscribe
svelte1<script lang="ts"> 2 import { onDestroy } from 'svelte'; 3 import { contactSubmissions } from '$lib/stores/contact'; 4 5 let submissions = []; 6 7 const unsubscribe = contactSubmissions.subscribe(value => { 8 submissions = value; 9 }); 10 11 onDestroy(() => { 12 unsubscribe(); 13 }); 14</script>
Common Data Patterns
Filtering Arrays
svelte1<script lang="ts"> 2 import { campaigns } from '$lib/data/campaigns'; 3 4 let selectedCategory = 'all'; 5 6 $: filteredCampaigns = selectedCategory === 'all' 7 ? campaigns 8 : campaigns.filter(c => c.category === selectedCategory); 9</script>
Searching
svelte1<script lang="ts"> 2 let searchQuery = ''; 3 let campaigns = [...]; 4 5 $: searchResults = campaigns.filter(campaign => 6 campaign.title.toLowerCase().includes(searchQuery.toLowerCase()) || 7 campaign.shortDescription.toLowerCase().includes(searchQuery.toLowerCase()) 8 ); 9</script>
Sorting
svelte1<script lang="ts"> 2 let submissions = [...]; 3 let sortBy = 'timestamp'; // 'timestamp' | 'name' | 'status' 4 5 $: sortedSubmissions = [...submissions].sort((a, b) => { 6 if (sortBy === 'timestamp') { 7 return b.timestamp - a.timestamp; // Newest first 8 } 9 if (sortBy === 'name') { 10 return a.name.localeCompare(b.name); 11 } 12 return 0; 13 }); 14</script>
When to Use This Skill
- Working with Svelte stores
- Integrating Firebase/Firestore
- Managing form submissions
- Loading or filtering data
- Creating new data types
- Implementing CRUD operations
- Understanding data flow in the app
Related Skills
sikhaid-forms- Form submission patternssikhaid-components- Component state managementsikhaid-overview- Firebase configuration