diff --git a/website/app/clubs/[clubId]/manage/ranks-roles/page.tsx b/website/app/clubs/[clubId]/manage/ranks-roles/page.tsx index 6c74f0d..11fa882 100644 --- a/website/app/clubs/[clubId]/manage/ranks-roles/page.tsx +++ b/website/app/clubs/[clubId]/manage/ranks-roles/page.tsx @@ -34,7 +34,6 @@ export default function ClubFounderRanksRolesPage({ params }: PageProps) { const [members, setMembers] = useState([]); const [permissions, setPermissions] = useState(DEFAULT_RANK_PERMISSIONS); const [selectedRankId, setSelectedRankId] = useState('president'); - const [isSavingPermissions, setIsSavingPermissions] = useState(false); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [unauthenticated, setUnauthenticated] = useState(false); @@ -104,16 +103,6 @@ export default function ClubFounderRanksRolesPage({ params }: PageProps) { })); } - function handleResetDefaults() { - setPermissions(DEFAULT_RANK_PERMISSIONS); - } - - async function handleSavePermissions() { - setIsSavingPermissions(true); - await new Promise((r) => setTimeout(r, 400)); - setIsSavingPermissions(false); - } - if (loading) { return (
@@ -220,16 +209,23 @@ export default function ClubFounderRanksRolesPage({ params }: PageProps) { /> {/* Interactive Permission Toggle Switches Matrix */} + {/* + Per-rank capability permissions have no server-side persistence contract yet. + The enforced authorization model is role-based (Owner / Admin) via + getPortalPermissions in lib/club-data.ts, not this rank matrix. Rendering the + matrix read-only (persistenceAvailable={false}) keeps it truthful: it shows the + standard governance conventions without claiming a save that does not occur. + See website/docs/backend-contracts/club-rank-permissions.md for the proposed + contract that would flip persistenceAvailable to true. + */}
diff --git a/website/components/founder-portal/club-permission-matrix.tsx b/website/components/founder-portal/club-permission-matrix.tsx index ebb98d5..86dcfc9 100644 --- a/website/components/founder-portal/club-permission-matrix.tsx +++ b/website/components/founder-portal/club-permission-matrix.tsx @@ -9,10 +9,22 @@ interface ClubPermissionMatrixProps { permissions: Record>; selectedRankId: string | null; onTogglePermission: (rankId: string, capability: ClubCapability) => void; - onResetDefaults: () => void; - onSavePermissions: () => Promise; - isSaving: boolean; + onResetDefaults?: () => void; + onSavePermissions?: () => Promise; + isSaving?: boolean; canEdit: boolean; + /** + * Whether a server-side persistence contract for per-rank capability + * permissions actually exists. Defaults to false. + * + * The enforced club authorization model is role-based (Owner / Admin), not + * rank-based (see getPortalPermissions in lib/club-data.ts). No Firestore + * document or API persists this capability matrix today, so by default the + * matrix renders as a read-only reference of the standard governance model. + * The editable + save path is retained for when a backend contract ships + * (see website/docs/backend-contracts/club-rank-permissions.md). + */ + persistenceAvailable?: boolean; } export function ClubPermissionMatrix({ @@ -23,12 +35,19 @@ export function ClubPermissionMatrix({ onTogglePermission, onResetDefaults, onSavePermissions, - isSaving, + isSaving = false, canEdit, + persistenceAvailable = false, }: ClubPermissionMatrixProps) { const [saveSuccess, setSaveSuccess] = useState(false); + // Edit + save controls are only live when the caller both grants edit rights + // AND a real persistence backend is wired. Without persistence the matrix is + // a truthful read-only reference: it never mutates and never reports a save. + const editable = canEdit && persistenceAvailable && typeof onSavePermissions === 'function'; + async function handleSave() { + if (!editable || !onSavePermissions) return; await onSavePermissions(); setSaveSuccess(true); setTimeout(() => setSaveSuccess(false), 2500); @@ -48,19 +67,21 @@ export function ClubPermissionMatrix({

- Configure delegated operational privileges per squad rank. Administrative roles enforce backend limits. + Standard delegated operational privileges per squad rank. Administrative roles enforce backend limits.

- {canEdit && ( + {editable ? (
- + {onResetDefaults && ( + + )}
+ ) : ( + + visibility + View-only reference + )} + {/* Truthful availability notice: shown whenever persistence is not wired. */} + {!editable && ( +
+ + info + + + This matrix shows MotorSignal’s standard rank conventions for reference. Custom per-rank + permissions are not saved yet. Who can actually manage this club is enforced by the{' '} + Owner and{' '} + Admin roles in the member roster — see + Ranks & Roles above. Per-rank customization is planned. + +
+ )} + {/* Desktop / Tablet Matrix Table */}
@@ -142,17 +187,22 @@ export function ClubPermissionMatrix({ {capabilities.map((cap) => { const isGranted = rankPerms[cap.id] ?? false; const isImmutableOwner = rank.id === 'president'; + const toggleDisabled = !editable || isImmutableOwner; return (