Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PageContent } from "@/ui/layout/page-content";
import { PageWidthWrapper } from "@/ui/layout/page-width-wrapper";
import { ReactNode } from "react";

export default function BillingLayout({ children }: { children: ReactNode }) {
return (
<PageContent title="Billing">
<PageWidthWrapper>{children}</PageWidthWrapper>
</PageContent>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default function PaymentMethods() {
}

return (
<div className="rounded-lg border border-neutral-200 bg-white">
<div className="mb-8 rounded-xl border border-neutral-200 bg-white">
<div className="flex flex-col items-start justify-between gap-y-4 p-6 md:flex-row md:items-center md:p-8">
<div>
<h2 className="text-xl font-medium">Payment methods</h2>
Expand All @@ -63,7 +63,7 @@ export default function PaymentMethods() {
/>
)}
</div>
<div className="grid gap-4 border-t border-neutral-200 bg-neutral-100 p-6">
<div className="grid gap-4 rounded-b-xl border-t border-neutral-200 bg-neutral-100 p-6">
{regularPaymentMethods ? (
regularPaymentMethods.length > 0 ? (
regularPaymentMethods.map((paymentMethod) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export default function PlanUsage() {
}, [usage, usageLimit, linksUsage, linksLimit, totalLinks]);

return (
<div className="rounded-lg border border-neutral-200 bg-white">
<div className="rounded-xl border border-neutral-200 bg-white">
<div className="flex flex-col items-start justify-between gap-y-4 p-6 md:px-8 lg:flex-row">
<div>
<h2 className="text-xl font-medium">
Expand Down Expand Up @@ -206,7 +206,7 @@ export default function PlanUsage() {
href={`/${slug}/settings/people`}
/>
</div>
<div className="grid grid-cols-1 gap-[1px] overflow-hidden rounded-b-lg bg-neutral-200 md:grid-cols-4">
<div className="grid grid-cols-1 gap-[1px] overflow-hidden rounded-b-xl bg-neutral-200 md:grid-cols-4">
<UsageCategory
title="Partners"
icon={Users}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useAddEditEmailDomainModal } from "@/ui/modals/add-edit-email-domain-mo
import { AnimatedEmptyState } from "@/ui/shared/animated-empty-state";
import { ArrowTurnRight2, Button, buttonVariants } from "@dub/ui";
import { cn } from "@dub/utils";
import { EmailDomainCard } from "app/app.dub.co/(dashboard)/[slug]/settings/domains/email/email-domain-card";
import { EmailDomainCard } from "app/app.dub.co/(dashboard)/[slug]/(ee)/settings/domains/email/email-domain-card";
import { Mail } from "lucide-react";
import Link from "next/link";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,28 +67,26 @@ export default async function IntegrationPage(props: {
: undefined;

return (
<div className="mx-auto w-full max-w-screen-md">
<IntegrationPageClient
integration={{
...integration,
screenshots: integration.screenshots as string[],
installed: installed
? {
id: integration.installations[0].id,
by: {
id: integration.installations[0].userId,
name: integration.installations[0].user.name,
email: integration.installations[0].user.email,
image: integration.installations[0].user.image,
},
createdAt: integration.installations[0].createdAt,
}
: null,
credentials,
settings,
webhookId,
}}
/>
</div>
<IntegrationPageClient
integration={{
...integration,
screenshots: integration.screenshots as string[],
installed: installed
? {
id: integration.installations[0].id,
by: {
id: integration.installations[0].userId,
name: integration.installations[0].user.name,
email: integration.installations[0].user.email,
image: integration.installations[0].user.image,
},
createdAt: integration.installations[0].createdAt,
}
: null,
credentials,
settings,
webhookId,
}}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { PageContent } from "@/ui/layout/page-content";
import { PageWidthWrapper } from "@/ui/layout/page-width-wrapper";
import { ReactNode } from "react";

export default function IntegrationsLayout({
children,
}: {
children: ReactNode;
}) {
return (
<PageContent
title="Integrations"
titleInfo={{
title:
"Use Dub with your existing favorite tools with our seamless integrations.",
href: "https://d.to/integrations",
}}
>
<PageWidthWrapper className="max-w-[800px] pb-20">
{children}
</PageWidthWrapper>
</PageContent>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { IntegrationsList } from "./integrations-list";

export const revalidate = 300; // 5 minutes

export default function IntegrationsPage() {
return <IntegrationsList />;
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PageContent } from "@/ui/layout/page-content";
import { PageWidthWrapper } from "@/ui/layout/page-width-wrapper";
import { ReactNode } from "react";

export default function NotificationsLayout({
children,
}: {
children: ReactNode;
}) {
return (
<PageContent
title="Notifications"
titleInfo={{
title:
"Adjust your notification preferences and choose the updates you want to receive. These settings apply only to your account.",
}}
>
<PageWidthWrapper>{children}</PageWidthWrapper>
</PageContent>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
"use client";

import { updateNotificationPreference } from "@/lib/actions/update-notification-preference";
import useWorkspace from "@/lib/swr/use-workspace";
import { notificationTypes } from "@/lib/zod/schemas/workspaces";
import { Switch, useOptimisticUpdate } from "@dub/ui";
import { Globe, Hyperlink, Msgs, UserPlus } from "@dub/ui/icons";
import { isClickOnInteractiveChild } from "@dub/utils";
import { DollarSign, Trophy } from "lucide-react";
import { useAction } from "next-safe-action/hooks";
import React from "react";
import { z } from "zod";

type PreferenceType = z.infer<typeof notificationTypes>;
type Preferences = Record<PreferenceType, boolean>;

export default function NotificationsSettingsPageClient() {
const { id: workspaceId } = useWorkspace();
const { executeAsync } = useAction(updateNotificationPreference);

const workspaceNotifications = [
{
type: "domainConfigurationUpdates",
icon: Globe,
title: "Domain configuration updates",
description: "Updates to your custom domain configuration.",
},
{
type: "linkUsageSummary",
icon: Hyperlink,
title: "Monthly links usage summary",
description:
"Monthly summary email of your top 5 links by usage & total links created.",
},
];

const partnerProgramNotifications = [
{
type: "newPartnerApplication",
icon: UserPlus,
title: "New partner application",
description:
"Alert when a new partner application is made in your partner program.",
},
{
type: "newPartnerSale",
icon: DollarSign,
title: "New partner sale",
description: "Alert when a new sale is made in your partner program.",
},
{
type: "newBountySubmitted",
icon: Trophy,
title: "New bounty submitted",
description:
"Alert when a new bounty is submitted in your partner program.",
},
{
type: "newMessageFromPartner",
icon: Msgs,
title: "New message from partner",
description:
"Alert when a new message is received from a partner in your partner program.",
},
// {
// type: "fraudEventsSummary",
// icon: ShieldAlert,
// title: "Daily Fraud events summary",
// description:
// "Daily summary email of unresolved fraud events detected in your partner program.",
// },
];

const {
data: preferences,
isLoading,
update,
} = useOptimisticUpdate<Preferences>(
`/api/workspaces/${workspaceId}/notification-preferences`,
{
loading: "Updating notification preference...",
success: "Notification preference updated.",
error: "Failed to update notification preference.",
},
);

const handleUpdate = async ({
type,
value,
currentPreferences,
}: {
type: string;
value: boolean;
currentPreferences: Preferences;
}) => {
await executeAsync({
workspaceId: workspaceId!,
type: type as PreferenceType,
value,
});

return {
...currentPreferences,
[type]: value,
};
};

const renderNotificationItem = ({
type,
icon: Icon,
title,
description,
isLast,
}: {
type: string;
icon: React.ComponentType<{ className?: string }>;
title: string;
description: string;
isLast?: boolean;
}) => {
const handleRowClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (isClickOnInteractiveChild(e) || !preferences || isLoading) return;

const newValue = !preferences[type];
update(
() =>
handleUpdate({
type,
value: newValue,
currentPreferences: preferences,
}),
{
...preferences,
[type]: newValue,
},
);
};

return (
<div key={type}>
<div
onClick={handleRowClick}
className="flex cursor-pointer items-start justify-between py-5 pr-2 sm:items-center"
>
<div className="flex min-w-0 items-start gap-4 sm:items-center">
<div className="flex shrink-0 items-center justify-center rounded-full border border-neutral-200 bg-gradient-to-t from-neutral-100 p-2.5">
<Icon className="size-5" />
</div>
<div className="min-w-0 flex-1 pr-4">
<div className="text-sm font-medium text-neutral-800">
{title}
</div>
<div className="mt-0.5 text-xs text-neutral-500">
{description}
</div>
</div>
</div>
<Switch
checked={preferences?.[type] ?? false}
disabled={isLoading}
fn={(checked: boolean) => {
if (!preferences) return;

update(
() =>
handleUpdate({
type,
value: checked,
currentPreferences: preferences,
}),
{
...preferences,
[type]: checked,
},
);
}}
/>
</div>
{!isLast && <div className="border-t border-neutral-200" />}
</div>
);
};

const renderSection = ({
title,
notifications,
}: {
title: string;
notifications: Array<{
type: string;
icon: React.ComponentType<{ className?: string }>;
title: string;
description: string;
}>;
}) => (
<div className="rounded-xl border border-neutral-200 bg-white">
<div className="border-b border-neutral-200 p-5">
<h2 className="text-base font-semibold text-neutral-900">{title}</h2>
</div>
<div className="px-5">
{notifications.map((notification, index) =>
renderNotificationItem({
...notification,
isLast: index === notifications.length - 1,
}),
)}
</div>
</div>
);

return (
<div className="flex flex-col gap-6">
{renderSection({
title: "Short links",
notifications: workspaceNotifications,
})}
{renderSection({
title: "Partner program",
notifications: partnerProgramNotifications,
})}
</div>
);
}
Loading