@@ -14,18 +14,15 @@ import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/avail
1414import { WEBAPP_URL } from "@calcom/lib/constants" ;
1515import logger from "@calcom/lib/logger" ;
1616import { safeStringify } from "@calcom/lib/safeStringify" ;
17+ import { uploadLogo } from "@calcom/lib/server/avatar" ;
1718import { getTranslation } from "@calcom/lib/server/i18n" ;
1819import { OrganizationOnboardingRepository } from "@calcom/lib/server/repository/organizationOnboarding" ;
20+ import { isBase64Image , resizeBase64Image } from "@calcom/lib/server/resizeBase64Image" ;
1921import slugify from "@calcom/lib/slugify" ;
2022import { prisma } from "@calcom/prisma" ;
2123import type { Prisma , Team , User } from "@calcom/prisma/client" ;
2224import { CreationSource , MembershipRole , UserPermissionRole } from "@calcom/prisma/enums" ;
23- import {
24- userMetadata ,
25- orgOnboardingInvitedMembersSchema ,
26- orgOnboardingTeamsSchema ,
27- teamMetadataStrictSchema ,
28- } from "@calcom/prisma/zod-utils" ;
25+ import { userMetadata , teamMetadataStrictSchema } from "@calcom/prisma/zod-utils" ;
2926import { createTeamsHandler } from "@calcom/trpc/server/routers/viewer/organizations/createTeams.handler" ;
3027import { inviteMembersWithNoInviterPermissionCheck } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler" ;
3128
@@ -45,10 +42,6 @@ import type {
4542} from "./types" ;
4643
4744const log = logger . getSubLogger ( { prefix : [ "BaseOnboardingService" ] } ) ;
48- const invitedMembersSchema = orgOnboardingInvitedMembersSchema ;
49- const teamsSchema = orgOnboardingTeamsSchema ;
50-
51- type OrgOwner = Awaited < ReturnType < typeof findUserToBeOrgOwner > > ;
5245
5346export abstract class BaseOnboardingService implements IOrganizationOnboardingService {
5447 protected user : OnboardingUser ;
@@ -65,14 +58,18 @@ export abstract class BaseOnboardingService implements IOrganizationOnboardingSe
6558 this . permissionService = permissionService || new OrganizationPermissionService ( user ) ;
6659 }
6760
68- abstract createOnboardingIntent ( input : CreateOnboardingIntentInput ) : Promise < any > ;
61+ abstract createOnboardingIntent ( input : CreateOnboardingIntentInput ) : Promise < OnboardingIntentResult > ;
6962 abstract createOrganization (
7063 organizationOnboarding : OrganizationOnboardingData ,
7164 paymentDetails ?: { subscriptionId : string ; subscriptionItemId : string }
7265 ) : Promise < { organization : Team ; owner : User } > ;
7366
7467 protected async createOnboardingRecord ( input : CreateOnboardingIntentInput & { onboardingId ?: string } ) {
75- // If onboardingId exists, update the existing record (resume flow)
68+ const processedAssets = await this . processOnboardingBrandAssets ( {
69+ logo : input . logo ,
70+ bannerUrl : input . bannerUrl ,
71+ } ) ;
72+
7673 if ( input . onboardingId ) {
7774 log . debug (
7875 "Updating existing organization onboarding record (resume flow)" ,
@@ -83,14 +80,28 @@ export abstract class BaseOnboardingService implements IOrganizationOnboardingSe
8380 } )
8481 ) ;
8582
86- await OrganizationOnboardingRepository . update ( input . onboardingId , {
87- logo : input . logo ?? null ,
83+ const updateData : {
84+ logo ?: string | null ;
85+ bio ?: string | null ;
86+ brandColor ?: string | null ;
87+ bannerUrl ?: string | null ;
88+ teams ?: TeamData [ ] ;
89+ invitedMembers ?: InvitedMember [ ] ;
90+ } = {
8891 bio : input . bio ?? null ,
8992 brandColor : input . brandColor ?? null ,
90- bannerUrl : input . bannerUrl ?? null ,
9193 teams : input . teams ?? [ ] ,
9294 invitedMembers : input . invitedMembers ?? [ ] ,
93- } ) ;
95+ } ;
96+
97+ if ( processedAssets . logo !== undefined ) {
98+ updateData . logo = processedAssets . logo ;
99+ }
100+ if ( processedAssets . bannerUrl !== undefined ) {
101+ updateData . bannerUrl = processedAssets . bannerUrl ;
102+ }
103+
104+ await OrganizationOnboardingRepository . update ( input . onboardingId , updateData ) ;
94105
95106 const updatedOnboarding = await OrganizationOnboardingRepository . findById ( input . onboardingId ) ;
96107 if ( ! updatedOnboarding ) {
@@ -101,7 +112,6 @@ export abstract class BaseOnboardingService implements IOrganizationOnboardingSe
101112 return updatedOnboarding ;
102113 }
103114
104- // Create new onboarding record
105115 log . debug (
106116 "Creating organization onboarding record" ,
107117 safeStringify ( {
@@ -120,10 +130,10 @@ export abstract class BaseOnboardingService implements IOrganizationOnboardingSe
120130 pricePerSeat : input . pricePerSeat ,
121131 billingPeriod : input . billingPeriod ,
122132 createdByUserId : this . user . id ,
123- logo : input . logo ?? null ,
133+ logo : processedAssets . logo ?? null ,
124134 bio : input . bio ?? null ,
125135 brandColor : input . brandColor ?? null ,
126- bannerUrl : input . bannerUrl ?? null ,
136+ bannerUrl : processedAssets . bannerUrl ?? null ,
127137 teams : input . teams ?? [ ] ,
128138 invitedMembers : input . invitedMembers ?? [ ] ,
129139 } ) ;
@@ -209,6 +219,76 @@ export abstract class BaseOnboardingService implements IOrganizationOnboardingSe
209219 return this . user . role === UserPermissionRole . ADMIN && this . user . email === input . orgOwnerEmail ;
210220 }
211221
222+ private async processOnboardingBrandAssets ( input : {
223+ logo ?: string | null ;
224+ bannerUrl ?: string | null ;
225+ } ) : Promise < { logo ?: string | null ; bannerUrl ?: string | null } > {
226+ const [ logo , bannerUrl ] = await Promise . all ( [
227+ input . logo ? resizeBase64Image ( input . logo ) : Promise . resolve ( input . logo ) ,
228+ input . bannerUrl ? resizeBase64Image ( input . bannerUrl , { maxSize : 1500 } ) : Promise . resolve ( input . bannerUrl ) ,
229+ ] ) ;
230+
231+ return { logo, bannerUrl } ;
232+ }
233+
234+ private async uploadImageAsset ( {
235+ image,
236+ teamId,
237+ isBanner = false ,
238+ } : {
239+ image : string ;
240+ teamId : number ;
241+ isBanner ?: boolean ;
242+ } ) : Promise < string > {
243+ if ( ! isBase64Image ( image ) ) {
244+ return image ;
245+ }
246+
247+ return await uploadLogo ( {
248+ logo : image ,
249+ teamId,
250+ isBanner,
251+ } ) ;
252+ }
253+
254+ protected async uploadOrganizationBrandAssets ( {
255+ logoUrl,
256+ bannerUrl,
257+ organizationId,
258+ } : {
259+ logoUrl ?: string | null ;
260+ bannerUrl ?: string | null ;
261+ organizationId : number ;
262+ } ) : Promise < {
263+ logoUrl ?: string | null ;
264+ bannerUrl ?: string | null ;
265+ } > {
266+ const uploadedLogoUrl = ! ! logoUrl ? await this . uploadImageAsset ( { image : logoUrl , teamId : organizationId } ) : logoUrl ;
267+
268+ const uploadedBannerUrl = ! ! bannerUrl
269+ ? await this . uploadImageAsset ( { image : bannerUrl , teamId : organizationId , isBanner : true } )
270+ : bannerUrl ;
271+
272+ if ( uploadedLogoUrl === undefined && uploadedBannerUrl === undefined ) {
273+ return {
274+ logoUrl,
275+ bannerUrl,
276+ } ;
277+ }
278+
279+ return await prisma . team . update ( {
280+ where : { id : organizationId } ,
281+ data : {
282+ logoUrl : uploadedLogoUrl ,
283+ bannerUrl : uploadedBannerUrl ,
284+ } ,
285+ select : {
286+ logoUrl : true ,
287+ bannerUrl : true ,
288+ } ,
289+ } ) ;
290+ }
291+
212292 protected async createOrganizationWithExistingUserAsOwner ( {
213293 owner,
214294 orgData,
@@ -246,9 +326,14 @@ export abstract class BaseOnboardingService implements IOrganizationOnboardingSe
246326
247327 try {
248328 const nonOrgUsername = owner . username || "" ;
329+
330+ // Create organization first to get the ID
249331 const orgCreationResult = await OrganizationRepository . createWithExistingUserAsOwner ( {
250332 orgData : {
251333 ...orgData ,
334+ // Don't pass brand assets yet - will be uploaded after org is created
335+ logoUrl : null ,
336+ bannerUrl : null ,
252337 ...( canSetSlug ? { slug : orgData . slug } : { slug : null , requestedSlug : orgData . slug } ) ,
253338 } ,
254339 owner : {
@@ -257,9 +342,17 @@ export abstract class BaseOnboardingService implements IOrganizationOnboardingSe
257342 nonOrgUsername,
258343 } ,
259344 } ) ;
260- organization = orgCreationResult . organization ;
261- const ownerProfile = orgCreationResult . ownerProfile ;
262345
346+ organization = {
347+ ...orgCreationResult . organization ,
348+ ...await this . uploadOrganizationBrandAssets ( {
349+ logoUrl : orgData . logoUrl ,
350+ bannerUrl : orgData . bannerUrl ,
351+ organizationId : orgCreationResult . organization . id ,
352+ } )
353+ } ;
354+
355+ const ownerProfile = orgCreationResult . ownerProfile ;
263356 if ( ! orgData . isPlatform ) {
264357 await sendOrganizationCreationEmail ( {
265358 language : orgOwnerTranslation ,
@@ -318,13 +411,27 @@ export abstract class BaseOnboardingService implements IOrganizationOnboardingSe
318411 }
319412
320413 const orgCreationResult = await OrganizationRepository . createWithNonExistentOwner ( {
321- orgData,
414+ orgData : {
415+ ...orgData ,
416+ // To be uploaded after org is created
417+ logoUrl : null ,
418+ bannerUrl : null ,
419+ } ,
322420 owner : {
323421 email : email ,
324422 } ,
325423 creationSource : CreationSource . WEBAPP ,
326424 } ) ;
327- organization = orgCreationResult . organization ;
425+
426+ organization = {
427+ ...orgCreationResult . organization ,
428+ ...await this . uploadOrganizationBrandAssets ( {
429+ logoUrl : orgData . logoUrl ,
430+ bannerUrl : orgData . bannerUrl ,
431+ organizationId : orgCreationResult . organization . id ,
432+ } )
433+ } ;
434+
328435 const { ownerProfile, orgOwner : orgOwnerFromCreation } = orgCreationResult ;
329436 const orgOwner = await findUserToBeOrgOwner ( orgOwnerFromCreation . email ) ;
330437 if ( ! orgOwner ) {
@@ -462,8 +569,7 @@ export abstract class BaseOnboardingService implements IOrganizationOnboardingSe
462569 } else if ( member . teamName ) {
463570 targetTeamId = teamNameToId . get ( member . teamName . toLowerCase ( ) ) ;
464571 log . debug (
465- `Member ${ member . email } : teamName "${ member . teamName } " -> resolved to ${
466- targetTeamId || "not found"
572+ `Member ${ member . email } : teamName "${ member . teamName } " -> resolved to ${ targetTeamId || "not found"
467573 } `
468574 ) ;
469575 }
@@ -603,3 +709,4 @@ export abstract class BaseOnboardingService implements IOrganizationOnboardingSe
603709 } ) ;
604710 }
605711}
712+
0 commit comments