Skip to content

Commit 11e44df

Browse files
committed
abstract out hook for updating org logo
1 parent 30219f7 commit 11e44df

5 files changed

Lines changed: 47 additions & 38 deletions

File tree

src/lib/components/settings/OrgLogoUploader.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@ import { Loader2, Upload } from 'lucide-react';
66
import { toast } from 'sonner';
77
import useFileUpload from '@/lib/hooks/useFileUpload';
88
import useFileClient from '@/lib/hooks/useFileClient';
9-
import { authClient } from '@/lib/auth/auth-client';
109

1110
type OrgLogoUploaderProps = {
1211
organization: { id: string; name: string; logo: string | null };
12+
updateLogo: (logoUrl: string) => Promise<boolean>;
13+
disabled?: boolean;
1314
onUpdated: () => void;
1415
};
1516

16-
export default function OrgLogoUploader({ organization, onUpdated }: OrgLogoUploaderProps) {
17+
export default function OrgLogoUploader({
18+
organization,
19+
updateLogo,
20+
disabled = false,
21+
onUpdated,
22+
}: OrgLogoUploaderProps) {
1723
const {
1824
file,
1925
preview,
@@ -37,12 +43,8 @@ export default function OrgLogoUploader({ organization, onUpdated }: OrgLogoUplo
3743
toast.error('Failed to upload logo');
3844
return;
3945
}
40-
await authClient.organization.update({
41-
data: { logo: url },
42-
organizationId: organization.id,
43-
});
44-
toast.success('Logo updated');
45-
onUpdated();
46+
const ok = await updateLogo(url);
47+
if (ok) onUpdated();
4648
} catch (err) {
4749
toast.error(
4850
`Failed to update logo: ${err instanceof Error ? err.message : 'Unknown error'}`
@@ -52,14 +54,14 @@ export default function OrgLogoUploader({ organization, onUpdated }: OrgLogoUplo
5254
}
5355
};
5456
upload();
55-
}, [file, organization.id, uploadFile, onUpdated]);
57+
}, [file, organization.id, uploadFile, updateLogo, onUpdated]);
5658

5759
return (
5860
<div>
5961
<button
6062
type="button"
6163
onClick={openFilePicker}
62-
disabled={isUploading}
64+
disabled={isUploading || disabled}
6365
className="group bg-sarge-gray-500 text-sarge-gray-0 relative flex size-18 shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-lg disabled:cursor-not-allowed"
6466
>
6567
{logoSrc ? (

src/lib/components/settings/OrganizationTab.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ export default function OrganizationTab() {
1717
const orgId = activeOrganization?.id;
1818
const isOwner = activeMember?.role === 'owner';
1919

20-
const { members, refresh } = useOrgMembersAndInvites(orgId);
21-
const { renameOrg, isMutating, transferOwnership, deleteOrg } = useOrgSettings(orgId);
20+
const { members } = useOrgMembersAndInvites(orgId);
21+
const { renameOrg, updateLogo, isMutating, transferOwnership, deleteOrg } =
22+
useOrgSettings(orgId);
2223

2324
const [nameDraft, setNameDraft] = useState(activeOrganization?.name ?? '');
2425
const [transferOpen, setTransferOpen] = useState(false);
@@ -41,7 +42,6 @@ export default function OrganizationTab() {
4142
};
4243

4344
const redirectToDashboard = () => {
44-
refresh();
4545
router.push('/crm/dashboard');
4646
router.refresh();
4747
};
@@ -60,6 +60,8 @@ export default function OrganizationTab() {
6060
name: activeOrganization.name,
6161
logo: activeOrganization.logo ?? null,
6262
}}
63+
updateLogo={updateLogo}
64+
disabled={isMutating}
6365
onUpdated={() => router.refresh()}
6466
/>
6567

src/lib/hooks/useOrgSettings.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import { useState } from 'react';
44
import { toast } from 'sonner';
55
import { authClient } from '@/lib/auth/auth-client';
6-
import { transferOwnership as transferOwnershipApi } from '@/lib/api/organizations';
6+
import {
7+
transferOwnership as transferOwnershipApi,
8+
updateOrganization as updateOrganizationApi,
9+
} from '@/lib/api/organizations';
710

811
export type UseOrgSettingsResult = {
912
renameOrg: (newName: string) => Promise<boolean>;
@@ -51,10 +54,7 @@ export function useOrgSettings(organizationId: string | undefined): UseOrgSettin
5154
}
5255
try {
5356
setIsMutating(true);
54-
await authClient.organization.update({
55-
data: { logo: logoUrl },
56-
organizationId,
57-
});
57+
await updateOrganizationApi(organizationId, { logo: logoUrl });
5858
toast.success('Logo updated');
5959
return true;
6060
} catch (err) {

src/lib/schemas/organization.schema.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ export const getOrganizationSchema = z.object({
1313
id: z.string('Invalid organization ID'),
1414
});
1515

16-
export const updateOrganizationSchema = z.object({
17-
name: z
18-
.string()
19-
.min(2, 'Name must be at least 2 characters')
20-
.max(100, 'Name must be less than 100 characters')
21-
.trim(),
22-
logo: z.url(),
23-
});
16+
export const updateOrganizationSchema = z
17+
.object({
18+
name: z
19+
.string()
20+
.min(2, 'Name must be at least 2 characters')
21+
.max(100, 'Name must be less than 100 characters')
22+
.trim(),
23+
logo: z.url(),
24+
})
25+
.partial()
2426

2527
export type CreateOrganizationDTO = z.infer<typeof createOrganizationSchema>;
2628
export type UpdateOrganizationDTO = z.infer<typeof updateOrganizationSchema>;

src/lib/services/organization.service.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ async function updateOrganization(
7474
id: string,
7575
organization: UpdateOrganizationDTO
7676
): Promise<Organization> {
77-
const { name, logo } = organization;
78-
7977
const existingOrg = await prisma.organization.findUnique({
8078
where: { id },
8179
});
@@ -84,24 +82,29 @@ async function updateOrganization(
8482
throw new NotFoundException('Organization', id);
8583
}
8684

87-
const orgWithSameName = await prisma.organization.findFirst({
88-
where: {
89-
name,
90-
id: { not: id },
91-
},
92-
});
85+
const nextName = organization.name ?? existingOrg.name;
86+
const nextLogo = organization.logo ?? existingOrg.logo;
9387

94-
if (orgWithSameName) {
95-
throw new ConflictException('Organization', 'with that name');
88+
if (organization.name !== undefined && organization.name !== existingOrg.name) {
89+
const orgWithSameName = await prisma.organization.findFirst({
90+
where: {
91+
name: organization.name,
92+
id: { not: id },
93+
},
94+
});
95+
96+
if (orgWithSameName) {
97+
throw new ConflictException('Organization', 'with that name');
98+
}
9699
}
97100

98101
const updated = await prisma.organization.update({
99102
where: {
100103
id,
101104
},
102105
data: {
103-
name,
104-
logo,
106+
name: nextName,
107+
logo: nextLogo,
105108
},
106109
});
107110
return updated;

0 commit comments

Comments
 (0)