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
typescript
1import { 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:
svelte
1<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>
File: src/lib/stores/contact.ts
Purpose: Store contact form submissions (in-memory and Firestore)
typescript
1import { 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
typescript
1export 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
typescript
1export 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
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
svelte
1<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
typescript
1import { 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
typescript
1import { 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
typescript
1import { 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
typescript
1import { 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
typescript
1export 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
typescript
1export 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 submission
getCSRSubmissions() - Get all CSR submissions
- Both follow same pattern as contact/volunteer
Static Data Management
Campaigns Data
File: src/lib/data/campaigns.js
Structure:
javascript
1export 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:
svelte
1<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:
javascript
1export 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
typescript
1export 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)
typescript
1// 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
typescript
1import 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)
svelte
1<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
svelte
1<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
svelte
1<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
svelte
1<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
svelte
1<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
svelte
1<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
svelte
1<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
sikhaid-forms - Form submission patterns
sikhaid-components - Component state management
sikhaid-overview - Firebase configuration