+
+
CLUB MANAGEMENT / ROSTER
+
Members
+
Your active roster, officer ranks, and membership controls.
+
+
+ {context.isDemoFixture && Development preview • changes are disabled.
}
+
+ {([['Members loaded', members.length], ['Administrators loaded', members.filter((m) => m.role === 'admin' || m.role === 'owner').length], ['Showing', visible.length]] as const).map(([label, count]) => (
+
+
{label}
{loading || error ? '—' : count}
+
+ ))}
+
+ {hasMore && Search and counts cover loaded members. Load more to include the rest of your roster.
}
+
+
+
+
+
+
{notice}
+ {loading ?
Loading members…
+ : error ?
{error}
+ : visible.length === 0 ?
{members.length ? 'No members match your search.' : 'There are no active members to display.'}
+ :
+ | Rider | Display rank | Access | Actions |
+ {visible.map((member) =>
+ {member.displayName || 'Rider'} {member.handle || member.uid} |
+ {member.displayRank || 'Member'} |
+ {member.role} |
+
+
+ {canRemove(member) && }
+ |
+
)}
+
}
+
+ Display ranks are club titles. Owner and Admin roles control administrative access.
+ {selection && setSelection(null)}>
+
+ }
+
+ );
+}
diff --git a/website/components/founder-portal/founder-sidebar.tsx b/website/components/founder-portal/founder-sidebar.tsx
index 5bd75ab..7e4b596 100644
--- a/website/components/founder-portal/founder-sidebar.tsx
+++ b/website/components/founder-portal/founder-sidebar.tsx
@@ -41,7 +41,7 @@ export function FounderSidebar({
const navItemsTop = [
{ id: 'overview', label: 'Overview', icon: 'dashboard', badge: 'ACTIVE', badgeType: 'primary', disabled: false },
{ id: 'profile', label: 'Club Profile', icon: 'shield', disabled: false },
- { id: 'members', label: 'Members', icon: 'group', badge: 'SOON', badgeType: 'disabled', disabled: true },
+ { id: 'members', label: 'Members', icon: 'group', disabled: false },
{
id: 'applications',
label: 'Applications',
@@ -63,7 +63,7 @@ export function FounderSidebar({
disabled: false,
},
{ id: 'routes', label: 'Routes', icon: 'map', badge: 'SOON', badgeType: 'disabled', disabled: true },
- { id: 'wall', label: 'Wall', icon: 'forum', badge: 'SOON', badgeType: 'disabled', disabled: true },
+ { id: 'wall', label: 'Wall', icon: 'forum', disabled: false },
{ id: 'chat', label: 'Chat', icon: 'chat', badge: 'SOON', badgeType: 'disabled', disabled: true },
{ id: 'stats', label: 'Stats', icon: 'query_stats', badge: 'SOON', badgeType: 'disabled', disabled: true },
];
@@ -76,6 +76,8 @@ export function FounderSidebar({
function getRouteHref(id: string) {
if (id === 'overview') return `/clubs/${clubParam}/manage${queryParam}`;
if (id === 'profile') return `/clubs/${clubParam}/manage/profile${queryParam}`;
+ if (id === 'members') return `/clubs/${clubParam}/manage/members${queryParam}`;
+ if (id === 'wall') return `/clubs/${clubParam}/manage/wall${queryParam}`;
if (id === 'applications') return `/clubs/${clubParam}/manage/applications${queryParam}`;
if (id === 'ranks_roles') return `/clubs/${clubParam}/manage/ranks-roles${queryParam}`;
if (id === 'rides') return `/clubs/${clubParam}/manage/rides${queryParam}`;
@@ -102,7 +104,7 @@ export function FounderSidebar({
const isSettings = item.id === 'settings';
const isApplications = item.id === 'applications';
const isRides = item.id === 'rides';
- const isSubRoute = isProfile || isSettings || isApplications || isRides || item.id === 'ranks_roles';
+ const isSubRoute = isProfile || isSettings || isApplications || isRides || item.id === 'ranks_roles' || item.id === 'members' || item.id === 'wall';
if (item.disabled) {
return (
diff --git a/website/components/founder-portal/founder-wall.tsx b/website/components/founder-portal/founder-wall.tsx
new file mode 100644
index 0000000..150cc87
--- /dev/null
+++ b/website/components/founder-portal/founder-wall.tsx
@@ -0,0 +1,101 @@
+'use client';
+
+import React, { useEffect, useRef, useState } from 'react';
+import type { FounderClubContext } from '../../lib/club-data';
+import { sanitizePortalError } from '../../lib/club-data';
+import { createWallPost, deleteWallPost, replyToWallPost, setWallPostPinned, subscribeClubWall, type WallPost } from '../../lib/club-community';
+import { CommunityDialog, communityButton, communityInput } from './community-dialog';
+
+function postDate(value: string): string {
+ if (!value) return 'Sending…';
+ const date = new Date(value);
+ return Number.isNaN(date.getTime()) ? 'Date unavailable' : date.toLocaleString();
+}
+
+export function FounderWall({ context }: { context: FounderClubContext }) {
+ const [posts, setPosts] = useState