diff --git a/components/organization/cards/OrganzationCards.tsx b/components/organization/cards/OrganzationCards.tsx index 32f05ab38..5a097a562 100644 --- a/components/organization/cards/OrganzationCards.tsx +++ b/components/organization/cards/OrganzationCards.tsx @@ -1,6 +1,14 @@ -import { ArrowRight, HandCoins, Triangle, Trophy } from 'lucide-react'; +'use client'; + +import { ArrowRight, HandCoins, Trophy } from 'lucide-react'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; interface OrganizationCardProps { id: string; @@ -27,97 +35,72 @@ export default function OrganizationCard({ }: OrganizationCardProps) { const router = useRouter(); return ( -
router.push(`/organizations/${id}/settings`)} - className='hover:shadow-primary/10 cursor-pointer rounded-xl border border-zinc-800 bg-black transition-shadow duration-300 hover:shadow-lg' - > -
-
-
+ +
router.push(`/organizations/${id}/settings`)} + className='hover:shadow-primary/10 cursor-pointer rounded-lg border border-zinc-800 bg-black transition-shadow duration-300 hover:shadow-lg' + > +
+
{`${name} -
-
-

{name}

-

- Created {new Date(createdAt).toLocaleDateString()} -

-
-
- -
-
-
-
- -
- - Hackathons - -
-
- {hackathons.count} -
-
- {hackathons.submissions > 5 ? ( - - ) : ( - - )} - - {hackathons.submissions} submission - {hackathons.submissions !== 1 ? 's' : ''} - +
+

+ {name} +

+

+ {new Date(createdAt).toLocaleDateString()} +

-
-
-
- -
- - Grants - -
-
- {grants.count} -
-
- {grants.applications > 5 ? ( - - ) : ( - - )} - - {grants.applications} application - {grants.applications !== 1 ? 's' : ''} - -
+
+ + +
+
+ +
+ + {hackathons.count} + +
+
+ +

+ {hackathons.count} hackathons ({hackathons.submissions}{' '} + submissions) +

+
+
+ + + +
+
+ +
+ + {grants.count} + +
+
+ +

+ {grants.count} grants ({grants.applications} applications) +

+
+
+ +
-
- - Manage Organization - - -
+
+ ); } diff --git a/components/organization/tabs/MembersTab/PermissionsTable.tsx b/components/organization/tabs/MembersTab/PermissionsTable.tsx index 027755cee..b80fd7b18 100644 --- a/components/organization/tabs/MembersTab/PermissionsTable.tsx +++ b/components/organization/tabs/MembersTab/PermissionsTable.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useState, useEffect } from 'react'; import { Table, TableBody, @@ -8,78 +9,362 @@ import { TableHeader, TableRow, } from '@/components/ui/table'; -import { Check, X, Users2 } from 'lucide-react'; +import { Check, X, Users2, Edit3, RotateCcw, Save } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { useOrganization } from '@/lib/providers/OrganizationProvider'; +import { toast } from 'sonner'; +import { OrganizationPermissions } from '@/types/organization-permission'; -const permissions = [ +interface PermissionConfig { + key: keyof OrganizationPermissions; + label: string; + owner: boolean; + admin: boolean | { value: boolean; note: string }; + member: boolean | { value: boolean; note: string }; + description?: string; +} + +const defaultPermissionConfigs: PermissionConfig[] = [ { - permission: 'Create / edit organization profile', + key: 'canEditProfile', + label: 'Create / edit organization profile', owner: true, admin: { value: true, note: 'edit only' }, member: false, + description: 'Modify organization name, logo, tagline, and about section', }, { - permission: 'Create and manage hackathons and grants', + key: 'canManageHackathons', + label: 'Create and manage hackathons and grants', owner: true, admin: true, member: false, + description: 'Create new hackathons/grants and manage existing ones', }, { - permission: 'Publish hackathons / grants', + key: 'canPublishHackathons', + label: 'Publish hackathons / grants', owner: true, admin: false, member: false, + description: 'Make hackathons and grants publicly visible', }, { - permission: 'View hackathons & analytics', + key: 'canViewAnalytics', + label: 'View hackathons & analytics', owner: true, admin: true, member: true, + description: 'Access analytics and view hackathon/grants data', }, { - permission: 'Invite & remove members', + key: 'canInviteMembers', + label: 'Invite & remove members', owner: true, admin: true, member: false, + description: 'Invite new members and remove existing members', }, { - permission: 'Assign roles', + key: 'canAssignRoles', + label: 'Assign roles', owner: true, admin: false, member: false, + description: 'Promote/demote members between admin and member roles', }, { - permission: 'Post announcements & messages', + key: 'canPostAnnouncements', + label: 'Post announcements & messages', owner: true, admin: false, member: false, + description: 'Create organization-wide announcements', }, { - permission: 'Comment / participate in discussions', + key: 'canComment', + label: 'Comment / participate in discussions', owner: true, admin: true, member: true, + description: 'Participate in organization discussions and comments', }, { - permission: 'Access submissions (view/judge if assigned)', + key: 'canAccessSubmissions', + label: 'Access submissions (view/judge if assigned)', owner: true, admin: true, member: { value: true, note: 'view only, unless assigned as judge' }, + description: 'View hackathon submissions and judge if assigned', }, { - permission: 'Delete organization', + key: 'canDeleteOrganization', + label: 'Delete organization', owner: true, admin: false, member: false, + description: 'Permanently delete the organization', }, ]; +interface EditablePermissions { + admin: Partial; + member: Partial; +} + export default function PermissionsTable() { + const [isEditing, setIsEditing] = useState(false); + const [permissions, setPermissions] = + useState(null); + const [editablePermissions, setEditablePermissions] = + useState({ admin: {}, member: {} }); + const [isCustom, setIsCustom] = useState(false); + const [canEdit, setCanEdit] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const { + activeOrgId, + getOrganizationPermissions, + updateOrganizationPermissions, + resetOrganizationPermissions, + isOwner, + } = useOrganization(); + + useEffect(() => { + loadPermissions(); + }, [activeOrgId]); + + const loadPermissions = async () => { + if (!activeOrgId) return; + + try { + setIsLoading(true); + const response = await getOrganizationPermissions(activeOrgId); + setPermissions(response.permissions); + setIsCustom(response.isCustom); + setCanEdit(response.canEdit); + + // Initialize editable permissions + const adminPerms: Partial = {}; + const memberPerms: Partial = {}; + + defaultPermissionConfigs.forEach(config => { + const currentValue = + response.permissions[config.key] ?? + (typeof config.admin === 'object' + ? config.admin.value + : config.admin); + + adminPerms[config.key] = currentValue; + memberPerms[config.key] = + response.permissions[config.key] ?? + (typeof config.member === 'object' + ? config.member.value + : config.member); + }); + + setEditablePermissions({ admin: adminPerms, member: memberPerms }); + } catch (error) { + toast.error('Failed to load permissions'); + console.error('Error loading permissions:', error); + } finally { + setIsLoading(false); + } + }; + + const handleAdminPermissionChange = ( + permissionKey: keyof OrganizationPermissions, + value: boolean + ) => { + if (!canEdit || !isEditing) return; + + setEditablePermissions(prev => ({ + ...prev, + admin: { + ...prev.admin, + [permissionKey]: value, + }, + })); + }; + + const handleMemberPermissionChange = ( + permissionKey: keyof OrganizationPermissions, + value: boolean + ) => { + if (!canEdit || !isEditing) return; + + setEditablePermissions(prev => ({ + ...prev, + member: { + ...prev.member, + [permissionKey]: value, + }, + })); + }; + + const handleSave = async () => { + if (!activeOrgId || !canEdit) return; + + setIsLoading(true); + try { + // Merge admin and member permissions + const mergedPermissions: OrganizationPermissions = { ...permissions! }; + + defaultPermissionConfigs.forEach(config => { + const adminValue = editablePermissions.admin[config.key]; + const memberValue = editablePermissions.member[config.key]; + + if (adminValue !== undefined) { + mergedPermissions[config.key] = adminValue; + } + if (memberValue !== undefined && config.key in mergedPermissions) { + mergedPermissions[config.key] = memberValue; + } + }); + + await updateOrganizationPermissions(activeOrgId, mergedPermissions); + setIsEditing(false); + setIsCustom(true); + setPermissions(mergedPermissions); + toast.success('Permissions updated successfully'); + } catch (error) { + toast.error('Failed to update permissions'); + console.error('Error updating permissions:', error); + } finally { + setIsLoading(false); + } + }; + + const handleReset = async () => { + if (!activeOrgId || !canEdit) return; + + setIsLoading(true); + try { + await resetOrganizationPermissions(activeOrgId); + await loadPermissions(); // Reload to get default permissions + setIsEditing(false); + setIsCustom(false); + toast.success('Permissions reset to defaults'); + } catch (error) { + toast.error('Failed to reset permissions'); + console.error('Error resetting permissions:', error); + } finally { + setIsLoading(false); + } + }; + + const getBooleanValue = ( + config: PermissionConfig, + role: 'admin' | 'member' + ): boolean => { + if (isEditing) { + const editableValue = editablePermissions[role][config.key]; + if (editableValue !== undefined) return editableValue; + } + + if (permissions && permissions[config.key] !== undefined) { + return permissions[config.key]; + } + + return typeof config[role] === 'object' ? config[role].value : config[role]; + }; + + const getNote = ( + config: PermissionConfig, + role: 'admin' | 'member' + ): string | null => { + const value = config[role]; + return typeof value === 'object' ? value.note : null; + }; + + if (isLoading && !permissions) { + return ( +
+
+

+ {' '} + Organization Roles +

+
+
+ Loading permissions... +
+
+ ); + } + return (
-

- {' '} - Organization Roles -

+
+
+

+ {' '} + Organization Roles + {isCustom && ( + + Custom + + )} +

+ {canEdit && ( +

+ {isEditing + ? 'Editing permissions - changes will be saved when you click Save' + : 'You can customize permissions for your organization'} +

+ )} +
+ + {canEdit && ( +
+ {!isEditing ? ( + + ) : ( + <> + + + + + )} +
+ )} +
+ @@ -98,53 +383,93 @@ export default function PermissionsTable() { - {permissions.map((perm, index) => ( + {defaultPermissionConfigs.map((config, index) => ( - - {perm.permission} + +
{config.label}
+ {config.description && ( +
+ {config.description} +
+ )}
+ + {/* Owner - Always true and not editable */} - {perm.owner ? ( - - ) : ( - - )} + + + {/* Admin - Separate control */} - {typeof perm.admin === 'object' ? ( -
- - - {perm.admin.note} - -
- ) : perm.admin ? ( - + {isEditing && canEdit ? ( + + handleAdminPermissionChange(config.key, e.target.checked) + } + className='h-4 w-4 rounded border-gray-600 bg-gray-800 text-green-500 focus:ring-green-500 focus:ring-offset-gray-900' + /> ) : ( - +
+ {getBooleanValue(config, 'admin') ? ( + <> + + {getNote(config, 'admin') && ( + + {getNote(config, 'admin')} + + )} + + ) : ( + + )} +
)}
+ + {/* Member - Separate control */} - {typeof perm.member === 'object' ? ( -
- - - {perm.member.note} - -
- ) : perm.member ? ( - + {isEditing && canEdit ? ( + + handleMemberPermissionChange(config.key, e.target.checked) + } + className='h-4 w-4 rounded border-gray-600 bg-gray-800 text-green-500 focus:ring-green-500 focus:ring-offset-gray-900' + /> ) : ( - +
+ {getBooleanValue(config, 'member') ? ( + <> + + {getNote(config, 'member') && ( + + {getNote(config, 'member')} + + )} + + ) : ( + + )} +
)}
))}
+ + {!canEdit && isOwner() && ( +
+ Note: Only organization owners can modify + permissions. You are viewing the permissions but cannot make changes. +
+ )}
); } diff --git a/lib/api/organization.ts b/lib/api/organization.ts index e4604b052..0648fb3cf 100644 --- a/lib/api/organization.ts +++ b/lib/api/organization.ts @@ -31,6 +31,11 @@ export interface AssignRoleRequest { email: string; } +export interface AssignRoleRequest { + action: 'promote' | 'demote'; + email: string; +} + export interface CreateOrganizationRequest { name: string; logo?: string; @@ -138,6 +143,33 @@ export interface AssignRoleResponse extends ApiResponse { message: string; } +export interface RawOrganizationPermissions { + [key: string]: { + owner: boolean; + admin: boolean; + member: boolean; + }; +} + +export interface GetPermissionsResponse + extends ApiResponse<{ + permissions: RawOrganizationPermissions; + isCustom: boolean; + canEdit: boolean; + }> { + success: true; + data: { + permissions: RawOrganizationPermissions; + isCustom: boolean; + canEdit: boolean; + }; + message: string; +} + +export interface UpdatePermissionsRequest { + permissions: RawOrganizationPermissions; +} + /** * Create a new organization */ @@ -537,6 +569,33 @@ export const importOrganizationData = async ( return res.data; }; +export const getOrganizationPermissions = async ( + organizationId: string +): Promise => { + const res = await api.get(`/organizations/${organizationId}/permissions`); + return res.data; +}; + +export const updateOrganizationPermissions = async ( + organizationId: string, + data: UpdatePermissionsRequest +): Promise => { + const res = await api.patch( + `/organizations/${organizationId}/permissions`, + data + ); + return res.data; +}; + +export const resetOrganizationPermissions = async ( + organizationId: string +): Promise => { + const res = await api.post( + `/organizations/${organizationId}/permissions/reset` + ); + return res.data; +}; + export const assignOrganizationRole = async ( organizationId: string, data: AssignRoleRequest diff --git a/lib/providers/OrganizationProvider.tsx b/lib/providers/OrganizationProvider.tsx index cc201a0fb..3d44ba2eb 100644 --- a/lib/providers/OrganizationProvider.tsx +++ b/lib/providers/OrganizationProvider.tsx @@ -32,8 +32,16 @@ import { deleteOrganization, assignOrganizationRole, transferOrganizationOwnership, + getOrganizationPermissions as fetchOrganizationPermissions, + updateOrganizationPermissions as updateOrganizationPermissionsAPI, + resetOrganizationPermissions as resetOrganizationPermissionsAPI, } from '../api/organization'; import { getProfileCompletionStatus as getOrgProfileCompletionStatus } from '../organization-utils'; +import type { + GetPermissionsResponse, + RawOrganizationPermissions, +} from '../api/organization'; +import { OrganizationPermissions } from '@/types/organization-permission'; const OrganizationContext = createContext( undefined @@ -90,6 +98,82 @@ const initialState: OrganizationContextState = { refreshCount: 0, }; +export function mapRawToResolvedPermissions( + raw: RawOrganizationPermissions +): OrganizationPermissions { + return { + canEditProfile: raw['create_edit_profile']?.admin ?? false, + canCreateProfile: raw['create_edit_profile']?.owner ?? false, + canManageHackathons: raw['manage_hackathons_grants']?.admin ?? false, + canPublishHackathons: raw['publish_hackathons']?.owner ?? false, + canViewAnalytics: raw['view_analytics']?.member ?? false, + canInviteMembers: raw['invite_remove_members']?.admin ?? false, + canRemoveMembers: raw['invite_remove_members']?.admin ?? false, + canAssignRoles: raw['assign_roles']?.owner ?? false, + canPostAnnouncements: raw['post_announcements']?.admin ?? false, + canComment: raw['comment_discussions']?.member ?? false, + canAccessSubmissions: raw['access_submissions']?.member ?? false, + canDeleteOrganization: raw['delete_organization']?.owner ?? false, + }; +} + +export function mapResolvedToRawPermissions( + resolved: OrganizationPermissions +): RawOrganizationPermissions { + return { + create_edit_profile: { + owner: true, // Owner always has this permission + admin: resolved.canEditProfile, + member: false, + }, + manage_hackathons_grants: { + owner: true, // Owner always has this permission + admin: resolved.canManageHackathons, + member: false, + }, + publish_hackathons: { + owner: true, // Owner always has this permission + admin: false, + member: false, + }, + view_analytics: { + owner: true, // Owner always has this permission + admin: true, // Admin always has view access + member: resolved.canViewAnalytics, + }, + invite_remove_members: { + owner: true, // Owner always has this permission + admin: resolved.canInviteMembers || resolved.canRemoveMembers, + member: false, + }, + assign_roles: { + owner: true, // Owner always has this permission + admin: false, + member: false, + }, + post_announcements: { + owner: true, // Owner always has this permission + admin: resolved.canPostAnnouncements, + member: false, + }, + comment_discussions: { + owner: true, // Owner always has this permission + admin: true, // Admin always can comment + member: resolved.canComment, + }, + access_submissions: { + owner: true, // Owner always has this permission + admin: true, // Admin always has access + member: resolved.canAccessSubmissions, + }, + delete_organization: { + owner: true, // Owner always has this permission + admin: false, + member: false, + }, + }; +} + function organizationReducer( state: OrganizationContextState, action: OrganizationAction @@ -831,6 +915,7 @@ export function OrganizationProvider({ .then(() => { const savedOrgId = initialOrgId || localStorage.getItem(STORAGE_KEYS.ACTIVE_ORG_ID); + initialOrgId || localStorage.getItem(STORAGE_KEYS.ACTIVE_ORG_ID); if (savedOrgId) { logger.info({ eventType: 'org.initialize.set_active_after_fetch', @@ -894,6 +979,86 @@ export function OrganizationProvider({ }, [] ); + + const getOrganizationPermissions = useCallback( + async ( + orgId: string + ): Promise<{ + permissions: OrganizationPermissions; + isCustom: boolean; + canEdit: boolean; + }> => { + try { + const response: GetPermissionsResponse = + await fetchOrganizationPermissions(orgId); + const { + permissions: rawPermissions, + isCustom, + canEdit, + } = response.data; + const permissions = mapRawToResolvedPermissions(rawPermissions); + return { permissions, isCustom, canEdit }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Failed to get permissions'; + dispatch({ type: 'SET_ERROR', payload: { error: errorMessage } }); + throw error; + } + }, + [dispatch] + ); + + const updateOrganizationPermissions = useCallback( + async ( + orgId: string, + permissions: OrganizationPermissions + ): Promise => { + try { + dispatch({ type: 'SET_LOADING', payload: { isLoading: true } }); + + const rawPermissions = mapResolvedToRawPermissions(permissions); + const response = await updateOrganizationPermissionsAPI(orgId, { + permissions: rawPermissions, + }); + + const updatedOrg = response.data; + dispatch({ type: 'UPDATE_ORGANIZATION', payload: updatedOrg }); + return updatedOrg; + } catch (error) { + const errorMessage = + error instanceof Error + ? error.message + : 'Failed to update permissions'; + dispatch({ type: 'SET_ERROR', payload: { error: errorMessage } }); + throw error; + } finally { + dispatch({ type: 'SET_LOADING', payload: { isLoading: false } }); + } + }, + [dispatch] + ); + + const resetOrganizationPermissions = useCallback( + async (orgId: string): Promise => { + try { + dispatch({ type: 'SET_LOADING', payload: { isLoading: true } }); + const response = await resetOrganizationPermissionsAPI(orgId); + const updatedOrg = response.data; + dispatch({ type: 'UPDATE_ORGANIZATION', payload: updatedOrg }); + return updatedOrg; + } catch (error) { + const errorMessage = + error instanceof Error + ? error.message + : 'Failed to reset permissions'; + dispatch({ type: 'SET_ERROR', payload: { error: errorMessage } }); + throw error; + } finally { + dispatch({ type: 'SET_LOADING', payload: { isLoading: false } }); + } + }, + [dispatch] + ); // Context value const contextValue: OrganizationContextValue = { ...state, @@ -919,6 +1084,9 @@ export function OrganizationProvider({ getProfileCompletionStatus, assignRole, transferOwnership, + getOrganizationPermissions, + updateOrganizationPermissions, + resetOrganizationPermissions, }; return ( diff --git a/lib/providers/organization-types.ts b/lib/providers/organization-types.ts index 589be6988..ba4f21441 100644 --- a/lib/providers/organization-types.ts +++ b/lib/providers/organization-types.ts @@ -1,3 +1,4 @@ +import { OrganizationPermissions } from '@/types/organization-permission'; import { Organization } from '../api/types'; export interface OrganizationSummary { @@ -194,4 +195,14 @@ export interface OrganizationContextActions { orgId: string, newOwnerEmail: string ) => Promise; + getOrganizationPermissions: (orgId: string) => Promise<{ + permissions: OrganizationPermissions; + isCustom: boolean; + canEdit: boolean; + }>; + updateOrganizationPermissions: ( + orgId: string, + permissions: OrganizationPermissions + ) => Promise; + resetOrganizationPermissions: (orgId: string) => Promise; } diff --git a/types/organization-permission.ts b/types/organization-permission.ts new file mode 100644 index 000000000..26398373f --- /dev/null +++ b/types/organization-permission.ts @@ -0,0 +1,295 @@ +// ===== Organization Types ===== + +export type OrganizationRole = 'owner' | 'admin' | 'member'; + +export type PermissionValue = boolean | { value: boolean; note?: string }; + +export interface RolePermissions { + owner: boolean; // Owner always has full permissions + admin: PermissionValue; + member: PermissionValue; +} + +export interface CustomPermissions { + create_edit_profile: RolePermissions; + manage_hackathons_grants: RolePermissions; + publish_hackathons: RolePermissions; + view_analytics: RolePermissions; + invite_remove_members: RolePermissions; + assign_roles: RolePermissions; + post_announcements: RolePermissions; + comment_discussions: RolePermissions; + access_submissions: RolePermissions; + delete_organization: RolePermissions; +} + +export interface OrganizationPermissions { + canEditProfile: boolean; + canCreateProfile: boolean; + canManageHackathons: boolean; + canPublishHackathons: boolean; + canViewAnalytics: boolean; + canInviteMembers: boolean; + canRemoveMembers: boolean; + canAssignRoles: boolean; + canPostAnnouncements: boolean; + canComment: boolean; + canAccessSubmissions: boolean; + canDeleteOrganization: boolean; +} + +export interface OrganizationMember { + email: string; + firstName?: string; + lastName?: string; + username?: string; + avatar?: string; +} + +export interface Organization { + _id: string; + name: string; + logo: string; + tagline: string; + about: string; + links: { + website: string; + x: string; + github: string; + others: string; + }; + members: string[]; + admins: string[]; + owner: string; + hackathons: string[]; + grants: string[]; + isProfileComplete: boolean; + pendingInvites: string[]; + customPermissions?: CustomPermissions; + createdAt: string; + updatedAt: string; +} + +export interface UserRoleResponse { + role: OrganizationRole; + permissions: OrganizationPermissions; +} + +export interface MembersWithRolesResponse { + owner: OrganizationMember; + admins: OrganizationMember[]; + members: OrganizationMember[]; + totalCount: { + owner: number; + admins: number; + members: number; + total: number; + }; +} + +// ===== API Request Types ===== + +export interface AssignRoleRequest { + action: 'promote' | 'demote'; + email: string; +} + +export interface UpdateMembersRequest { + action: 'add' | 'remove'; + email: string; +} + +export interface UpdateHackathonsRequest { + action: 'add' | 'remove'; + hackathonId: string; +} + +export interface UpdateGrantsRequest { + action: 'add' | 'remove'; + grantId: string; +} + +export interface SendInviteRequest { + emails: string[]; +} + +export interface TransferOwnershipRequest { + newOwnerEmail: string; +} + +export interface UpdateProfileRequest { + name?: string; + logo?: string; + tagline?: string; + about?: string; +} + +export interface UpdateLinksRequest { + website?: string; + x?: string; + github?: string; + others?: string; +} + +// ===== API Response Types ===== + +export interface ApiResponse { + success: boolean; + message: string; + data?: T; + error?: string; +} + +// ===== React Hook Example ===== + +/** + * Example custom hook for managing organization permissions + * + * Usage: + * ```tsx + * const { role, permissions, isLoading } = useOrganizationRole(orgId); + * + * if (permissions.canEditProfile) { + * // Show edit button + * } + * ``` + */ +export interface UseOrganizationRoleReturn { + role: OrganizationRole | null; + permissions: OrganizationPermissions | null; + isLoading: boolean; + error: string | null; + refetch: () => Promise; +} + +// ===== Permission Helper Functions ===== + +/** + * Check if user has specific permission + */ +export function hasPermission( + permissions: OrganizationPermissions | null, + permission: keyof OrganizationPermissions +): boolean { + return permissions?.[permission] ?? false; +} + +/** + * Get permission level for a specific action + */ +export function getRequiredRole( + action: + | 'editProfile' + | 'createProfile' + | 'manageHackathons' + | 'publishHackathons' + | 'viewAnalytics' + | 'inviteMembers' + | 'removeMembers' + | 'assignRoles' + | 'postAnnouncements' + | 'comment' + | 'accessSubmissions' + | 'deleteOrganization' +): OrganizationRole[] { + const permissionMap: Record = { + editProfile: ['owner', 'admin'], + createProfile: ['owner'], + manageHackathons: ['owner', 'admin'], + publishHackathons: ['owner'], + viewAnalytics: ['owner', 'admin', 'member'], + inviteMembers: ['owner', 'admin'], + removeMembers: ['owner', 'admin'], + assignRoles: ['owner'], + postAnnouncements: ['owner'], + comment: ['owner', 'admin', 'member'], + accessSubmissions: ['owner', 'admin', 'member'], + deleteOrganization: ['owner'], + }; + + return permissionMap[action] || []; +} + +/** + * Check if a role has permission for an action + */ +export function roleHasPermission( + role: OrganizationRole, + action: Parameters[0] +): boolean { + const requiredRoles = getRequiredRole(action); + return requiredRoles.includes(role); +} + +/** + * Get role badge color for UI + */ +export function getRoleBadgeColor(role: OrganizationRole): string { + const colors = { + owner: 'bg-purple-500/10 text-purple-400 border-purple-500/20', + admin: 'bg-blue-500/10 text-blue-400 border-blue-500/20', + member: 'bg-gray-500/10 text-gray-400 border-gray-500/20', + }; + + return colors[role]; +} + +/** + * Get role display name + */ +export function getRoleDisplayName(role: OrganizationRole): string { + const names = { + owner: 'Owner', + admin: 'Admin', + member: 'Member', + }; + + return names[role]; +} + +// ===== Permission Constants ===== + +export const OWNER_PERMISSIONS: OrganizationPermissions = { + canEditProfile: true, + canCreateProfile: true, + canManageHackathons: true, + canPublishHackathons: true, + canViewAnalytics: true, + canInviteMembers: true, + canRemoveMembers: true, + canAssignRoles: true, + canPostAnnouncements: true, + canComment: true, + canAccessSubmissions: true, + canDeleteOrganization: true, +}; + +export const ADMIN_PERMISSIONS: OrganizationPermissions = { + canEditProfile: true, + canCreateProfile: false, + canManageHackathons: true, + canPublishHackathons: false, + canViewAnalytics: true, + canInviteMembers: true, + canRemoveMembers: true, + canAssignRoles: false, + canPostAnnouncements: false, + canComment: true, + canAccessSubmissions: true, + canDeleteOrganization: false, +}; + +export const MEMBER_PERMISSIONS: OrganizationPermissions = { + canEditProfile: false, + canCreateProfile: false, + canManageHackathons: false, + canPublishHackathons: false, + canViewAnalytics: true, + canInviteMembers: false, + canRemoveMembers: false, + canAssignRoles: false, + canPostAnnouncements: false, + canComment: true, + canAccessSubmissions: true, // view only + canDeleteOrganization: false, +};