feat: add new theme (#157)

This commit is contained in:
KM Koushik
2025-05-06 22:00:50 +10:00
committed by GitHub
parent 2de7147cdf
commit b394c78be2
40 changed files with 1236 additions and 494 deletions

View File

@@ -28,7 +28,7 @@ export default function CampaignDetailsPage({
if (isLoading) {
return (
<div className="flex items-center justify-center h-screen">
<Spinner className="w-5 h-5 text-primary" />
<Spinner className="w-5 h-5 text-foreground" />
</div>
);
}
@@ -94,7 +94,7 @@ export default function CampaignDetailsPage({
<div className="capitalize">{card.status.toLowerCase()}</div>
</div>
<div className="flex justify-between items-end">
<div className="text-primary font-light text-2xl font-mono">
<div className="text-foreground font-light text-2xl font-mono">
{card.count}
</div>
{card.status !== "total" ? (

View File

@@ -89,7 +89,7 @@ export default function CampaignList() {
<TableRow key={campaign.id} className="">
<TableCell className="font-medium">
<Link
className="underline underline-offset-4 decoration-dashed text-foreground hover:text-primary"
className="underline underline-offset-4 decoration-dashed text-foreground hover:text-foreground"
href={
campaign.status === CampaignStatus.DRAFT
? `/campaigns/${campaign.id}/edit`
@@ -154,170 +154,3 @@ export default function CampaignList() {
</div>
);
}
// "use client";
// import {
// Table,
// TableHeader,
// TableRow,
// TableHead,
// TableBody,
// TableCell,
// } from "@unsend/ui/src/table";
// import { api } from "~/trpc/react";
// import { useUrlState } from "~/hooks/useUrlState";
// import { Button } from "@unsend/ui/src/button";
// import Spinner from "@unsend/ui/src/spinner";
// import { formatDistanceToNow } from "date-fns";
// import { CampaignStatus } from "@prisma/client";
// import DeleteCampaign from "./delete-campaign";
// import Link from "next/link";
// import DuplicateCampaign from "./duplicate-campaign";
// import { motion } from "framer-motion";
// import { useRouter } from "next/navigation";
// import {
// Select,
// SelectTrigger,
// SelectContent,
// SelectItem,
// } from "@unsend/ui/src/select";
// export default function CampaignList() {
// const [page, setPage] = useUrlState("page", "1");
// const [status, setStatus] = useUrlState("status");
// const pageNumber = Number(page);
// const campaignsQuery = api.campaign.getCampaigns.useQuery({
// page: pageNumber,
// status: status as CampaignStatus | null,
// });
// const router = useRouter();
// return (
// <div className="mt-10 flex flex-col gap-4">
// <div className="flex justify-end">
// <Select
// value={status ?? "all"}
// onValueChange={(val) => setStatus(val === "all" ? null : val)}
// >
// <SelectTrigger className="w-[180px] capitalize">
// {status ? status.toLowerCase() : "All statuses"}
// </SelectTrigger>
// <SelectContent>
// <SelectItem value="all" className=" capitalize">
// All statuses
// </SelectItem>
// <SelectItem value={CampaignStatus.DRAFT} className=" capitalize">
// Draft
// </SelectItem>
// <SelectItem
// value={CampaignStatus.SCHEDULED}
// className=" capitalize"
// >
// Scheduled
// </SelectItem>
// <SelectItem value={CampaignStatus.SENT} className=" capitalize">
// Sent
// </SelectItem>
// </SelectContent>
// </Select>
// </div>
// {campaignsQuery.isLoading ? (
// <div className="flex justify-center items-center mt-20">
// <Spinner
// className="w-5 h-5 text-primary"
// innerSvgClass="stroke-primary"
// />
// </div>
// ) : (
// <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 ">
// {campaignsQuery.data?.campaigns.map((campaign) => (
// <motion.div
// whileHover={{ scale: 1.01 }}
// transition={{ type: "spring", stiffness: 600, damping: 10 }}
// whileTap={{ scale: 0.99 }}
// className="border rounded-xl shadow hover:shadow-lg"
// key={campaign.id}
// >
// <div className="flex flex-col">
// <Link
// href={
// campaign.status === CampaignStatus.DRAFT
// ? `/campaigns/${campaign.id}/edit`
// : `/campaigns/${campaign.id}`
// }
// >
// <div className="h-40 overflow-hidden flex justify-center rounded-t-xl bg-muted/10">
// <div
// className="transform scale-[0.5] "
// dangerouslySetInnerHTML={{ __html: campaign.html ?? "" }}
// />
// </div>
// </Link>
// <div className="flex justify-between items-center shadow-[0px_-5px_25px_-8px_rgba(0,0,0,0.3)] rounded-xl -mt-2 z-10 bg-background">
// <div
// className="cursor-pointer w-full py-3 pl-4 flex gap-2 items-start"
// onClick={() => router.push(`/campaigns/${campaign.id}`)}
// >
// <div className="flex flex-col gap-2">
// <div className="flex gap-4">
// <div className="font-semibold text-sm">
// {campaign.name}
// </div>
// <div
// className={`text-center px-4 rounded capitalize py-0.5 text-xs ${
// campaign.status === CampaignStatus.DRAFT
// ? "bg-gray-500/15 dark:bg-gray-600/10 text-gray-700 dark:text-gray-600/90 border border-gray-500/25 dark:border-gray-700/25"
// : campaign.status === CampaignStatus.SENT
// ? "bg-green-500/15 dark:bg-green-600/10 text-green-700 dark:text-green-600/90 border border-green-500/25 dark:border-green-700/25"
// : "bg-yellow-500/15 dark:bg-yellow-600/10 text-yellow-700 dark:text-yellow-600/90 border border-yellow-500/25 dark:border-yellow-700/25"
// }`}
// >
// {campaign.status.toLowerCase()}
// </div>
// </div>
// <div className="text-muted-foreground text-xs">
// {formatDistanceToNow(campaign.createdAt, {
// addSuffix: true,
// })}
// </div>
// </div>
// </div>
// <div className="flex gap-2 pr-4">
// <DuplicateCampaign campaign={campaign} />
// <DeleteCampaign campaign={campaign} />
// </div>
// </div>
// </div>
// </motion.div>
// ))}
// </div>
// )}
// {campaignsQuery.data?.totalPage && campaignsQuery.data.totalPage > 1 ? (
// <div className="flex gap-4 justify-end">
// <Button
// size="sm"
// onClick={() => setPage((pageNumber - 1).toString())}
// disabled={pageNumber === 1}
// >
// Previous
// </Button>
// <Button
// size="sm"
// onClick={() => setPage((pageNumber + 1).toString())}
// disabled={pageNumber >= (campaignsQuery.data?.totalPage ?? 0)}
// >
// Next
// </Button>
// </div>
// ) : null}
// </div>
// );
// }

View File

@@ -83,8 +83,10 @@ export const DeleteCampaign: React.FC<{
<DialogTitle>Delete Campaign</DialogTitle>
<DialogDescription>
Are you sure you want to delete{" "}
<span className="font-semibold text-primary">{campaign.name}</span>?
You can't reverse this.
<span className="font-semibold text-foreground">
{campaign.name}
</span>
? You can't reverse this.
</DialogDescription>
</DialogHeader>
<div className="py-2">

View File

@@ -54,7 +54,10 @@ export const DuplicateCampaign: React.FC<{
<DialogTitle>Duplicate Campaign</DialogTitle>
<DialogDescription>
Are you sure you want to duplicate{" "}
<span className="font-semibold text-primary">{campaign.name}</span>?
<span className="font-semibold text-foreground">
{campaign.name}
</span>
?
</DialogDescription>
</DialogHeader>
<div className="py-2">

View File

@@ -87,8 +87,10 @@ export const DeleteContact: React.FC<{
<DialogTitle>Delete Contact</DialogTitle>
<DialogDescription>
Are you sure you want to delete{" "}
<span className="font-semibold text-primary">{contact.email}</span>?
You can't reverse this.
<span className="font-semibold text-foreground">
{contact.email}
</span>
? You can't reverse this.
</DialogDescription>
</DialogHeader>
<div className="py-2">

View File

@@ -145,7 +145,7 @@ export const EditContact: React.FC<{
<Switch
checked={field.value}
onCheckedChange={field.onChange}
className="data-[state=checked]:bg-emerald-500"
className="data-[state=checked]:bg-success"
/>
</FormControl>
</FormItem>

View File

@@ -86,7 +86,7 @@ export const DeleteContactBook: React.FC<{
<DialogTitle>Delete Contact Book</DialogTitle>
<DialogDescription>
Are you sure you want to delete{" "}
<span className="font-semibold text-primary">
<span className="font-semibold text-foreground">
{contactBook.name}
</span>
? You can't reverse this.

View File

@@ -78,7 +78,7 @@ export const EditContactBook: React.FC<{
className="p-0 hover:bg-transparent"
onClick={(e) => e.stopPropagation()}
>
<Edit className="h-4 w-4 text-primary/80 hover:text-primary/70" />
<Edit className="h-4 w-4 text-foreground/80 hover:text-foreground/70" />
</Button>
</DialogTrigger>
<DialogContent>

View File

@@ -1,211 +1,26 @@
"use client";
import { useSession } from "next-auth/react";
import Link from "next/link";
import { LogoutButton, NavButton } from "./nav-button";
import {
BookOpenText,
BookUser,
CircleUser,
Code,
Cog,
Globe,
Home,
LayoutDashboard,
LayoutTemplate,
LineChart,
Mail,
Menu,
Package,
Package2,
Server,
Settings,
ShoppingCart,
Users,
Volume2,
} from "lucide-react";
import { env } from "~/env";
import { Sheet, SheetContent, SheetTrigger } from "@unsend/ui/src/sheet";
import { Button } from "@unsend/ui/src/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@unsend/ui/src/dropdown-menu";
import { ThemeSwitcher } from "~/components/theme/ThemeSwitcher";
import { isCloud, isSelfHosted } from "~/utils/common";
import { AppSidebar } from "~/components/AppSideBar";
import { SidebarInset, SidebarTrigger } from "@unsend/ui/src/sidebar";
import { SidebarProvider } from "@unsend/ui/src/sidebar";
import { useIsMobile } from "@unsend/ui/src/hooks/use-mobile";
export function DashboardLayout({ children }: { children: React.ReactNode }) {
const { data: session } = useSession();
const isMobile = useIsMobile();
return (
<div className="flex min-h-screen w-full h-full">
<div className="hidden bg-muted/20 md:block md:w-[230px]">
<div className="flex h-full max-h-screen flex-col gap-2">
<div className="flex h-14 gap-4 items-center px-4 lg:h-[60px] lg:px-6">
<Link href="/" className="flex items-center gap-2 font-semibold">
<span className=" text-lg">Unsend</span>
</Link>
<span className="text-[10px] text-muted-foreground bg-muted p-0.5 px-2 rounded-full">
Early access
</span>
</div>
<div className="flex-1 h-full">
<nav className=" flex-1 h-full flex-col justify-between items-center px-2 text-sm font-medium lg:px-4">
<div className="h-[calc(100%-120px)]">
<NavButton href="/dashboard">
<LayoutDashboard className="h-4 w-4" />
Dashboard
</NavButton>
<NavButton href="/emails">
<Mail className="h-4 w-4" />
Emails
</NavButton>
<NavButton href="/contacts">
<BookUser className="h-4 w-4" />
Contacts
</NavButton>
<NavButton href="/templates">
<LayoutTemplate className="h-4 w-4" />
Templates
</NavButton>
<NavButton href="/campaigns">
<Volume2 className="h-4 w-4" />
Campaigns
</NavButton>
<NavButton href="/domains">
<Globe className="h-4 w-4" />
Domains
</NavButton>
<NavButton href="/dev-settings">
<Code className="h-4 w-4" />
Developer settings
</NavButton>
<NavButton href="/settings">
<Cog className="h-4 w-4" />
Settings
</NavButton>
{isSelfHosted() || session?.user.isAdmin ? (
<NavButton href="/admin">
<Server className="h-4 w-4" />
Admin
</NavButton>
) : null}
</div>
<div className="pl-4 flex flex-col gap-2 w-full">
<Link
href="https://docs.unsend.dev"
target="_blank"
className="flex gap-2 items-center hover:text-primary text-muted-foreground"
>
<BookOpenText className="h-4 w-4" />
<span className="">Docs</span>
</Link>
<LogoutButton />
<div>
<ThemeSwitcher />
</div>
</div>
</nav>
</div>
<div className="mt-auto p-4"></div>
</div>
</div>
<div className="flex flex-1 flex-col">
<header className=" h-14 items-center gap-4 hidden bg-muted/20 px-4 lg:h-[60px] lg:px-6">
<Sheet>
<SheetTrigger asChild>
<Button
variant="outline"
size="icon"
className="shrink-0 md:hidden"
>
<Menu className="h-5 w-5" />
<span className="sr-only">Toggle navigation menu</span>
</Button>
</SheetTrigger>
<SheetContent side="left" className="flex flex-col">
<nav className="grid gap-2 text-lg font-medium">
<Link
href="#"
className="flex items-center gap-2 text-lg font-semibold"
>
<Package2 className="h-6 w-6" />
<span className="sr-only">Acme Inc</span>
</Link>
<Link
href="#"
className="mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 text-muted-foreground hover:text-foreground"
>
<Home className="h-5 w-5" />
Dashboard
</Link>
<Link
href="#"
className="mx-[-0.65rem] flex items-center gap-4 rounded-xl bg-muted px-3 py-2 text-foreground hover:text-foreground"
>
<ShoppingCart className="h-5 w-5" />
Orders
</Link>
<Link
href="#"
className="mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 text-muted-foreground hover:text-foreground"
>
<Package className="h-5 w-5" />
Products
</Link>
<Link
href="#"
className="mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 text-muted-foreground hover:text-foreground"
>
<Users className="h-5 w-5" />
Customers
</Link>
<Link
href="#"
className="mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 text-muted-foreground hover:text-foreground"
>
<LineChart className="h-5 w-5" />
Analytics
</Link>
</nav>
<div className="mt-auto"></div>
</SheetContent>
</Sheet>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" size="icon" className="rounded-full">
<CircleUser className="h-5 w-5" />
<span className="sr-only">Toggle user menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuItem>Support</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Logout</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</header>
<main className="flex-1 overflow-y-auto h-full">
<div className="flex flex-col gap-4 p-4 w-full lg:max-w-6xl mx-auto lg:gap-6 lg:p-6">
<div className="h-full bg-sidebar-background">
<SidebarProvider>
<AppSidebar />
<SidebarInset>
<main className="flex-1 overflow-auto h-full p-4 px-40">
{isMobile ? (
<SidebarTrigger className="h-5 w-5 text-muted-foreground" />
) : null}
{children}
</div>
</main>
</div>
</main>
</SidebarInset>
</SidebarProvider>
</div>
);
}

View File

@@ -235,11 +235,13 @@ const DashboardItemCard: React.FC<DashboardItemCardProps> = ({
<div className=" capitalize">{status.toLowerCase()}</div>
</div>
<div className="flex justify-between items-end">
<div className="text-primary font-light text-2xl font-mono">
<div className="text-foreground font-light text-2xl font-mono">
{count}
</div>
{status !== "total" ? (
<div className="text-sm pb-1">{count > 0 ? (percentage * 100).toFixed(0) : 0}%</div>
<div className="text-sm pb-1">
{count > 0 ? (percentage * 100).toFixed(0) : 0}%
</div>
) : null}
</div>
</div>

View File

@@ -83,8 +83,8 @@ export const DeleteApiKey: React.FC<{
<DialogTitle>Delete API key</DialogTitle>
<DialogDescription>
Are you sure you want to delete{" "}
<span className="font-semibold text-primary">{apiKey.name}</span>?
You can't reverse this.
<span className="font-semibold text-foreground">{apiKey.name}</span>
? You can't reverse this.
</DialogDescription>
</DialogHeader>
<div className="py-2">

View File

@@ -15,9 +15,9 @@ export const SettingsNavButton: React.FC<{
if (comingSoon) {
return (
<div className="flex items-center justify-between hover:text-primary cursor-not-allowed mt-1">
<div className="flex items-center justify-between hover:text-foreground cursor-not-allowed mt-1">
<div
className={`flex items-center gap-3 rounded-lg px-3 py-2 transition-all hover:text-primary cursor-not-allowed ${isActive ? " bg-secondary" : "text-muted-foreground"}`}
className={`flex items-center gap-3 rounded-lg px-3 py-2 transition-all hover:text-foreground cursor-not-allowed ${isActive ? " bg-secondary" : "text-muted-foreground"}`}
>
{children}
</div>
@@ -31,7 +31,7 @@ export const SettingsNavButton: React.FC<{
return (
<Link
href={href}
className={`flex text-sm items-center mt-1 gap-3 rounded px-2 py-1 transition-all hover:text-primary ${isActive ? " bg-accent" : "text-muted-foreground"}`}
className={`flex text-sm items-center mt-1 gap-3 rounded px-2 py-1 transition-all hover:text-foreground ${isActive ? " bg-accent" : "text-muted-foreground"}`}
>
{children}
</Link>

View File

@@ -5,9 +5,10 @@ import { Input } from "@unsend/ui/src/input";
import {
Dialog,
DialogContent,
DialogDescription, DialogHeader,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger
DialogTrigger,
} from "@unsend/ui/src/dialog";
import {
@@ -84,8 +85,8 @@ export const DeleteDomain: React.FC<{ domain: Domain }> = ({ domain }) => {
<DialogTitle>Delete domain</DialogTitle>
<DialogDescription>
Are you sure you want to delete{" "}
<span className="font-semibold text-primary">{domain.name}</span>?
You can't reverse this.
<span className="font-semibold text-foreground">{domain.name}</span>
? You can't reverse this.
</DialogDescription>
</DialogHeader>
<Form {...domainForm}>

View File

@@ -274,7 +274,7 @@ const DomainSettings: React.FC<{ domain: Domain }> = ({ domain }) => {
<Switch
checked={clickTracking}
onCheckedChange={handleClickTrackingChange}
className="data-[state=checked]:bg-emerald-500"
className="data-[state=checked]:bg-success"
/>
</div>
@@ -288,7 +288,7 @@ const DomainSettings: React.FC<{ domain: Domain }> = ({ domain }) => {
<Switch
checked={openTracking}
onCheckedChange={handleOpenTrackingChange}
className="data-[state=checked]:bg-emerald-500"
className="data-[state=checked]:bg-success"
/>
</div>

View File

@@ -104,7 +104,7 @@ const DomainItem: React.FC<{ domain: Domain }> = ({ domain }) => {
<Switch
checked={clickTracking}
onCheckedChange={handleClickTrackingChange}
className="data-[state=checked]:bg-emerald-500"
className="data-[state=checked]:bg-success"
/>
</div>
<div className="flex gap-2 items-center">
@@ -112,7 +112,7 @@ const DomainItem: React.FC<{ domain: Domain }> = ({ domain }) => {
<Switch
checked={openTracking}
onCheckedChange={handleOpenTrackingChange}
className="data-[state=checked]:bg-emerald-500"
className="data-[state=checked]:bg-success"
/>
</div>
</div>

View File

@@ -115,7 +115,7 @@ export default function EmailDetails({ emailId }: { emailId: string }) {
<div className="text-xs text-muted-foreground mt-2">
{formatDate(evt.createdAt, "MMM dd, hh:mm a")}
</div>
<div className="mt-1 text-primary/80">
<div className="mt-1 text-foreground/80">
<EmailStatusText
status={evt.status}
data={evt.data}

View File

@@ -1,53 +0,0 @@
"use client";
import { LogOut } from "lucide-react";
import { signOut } from "next-auth/react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import React from "react";
export const NavButton: React.FC<{
href: string;
children: React.ReactNode;
comingSoon?: boolean;
}> = ({ href, children, comingSoon }) => {
const pathname = usePathname();
const isActive = pathname?.startsWith(href);
if (comingSoon) {
return (
<div className="flex items-center justify-between hover:text-primary cursor-not-allowed mt-1">
<div
className={`flex items-center gap-3 rounded-lg px-3 py-2 transition-all hover:text-primary cursor-not-allowed ${isActive ? " bg-secondary" : "text-muted-foreground"}`}
>
{children}
</div>
<div className="text-muted-foreground px-4 py-0.5 text-xs bg-muted rounded-full">
soon
</div>
</div>
);
}
return (
<Link
href={href}
className={`flex items-center mt-1 gap-3 rounded-lg px-3 py-2 transition-all hover:text-primary ${isActive ? " bg-secondary" : "text-muted-foreground"}`}
>
{children}
</Link>
);
};
export const LogoutButton: React.FC = () => {
return (
<button
className={` w-full justify-start flex items-center gap-2 rounded-lg py-2 transition-all hover:text-primary text-muted-foreground`}
onClick={() => signOut()}
>
<LogOut className="h-4 w-4" />
Logout
</button>
);
};

View File

@@ -55,7 +55,10 @@ export const DeleteTeamInvite: React.FC<{
<DialogTitle>Cancel Invite</DialogTitle>
<DialogDescription>
Are you sure you want to cancel the invite for{" "}
<span className="font-semibold text-primary">{invite.email}</span>?
<span className="font-semibold text-foreground">
{invite.email}
</span>
?
</DialogDescription>
</DialogHeader>
<div className="flex justify-end gap-4 mt-6">

View File

@@ -83,8 +83,10 @@ export const DeleteTemplate: React.FC<{
<DialogTitle>Delete Template</DialogTitle>
<DialogDescription>
Are you sure you want to delete{" "}
<span className="font-semibold text-primary">{template.name}</span>?
You can't reverse this.
<span className="font-semibold text-foreground">
{template.name}
</span>
? You can't reverse this.
</DialogDescription>
</DialogHeader>
<div className="py-2">

View File

@@ -54,7 +54,10 @@ export const DuplicateTemplate: React.FC<{
<DialogTitle>Duplicate Template</DialogTitle>
<DialogDescription>
Are you sure you want to duplicate{" "}
<span className="font-semibold text-primary">{template.name}</span>?
<span className="font-semibold text-foreground">
{template.name}
</span>
?
</DialogDescription>
</DialogHeader>
<div className="py-2">

View File

@@ -57,7 +57,7 @@ export default function TemplateList() {
<TableRow key={template.id} className="">
<TableCell className="font-medium">
<Link
className="underline underline-offset-4 decoration-dashed text-foreground hover:text-primary"
className="underline underline-offset-4 decoration-dashed text-foreground hover:text-foreground"
href={`/templates/${template.id}/edit`}
>
{template.name}