) => void;
+ isCreating?: boolean;
}
-export default function ProfileTab({ initialData, onSave }: ProfileTabProps) {
+export default function ProfileTab({
+ organizationId,
+ initialData,
+ onSave,
+ isCreating = false,
+}: ProfileTabProps) {
const {
activeOrgId,
activeOrg,
@@ -52,13 +58,20 @@ export default function ProfileTab({ initialData, onSave }: ProfileTabProps) {
const previousOrgIdRef = useRef(null);
const router = useRouter();
- // useEffect(() => {
- // setIsInitialized(false);
- // previousOrgIdRef.current = null;
- // }, [organizationId]);
-
useEffect(() => {
- if (!isInitialized) {
+ if (isCreating) {
+ // For new organization, start with empty form
+ setFormData({
+ name: '',
+ logo: '',
+ tagline: '',
+ about: '',
+ });
+ setLogoPreview('');
+ setHasUserChanges(false);
+ setIsInitialized(true);
+ } else if (!isInitialized) {
+ // For existing organization, load from activeOrg or initialData
if (activeOrg) {
setFormData({
name: activeOrg.name || '',
@@ -82,10 +95,10 @@ export default function ProfileTab({ initialData, onSave }: ProfileTabProps) {
setIsInitialized(true);
}
}
- }, [activeOrg, initialData, isInitialized]);
+ }, [activeOrg, initialData, isInitialized, isCreating]);
useEffect(() => {
- if (activeOrg && isInitialized) {
+ if (activeOrg && isInitialized && !isCreating) {
const currentOrgId = activeOrg._id;
const previousOrgId = previousOrgIdRef.current;
@@ -101,7 +114,7 @@ export default function ProfileTab({ initialData, onSave }: ProfileTabProps) {
previousOrgIdRef.current = currentOrgId;
}
}
- }, [activeOrg, isInitialized]);
+ }, [activeOrg, isInitialized, isCreating]);
const handleInputChange = (field: string, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
@@ -248,6 +261,11 @@ export default function ProfileTab({ initialData, onSave }: ProfileTabProps) {
return;
}
+ if (!formData.logo.trim()) {
+ toast.error('Logo is required');
+ return;
+ }
+
if (!formData.tagline.trim()) {
toast.error('Tagline is required');
return;
@@ -267,35 +285,34 @@ export default function ProfileTab({ initialData, onSave }: ProfileTabProps) {
);
}
- if (activeOrgId && activeOrg) {
- await updateOrganization(activeOrgId, {
+ if (isCreating) {
+ // POST request - create new organization
+ const newOrg = await createOrganization({
name: formData.name,
logo: formData.logo,
tagline: formData.tagline,
about: formData.about,
});
- toast.success('Organization profile updated successfully');
- if (activeOrgId) {
- setTimeout(() => {
- router.push(`/organizations/${activeOrgId}/settings`);
- }, 500);
- }
- } else {
- await createOrganization({
+
+ toast.success('Organization created successfully');
+
+ // Redirect to the edit page
+ setTimeout(() => {
+ router.push(`/organizations/${newOrg._id}/settings`);
+ }, 500);
+ } else if (organizationId || activeOrgId) {
+ // PATCH request - update existing organization
+ const orgId = organizationId || activeOrgId;
+ await updateOrganization(orgId as string, {
name: formData.name,
logo: formData.logo,
tagline: formData.tagline,
about: formData.about,
});
- toast.success('Organization created successfully');
- if (activeOrgId) {
- setTimeout(() => {
- router.push(`/organizations/${activeOrgId}/settings`);
- }, 500);
- }
- }
- setHasUserChanges(false);
+ toast.success('Organization profile updated successfully');
+ setHasUserChanges(false);
+ }
if (onSave) {
onSave(formData);
@@ -548,7 +565,7 @@ export default function ProfileTab({ initialData, onSave }: ProfileTabProps) {
- {hasUserChanges && (
+ {hasUserChanges && !isCreating && (
You have unsaved changes
@@ -565,10 +582,12 @@ export default function ProfileTab({ initialData, onSave }: ProfileTabProps) {
)}
>
{isSaving
- ? 'Saving...'
- : activeOrgId
- ? 'Save Changes'
- : 'Create Organization'}
+ ? isCreating
+ ? 'Creating...'
+ : 'Saving...'
+ : isCreating
+ ? 'Create Organization'
+ : 'Save Changes'}
From a194d9059b399c9b114231a6ead4f386dd450f58 Mon Sep 17 00:00:00 2001
From: Benjtalkshow
Date: Fri, 7 Nov 2025 20:58:02 +0100
Subject: [PATCH 6/6] fix: modify create organization
---
components/organization/tabs/MembersTab.tsx | 22 ++-
.../tabs/MembersTab/TeamManagementSection.tsx | 49 +++---
.../tabs/TransferOwnershipTab.tsx | 149 ++++++++++--------
lib/api/organization.ts | 19 +++
lib/providers/OrganizationProvider.tsx | 48 ++++++
lib/providers/organization-types.ts | 9 ++
6 files changed, 198 insertions(+), 98 deletions(-)
diff --git a/components/organization/tabs/MembersTab.tsx b/components/organization/tabs/MembersTab.tsx
index 87ff15f9a..d1050b4f1 100644
--- a/components/organization/tabs/MembersTab.tsx
+++ b/components/organization/tabs/MembersTab.tsx
@@ -6,6 +6,7 @@ import { useOrganization } from '@/lib/providers/OrganizationProvider';
import EmailInviteSection from './MembersTab/EmailInviteSection';
import PermissionsTable from './MembersTab/PermissionsTable';
import TeamManagementSection from './MembersTab/TeamManagementSection';
+import { toast } from 'sonner';
interface Member {
id: string;
@@ -28,6 +29,7 @@ export default function MembersTab({ onSave }: MembersTabProps) {
updateOrganizationMembers,
inviteMember,
removeMember,
+ assignRole,
isLoading,
} = useOrganization();
@@ -54,12 +56,21 @@ export default function MembersTab({ onSave }: MembersTabProps) {
}
};
- const handleRoleChange = (memberId: string) => {
+ const handleRoleChange = async (memberId: string, newRole: string) => {
if (!activeOrgId) return;
- const m = members.find(x => x.id === memberId);
- if (!m) return;
- updateOrganizationMembers(activeOrgId, [m.email]);
- setHasUserChanges(true);
+ const member = members.find(m => m.id === memberId);
+ if (!member) return;
+
+ try {
+ const action = newRole === 'admin' ? 'promote' : 'demote';
+ await assignRole(activeOrgId, member.email, action);
+ setHasUserChanges(true);
+ toast.success(`Member role updated to ${newRole}`);
+ } catch (error) {
+ // Handle error (show toast, etc.)
+ const msg = error instanceof Error ? error.message : String(error);
+ toast.error(`Failed to update member role: ${msg}`);
+ }
};
const handleRemoveMember = (memberId: string) => {
@@ -95,6 +106,7 @@ export default function MembersTab({ onSave }: MembersTabProps) {
members={members}
onRoleChange={handleRoleChange}
onRemoveMember={handleRemoveMember}
+ activeOrg={activeOrg}
/>
diff --git a/components/organization/tabs/MembersTab/TeamManagementSection.tsx b/components/organization/tabs/MembersTab/TeamManagementSection.tsx
index 74a1952d2..ddd6d6706 100644
--- a/components/organization/tabs/MembersTab/TeamManagementSection.tsx
+++ b/components/organization/tabs/MembersTab/TeamManagementSection.tsx
@@ -2,6 +2,7 @@
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import MemberCard from './MemberCard';
+import { Organization } from '@/lib/api/types';
interface Member {
id: string;
@@ -17,25 +18,18 @@ interface TeamManagementSectionProps {
members: Member[];
onRoleChange: (memberId: string, newRole: string) => void;
onRemoveMember: (memberId: string) => void;
+ activeOrg?: Organization | null;
}
export default function TeamManagementSection({
members,
onRoleChange,
onRemoveMember,
+ activeOrg,
}: TeamManagementSectionProps) {
- const ownerMember = {
- id: 'owner-1',
- name: 'Collins Chikangwu',
- email: 'collins@boundless.com',
- avatar: '/avatar.png',
- role: 'owner' as const,
- joinedAt: new Date().toISOString(),
- status: 'active' as const,
- };
-
const activeMembers = members.filter(member => member.status === 'active');
const pendingMembers = members.filter(member => member.status === 'pending');
+ const orgOwner = members.find(member => member.email === activeOrg?.owner);
return (
@@ -49,26 +43,29 @@ export default function TeamManagementSection({
-
-
-
-
- {ownerMember.name.charAt(0).toUpperCase()}
-
-
-
-
{ownerMember.name}
-
{ownerMember.email}
-
- Owner
-
+ {orgOwner && (
+
+
+
+
+ {orgOwner.name.charAt(0).toUpperCase()}
+
+
+
+
{orgOwner.name}
+
{orgOwner.email}
+
+ Owner
+
+
-
+ )}
- {activeMembers.filter(member => member.role !== 'owner').length > 0 && (
+ {activeMembers.filter(member => member.email !== activeOrg?.owner)
+ .length > 0 && (
{activeMembers
- .filter(member => member.role !== 'owner')
+ .filter(member => member.email !== activeOrg?.owner)
.map(member => (
void;
}
export default function TransferOwnershipTab({
- members = [],
onTransfer,
}: TransferOwnershipTabProps) {
const [selectedMember, setSelectedMember] = useState('');
const [isTransferring, setIsTransferring] = useState(false);
const [popoverWidth, setPopoverWidth] = useState(0);
const triggerRef = useRef(null);
+ const { activeOrg, activeOrgId, transferOwnership } = useOrganization();
+
+ // Create members list using the SAME logic as MembersTab
+ const members: Member[] =
+ activeOrg?.members
+ ?.filter(email => email !== activeOrg?.owner) // Exclude owner
+ ?.map((email, idx) => ({
+ id: `${idx}-${email}`,
+ name: email.split('@')[0] || email,
+ email,
+ role: 'member' as const,
+ joinedAt: new Date().toISOString(),
+ status: 'active' as const,
+ })) || [];
useEffect(() => {
const updateWidth = () => {
@@ -52,50 +61,41 @@ export default function TransferOwnershipTab({
return () => window.removeEventListener('resize', updateWidth);
}, []);
- // Dummy data for demonstration
- const dummyMembers: Member[] = [
- {
- id: 'admin-1',
- name: 'Robert Fox',
- email: 'robert.fox@example.com',
- avatar: '/avatar.png',
- role: 'admin',
- },
- {
- id: 'admin-2',
- name: 'Sarah Johnson',
- email: 'sarah.johnson@example.com',
- avatar: '/avatar.png',
- role: 'admin',
- },
- {
- id: 'admin-3',
- name: 'Mike Chen',
- email: 'mike.chen@example.com',
- avatar: '/avatar.png',
- role: 'admin',
- },
- ];
-
- const eligibleMembers =
- members.length > 0
- ? members.filter(member => member.role === 'admin')
- : dummyMembers;
- const selectedMemberData = eligibleMembers.find(
+
+ const selectedMemberData = members.find(
member => member.id === selectedMember
);
const handleTransfer = async () => {
- if (!selectedMember) return;
+ if (!selectedMember || !activeOrgId) return;
setIsTransferring(true);
try {
- await onTransfer?.(selectedMember);
+ const selectedMemberData = members.find(m => m.id === selectedMember);
+ if (!selectedMemberData) {
+ toast.error('Selected member not found');
+ return;
+ }
+
+ await transferOwnership(activeOrgId, selectedMemberData.email);
+
+ toast.success('Ownership transferred successfully');
+
+ onTransfer?.(selectedMember);
+ setSelectedMember(''); // Reset selection
+ } catch (error) {
+ toast.error(
+ error instanceof Error ? error.message : 'Failed to transfer ownership'
+ );
} finally {
setIsTransferring(false);
}
};
+ // Debug: Log the members to see what's happening
+ console.log('TransferOwnershipTab - activeOrg:', activeOrg);
+ console.log('TransferOwnershipTab - members:', members);
+
return (
<>
@@ -111,6 +111,10 @@ export default function TransferOwnershipTab({
you will be demoted to admin.
+ {/* Debug info */}
+
+ Available members: {members.length}
+
@@ -148,41 +152,52 @@ export default function TransferOwnershipTab({
className='bg-background w-full border-gray-900'
style={{ width: popoverWidth > 0 ? `${popoverWidth}px` : '100%' }}
>
- {eligibleMembers.map(member => (
-
setSelectedMember(member.id)}
- >
-
-
-
- {member.name.charAt(0).toUpperCase()}
-
-
-
-
{member.name}
-
{member.email}
-
- {selectedMember === member.id && (
-
- )}
-
- ))}
+ {members.length > 0 ? (
+ members.map(member => (
+
setSelectedMember(member.id)}
+ >
+
+
+
+ {member.name.charAt(0).toUpperCase()}
+
+
+
+
{member.name}
+
+ {member.email}
+
+
+ {selectedMember === member.id && (
+
+ )}
+
+ ))
+ ) : (
+
+ No members found (only owner in organization)
+
+ )}
- {isTransferring ? 'Transferring...' : 'Save Changes'}
+ {isTransferring ? 'Transferring...' : 'Transfer Ownership'}
>
);
diff --git a/lib/api/organization.ts b/lib/api/organization.ts
index 68ebb96d5..e4604b052 100644
--- a/lib/api/organization.ts
+++ b/lib/api/organization.ts
@@ -26,6 +26,11 @@ export interface Organization {
updatedAt: string;
}
+export interface AssignRoleRequest {
+ action: 'promote' | 'demote';
+ email: string;
+}
+
export interface CreateOrganizationRequest {
name: string;
logo?: string;
@@ -127,6 +132,12 @@ export interface DeleteOrganizationResponse extends ApiResponse {
message: string;
}
+export interface AssignRoleResponse extends ApiResponse
{
+ success: true;
+ data: Organization;
+ message: string;
+}
+
/**
* Create a new organization
*/
@@ -526,6 +537,14 @@ export const importOrganizationData = async (
return res.data;
};
+export const assignOrganizationRole = async (
+ organizationId: string,
+ data: AssignRoleRequest
+): Promise => {
+ const res = await api.patch(`/organizations/${organizationId}/roles`, data);
+ return res.data;
+};
+
// Error handling utilities
export const isOrganizationError = (error: unknown): error is ErrorResponse => {
return (
diff --git a/lib/providers/OrganizationProvider.tsx b/lib/providers/OrganizationProvider.tsx
index ae566c7ba..cc201a0fb 100644
--- a/lib/providers/OrganizationProvider.tsx
+++ b/lib/providers/OrganizationProvider.tsx
@@ -30,6 +30,8 @@ import {
updateOrganizationHackathons,
updateOrganizationGrants,
deleteOrganization,
+ assignOrganizationRole,
+ transferOrganizationOwnership,
} from '../api/organization';
import { getProfileCompletionStatus as getOrgProfileCompletionStatus } from '../organization-utils';
@@ -848,6 +850,50 @@ export function OrganizationProvider({
}
}, [fetchOrganizations, initialOrgId]);
+ const assignRole = useCallback(
+ async (orgId: string, email: string, action: 'promote' | 'demote') => {
+ try {
+ dispatch({ type: 'SET_LOADING', payload: { isLoading: true } });
+ const response = await assignOrganizationRole(orgId, { action, email });
+ const updatedOrg = response.data;
+ dispatch({ type: 'UPDATE_ORGANIZATION', payload: updatedOrg });
+ return updatedOrg;
+ } catch (error) {
+ const errorMessage =
+ error instanceof Error ? error.message : 'Failed to assign role';
+ dispatch({ type: 'SET_ERROR', payload: { error: errorMessage } });
+ throw error;
+ } finally {
+ dispatch({ type: 'SET_LOADING', payload: { isLoading: false } });
+ }
+ },
+ []
+ );
+
+ const transferOwnership = useCallback(
+ async (orgId: string, newOwnerEmail: string) => {
+ try {
+ dispatch({ type: 'SET_LOADING', payload: { isLoading: true } });
+ const response = await transferOrganizationOwnership(
+ orgId,
+ newOwnerEmail
+ );
+ const updatedOrg = response.data;
+ dispatch({ type: 'UPDATE_ORGANIZATION', payload: updatedOrg });
+ return updatedOrg;
+ } catch (error) {
+ const errorMessage =
+ error instanceof Error
+ ? error.message
+ : 'Failed to transfer ownership';
+ dispatch({ type: 'SET_ERROR', payload: { error: errorMessage } });
+ throw error;
+ } finally {
+ dispatch({ type: 'SET_LOADING', payload: { isLoading: false } });
+ }
+ },
+ []
+ );
// Context value
const contextValue: OrganizationContextValue = {
...state,
@@ -871,6 +917,8 @@ export function OrganizationProvider({
isMember,
canManage,
getProfileCompletionStatus,
+ assignRole,
+ transferOwnership,
};
return (
diff --git a/lib/providers/organization-types.ts b/lib/providers/organization-types.ts
index 84ceef752..589be6988 100644
--- a/lib/providers/organization-types.ts
+++ b/lib/providers/organization-types.ts
@@ -185,4 +185,13 @@ export interface OrganizationContextActions {
percentage: number;
missingFields: string[];
};
+ assignRole: (
+ orgId: string,
+ email: string,
+ action: 'promote' | 'demote'
+ ) => Promise;
+ transferOwnership: (
+ orgId: string,
+ newOwnerEmail: string
+ ) => Promise;
}