diff --git a/messages/en.json b/messages/en.json
index 2f9bed3..23d83fd 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -320,5 +320,11 @@
"events_mod_team": "Team",
"events_mod_team_desc": "Team members and shift scheduling",
"events_mod_sponsors": "Sponsors",
- "events_mod_sponsors_desc": "Sponsors, partners, and deliverables"
+ "events_mod_sponsors_desc": "Sponsors, partners, and deliverables",
+ "overview_subtitle": "Welcome back. Here's what's happening.",
+ "overview_stat_events": "Events",
+ "overview_upcoming_events": "Upcoming Events",
+ "overview_upcoming_empty": "No upcoming events. Create one to get started.",
+ "overview_view_all_events": "View all events",
+ "overview_more_members": "+{count} more"
}
\ No newline at end of file
diff --git a/messages/et.json b/messages/et.json
index b3be0d3..228d7cc 100644
--- a/messages/et.json
+++ b/messages/et.json
@@ -320,5 +320,11 @@
"events_mod_team": "Meeskond",
"events_mod_team_desc": "Meeskonnaliikmed ja vahetuste planeerimine",
"events_mod_sponsors": "Sponsorid",
- "events_mod_sponsors_desc": "Sponsorid, partnerid ja kohustused"
+ "events_mod_sponsors_desc": "Sponsorid, partnerid ja kohustused",
+ "overview_subtitle": "Tere tagasi. Siin on ülevaade toimuvast.",
+ "overview_stat_events": "Üritused",
+ "overview_upcoming_events": "Tulevased üritused",
+ "overview_upcoming_empty": "Tulevasi üritusi pole. Loo üks alustamiseks.",
+ "overview_view_all_events": "Vaata kõiki üritusi",
+ "overview_more_members": "+{count} veel"
}
\ No newline at end of file
diff --git a/src/lib/components/ui/ActivityFeed.svelte b/src/lib/components/ui/ActivityFeed.svelte
new file mode 100644
index 0000000..1f2db1d
--- /dev/null
+++ b/src/lib/components/ui/ActivityFeed.svelte
@@ -0,0 +1,132 @@
+
+
+{#if entries.length === 0}
+
+
history
+
{emptyLabel ?? m.activity_empty()}
+
+{:else}
+
+ {#each entries as entry}
+
+
{getActivityIcon(entry.action)}
+
+
+ {getDescription(entry)}
+
+
+
{formatTimeAgo(entry.created_at)}
+
+ {/each}
+
+{/if}
diff --git a/src/lib/components/ui/ContentSkeleton.svelte b/src/lib/components/ui/ContentSkeleton.svelte
new file mode 100644
index 0000000..e6e59da
--- /dev/null
+++ b/src/lib/components/ui/ContentSkeleton.svelte
@@ -0,0 +1,109 @@
+
+
+
+ {#if variant === "kanban"}
+
+ {#each Array(3) as _}
+
+
+
+
+
+ {#each Array(3) as __}
+
+ {/each}
+
+ {/each}
+
+ {:else if variant === "files"}
+
+
+ {#each Array(12) as _}
+
+ {/each}
+
+ {:else if variant === "calendar"}
+
+
+ {#each Array(7) as _}
+
+ {/each}
+ {#each Array(35) as _}
+
+ {/each}
+
+ {:else if variant === "settings"}
+
+
+
+
+
+
+ {:else if variant === "list"}
+
+ {#each Array(4) as _}
+
+ {/each}
+
+
+ {#each Array(5) as _}
+
+ {/each}
+
+ {:else if variant === "detail"}
+
+ {:else}
+
+ {#each Array(4) as _}
+
+ {/each}
+
+
+ {/if}
+
+
+
diff --git a/src/lib/components/ui/EventCard.svelte b/src/lib/components/ui/EventCard.svelte
new file mode 100644
index 0000000..4285283
--- /dev/null
+++ b/src/lib/components/ui/EventCard.svelte
@@ -0,0 +1,116 @@
+
+
+{#if compact}
+
+
+
+
+
+ {name}
+
+
+ {#if startDate}
+ {formatDate(startDate)}{endDate
+ ? ` — ${formatDate(endDate)}`
+ : ""}
+ {/if}
+ {#if venueName}
+ · {venueName}
+ {/if}
+
+
+
+
+{:else}
+
+
+
+
+
+ {#if startDate}
+
+ calendar_today
+ {formatDate(startDate)}{endDate
+ ? ` — ${formatDate(endDate)}`
+ : ""}
+
+ {/if}
+ {#if venueName}
+
+ location_on
+ {venueName}
+
+ {/if}
+
+
+{/if}
diff --git a/src/lib/components/ui/MemberList.svelte b/src/lib/components/ui/MemberList.svelte
new file mode 100644
index 0000000..97878a0
--- /dev/null
+++ b/src/lib/components/ui/MemberList.svelte
@@ -0,0 +1,71 @@
+
+
+
+ {#each visible as member}
+
+
+
+
+ {member.profiles?.full_name ||
+ member.profiles?.email ||
+ "Unknown"}
+
+
+ {member.role}
+
+
+
+ {/each}
+ {#if remaining > 0 && moreHref && moreLabel}
+
+ {moreLabel}
+
+ {/if}
+ {#if members.length === 0 && emptyLabel}
+
+ {emptyLabel}
+
+ {/if}
+
diff --git a/src/lib/components/ui/ModuleCard.svelte b/src/lib/components/ui/ModuleCard.svelte
new file mode 100644
index 0000000..daec6e7
--- /dev/null
+++ b/src/lib/components/ui/ModuleCard.svelte
@@ -0,0 +1,40 @@
+
+
+
+
+ {icon}
+
+
+ {label}
+
+ {description}
+
diff --git a/src/lib/components/ui/PageHeader.svelte b/src/lib/components/ui/PageHeader.svelte
new file mode 100644
index 0000000..034b157
--- /dev/null
+++ b/src/lib/components/ui/PageHeader.svelte
@@ -0,0 +1,46 @@
+
+
+
+
+ {#if icon}
+
{icon}
+ {/if}
+
+
{title}
+ {#if subtitle}
+
{subtitle}
+ {/if}
+
+
+ {#if actions}
+
+ {@render actions()}
+
+ {/if}
+
diff --git a/src/lib/components/ui/QuickLinkGrid.svelte b/src/lib/components/ui/QuickLinkGrid.svelte
new file mode 100644
index 0000000..40707e6
--- /dev/null
+++ b/src/lib/components/ui/QuickLinkGrid.svelte
@@ -0,0 +1,30 @@
+
+
+
diff --git a/src/lib/components/ui/SectionCard.svelte b/src/lib/components/ui/SectionCard.svelte
new file mode 100644
index 0000000..bdb4e27
--- /dev/null
+++ b/src/lib/components/ui/SectionCard.svelte
@@ -0,0 +1,41 @@
+
+
+
+ {#if title || titleRight}
+
+ {#if title}
+
{title}
+ {/if}
+ {#if titleRight}
+ {@render titleRight()}
+ {/if}
+
+ {/if}
+ {@render children()}
+
diff --git a/src/lib/components/ui/StatCard.svelte b/src/lib/components/ui/StatCard.svelte
new file mode 100644
index 0000000..54f06ea
--- /dev/null
+++ b/src/lib/components/ui/StatCard.svelte
@@ -0,0 +1,58 @@
+
+
+{#if href}
+
+
+ {icon}
+
+
+
+{:else}
+
+{/if}
diff --git a/src/lib/components/ui/StatusBadge.svelte b/src/lib/components/ui/StatusBadge.svelte
new file mode 100644
index 0000000..7e45307
--- /dev/null
+++ b/src/lib/components/ui/StatusBadge.svelte
@@ -0,0 +1,30 @@
+
+
+{status}
diff --git a/src/lib/components/ui/TabBar.svelte b/src/lib/components/ui/TabBar.svelte
new file mode 100644
index 0000000..fa033cf
--- /dev/null
+++ b/src/lib/components/ui/TabBar.svelte
@@ -0,0 +1,37 @@
+
+
+
+ {#each tabs as tab}
+
+ {/each}
+
diff --git a/src/lib/components/ui/index.ts b/src/lib/components/ui/index.ts
index eb32b1c..305a794 100644
--- a/src/lib/components/ui/index.ts
+++ b/src/lib/components/ui/index.ts
@@ -26,6 +26,17 @@ export { default as Icon } from './Icon.svelte';
export { default as AssigneePicker } from './AssigneePicker.svelte';
export { default as ContextMenu } from './ContextMenu.svelte';
export { default as PageSkeleton } from './PageSkeleton.svelte';
+export { default as PageHeader } from './PageHeader.svelte';
+export { default as SectionCard } from './SectionCard.svelte';
+export { default as StatCard } from './StatCard.svelte';
+export { default as StatusBadge } from './StatusBadge.svelte';
+export { default as TabBar } from './TabBar.svelte';
+export { default as MemberList } from './MemberList.svelte';
+export { default as ActivityFeed } from './ActivityFeed.svelte';
+export { default as EventCard } from './EventCard.svelte';
+export { default as ContentSkeleton } from './ContentSkeleton.svelte';
+export { default as QuickLinkGrid } from './QuickLinkGrid.svelte';
+export { default as ModuleCard } from './ModuleCard.svelte';
export { default as ImagePreviewModal } from './ImagePreviewModal.svelte';
export { default as Twemoji } from './Twemoji.svelte';
export { default as EmojiPicker } from './EmojiPicker.svelte';
diff --git a/src/routes/[orgSlug]/+layout.server.ts b/src/routes/[orgSlug]/+layout.server.ts
index e2345fa..153acd2 100644
--- a/src/routes/[orgSlug]/+layout.server.ts
+++ b/src/routes/[orgSlug]/+layout.server.ts
@@ -20,7 +20,7 @@ export const load: LayoutServerLoad = async ({ params, locals }) => {
}
// Now fetch membership, members, activity, and user profile in parallel (all depend on org.id)
- const [membershipResult, membersResult, activityResult, profileResult, docCountResult, folderCountResult, kanbanCountResult] = await Promise.all([
+ const [membershipResult, membersResult, activityResult, profileResult, docCountResult, folderCountResult, kanbanCountResult, eventCountResult] = await Promise.all([
locals.supabase
.from('org_members')
.select('role, role_id')
@@ -68,7 +68,11 @@ export const load: LayoutServerLoad = async ({ params, locals }) => {
.from('documents')
.select('id', { count: 'exact', head: true })
.eq('org_id', org.id)
- .eq('type', 'kanban')
+ .eq('type', 'kanban'),
+ locals.supabase
+ .from('events')
+ .select('id', { count: 'exact', head: true })
+ .eq('org_id', org.id)
]);
const { data: membership } = membershipResult;
@@ -81,6 +85,7 @@ export const load: LayoutServerLoad = async ({ params, locals }) => {
documentCount: docCountResult.count ?? 0,
folderCount: folderCountResult.count ?? 0,
kanbanCount: kanbanCountResult.count ?? 0,
+ eventCount: eventCountResult.count ?? 0,
};
if (!membership) {
@@ -121,6 +126,15 @@ export const load: LayoutServerLoad = async ({ params, locals }) => {
profiles: (m.user_id ? memberProfilesMap[m.user_id] : null) ?? null
}));
+ // Fetch upcoming events for the overview
+ const { data: upcomingEvents } = await locals.supabase
+ .from('events')
+ .select('id, name, slug, status, start_date, end_date, color, venue_name')
+ .eq('org_id', org.id)
+ .in('status', ['planning', 'active'])
+ .order('start_date', { ascending: true, nullsFirst: false })
+ .limit(5);
+
return {
org,
userRole: membership.role,
@@ -128,6 +142,7 @@ export const load: LayoutServerLoad = async ({ params, locals }) => {
members,
recentActivity: recentActivity ?? [],
stats,
+ upcomingEvents: upcomingEvents ?? [],
user,
profile: profile ?? { id: user.id, email: user.email ?? '', full_name: null, avatar_url: null }
};
diff --git a/src/routes/[orgSlug]/+layout.svelte b/src/routes/[orgSlug]/+layout.svelte
index 9cffd9e..6da719f 100644
--- a/src/routes/[orgSlug]/+layout.svelte
+++ b/src/routes/[orgSlug]/+layout.svelte
@@ -1,10 +1,10 @@
{data.org.name} | Root
-
-
-
- {data.org.name}
- {m.overview_title()}
-
-
-
-
- {#each statCards as stat}
- {#if stat.href}
+
-
-
-
-
-
- {m.activity_title()}
-
-
- {#if recentActivity.length === 0}
-
celebration
- history
-
-
{m.activity_empty()}
-
- {:else}
-
- {#each recentActivity as entry}
-
-
- {getActivityIcon(entry.action)}
-
-
-
- {getActivityDescription(entry)}
-
-
- {formatTimeAgo(entry.created_at)}
-
-
-
- {/each}
-
+ {m.nav_events()}
+
{/if}
+ {/snippet}
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- {m.overview_quick_links()}
-
-
- {#each quickLinks as link}
+
+
+
+
+
+ {#snippet titleRight()}
{m.overview_view_all_events()}
+ {/snippet}
+
+ {#if upcomingEvents.length === 0}
+
celebration
- {link.icon}
-
- {link.label}
-
- {/each}
-
+ {m.overview_upcoming_empty()}
+
+ {:else}
+
+ {#each upcomingEvents as event}
+
+ {/each}
+
+ {/if}
+
+
+
+
+
+
-
-
-
-
- {m.overview_stat_members()}
-
-
{stats.memberCount}
+
+
+
+
+
+
+ {#snippet titleRight()}
+ {stats.memberCount}
+ {/snippet}
+
+
+
+ {#if isAdmin}
+
-
-
- {#each members.slice(0, 5) as member}
-
-
-
-
- {member.profiles?.full_name ||
- member.profiles?.email ||
- "Unknown"}
-
-
- {member.role}
-
-
+
+ settings
- {/each}
- {#if stats.memberCount > 5}
-
- +{stats.memberCount - 5} more
-
- {/if}
-
+
+
+ {m.nav_settings()}
+
+
{m.settings_general_title()}
+
+
+ {/if}
diff --git a/src/routes/[orgSlug]/account/+layout.svelte b/src/routes/[orgSlug]/account/+layout.svelte
new file mode 100644
index 0000000..ed00bc3
--- /dev/null
+++ b/src/routes/[orgSlug]/account/+layout.svelte
@@ -0,0 +1,36 @@
+
+
+
+
+
+ {#if isNavigatingHere}
+
+ {:else}
+
+ {@render children()}
+
+ {/if}
+
diff --git a/src/routes/[orgSlug]/account/+page.svelte b/src/routes/[orgSlug]/account/+page.svelte
index d05b6ce..9aba7c9 100644
--- a/src/routes/[orgSlug]/account/+page.svelte
+++ b/src/routes/[orgSlug]/account/+page.svelte
@@ -227,16 +227,8 @@
Account Settings | Root
-
-
-
-
{m.account_title()}
-
- {m.account_subtitle()}
-
-
-
-
+
+
diff --git a/src/routes/[orgSlug]/calendar/+layout.svelte b/src/routes/[orgSlug]/calendar/+layout.svelte
new file mode 100644
index 0000000..76cd2eb
--- /dev/null
+++ b/src/routes/[orgSlug]/calendar/+layout.svelte
@@ -0,0 +1,31 @@
+
+
+
+
+
+ {#if isNavigatingHere}
+
+ {:else}
+
+ {@render children()}
+
+ {/if}
+
diff --git a/src/routes/[orgSlug]/calendar/+page.svelte b/src/routes/[orgSlug]/calendar/+page.svelte
index bda44a0..a33f42b 100644
--- a/src/routes/[orgSlug]/calendar/+page.svelte
+++ b/src/routes/[orgSlug]/calendar/+page.svelte
@@ -456,13 +456,11 @@
Calendar - {data.org.name} | Root
-
-
-
-
- {m.calendar_title()}
-
-