Skip to content

Commit 505bcf5

Browse files
committed
cleanup public button, transfer monitors
1 parent 6073324 commit 505bcf5

File tree

9 files changed

+689
-177
lines changed

9 files changed

+689
-177
lines changed

apps/dashboard/app/(main)/monitors/[id]/page.tsx

Lines changed: 51 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,11 @@
11
"use client";
22

3-
import {
4-
ArrowClockwiseIcon,
5-
ArrowLeftIcon,
6-
GlobeIcon,
7-
HeartbeatIcon,
8-
PauseIcon,
9-
PencilIcon,
10-
PlayIcon,
11-
TrashIcon,
12-
} from "@phosphor-icons/react";
13-
import { keepPreviousData, useMutation, useQuery } from "@tanstack/react-query";
14-
import dynamic from "next/dynamic";
15-
import Link from "next/link";
16-
import { useParams, useRouter } from "next/navigation";
17-
import { useCallback, useEffect, useMemo, useState } from "react";
18-
import { toast } from "sonner";
193
import { MonitorDetailLoading } from "@/app/(main)/monitors/_components/monitor-detail-loading";
204
import { PageHeader } from "@/app/(main)/websites/_components/page-header";
215
import { FaviconImage } from "@/components/analytics/favicon-image";
226
import { EmptyState } from "@/components/empty-state";
237
import { MonitorSheet } from "@/components/monitors/monitor-sheet";
8+
import { TransferToOrgDialog } from "@/components/transfer-to-org-dialog";
249
import {
2510
AlertDialog,
2611
AlertDialogAction,
@@ -40,6 +25,23 @@ import { fromNow, localDayjs } from "@/lib/time";
4025
import { LatencyChartChunkPlaceholder } from "@/lib/uptime/latency-chart-chunk-placeholder";
4126
import { UptimeHeatmap } from "@/lib/uptime/uptime-heatmap";
4227
import { cn } from "@/lib/utils";
28+
import {
29+
ArrowClockwiseIcon,
30+
ArrowLeftIcon,
31+
ArrowSquareOutIcon,
32+
GlobeIcon,
33+
HeartbeatIcon,
34+
PauseIcon,
35+
PencilIcon,
36+
PlayIcon,
37+
TrashIcon,
38+
} from "@phosphor-icons/react";
39+
import { keepPreviousData, useMutation, useQuery } from "@tanstack/react-query";
40+
import dynamic from "next/dynamic";
41+
import Link from "next/link";
42+
import { useParams, useRouter } from "next/navigation";
43+
import { useCallback, useEffect, useMemo, useState } from "react";
44+
import { toast } from "sonner";
4345
import {
4446
RecentActivity,
4547
type RecentActivityCheck,
@@ -72,6 +74,7 @@ const granularityLabels: Record<string, string> = {
7274

7375
interface ScheduleData {
7476
id: string;
77+
organizationId: string;
7578
websiteId: string | null;
7679
url: string;
7780
name: string | null;
@@ -151,6 +154,7 @@ export default function MonitorDetailsPage() {
151154
const router = useRouter();
152155
const { dateRange } = useDateFilters();
153156

157+
const [isTransferOpen, setIsTransferOpen] = useState(false);
154158
const [isSheetOpen, setIsSheetOpen] = useState(false);
155159
const [editingSchedule, setEditingSchedule] = useState<{
156160
id: string;
@@ -205,8 +209,9 @@ export default function MonitorDetailsPage() {
205209
const deleteMutation = useMutation({
206210
...orpc.uptime.deleteSchedule.mutationOptions(),
207211
});
208-
const togglePublicMutation = useMutation({
209-
...orpc.statusPage.togglePublicMonitor.mutationOptions(),
212+
213+
const transferMutation = useMutation({
214+
...orpc.uptime.transfer.mutationOptions(),
210215
});
211216

212217
// --- Recent checks (paginated) ---
@@ -470,30 +475,25 @@ export default function MonitorDetailsPage() {
470475
setIsRefreshing(false);
471476
};
472477

473-
const handleTogglePublic = async () => {
478+
const handleTransfer = async (targetOrganizationId: string) => {
474479
if (!schedule) {
475480
return;
476481
}
477482
try {
478-
const result = await togglePublicMutation.mutateAsync({
483+
await transferMutation.mutateAsync({
479484
scheduleId: schedule.id,
480-
isPublic: !schedule.isPublic,
485+
targetOrganizationId,
481486
});
482-
await refetchSchedule();
483-
toast.success(
484-
result.isPublic
485-
? "Monitor is now visible on the public status page"
486-
: "Monitor removed from the public status page"
487-
);
487+
toast.success("Monitor transferred successfully");
488+
setIsTransferOpen(false);
489+
router.push("/monitors");
488490
} catch (error) {
489491
const errorMessage =
490-
error instanceof Error ? error.message : "Failed to update visibility";
492+
error instanceof Error ? error.message : "Failed to transfer monitor";
491493
toast.error(errorMessage);
492494
}
493495
};
494496

495-
// --- Render ---
496-
497497
if (isLoadingSchedule) {
498498
return <MonitorDetailLoading />;
499499
}
@@ -568,21 +568,6 @@ export default function MonitorDetailsPage() {
568568
className={isRefreshing ? "animate-spin" : ""}
569569
/>
570570
</Button>
571-
<Button
572-
disabled={togglePublicMutation.isPending}
573-
onClick={handleTogglePublic}
574-
size="sm"
575-
type="button"
576-
variant={schedule.isPublic ? "default" : "outline"}
577-
>
578-
<GlobeIcon size={16} weight="duotone" />
579-
<span className="hidden sm:inline">
580-
{schedule.isPublic ? "Public" : "Make public"}
581-
</span>
582-
<span className="sm:hidden">
583-
{schedule.isPublic ? "Listed" : "List"}
584-
</span>
585-
</Button>
586571
<Button
587572
disabled={
588573
isPausing || pauseMutation.isPending || resumeMutation.isPending
@@ -614,6 +599,16 @@ export default function MonitorDetailsPage() {
614599
<PencilIcon size={16} weight="duotone" />
615600
<span className="hidden sm:inline">Configure</span>
616601
</Button>
602+
<Button
603+
aria-label="Transfer monitor"
604+
onClick={() => setIsTransferOpen(true)}
605+
size="sm"
606+
type="button"
607+
variant="outline"
608+
>
609+
<ArrowSquareOutIcon size={16} weight="duotone" />
610+
<span className="hidden sm:inline">Transfer</span>
611+
</Button>
617612
<Button
618613
aria-label="Delete monitor"
619614
disabled={deleteMutation.isPending}
@@ -726,6 +721,17 @@ export default function MonitorDetailsPage() {
726721
/>
727722
) : null}
728723

724+
<TransferToOrgDialog
725+
currentOrganizationId={schedule.organizationId}
726+
description={`Move "${displayName}" to a different workspace.`}
727+
isPending={transferMutation.isPending}
728+
onOpenChangeAction={setIsTransferOpen}
729+
onTransferAction={handleTransfer}
730+
open={isTransferOpen}
731+
title="Transfer Monitor"
732+
warning="All monitoring data and configuration will be transferred to {orgName}."
733+
/>
734+
729735
<AlertDialog
730736
onOpenChange={setIsDeleteDialogOpen}
731737
open={isDeleteDialogOpen}

apps/dashboard/app/(main)/monitors/page.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
"use client";
22

3-
import {
4-
ArrowClockwiseIcon,
5-
HeartbeatIcon,
6-
PlusIcon,
7-
UserPlusIcon,
8-
} from "@phosphor-icons/react";
9-
import { useQuery } from "@tanstack/react-query";
10-
import { Suspense, useState } from "react";
113
import { PageHeader } from "@/app/(main)/websites/_components/page-header";
124
import { ErrorBoundary } from "@/components/error-boundary";
135
import { FeatureAccessGate } from "@/components/feature-access-gate";
@@ -20,9 +12,18 @@ import { useFeatureAccess } from "@/hooks/use-feature-access";
2012
import type { ListQuerySlice } from "@/lib/list-query-outcome";
2113
import { orpc } from "@/lib/orpc";
2214
import { cn } from "@/lib/utils";
15+
import {
16+
ArrowClockwiseIcon,
17+
HeartbeatIcon,
18+
PlusIcon,
19+
UserPlusIcon,
20+
} from "@phosphor-icons/react";
21+
import { useQuery } from "@tanstack/react-query";
22+
import { Suspense, useState } from "react";
2323

2424
export interface Monitor {
2525
id: string;
26+
organizationId: string;
2627
websiteId: string | null;
2728
url: string | null;
2829
name: string | null;

apps/dashboard/app/(main)/monitors/status-pages/[id]/page.tsx

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,57 @@ import { EmptyState } from "@/components/empty-state";
55
import { ErrorBoundary } from "@/components/error-boundary";
66
import { FeatureLockedPanel } from "@/components/feature-access-gate";
77
import { PageNavigation } from "@/components/layout/page-navigation";
8+
import { TransferToOrgDialog } from "@/components/transfer-to-org-dialog";
89
import { Badge } from "@/components/ui/badge";
910
import { Button } from "@/components/ui/button";
1011
import { List } from "@/components/ui/composables/list";
1112
import { DeleteDialog } from "@/components/ui/delete-dialog";
13+
import { Label } from "@/components/ui/label";
1214
import { Skeleton } from "@/components/ui/skeleton";
15+
import { Switch } from "@/components/ui/switch";
1316
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
1417
import { useFeatureAccess } from "@/hooks/use-feature-access";
1518
import { getStatusPageUrl } from "@/lib/app-url";
1619
import { orpc } from "@/lib/orpc";
1720
import { cn } from "@/lib/utils";
1821
import {
1922
ArrowClockwiseIcon,
23+
ArrowSquareOutIcon,
2024
BrowserIcon,
2125
HeartbeatIcon,
2226
PlusIcon,
2327
SirenIcon,
2428
} from "@phosphor-icons/react";
2529
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2630
import Link from "next/link";
27-
import { useParams } from "next/navigation";
28-
import { useState, type ReactNode } from "react";
31+
import { useParams, useRouter } from "next/navigation";
32+
import { type ReactNode, useState } from "react";
2933
import { toast } from "sonner";
3034
import { AddMonitorDialog } from "./_components/add-monitor-dialog";
3135
import {
32-
StatusPageMonitorRow,
3336
type StatusPageMonitor,
37+
StatusPageMonitorRow,
3438
} from "./_components/status-page-monitor-row";
3539

3640
export default function StatusPageDetailsPage() {
3741
const params = useParams();
42+
const router = useRouter();
3843
const statusPageId = params.id as string;
3944
const queryClient = useQueryClient();
4045
const [isDialogOpen, setIsDialogOpen] = useState(false);
46+
const [isTransferOpen, setIsTransferOpen] = useState(false);
47+
const [includeMonitors, setIncludeMonitors] = useState(true);
4148
const [monitorToRemove, setMonitorToRemove] = useState<string | null>(null);
4249

4350
const statusPageQuery = useQuery({
4451
...orpc.statusPage.get.queryOptions({ input: { statusPageId } }),
4552
enabled: !!statusPageId,
4653
});
4754

55+
const transferMutation = useMutation({
56+
...orpc.statusPage.transfer.mutationOptions(),
57+
});
58+
4859
const removeMutation = useMutation({
4960
...orpc.statusPage.removeMonitor.mutationOptions(),
5061
onSuccess: () => {
@@ -69,6 +80,25 @@ export default function StatusPageDetailsPage() {
6980
});
7081
};
7182

83+
const handleTransfer = async (targetOrganizationId: string) => {
84+
try {
85+
await transferMutation.mutateAsync({
86+
statusPageId,
87+
targetOrganizationId,
88+
includeMonitors,
89+
});
90+
toast.success("Status page transferred successfully");
91+
setIsTransferOpen(false);
92+
router.push("/monitors/status-pages");
93+
} catch (error) {
94+
const errorMessage =
95+
error instanceof Error
96+
? error.message
97+
: "Failed to transfer status page";
98+
toast.error(errorMessage);
99+
}
100+
};
101+
72102
const handleConfirmRemove = () => {
73103
if (!monitorToRemoveData) {
74104
return;
@@ -166,6 +196,14 @@ export default function StatusPageDetailsPage() {
166196
)}
167197
/>
168198
</Button>
199+
<Button
200+
onClick={() => setIsTransferOpen(true)}
201+
size="sm"
202+
variant="outline"
203+
>
204+
<ArrowSquareOutIcon weight="duotone" />
205+
<span className="hidden sm:inline">Transfer</span>
206+
</Button>
169207
<Button onClick={() => setIsDialogOpen(true)} size="sm">
170208
<PlusIcon />
171209
Add Monitor
@@ -259,6 +297,33 @@ export default function StatusPageDetailsPage() {
259297
onConfirm={handleConfirmRemove}
260298
title="Remove Monitor"
261299
/>
300+
301+
{statusPage ? (
302+
<TransferToOrgDialog
303+
currentOrganizationId={statusPage.organizationId}
304+
description={`Move "${statusPage.name}" to a different workspace.`}
305+
isPending={transferMutation.isPending}
306+
onOpenChangeAction={setIsTransferOpen}
307+
onTransferAction={handleTransfer}
308+
open={isTransferOpen}
309+
title="Transfer Status Page"
310+
warning="The status page and its configuration will be transferred to {orgName}."
311+
>
312+
<div className="flex items-center justify-between rounded border p-3">
313+
<Label
314+
className="cursor-pointer text-sm"
315+
htmlFor="include-monitors-detail"
316+
>
317+
Include all linked monitors
318+
</Label>
319+
<Switch
320+
checked={includeMonitors}
321+
id="include-monitors-detail"
322+
onCheckedChange={setIncludeMonitors}
323+
/>
324+
</div>
325+
</TransferToOrgDialog>
326+
) : null}
262327
</div>
263328
</ErrorBoundary>
264329
);

apps/dashboard/app/(main)/monitors/status-pages/page.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
"use client";
22

3-
import {
4-
ArrowClockwiseIcon,
5-
BrowserIcon,
6-
PlusIcon,
7-
} from "@phosphor-icons/react";
8-
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
9-
import { Suspense, useState } from "react";
10-
import { toast } from "sonner";
113
import { PageHeader } from "@/app/(main)/websites/_components/page-header";
124
import { EmptyState } from "@/components/empty-state";
135
import { ErrorBoundary } from "@/components/error-boundary";
@@ -25,6 +17,14 @@ import { DeleteDialog } from "@/components/ui/delete-dialog";
2517
import { useFeatureAccess } from "@/hooks/use-feature-access";
2618
import { orpc } from "@/lib/orpc";
2719
import { cn } from "@/lib/utils";
20+
import {
21+
ArrowClockwiseIcon,
22+
BrowserIcon,
23+
PlusIcon,
24+
} from "@phosphor-icons/react";
25+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
26+
import { Suspense, useState } from "react";
27+
import { toast } from "sonner";
2828

2929
export default function StatusPagesListPage() {
3030
const { hasAccess, isLoading: isAccessLoading } =
@@ -187,6 +187,7 @@ export default function StatusPagesListPage() {
187187
key={statusPage.id}
188188
onDeleteAction={() => setStatusPageToDelete(statusPage)}
189189
onEditAction={() => handleEdit(statusPage)}
190+
onTransferSuccessAction={() => statusPagesQuery.refetch()}
190191
statusPage={statusPage}
191192
/>
192193
))}

0 commit comments

Comments
 (0)