Skip to content

Commit df12238

Browse files
committed
Improve globe rendering and responsive positioning
1 parent d4cd45d commit df12238

17 files changed

Lines changed: 1409 additions & 68 deletions

apps/web/src/app/(dashboard)/[websiteSlug]/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from "@/lib/trpc/server";
1515
import { isValidWebsiteSlug } from "@/lib/url";
1616
import { ContactVisitorDetailOverlay } from "./overlays/detail-page-overlay";
17+
import { LiveVisitorsOverlay } from "./overlays/live-visitors-overlay";
1718
import { ModalsAndSheets } from "./overlays/modals-and-sheets";
1819
import { Realtime } from "./providers/realtime";
1920
import { DashboardWebSocketProvider } from "./providers/websocket";
@@ -113,6 +114,7 @@ export default async function Layout({ children, params }: LayoutProps) {
113114
latestRelease={latestRelease}
114115
/>
115116
<CentralContainer>{children}</CentralContainer>
117+
<LiveVisitorsOverlay />
116118
<ContactVisitorDetailOverlay />
117119
<ModalsAndSheets />
118120
</div>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"use client";
2+
3+
import type * as React from "react";
4+
import { cn } from "@/lib/utils";
5+
6+
type DashboardOverlayShellProps = React.ComponentProps<"div"> & {
7+
children: React.ReactNode;
8+
dataSlot: string;
9+
zIndexClassName?: string;
10+
};
11+
12+
export function DashboardOverlayShell({
13+
children,
14+
className,
15+
dataSlot,
16+
zIndexClassName = "z-20",
17+
...props
18+
}: DashboardOverlayShellProps) {
19+
return (
20+
<div
21+
className={cn(
22+
"absolute inset-x-0 top-15 bottom-0 flex flex-col overflow-hidden bg-background",
23+
zIndexClassName,
24+
className
25+
)}
26+
data-slot={dataSlot}
27+
{...props}
28+
>
29+
{children}
30+
</div>
31+
);
32+
}
33+
34+
type DashboardOverlayCenteredStateProps = React.ComponentProps<"div"> & {
35+
children: React.ReactNode;
36+
};
37+
38+
export function DashboardOverlayCenteredState({
39+
children,
40+
className,
41+
...props
42+
}: DashboardOverlayCenteredStateProps) {
43+
return (
44+
<div
45+
className={cn("flex h-full items-center justify-center", className)}
46+
{...props}
47+
>
48+
{children}
49+
</div>
50+
);
51+
}

apps/web/src/app/(dashboard)/[websiteSlug]/overlays/detail-page-overlay.test.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@ mock.module("facehash", () => ({
88
),
99
}));
1010

11+
mock.module("@normy/react-query", () => ({
12+
useQueryNormalizer: () => ({
13+
getObjectById: () => {},
14+
}),
15+
}));
16+
17+
mock.module("@tanstack/react-query", () => ({
18+
useQueries: () => [],
19+
useQuery: () => ({
20+
data: null,
21+
isError: false,
22+
isLoading: false,
23+
}),
24+
}));
25+
1126
mock.module("@/components/ui/avatar", () => ({
1227
Avatar: ({
1328
className,
@@ -92,6 +107,33 @@ mock.module("@/components/ui/layout/sidebars/visitor/utils", () => ({
92107
}),
93108
}));
94109

110+
mock.module("@/contexts/website", () => ({
111+
useWebsite: () => ({
112+
slug: "acme",
113+
}),
114+
}));
115+
116+
mock.module("@/hooks/use-contact-visitor-detail-state", () => ({
117+
useContactVisitorDetailState: () => ({
118+
activeDetail: null,
119+
}),
120+
}));
121+
122+
mock.module("@/lib/trpc/client", () => ({
123+
useTRPC: () => ({
124+
contact: {
125+
get: {
126+
queryOptions: () => ({}),
127+
},
128+
},
129+
conversation: {
130+
getVisitorById: {
131+
queryOptions: () => ({}),
132+
},
133+
},
134+
}),
135+
}));
136+
95137
const modulePromise = import("./detail-page-overlay");
96138

97139
const contact = {
@@ -245,6 +287,8 @@ describe("ContactVisitorDetailView", () => {
245287
const html = await renderView({});
246288

247289
expect(html).toContain('data-slot="contact-visitor-detail-overlay"');
290+
expect(html).toContain("top-15");
291+
expect(html).toContain("z-20");
248292
expect(html).toContain('data-slot="contact-visitor-detail-layout"');
249293
expect(html).toContain("grid-cols-1 lg:grid-cols-2");
250294
expect(html.match(/max-w-sm/g)?.length).toBe(2);

apps/web/src/app/(dashboard)/[websiteSlug]/overlays/detail-page-overlay.tsx

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { RouterOutputs } from "@cossistant/api/types";
44
import { resolveCountryDetails } from "@cossistant/location/country-utils";
55
import type { ContactDetailResponse } from "@cossistant/types";
66
import { useQueryNormalizer } from "@normy/react-query";
7-
import { useQueries, useQuery } from "@tanstack/react-query";
7+
import * as ReactQuery from "@tanstack/react-query";
88
import { Monitor, Smartphone } from "lucide-react";
99
import { useMemo } from "react";
1010
import { Globe, type GlobeFocus, type GlobeVisitor } from "@/components/globe";
@@ -29,6 +29,10 @@ import { formatFullDateTime, formatLastSeenAt } from "@/lib/date";
2929
import { useTRPC } from "@/lib/trpc/client";
3030
import { cn } from "@/lib/utils";
3131
import { getVisitorNameWithFallback } from "@/lib/visitors";
32+
import {
33+
DashboardOverlayCenteredState,
34+
DashboardOverlayShell,
35+
} from "./dashboard-overlay-shell";
3236

3337
type ContactDetail = RouterOutputs["contact"]["get"];
3438
type DetailContact = ContactDetailResponse["contact"];
@@ -525,24 +529,6 @@ function DetailMetric({ label, tooltip, value }: DetailMetricProps) {
525529
return <TooltipOnHover content={tooltip}>{content}</TooltipOnHover>;
526530
}
527531

528-
function DetailOverlayShell({
529-
children,
530-
mode,
531-
}: {
532-
children: React.ReactNode;
533-
mode: "contact" | "visitor";
534-
}) {
535-
return (
536-
<div
537-
className="absolute inset-x-0 top-15 bottom-0 z-20 flex flex-col overflow-hidden bg-background"
538-
data-mode={mode}
539-
data-slot="contact-visitor-detail-overlay"
540-
>
541-
{children}
542-
</div>
543-
);
544-
}
545-
546532
function DevicesSection({
547533
deviceDetailsById,
548534
heroVisitor,
@@ -957,20 +943,26 @@ export function ContactVisitorDetailView({
957943

958944
if (isLoading) {
959945
return (
960-
<DetailOverlayShell mode={mode}>
961-
<div className="flex h-full items-center justify-center">
946+
<DashboardOverlayShell
947+
data-mode={mode}
948+
dataSlot="contact-visitor-detail-overlay"
949+
>
950+
<DashboardOverlayCenteredState>
962951
<div className="flex items-center gap-3 text-primary/60 text-sm">
963952
<Spinner className="h-5 w-5" />
964953
<span>Loading details...</span>
965954
</div>
966-
</div>
967-
</DetailOverlayShell>
955+
</DashboardOverlayCenteredState>
956+
</DashboardOverlayShell>
968957
);
969958
}
970959

971960
if (isError) {
972961
return (
973-
<DetailOverlayShell mode={mode}>
962+
<DashboardOverlayShell
963+
data-mode={mode}
964+
dataSlot="contact-visitor-detail-overlay"
965+
>
974966
<div className="px-4 py-6 lg:px-6 lg:py-8">
975967
<Alert variant="destructive">
976968
<AlertTitle>Unable to load details</AlertTitle>
@@ -980,22 +972,28 @@ export function ContactVisitorDetailView({
980972
</AlertDescription>
981973
</Alert>
982974
</div>
983-
</DetailOverlayShell>
975+
</DashboardOverlayShell>
984976
);
985977
}
986978

987979
if (!(contact || heroVisitor) && visitors.length === 0) {
988980
return (
989-
<DetailOverlayShell mode={mode}>
990-
<div className="flex h-full items-center justify-center px-6 text-center text-primary/60 text-sm">
981+
<DashboardOverlayShell
982+
data-mode={mode}
983+
dataSlot="contact-visitor-detail-overlay"
984+
>
985+
<DashboardOverlayCenteredState className="px-6 text-center text-primary/60 text-sm">
991986
No details are available for this selection.
992-
</div>
993-
</DetailOverlayShell>
987+
</DashboardOverlayCenteredState>
988+
</DashboardOverlayShell>
994989
);
995990
}
996991

997992
return (
998-
<DetailOverlayShell mode={mode}>
993+
<DashboardOverlayShell
994+
data-mode={mode}
995+
dataSlot="contact-visitor-detail-overlay"
996+
>
999997
<div
1000998
className="grid h-full grid-cols-1 lg:grid-cols-2"
1001999
data-slot="contact-visitor-detail-layout"
@@ -1019,7 +1017,7 @@ export function ContactVisitorDetailView({
10191017
visitors={visitors}
10201018
/>
10211019
</div>
1022-
</DetailOverlayShell>
1020+
</DashboardOverlayShell>
10231021
);
10241022
}
10251023

@@ -1044,7 +1042,7 @@ export function ContactVisitorDetailOverlay() {
10441042
return isContactDetailResponse(candidate) ? candidate : undefined;
10451043
}, [activeContactId, queryNormalizer]);
10461044

1047-
const contactQuery = useQuery({
1045+
const contactQuery = ReactQuery.useQuery({
10481046
...trpc.contact.get.queryOptions({
10491047
contactId: activeContactId ?? "",
10501048
websiteSlug: website.slug,
@@ -1064,7 +1062,7 @@ export function ContactVisitorDetailOverlay() {
10641062
return queryNormalizer.getObjectById<VisitorDetail>(leadVisitorId);
10651063
}, [leadVisitorId, queryNormalizer]);
10661064

1067-
const visitorQuery = useQuery({
1065+
const visitorQuery = ReactQuery.useQuery({
10681066
...trpc.conversation.getVisitorById.queryOptions({
10691067
visitorId: leadVisitorId ?? "",
10701068
websiteSlug: website.slug,
@@ -1076,7 +1074,7 @@ export function ContactVisitorDetailOverlay() {
10761074
});
10771075
const resolvedHeroVisitor = visitorQuery.data ?? null;
10781076

1079-
const contactVisitorDetailsQueries = useQueries({
1077+
const contactVisitorDetailsQueries = ReactQuery.useQueries({
10801078
queries:
10811079
activeDetail?.type === "contact"
10821080
? (contactQuery.data?.visitors ?? []).map((visitor) => ({

0 commit comments

Comments
 (0)