@@ -34,22 +34,16 @@ import { Button } from "~/components/primitives/Buttons";
3434import { Fieldset } from "~/components/primitives/Fieldset" ;
3535import { FormButtons } from "~/components/primitives/FormButtons" ;
3636import { FormError } from "~/components/primitives/FormError" ;
37- import { Header2 , Header3 } from "~/components/primitives/Headers" ;
37+ import { Header2 } from "~/components/primitives/Headers" ;
3838import { Hint } from "~/components/primitives/Hint" ;
3939import { Input } from "~/components/primitives/Input" ;
4040import { InputGroup } from "~/components/primitives/InputGroup" ;
4141import { Label } from "~/components/primitives/Label" ;
4242import { NavBar , PageTitle } from "~/components/primitives/PageHeader" ;
43- import {
44- Popover ,
45- PopoverContent ,
46- PopoverCustomTrigger ,
47- PopoverTrigger ,
48- } from "~/components/primitives/Popover" ;
49- import { Spinner , SpinnerWhite } from "~/components/primitives/Spinner" ;
43+ import { Popover , PopoverContent , PopoverTrigger } from "~/components/primitives/Popover" ;
44+ import { SpinnerWhite } from "~/components/primitives/Spinner" ;
5045import { prisma } from "~/db.server" ;
5146import { useFaviconUrl } from "~/hooks/useFaviconUrl" ;
52- import { useOrganization } from "~/hooks/useOrganizations" ;
5347import { redirectWithErrorMessage , redirectWithSuccessMessage } from "~/models/message.server" ;
5448import { clearCurrentProject } from "~/services/dashboardPreferences.server" ;
5549import { DeleteOrganizationService } from "~/services/deleteOrganization.server" ;
@@ -59,7 +53,6 @@ import { cn } from "~/utils/cn";
5953import { extractDomain , faviconUrl as buildFaviconUrl } from "~/utils/favicon" ;
6054import {
6155 OrganizationParamsSchema ,
62- organizationPath ,
6356 organizationSettingsPath ,
6457 rootPath ,
6558} from "~/utils/pathBuilder" ;
@@ -92,14 +85,11 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
9285 throw new Response ( "Not found" , { status : 404 } ) ;
9386 }
9487
95- const onboardingData =
96- organization . onboardingData && typeof organization . onboardingData === "object"
97- ? ( organization . onboardingData as Record < string , unknown > )
98- : { } ;
88+ const onboardingData = toRecord ( organization . onboardingData ) ;
9989
10090 const parsedAvatar = parseAvatar ( organization . avatar , defaultAvatar ) ;
10191 const lastIconHex =
102- parsedAvatar . type === "image" && "lastIconHex" in parsedAvatar && typeof parsedAvatar . lastIconHex === "string"
92+ parsedAvatar . type === "image" && parsedAvatar . lastIconHex
10393 ? parsedAvatar . lastIconHex
10494 : defaultAvatarHex ;
10595
@@ -236,19 +226,9 @@ export const action: ActionFunction = async ({ request, params }) => {
236226 select : { avatar : true , onboardingData : true } ,
237227 } ) ;
238228
239- const existingData =
240- existing ?. onboardingData && typeof existing . onboardingData === "object"
241- ? ( existing . onboardingData as Record < string , unknown > )
242- : { } ;
243-
244- const existingAvatarJson = existing ? existing . avatar : null ;
245- const existingAvatar = parseAvatar ( existingAvatarJson , defaultAvatar ) ;
246- const lastIconHex =
247- "hex" in existingAvatar
248- ? existingAvatar . hex
249- : existingAvatar . type === "image" && "lastIconHex" in existingAvatar
250- ? ( existingAvatar . lastIconHex as string )
251- : undefined ;
229+ const existingData = toRecord ( existing ?. onboardingData ) ;
230+ const existingAvatar = parseAvatar ( existing ?. avatar ?? null , defaultAvatar ) ;
231+ const lastIconHex = extractLastIconHex ( existingAvatar ) ;
252232
253233 await prisma . organization . update ( {
254234 where : orgWhere ,
@@ -293,8 +273,9 @@ export const action: ActionFunction = async ({ request, params }) => {
293273 ) ;
294274 }
295275 }
296- } catch ( error : any ) {
297- return json ( { errors : { body : error . message } } , { status : 400 } ) ;
276+ } catch ( error : unknown ) {
277+ const message = error instanceof Error ? error . message : "An unexpected error occurred" ;
278+ return json ( { errors : { body : message } } , { status : 400 } ) ;
298279 }
299280} ;
300281
@@ -451,7 +432,7 @@ function LogoForm({
451432 const hex =
452433 "hex" in avatar
453434 ? avatar . hex
454- : avatar . type === "image" && "lastIconHex" in avatar && avatar . lastIconHex
435+ : avatar . type === "image" && avatar . lastIconHex
455436 ? avatar . lastIconHex
456437 : organization . lastIconHex ;
457438 const mode : "logo" | "icon" = avatar . type === "image" ? "logo" : "icon" ;
@@ -489,24 +470,14 @@ function LogoForm({
489470 < input type = "hidden" name = "action" value = "avatar" />
490471 < input type = "hidden" name = "type" value = "image" />
491472 < input type = "hidden" name = "url" value = { companyUrl } />
492- < button
493- type = "submit"
494- className = "flex shrink-0 items-center gap-3"
495- >
496- < div
497- className = { cn (
498- "flex shrink-0 items-center justify-center rounded-full border-2 p-0.5 transition" ,
499- mode === "logo" ? "border-indigo-500" : "border-charcoal-700 hover:border-charcoal-600"
500- ) }
501- >
502- < div className = "size-2 rounded-full" style = { { backgroundColor : mode === "logo" ? "#6366f1" : "transparent" } } />
503- </ div >
473+ < button type = "submit" className = "flex shrink-0 items-center gap-3" >
474+ < RadioDot active = { mode === "logo" } />
504475 </ button >
505476 < div className = "flex flex-1 items-center gap-1.5" >
506477 < button
507478 type = "submit"
508479 className = { cn (
509- "box-content grid size-10 shrink-0 place-items-center rounded-sm border-2 bg-charcoal-775" ,
480+ iconTileClass ,
510481 mode === "logo"
511482 ? "border-indigo-500"
512483 : "border-charcoal-775 hover:border-charcoal-600"
@@ -551,14 +522,8 @@ function LogoForm({
551522 < input type = "hidden" name = "action" value = "avatar" />
552523 < input type = "hidden" name = "type" value = "letters" />
553524 < input type = "hidden" name = "hex" value = { hex } />
554- < button
555- type = "submit"
556- className = { cn (
557- "flex items-center justify-center rounded-full border-2 p-0.5 transition" ,
558- mode === "icon" ? "border-indigo-500" : "border-charcoal-700 hover:border-charcoal-600"
559- ) }
560- >
561- < div className = "size-2 rounded-full" style = { { backgroundColor : mode === "icon" ? "#6366f1" : "transparent" } } />
525+ < button type = "submit" >
526+ < RadioDot active = { mode === "icon" } />
562527 </ button >
563528 </ Form >
564529 < div className = "flex flex-wrap items-center gap-1.5" >
@@ -570,10 +535,8 @@ function LogoForm({
570535 < button
571536 type = "submit"
572537 className = { cn (
573- "box-content grid size-10 place-items-center rounded-sm border-2 bg-charcoal-775" ,
574- avatar . type === "letters"
575- ? undefined
576- : "border-charcoal-775 hover:border-charcoal-600"
538+ iconTileClass ,
539+ avatar . type !== "letters" && "border-charcoal-775 hover:border-charcoal-600"
577540 ) }
578541 style = { {
579542 borderColor : avatar . type === "letters" ? hex : undefined ,
@@ -597,10 +560,9 @@ function LogoForm({
597560 < button
598561 type = "submit"
599562 className = { cn (
600- "box-content grid size-10 place-items-center rounded-sm border-2 bg-charcoal-775" ,
601- avatar . type === "icon" && avatar . name === name
602- ? undefined
603- : "border-charcoal-775 hover:border-charcoal-600"
563+ iconTileClass ,
564+ ! ( avatar . type === "icon" && avatar . name === name ) &&
565+ "border-charcoal-775 hover:border-charcoal-600"
604566 ) }
605567 style = { {
606568 borderColor :
@@ -629,18 +591,18 @@ function LogoForm({
629591function HexPopover ( { avatar, hex } : { avatar : Avatar ; hex : string } ) {
630592 return (
631593 < Popover >
632- < PopoverTrigger className = "box-content grid size-10 place-items-center rounded-sm border-2 border- charcoal-775 bg-charcoal-775 hover:border-charcoal-600">
594+ < PopoverTrigger className = { cn ( iconTileClass , " border-charcoal-775 hover:border-charcoal-600") } >
633595 < img src = { colorWheelIcon } className = "m-0 block size-[30px] p-0" />
634596 </ PopoverTrigger >
635597 < PopoverContent
636598 className = "overflow-y-auto p-1 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
637599 align = "start"
638600 style = { { maxHeight : `calc(var(--radix-popover-content-available-height) - 10vh)` } }
639601 >
640- < Form method = "post" className = "flex w-fit min-w-40 flex-col gap-1 " >
602+ < Form method = "post" className = "flex w-fit min-w-40 flex-col gap-1" >
641603 < input type = "hidden" name = "action" value = "avatar" />
642- < input type = "hidden" name = "type" value = { avatar . type } />
643- { "name" in avatar && < input type = "hidden" name = "name" value = { avatar . name } /> }
604+ < input type = "hidden" name = "type" value = { avatar . type === "image" ? "letters" : avatar . type } />
605+ { avatar . type === "icon" && < input type = "hidden" name = "name" value = { avatar . name } /> }
644606 { defaultAvatarColors . map ( ( color ) => (
645607 < Button
646608 key = { color . hex }
@@ -674,35 +636,52 @@ function HexPopover({ avatar, hex }: { avatar: Avatar; hex: string }) {
674636 ) ;
675637}
676638
677- function avatarFromFormData ( formData : FormData ) : AvatarT | undefined {
678- const action = formData . get ( "action" ) ;
679- if ( ! action || action !== "avatar" ) {
680- return undefined ;
681- }
639+ function RadioDot ( { active } : { active : boolean } ) {
640+ return (
641+ < div
642+ className = { cn (
643+ "flex shrink-0 items-center justify-center rounded-full border-2 p-0.5 transition" ,
644+ active ? "border-indigo-500" : "border-charcoal-700 hover:border-charcoal-600"
645+ ) }
646+ >
647+ < div
648+ className = "size-2 rounded-full"
649+ style = { { backgroundColor : active ? "#6366f1" : "transparent" } }
650+ />
651+ </ div >
652+ ) ;
653+ }
682654
683- const type = formData . get ( "type" ) ;
684- const hex = formData . get ( "hex" ) ;
655+ const iconTileClass = "box-content grid size-10 shrink-0 place-items-center rounded-sm border-2 bg-charcoal-775" ;
685656
686- if ( type === "letters" ) {
687- return {
688- type : "letters" ,
689- hex : hex as string ,
690- } ;
691- }
657+ function toRecord ( json : unknown ) : Record < string , unknown > {
658+ return json && typeof json === "object" ? ( json as Record < string , unknown > ) : { } ;
659+ }
692660
693- if ( type === "icon" ) {
694- return {
695- type : "icon" ,
696- name : formData . get ( "name" ) as string ,
697- hex : hex as string ,
698- } ;
699- }
661+ function extractLastIconHex ( avatar : AvatarT ) : string | undefined {
662+ if ( "hex" in avatar ) return avatar . hex ;
663+ if ( avatar . type === "image" ) return avatar . lastIconHex ;
664+ return undefined ;
665+ }
700666
701- if ( type === "image" ) {
702- const url = formData . get ( "url" ) as string ;
703- const domain = url ? extractDomain ( url ) : null ;
704- return { type : "image" , url : domain ? buildFaviconUrl ( domain ) : "" } ;
705- }
667+ function avatarFromFormData ( formData : FormData ) : AvatarT | undefined {
668+ const action = formData . get ( "action" ) ;
669+ if ( action !== "avatar" ) return undefined ;
706670
707- return undefined ;
671+ const type = formData . get ( "type" ) ;
672+ const hex = formData . get ( "hex" ) as string ;
673+
674+ switch ( type ) {
675+ case "letters" :
676+ return { type : "letters" , hex } ;
677+ case "icon" :
678+ return { type : "icon" , name : formData . get ( "name" ) as string , hex } ;
679+ case "image" : {
680+ const url = formData . get ( "url" ) as string ;
681+ const domain = url ? extractDomain ( url ) : null ;
682+ return { type : "image" , url : domain ? buildFaviconUrl ( domain ) : "" } ;
683+ }
684+ default :
685+ return undefined ;
686+ }
708687}
0 commit comments