@@ -11,6 +11,7 @@ import useProgramEnrollment from "@/lib/swr/use-program-enrollment";
1111import { useProgramMessages } from "@/lib/swr/use-program-messages" ;
1212import useUser from "@/lib/swr/use-user" ;
1313import { ProgramEnrollmentProps } from "@/lib/types" ;
14+ import { INACTIVE_ENROLLMENT_STATUSES } from "@/lib/zod/schemas/partners" ;
1415import { useMessagesContext } from "@/ui/messages/messages-context" ;
1516import { MessagesPanel } from "@/ui/messages/messages-panel" ;
1617import { ToggleSidePanelButton } from "@/ui/messages/toggle-side-panel-button" ;
@@ -89,6 +90,47 @@ export function PartnerMessagesProgramPageClient() {
8990 const program = programMessages ?. [ 0 ] ?. program ;
9091 const messages = programMessages ?. [ 0 ] ?. messages ;
9192
93+ const isBannedOrRejected = [ "banned" , "rejected" ] . includes (
94+ programEnrollment ?. status ?? "" ,
95+ ) ;
96+ const shouldShowExternalSupportEmptyState =
97+ program ?. messagingEnabledAt === null || isBannedOrRejected ;
98+
99+ const ExternalSupportEmptyState = ( ) => {
100+ return (
101+ < div className = "flex size-full flex-col items-center justify-center px-4" >
102+ < MsgsDotted className = "size-10 text-neutral-700" />
103+ < div className = "mt-6 max-w-md text-center" >
104+ < span className = "text-content-emphasis text-base font-semibold" >
105+ { isBannedOrRejected
106+ ? `This program has ${ programEnrollment ?. status } you`
107+ : "This program uses external support" }
108+ </ span >
109+ < p className = "text-content-subtle text-sm font-medium" >
110+ { isBannedOrRejected
111+ ? "For more information, please contact the program via email."
112+ : "You can contact them directly via email." }
113+ </ p >
114+ </ div >
115+ < a
116+ href = {
117+ enrolledProgram ?. supportEmail
118+ ? `mailto:${ enrolledProgram . supportEmail } `
119+ : "#"
120+ }
121+ target = "_blank"
122+ >
123+ < Button
124+ className = "mt-4 h-9 rounded-lg px-3"
125+ variant = "secondary"
126+ text = { isBannedOrRejected ? "Contact program" : "Email support" }
127+ icon = { < EnvelopeArrowRight className = "size-4" /> }
128+ />
129+ </ a >
130+ </ div >
131+ ) ;
132+ } ;
133+
92134 const { executeAsync : sendMessage } = useAction ( messageProgramAction , {
93135 onError ( { error } ) {
94136 toast . error ( parseActionError ( error , "Failed to send message" ) ) ;
@@ -127,19 +169,19 @@ export function PartnerMessagesProgramPageClient() {
127169 type = "button"
128170 onClick = { ( ) => setIsRightPanelOpen ( ( o ) => ! o ) }
129171 disabled = { ! programEnrollment }
130- className = "-mx-2 -my-1 flex items-center gap-3 rounded-lg px-2 py-1 transition-colors duration-100 enabled:hover:bg-black/5 enabled:active:bg-black/10"
172+ className = "-mx-2 -my-1 flex items-center gap-2 rounded-lg px-2.5 py-1.5 transition-colors duration-100 enabled:hover:bg-black/5 enabled:active:bg-black/10"
131173 >
132174 { ! program ? (
133175 < >
134- < div className = "size-8 animate-pulse rounded-full bg-neutral-200" />
135- < div className = "h-8 w-36 animate-pulse rounded-md bg-neutral-200" />
176+ < div className = "size-7 animate-pulse rounded-full bg-neutral-200" />
177+ < div className = "h-7 w-36 animate-pulse rounded-md bg-neutral-200" />
136178 </ >
137179 ) : (
138180 < >
139181 < img
140182 src = { program ?. logo || "https://assets.dub.co/logo.png" }
141183 alt = { `${ program ?. name } logo` }
142- className = "size-8 shrink-0 rounded-full"
184+ className = "size-7 shrink-0 rounded-full"
143185 />
144186 < h2 className = "text-content-emphasis text-lg font-semibold leading-7" >
145187 { program ?. name ?? "Program" }
@@ -158,34 +200,8 @@ export function PartnerMessagesProgramPageClient() {
158200 < ViewProgramButton programSlug = { programSlug } />
159201 ) : null }
160202 </ div >
161- { [ "banned" , "rejected" ] . includes ( programEnrollment ?. status ?? "" ) ||
162- ( program ?. messagingEnabledAt === null &&
163- messages &&
164- ! messages . length ) ? (
165- < div className = "flex size-full flex-col items-center justify-center px-4" >
166- < MsgsDotted className = "size-10 text-neutral-700" />
167- < div className = "mt-6 max-w-md text-center" >
168- < span className = "text-content-emphasis text-base font-semibold" >
169- This program uses external support
170- </ span >
171- < p className = "text-content-subtle text-sm font-medium" >
172- You can contact them directly via email.
173- </ p >
174- </ div >
175- { enrolledProgram ?. supportEmail && (
176- < Link
177- href = { `mailto:${ enrolledProgram . supportEmail } ` }
178- target = "_blank"
179- >
180- < Button
181- className = "mt-4 h-9 rounded-lg px-3"
182- variant = "secondary"
183- text = "Email support"
184- icon = { < EnvelopeArrowRight className = "size-4" /> }
185- />
186- </ Link >
187- ) }
188- </ div >
203+ { shouldShowExternalSupportEmptyState && messages ?. length === 0 ? (
204+ < ExternalSupportEmptyState />
189205 ) : (
190206 < div className = "min-h-0 grow" >
191207 < MessagesPanel
@@ -194,6 +210,15 @@ export function PartnerMessagesProgramPageClient() {
194210 currentUserType = "partner"
195211 currentUserId = { partner ?. id || "" }
196212 program = { program }
213+ { ...( shouldShowExternalSupportEmptyState && messages ?. length
214+ ? {
215+ footerSlot : (
216+ < div className = "py-12" >
217+ < ExternalSupportEmptyState />
218+ </ div >
219+ ) ,
220+ }
221+ : { } ) }
197222 onSendMessage = { async ( message ) => {
198223 const createdAt = new Date ( ) ;
199224
@@ -344,67 +369,71 @@ function ProgramInfoPanel({
344369 { program . name }
345370 </ span >
346371 < span className = "text-content-subtle text-sm font-medium" >
347- Partner since { formatDate ( programEnrollment . createdAt ) }
372+ { INACTIVE_ENROLLMENT_STATUSES . includes ( programEnrollment . status )
373+ ? `You are ${ programEnrollment . status } from this program`
374+ : `Partner since ${ formatDate ( programEnrollment . createdAt ) } ` }
348375 </ span >
349376 </ div >
350377 </ div >
351378 </ div >
352379
353380 { /* Referral link */ }
354- { programEnrollment . links && programEnrollment . links . length > 0 && (
355- < div className = "pl-6 pr-6 pt-7" >
356- < div className = "flex items-end justify-between" >
357- < h3 className = "text-content-emphasis text-sm font-semibold" >
358- Referral link
359- </ h3 >
360- < Link
361- href = { `/programs/${ program . slug } /links` }
362- target = "_blank"
363- className = "text-sm font-medium text-neutral-500 hover:text-neutral-700"
364- >
365- View all
366- </ Link >
367- </ div >
381+ { programEnrollment . links &&
382+ programEnrollment . links . length > 0 &&
383+ ! INACTIVE_ENROLLMENT_STATUSES . includes ( programEnrollment . status ) && (
384+ < div className = "pl-6 pr-6 pt-7" >
385+ < div className = "flex items-end justify-between" >
386+ < h3 className = "text-content-emphasis text-sm font-semibold" >
387+ Referral link
388+ </ h3 >
389+ < Link
390+ href = { `/programs/${ program . slug } /links` }
391+ target = "_blank"
392+ className = "text-sm font-medium text-neutral-500 hover:text-neutral-700"
393+ >
394+ View all
395+ </ Link >
396+ </ div >
368397
369- < div className = "relative mt-2" >
370- < input
371- type = "text"
372- readOnly
373- value = { getPrettyUrl ( partnerLink ) }
374- className = "text-content-default focus:border-border-emphasis bg-bg-default block h-11 w-full rounded-xl border border-neutral-200 pl-3 pr-12 text-sm focus:outline-none focus:ring-neutral-500"
375- />
376- { /* Gradient fade overlay */ }
377- < div className = "pointer-events-none absolute right-12 top-1 h-8 w-10 bg-gradient-to-r from-transparent to-white" />
378- < button
379- type = "button"
380- onClick = { ( ) => {
381- copyToClipboard ( partnerLink ) ;
382- toast . success ( "Link copied" ) ;
383- } }
384- className = "absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-lg bg-neutral-900 text-white transition-colors hover:bg-gray-800"
385- >
386- < div className = "relative size-3" >
387- < div
388- className = { cn (
389- "absolute inset-0 transition-[transform,opacity]" ,
390- copied && "translate-y-1 opacity-0" ,
391- ) }
392- >
393- < Copy className = "size-3" />
394- </ div >
395- < div
396- className = { cn (
397- "absolute inset-0 transition-[transform,opacity]" ,
398- ! copied && "translate-y-1 opacity-0" ,
399- ) }
400- >
401- < Check className = "size-3" />
398+ < div className = "relative mt-2" >
399+ < input
400+ type = "text"
401+ readOnly
402+ value = { getPrettyUrl ( partnerLink ) }
403+ className = "text-content-default focus:border-border-emphasis bg-bg-default block h-11 w-full rounded-xl border border-neutral-200 pl-3 pr-12 text-sm focus:outline-none focus:ring-neutral-500"
404+ />
405+ { /* Gradient fade overlay */ }
406+ < div className = "pointer-events-none absolute right-12 top-1 h-8 w-10 bg-gradient-to-r from-transparent to-white" />
407+ < button
408+ type = "button"
409+ onClick = { ( ) => {
410+ copyToClipboard ( partnerLink ) ;
411+ toast . success ( "Link copied" ) ;
412+ } }
413+ className = "absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-lg bg-neutral-900 text-white transition-colors hover:bg-gray-800"
414+ >
415+ < div className = "relative size-3" >
416+ < div
417+ className = { cn (
418+ "absolute inset-0 transition-[transform,opacity]" ,
419+ copied && "translate-y-1 opacity-0" ,
420+ ) }
421+ >
422+ < Copy className = "size-3" />
423+ </ div >
424+ < div
425+ className = { cn (
426+ "absolute inset-0 transition-[transform,opacity]" ,
427+ ! copied && "translate-y-1 opacity-0" ,
428+ ) }
429+ >
430+ < Check className = "size-3" />
431+ </ div >
402432 </ div >
403- </ div >
404- </ button >
433+ </ button >
434+ </ div >
405435 </ div >
406- </ div >
407- ) }
436+ ) }
408437
409438 { /* Stats */ }
410439 < div className = "pl-6 pr-6 pt-7" >
@@ -451,22 +480,26 @@ function ProgramInfoPanel({
451480 </ div >
452481
453482 { /* Rewards */ }
454- < div className = "pl-6 pr-6 pt-7" >
455- < h3 className = "text-content-emphasis text-sm font-semibold" > Rewards</ h3 >
456- < div className = "mt-1" >
457- < ProgramRewardsPanel
458- rewards = { programEnrollment . rewards ?? [ ] }
459- discount = { programEnrollment . discount }
460- />
483+ { ! INACTIVE_ENROLLMENT_STATUSES . includes ( programEnrollment . status ) && (
484+ < div className = "pl-6 pr-6 pt-7" >
485+ < h3 className = "text-content-emphasis text-sm font-semibold" >
486+ Rewards
487+ </ h3 >
488+ < div className = "mt-1" >
489+ < ProgramRewardsPanel
490+ rewards = { programEnrollment . rewards ?? [ ] }
491+ discount = { programEnrollment . discount }
492+ />
493+ </ div >
461494 </ div >
462- </ div >
495+ ) }
463496
464497 { /* Help & support */ }
465498 < div className = "border-border-subtle pl-6 pr-6 pt-7" >
466499 < h3 className = "text-content-emphasis text-sm font-semibold" >
467500 Help and support
468501 </ h3 >
469- < div className = "mt-1" >
502+ < div className = "-ml-2 mt-1" >
470503 < ProgramHelpLinks />
471504 </ div >
472505 </ div >
0 commit comments