fix: Migrate over to new pagination format in dashboard
This commit is contained in:
@@ -77,13 +77,7 @@ export class Campaigns {
|
|||||||
pageSize,
|
pageSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.json({
|
return res.json(result);
|
||||||
campaigns: result.campaigns,
|
|
||||||
page: result.page,
|
|
||||||
pageSize: result.pageSize,
|
|
||||||
total: result.total,
|
|
||||||
totalPages: result.totalPages,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type {Campaign, Contact, Prisma} from '@plunk/db';
|
import type {Campaign, Contact, Prisma} from '@plunk/db';
|
||||||
import {CampaignAudienceType, CampaignStatus, EmailSourceType} from '@plunk/db';
|
import {CampaignAudienceType, CampaignStatus, EmailSourceType} from '@plunk/db';
|
||||||
import type {CreateCampaignData, FilterCondition, UpdateCampaignData} from '@plunk/types';
|
import type {CreateCampaignData, FilterCondition, PaginatedResponse, UpdateCampaignData} from '@plunk/types';
|
||||||
import {fromPrismaJson, toPrismaJson} from '@plunk/types';
|
import {fromPrismaJson, toPrismaJson} from '@plunk/types';
|
||||||
import signale from 'signale';
|
import signale from 'signale';
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ export class CampaignService {
|
|||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
} = {},
|
} = {},
|
||||||
): Promise<{campaigns: Campaign[]; total: number; page: number; pageSize: number; totalPages: number}> {
|
): Promise<PaginatedResponse<Campaign>> {
|
||||||
const {status, page = 1, pageSize = 20} = options;
|
const {status, page = 1, pageSize = 20} = options;
|
||||||
const skip = (page - 1) * pageSize;
|
const skip = (page - 1) * pageSize;
|
||||||
|
|
||||||
@@ -207,7 +207,7 @@ export class CampaignService {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
campaigns,
|
data: campaigns,
|
||||||
total,
|
total,
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
|
|||||||
@@ -21,18 +21,11 @@ import {
|
|||||||
} from '@plunk/ui';
|
} from '@plunk/ui';
|
||||||
import type {Campaign} from '@plunk/db';
|
import type {Campaign} from '@plunk/db';
|
||||||
import {CampaignStatus} from '@plunk/db';
|
import {CampaignStatus} from '@plunk/db';
|
||||||
|
import type {PaginatedResponse} from '@plunk/types';
|
||||||
import {ArrowLeft, Calendar, Mail, Users} from 'lucide-react';
|
import {ArrowLeft, Calendar, Mail, Users} from 'lucide-react';
|
||||||
import {useState} from 'react';
|
import {useState} from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
interface PaginatedCampaigns {
|
|
||||||
campaigns: Campaign[];
|
|
||||||
total: number;
|
|
||||||
page: number;
|
|
||||||
pageSize: number;
|
|
||||||
totalPages: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CampaignSelectionDialogProps {
|
interface CampaignSelectionDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
@@ -62,7 +55,7 @@ export function CampaignSelectionDialog({open, onOpenChange, onSelectCampaign}:
|
|||||||
audience: true,
|
audience: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {data, isLoading} = useSWR<PaginatedCampaigns>(
|
const {data, isLoading} = useSWR<PaginatedResponse<Campaign>>(
|
||||||
open ? `/campaigns?page=${page}&pageSize=10${statusFilter !== 'ALL' ? `&status=${statusFilter}` : ''}` : null,
|
open ? `/campaigns?page=${page}&pageSize=10${statusFilter !== 'ALL' ? `&status=${statusFilter}` : ''}` : null,
|
||||||
{revalidateOnFocus: false},
|
{revalidateOnFocus: false},
|
||||||
);
|
);
|
||||||
@@ -192,7 +185,7 @@ export function CampaignSelectionDialog({open, onOpenChange, onSelectCampaign}:
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isLoading && data?.campaigns.length === 0 && (
|
{!isLoading && data?.data.length === 0 && (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<Mail className="h-12 w-12 text-neutral-400 mx-auto mb-4" />
|
<Mail className="h-12 w-12 text-neutral-400 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-neutral-900 mb-2">No campaigns found</h3>
|
<h3 className="text-lg font-medium text-neutral-900 mb-2">No campaigns found</h3>
|
||||||
@@ -202,7 +195,7 @@ export function CampaignSelectionDialog({open, onOpenChange, onSelectCampaign}:
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data?.campaigns.map(campaign => (
|
{data?.data.map(campaign => (
|
||||||
<Card
|
<Card
|
||||||
key={campaign.id}
|
key={campaign.id}
|
||||||
className="cursor-pointer hover:border-primary/50 hover:shadow-md transition-all"
|
className="cursor-pointer hover:border-primary/50 hover:shadow-md transition-all"
|
||||||
|
|||||||
@@ -16,18 +16,11 @@ import {
|
|||||||
Label,
|
Label,
|
||||||
} from '@plunk/ui';
|
} from '@plunk/ui';
|
||||||
import type {Template} from '@plunk/db';
|
import type {Template} from '@plunk/db';
|
||||||
|
import type {PaginatedResponse} from '@plunk/types';
|
||||||
import {ArrowLeft, FileText, Search} from 'lucide-react';
|
import {ArrowLeft, FileText, Search} from 'lucide-react';
|
||||||
import {useState} from 'react';
|
import {useState} from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
interface PaginatedTemplates {
|
|
||||||
templates: Template[];
|
|
||||||
total: number;
|
|
||||||
page: number;
|
|
||||||
pageSize: number;
|
|
||||||
totalPages: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TemplateSelectionDialogProps {
|
interface TemplateSelectionDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
@@ -57,7 +50,7 @@ export function TemplateSelectionDialog({open, onOpenChange, onSelectTemplate}:
|
|||||||
replyTo: true,
|
replyTo: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {data, isLoading} = useSWR<PaginatedTemplates>(
|
const {data, isLoading} = useSWR<PaginatedResponse<Template>>(
|
||||||
open
|
open
|
||||||
? `/templates?page=${page}&pageSize=10${search ? `&search=${search}` : ''}${typeFilter !== 'ALL' ? `&type=${typeFilter}` : ''}`
|
? `/templates?page=${page}&pageSize=10${search ? `&search=${search}` : ''}${typeFilter !== 'ALL' ? `&type=${typeFilter}` : ''}`
|
||||||
: null,
|
: null,
|
||||||
@@ -219,7 +212,7 @@ export function TemplateSelectionDialog({open, onOpenChange, onSelectTemplate}:
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isLoading && data?.templates.length === 0 && (
|
{!isLoading && data?.data.length === 0 && (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<FileText className="h-12 w-12 text-neutral-400 mx-auto mb-4" />
|
<FileText className="h-12 w-12 text-neutral-400 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-neutral-900 mb-2">No templates found</h3>
|
<h3 className="text-lg font-medium text-neutral-900 mb-2">No templates found</h3>
|
||||||
@@ -229,7 +222,7 @@ export function TemplateSelectionDialog({open, onOpenChange, onSelectTemplate}:
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data?.templates.map(template => (
|
{data?.data.map(template => (
|
||||||
<Card
|
<Card
|
||||||
key={template.id}
|
key={template.id}
|
||||||
className="cursor-pointer hover:border-primary/50 hover:shadow-md transition-all"
|
className="cursor-pointer hover:border-primary/50 hover:shadow-md transition-all"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
} from '@plunk/ui';
|
} from '@plunk/ui';
|
||||||
import type {Campaign, Template} from '@plunk/db';
|
import type {Campaign, Template} from '@plunk/db';
|
||||||
import {CampaignStatus} from '@plunk/db';
|
import {CampaignStatus} from '@plunk/db';
|
||||||
|
import type {PaginatedResponse} from '@plunk/types';
|
||||||
import {DashboardLayout} from '../../components/DashboardLayout';
|
import {DashboardLayout} from '../../components/DashboardLayout';
|
||||||
import {TemplateSelectionDialog} from '../../components/TemplateSelectionDialog';
|
import {TemplateSelectionDialog} from '../../components/TemplateSelectionDialog';
|
||||||
import {CampaignSelectionDialog} from '../../components/CampaignSelectionDialog';
|
import {CampaignSelectionDialog} from '../../components/CampaignSelectionDialog';
|
||||||
@@ -33,14 +34,6 @@ import {toast} from 'sonner';
|
|||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
interface PaginatedCampaigns {
|
|
||||||
campaigns: Campaign[];
|
|
||||||
total: number;
|
|
||||||
page: number;
|
|
||||||
pageSize: number;
|
|
||||||
totalPages: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CampaignsPage() {
|
export default function CampaignsPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
@@ -52,7 +45,7 @@ export default function CampaignsPage() {
|
|||||||
const [showTemplateDialog, setShowTemplateDialog] = useState(false);
|
const [showTemplateDialog, setShowTemplateDialog] = useState(false);
|
||||||
const [showCampaignDialog, setShowCampaignDialog] = useState(false);
|
const [showCampaignDialog, setShowCampaignDialog] = useState(false);
|
||||||
|
|
||||||
const {data, mutate, isLoading} = useSWR<PaginatedCampaigns>(
|
const {data, mutate, isLoading} = useSWR<PaginatedResponse<Campaign>>(
|
||||||
`/campaigns?page=${page}&pageSize=20${statusFilter !== 'ALL' ? `&status=${statusFilter}` : ''}`,
|
`/campaigns?page=${page}&pageSize=20${statusFilter !== 'ALL' ? `&status=${statusFilter}` : ''}`,
|
||||||
{revalidateOnFocus: false},
|
{revalidateOnFocus: false},
|
||||||
);
|
);
|
||||||
@@ -293,7 +286,7 @@ export default function CampaignsPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isLoading && data?.campaigns.length === 0 && (
|
{!isLoading && data?.data.length === 0 && (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="py-16 text-center">
|
<CardContent className="py-16 text-center">
|
||||||
<div className="max-w-md mx-auto">
|
<div className="max-w-md mx-auto">
|
||||||
@@ -367,7 +360,7 @@ export default function CampaignsPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data?.campaigns.map(campaign => {
|
{data?.data.map(campaign => {
|
||||||
const openRate = campaign.sentCount > 0 ? (campaign.openedCount / campaign.sentCount) * 100 : 0;
|
const openRate = campaign.sentCount > 0 ? (campaign.openedCount / campaign.sentCount) * 100 : 0;
|
||||||
const clickRate = campaign.sentCount > 0 ? (campaign.clickedCount / campaign.sentCount) * 100 : 0;
|
const clickRate = campaign.sentCount > 0 ? (campaign.clickedCount / campaign.sentCount) * 100 : 0;
|
||||||
const deliveryProgress =
|
const deliveryProgress =
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
} from '@plunk/ui';
|
} from '@plunk/ui';
|
||||||
import type {Template} from '@plunk/db';
|
import type {Template} from '@plunk/db';
|
||||||
|
import type {PaginatedResponse} from '@plunk/types';
|
||||||
import {DashboardLayout} from '../../components/DashboardLayout';
|
import {DashboardLayout} from '../../components/DashboardLayout';
|
||||||
import {network} from '../../lib/network';
|
import {network} from '../../lib/network';
|
||||||
import {formatRelativeTime} from '../../lib/dateUtils';
|
import {formatRelativeTime} from '../../lib/dateUtils';
|
||||||
@@ -21,14 +22,6 @@ import {toast} from 'sonner';
|
|||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
interface PaginatedTemplates {
|
|
||||||
templates: Template[];
|
|
||||||
total: number;
|
|
||||||
page: number;
|
|
||||||
pageSize: number;
|
|
||||||
totalPages: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TemplatesPage() {
|
export default function TemplatesPage() {
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
@@ -37,7 +30,7 @@ export default function TemplatesPage() {
|
|||||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||||
const [templateToDelete, setTemplateToDelete] = useState<string | null>(null);
|
const [templateToDelete, setTemplateToDelete] = useState<string | null>(null);
|
||||||
|
|
||||||
const {data, mutate, isLoading} = useSWR<PaginatedTemplates>(
|
const {data, mutate, isLoading} = useSWR<PaginatedResponse<Template>>(
|
||||||
`/templates?page=${page}&pageSize=20${search ? `&search=${search}` : ''}${typeFilter !== 'ALL' ? `&type=${typeFilter}` : ''}`,
|
`/templates?page=${page}&pageSize=20${search ? `&search=${search}` : ''}${typeFilter !== 'ALL' ? `&type=${typeFilter}` : ''}`,
|
||||||
{revalidateOnFocus: false},
|
{revalidateOnFocus: false},
|
||||||
);
|
);
|
||||||
@@ -182,7 +175,7 @@ export default function TemplatesPage() {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
) : data?.templates.length === 0 ? (
|
) : data?.data.length === 0 ? (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6">
|
<CardContent className="pt-6">
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
@@ -204,7 +197,7 @@ export default function TemplatesPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{data?.templates.map(template => (
|
{data?.data.map(template => (
|
||||||
<Card key={template.id}>
|
<Card key={template.id}>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@plunk/ui';
|
} from '@plunk/ui';
|
||||||
import type {Workflow} from '@plunk/db';
|
import type {Workflow} from '@plunk/db';
|
||||||
|
import type {PaginatedResponse} from '@plunk/types';
|
||||||
import {DashboardLayout} from '../../components/DashboardLayout';
|
import {DashboardLayout} from '../../components/DashboardLayout';
|
||||||
import {network} from '../../lib/network';
|
import {network} from '../../lib/network';
|
||||||
import {formatRelativeTime} from '../../lib/dateUtils';
|
import {formatRelativeTime} from '../../lib/dateUtils';
|
||||||
@@ -33,14 +34,6 @@ import useSWR from 'swr';
|
|||||||
import {WorkflowSchemas} from '@plunk/shared';
|
import {WorkflowSchemas} from '@plunk/shared';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
interface PaginatedWorkflows {
|
|
||||||
workflows: (Workflow & {_count?: {steps: number; executions: number}})[];
|
|
||||||
total: number;
|
|
||||||
page: number;
|
|
||||||
pageSize: number;
|
|
||||||
totalPages: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function WorkflowsPage() {
|
export default function WorkflowsPage() {
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
@@ -49,10 +42,9 @@ export default function WorkflowsPage() {
|
|||||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||||
const [workflowToDelete, setWorkflowToDelete] = useState<string | null>(null);
|
const [workflowToDelete, setWorkflowToDelete] = useState<string | null>(null);
|
||||||
|
|
||||||
const {data, mutate, isLoading} = useSWR<PaginatedWorkflows>(
|
const {data, mutate, isLoading} = useSWR<
|
||||||
`/workflows?page=${page}&pageSize=20${search ? `&search=${search}` : ''}`,
|
PaginatedResponse<Workflow & {_count?: {steps: number; executions: number}}>
|
||||||
{revalidateOnFocus: false},
|
>(`/workflows?page=${page}&pageSize=20${search ? `&search=${search}` : ''}`, {revalidateOnFocus: false});
|
||||||
);
|
|
||||||
|
|
||||||
const handleSearch = (e: React.FormEvent) => {
|
const handleSearch = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -164,7 +156,7 @@ export default function WorkflowsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
) : data?.workflows.length === 0 ? (
|
) : data?.data.length === 0 ? (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6">
|
<CardContent className="pt-6">
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
@@ -184,7 +176,7 @@ export default function WorkflowsPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{data?.workflows.map(workflow => (
|
{data?.data.map(workflow => (
|
||||||
<Card key={workflow.id} className={workflow.enabled ? 'border-green-200' : ''}>
|
<Card key={workflow.id} className={workflow.enabled ? 'border-green-200' : ''}>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
|
|||||||
Reference in New Issue
Block a user