-
-
-
- {hackathons.count}
-
-
- {hackathons.submissions > 5 ? (
-
- ) : (
-
- )}
-
- {hackathons.submissions} submission
- {hackathons.submissions !== 1 ? 's' : ''}
-
+
+
+ {name}
+
+
+ {new Date(createdAt).toLocaleDateString()}
+
-
-
-
- {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..188eec1ab 100644
--- a/lib/api/organization.ts
+++ b/lib/api/organization.ts
@@ -138,6 +138,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 +564,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..adc2273bb 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
@@ -894,6 +978,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 +1083,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,
+};