Skip to content

Commit 20be702

Browse files
committed
improve messaging empty / external support states
1 parent 26c0f94 commit 20be702

3 files changed

Lines changed: 270 additions & 234 deletions

File tree

apps/web/app/(ee)/api/partner-profile/messages/route.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,6 @@ export const GET = withPartnerProfile(async ({ partner, searchParams }) => {
1919

2020
const programs = await prisma.program.findMany({
2121
where: {
22-
// Partner is not banned from the program
23-
partners: {
24-
none: {
25-
partnerId: partner.id,
26-
status: "banned",
27-
},
28-
},
29-
3022
...(programSlug
3123
? {
3224
slug: programSlug,

apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx

Lines changed: 126 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import useProgramEnrollment from "@/lib/swr/use-program-enrollment";
1111
import { useProgramMessages } from "@/lib/swr/use-program-messages";
1212
import useUser from "@/lib/swr/use-user";
1313
import { ProgramEnrollmentProps } from "@/lib/types";
14+
import { INACTIVE_ENROLLMENT_STATUSES } from "@/lib/zod/schemas/partners";
1415
import { useMessagesContext } from "@/ui/messages/messages-context";
1516
import { MessagesPanel } from "@/ui/messages/messages-panel";
1617
import { 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

Comments
 (0)