mirror of
https://github.com/lukevella/rallly.git
synced 2025-04-29 02:06:34 +02:00
updatw
This commit is contained in:
parent
9928260436
commit
4c61e70506
21 changed files with 241 additions and 109 deletions
7
apps/web/public/static/zoom.svg
Normal file
7
apps/web/public/static/zoom.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="Zoom" role="img"
|
||||
viewBox="0 0 512 512"><rect
|
||||
width="512" height="512"
|
||||
rx="15%"
|
||||
fill="#2D8CFF"/><path fill="#ffffff" d="M428 357c8 2 15-2 19-8 2-3 2-8 2-19V179c0-11 0-15-2-19-3-8-11-11-19-8-21 14-67 55-68 72-.8 3-.8 8-.8 15v38c0 8 0 11 .8 15 1 8 4 15 8 19 12 9 52 45 61 45zM64 187c0-15 0-23 3-27 2-4 8-8 11-11 4-3 11-3 27-3h129c38 0 57 0 72 8 11 8 23 15 30 30 8 15 8 34 8 72v68c0 15 0 23-3 27-2 4-8 8-11 11-4 3-11 3-27 3H174c-38 0-57 0-72-8-11-8-23-15-30-30-8-15-8-34-8-72z"/></svg>
|
After Width: | Height: | Size: 652 B |
48
apps/web/src/app/[locale]/(admin)/create-button.tsx
Normal file
48
apps/web/src/app/[locale]/(admin)/create-button.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { cn } from "@rallly/ui";
|
||||
import { cva, VariantProps } from "class-variance-authority";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
const variants = cva(
|
||||
"inline-flex items-start w-48 justify-between gap-2 rounded-lg p-3 text-sm font-medium",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
purple:
|
||||
"bg-purple-50 text-purple-600 hover:bg-purple-100 active:bg-purple-200",
|
||||
indigo:
|
||||
"bg-indigo-50 text-indigo-600 hover:bg-indigo-100 active:bg-indigo-200",
|
||||
pink: "bg-pink-50 text-pink-600 hover:bg-pink-100 active:bg-pink-200",
|
||||
violet:
|
||||
"bg-violet-50 text-violet-600 hover:bg-violet-100 active:bg-violet-200",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "indigo",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export function CreateButton({
|
||||
href,
|
||||
icon,
|
||||
label,
|
||||
variant,
|
||||
}: {
|
||||
href: string;
|
||||
icon: React.ReactNode;
|
||||
label: React.ReactNode;
|
||||
variant?: VariantProps<typeof variants>["variant"];
|
||||
}) {
|
||||
return (
|
||||
<Link href={href} className={cn(variants({ variant }))}>
|
||||
<span className="flex flex-col gap-4">
|
||||
{icon}
|
||||
{label}
|
||||
</span>
|
||||
<span>
|
||||
<PlusIcon className="size-5" />
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
|
@ -1,9 +1,18 @@
|
|||
"use client";
|
||||
|
||||
import { Button } from "@rallly/ui/button";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { Icon } from "@rallly/ui/icon";
|
||||
import {
|
||||
BarChart2Icon,
|
||||
BookIcon,
|
||||
CalendarIcon,
|
||||
PlusIcon,
|
||||
TableIcon,
|
||||
UserIcon,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { CreateButton } from "@/app/[locale]/(admin)/create-button";
|
||||
import { GridCard, GridCardHeader } from "@/components/grid-card";
|
||||
import { GroupPollCard } from "@/components/group-poll-card";
|
||||
import { Subheading } from "@/components/heading";
|
||||
|
@ -19,6 +28,37 @@ const SectionHeading = ({ children }: React.PropsWithChildren) => {
|
|||
export default function Dashboard() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<Subheading>
|
||||
<Trans i18nKey="create" defaults="Create" />
|
||||
</Subheading>
|
||||
<div className="scrollbar-none -mx-6 flex gap-2 overflow-x-auto whitespace-nowrap px-6">
|
||||
<CreateButton
|
||||
variant="purple"
|
||||
href="/new"
|
||||
icon={<BarChart2Icon className="size-5" />}
|
||||
label={<Trans i18nKey="groupPoll" defaults="Group Poll" />}
|
||||
/>
|
||||
<CreateButton
|
||||
variant="violet"
|
||||
href="/new"
|
||||
icon={<UserIcon className="size-5" />}
|
||||
label={<Trans i18nKey="oneOnOne" defaults="One-on-One" />}
|
||||
/>
|
||||
<CreateButton
|
||||
variant="indigo"
|
||||
href="/new"
|
||||
icon={<BookIcon className="size-5" />}
|
||||
label={<Trans i18nKey="bookingPage" defaults="Booking Page" />}
|
||||
/>
|
||||
<CreateButton
|
||||
variant="pink"
|
||||
href="/new"
|
||||
icon={<TableIcon className="size-5" />}
|
||||
label={<Trans i18nKey="signUpSheet" defaults="Sign Up Sheet" />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<SectionHeading>
|
||||
<Subheading>
|
||||
|
@ -54,12 +94,8 @@ function PendingPolls() {
|
|||
suspense: true,
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="grid gap-2 md:grid-cols-3">
|
||||
{data.map((poll) => {
|
||||
return (
|
||||
<GroupPollCard
|
||||
|
@ -90,20 +126,23 @@ function UpcomingEvents() {
|
|||
return <Spinner />;
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className="text-muted-foreground">
|
||||
<Trans i18nKey="noUpcomingEvents" defaults="No upcoming events" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{data.length === 0 ? (
|
||||
<div className="text-muted-foreground">
|
||||
<Trans i18nKey="noUpcomingEvents" defaults="No upcoming events" />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{data.map((event) => {
|
||||
return (
|
||||
<GridCard key={event.id}>
|
||||
<GridCardHeader className="flex gap-2">
|
||||
<div>
|
||||
<div className="bg-primary-600 text-primary-100 inline-flex items-center justify-center rounded-md p-1.5">
|
||||
<CalendarIcon className="size-4" />
|
||||
<CalendarIcon className="size-5" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
|
|
|
@ -30,15 +30,18 @@ export default async function Layout({
|
|||
<Sidebar />
|
||||
</div>
|
||||
<div className={cn("pb-16 lg:min-w-0 lg:pb-0 lg:pl-72")}>
|
||||
<div className="mx-auto max-w-7xl p-6 xl:pr-12">
|
||||
<div className="mx-auto max-w-7xl p-3 sm:p-6 xl:pr-12">
|
||||
<div className="mb-6 flex justify-end gap-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="primary" asChild>
|
||||
<Button
|
||||
className="hidden sm:inline-flex"
|
||||
variant="primary"
|
||||
asChild
|
||||
>
|
||||
<Link href="/new">
|
||||
<Icon>
|
||||
<PlusIcon />
|
||||
</Icon>
|
||||
Create
|
||||
</Link>
|
||||
</Button>
|
||||
<UserDropdown />
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { HomeIcon } from "lucide-react";
|
||||
import { Trans } from "react-i18next/TransWithoutContext";
|
||||
|
||||
import Dashboard from "@/app/[locale]/(admin)/dashboard";
|
||||
import WelcomeMessage from "@/app/[locale]/(admin)/welcome-message";
|
||||
import { Params } from "@/app/[locale]/types";
|
||||
import {
|
||||
PageContainer,
|
||||
PageContent,
|
||||
PageHeader,
|
||||
PageIcon,
|
||||
PageTitle,
|
||||
} from "@/app/components/page-layout";
|
||||
import { getTranslation } from "@/app/i18n";
|
||||
|
@ -17,13 +16,13 @@ export default async function Page({ params }: { params: Params }) {
|
|||
return (
|
||||
<div>
|
||||
<PageContainer>
|
||||
<PageHeader>
|
||||
<PageIcon>
|
||||
<HomeIcon />
|
||||
</PageIcon>
|
||||
<PageHeader className="space-y-2">
|
||||
<PageTitle>
|
||||
<Trans t={t} i18nKey="home" defaults="Home" />
|
||||
</PageTitle>
|
||||
<p className="text-muted-foreground">
|
||||
<WelcomeMessage />
|
||||
</p>
|
||||
</PageHeader>
|
||||
<PageContent>
|
||||
<Dashboard />
|
||||
|
|
|
@ -59,7 +59,7 @@ function FilteredPolls({ status }: { status: PollStatus }) {
|
|||
<ol className="space-y-4">
|
||||
{data.pages.map((page, i) => (
|
||||
<li key={i}>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="grid gap-2 sm:gap-4 md:grid-cols-3">
|
||||
{page.polls.map((poll) => (
|
||||
<GroupPollCard
|
||||
key={poll.id}
|
||||
|
@ -141,7 +141,7 @@ export function UserPolls() {
|
|||
const parsedPollStatus = pollStatusSchema.parse(pollStatus);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-6">
|
||||
<PollStatusMenu
|
||||
status={parsedPollStatus}
|
||||
onStatusChange={setPollStatus}
|
||||
|
|
|
@ -22,10 +22,8 @@ export default async function ProfileLayout({
|
|||
<PageHeader>
|
||||
<PageTitle>{t("settings")}</PageTitle>
|
||||
</PageHeader>
|
||||
<PageContent className="space-y-3 sm:space-y-6">
|
||||
<div className="scrollbar-none -mx-3 overflow-auto px-3 sm:mx-0 sm:px-0">
|
||||
<SettingsMenu />
|
||||
</div>
|
||||
<PageContent className="space-y-6">
|
||||
<SettingsMenu />
|
||||
<div>{children}</div>
|
||||
</PageContent>
|
||||
</PageContainer>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { Icon } from "@rallly/ui/icon";
|
||||
import { CreditCardIcon, Settings2Icon, UserIcon } from "lucide-react";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
|
@ -11,22 +10,16 @@ export function SettingsMenu() {
|
|||
return (
|
||||
<TabMenu>
|
||||
<TabMenuItem href="/settings/profile">
|
||||
<Icon>
|
||||
<UserIcon />
|
||||
</Icon>
|
||||
<UserIcon className="size-4" />
|
||||
<Trans i18nKey="profile" />
|
||||
</TabMenuItem>
|
||||
<TabMenuItem href="/settings/preferences">
|
||||
<Icon>
|
||||
<Settings2Icon />
|
||||
</Icon>
|
||||
<Settings2Icon className="size-4" />
|
||||
<Trans i18nKey="preferences" />
|
||||
</TabMenuItem>
|
||||
<IfCloudHosted>
|
||||
<TabMenuItem href="/settings/billing">
|
||||
<Icon>
|
||||
<CreditCardIcon />
|
||||
</Icon>
|
||||
<CreditCardIcon className="size-4" />
|
||||
<Trans i18nKey="billing" />
|
||||
</TabMenuItem>
|
||||
</IfCloudHosted>
|
||||
|
|
|
@ -46,8 +46,8 @@ function NavItem({
|
|||
target={target}
|
||||
className={cn(
|
||||
current
|
||||
? "text-foreground bg-gray-200"
|
||||
: "text-muted-foreground border-transparent hover:bg-gray-200 focus:bg-gray-300",
|
||||
? "text-foreground bg-gray-100"
|
||||
: "text-muted-foreground hover:text-foreground border-transparent hover:bg-gray-100 focus:bg-gray-200",
|
||||
"group flex items-center gap-x-3 rounded-md px-3 py-2 text-sm font-semibold leading-6",
|
||||
)}
|
||||
>
|
||||
|
@ -102,15 +102,15 @@ export function Sidebar() {
|
|||
}
|
||||
asChild
|
||||
>
|
||||
<button className="mb-4 flex w-full flex-col rounded-md border bg-gray-50 px-4 py-3 focus:border-gray-300 focus:bg-gray-200">
|
||||
<button className="bg-primary-50 text-primary hover:bg-primary-100 focus:bg-primary-200 mb-4 flex w-full flex-col rounded-lg px-4 py-3">
|
||||
<span className="mb-2 flex items-center gap-x-2">
|
||||
<SparklesIcon className="size-5 text-gray-400" />
|
||||
<SparklesIcon className="size-5 opacity-50" />
|
||||
<span className="text-sm font-bold">
|
||||
<Trans i18nKey="upgrade" />
|
||||
</span>
|
||||
<ProBadge />
|
||||
</span>
|
||||
<span className="text-sm leading-relaxed text-gray-500">
|
||||
<span className="text-sm leading-relaxed opacity-75">
|
||||
<Trans
|
||||
i18nKey="unlockFeatures"
|
||||
defaults="Unlock all Pro features."
|
||||
|
|
47
apps/web/src/app/[locale]/(admin)/welcome-message.tsx
Normal file
47
apps/web/src/app/[locale]/(admin)/welcome-message.tsx
Normal file
|
@ -0,0 +1,47 @@
|
|||
"use client";
|
||||
|
||||
import { Trans } from "@/components/trans";
|
||||
import { useUser } from "@/components/user-provider";
|
||||
|
||||
function getTimeOfDay(): "morning" | "afternoon" | "evening" {
|
||||
const hours = new Date().getHours();
|
||||
if (hours < 12) {
|
||||
return "morning";
|
||||
} else if (hours < 18) {
|
||||
return "afternoon";
|
||||
} else {
|
||||
return "evening";
|
||||
}
|
||||
}
|
||||
|
||||
export default function WelcomeMessage() {
|
||||
const { user } = useUser();
|
||||
const timeOfDay = getTimeOfDay();
|
||||
|
||||
switch (timeOfDay) {
|
||||
case "morning":
|
||||
return (
|
||||
<Trans
|
||||
i18nKey="goodMorning"
|
||||
defaults="Good morning, {name}!"
|
||||
values={{ name: user.name }}
|
||||
/>
|
||||
);
|
||||
case "afternoon":
|
||||
return (
|
||||
<Trans
|
||||
i18nKey="goodAfternoon"
|
||||
defaults="Good afternoon, {name}!"
|
||||
values={{ name: user.name }}
|
||||
/>
|
||||
);
|
||||
case "evening":
|
||||
return (
|
||||
<Trans
|
||||
i18nKey="goodEvening"
|
||||
defaults="Good evening, {name}!"
|
||||
values={{ name: user.name }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ export default async function Page({ params }: { params: Params }) {
|
|||
const { t } = await getTranslation(params.locale);
|
||||
return (
|
||||
<div>
|
||||
<div className="sticky top-0 z-20 flex items-center justify-between border-b bg-gray-100/90 p-3 backdrop-blur-md sm:grid-cols-3">
|
||||
<div className="sticky top-0 z-20 flex items-center justify-between p-3 backdrop-blur-md sm:grid-cols-3">
|
||||
<div className="flex items-center justify-center gap-x-4">
|
||||
<BackButton />
|
||||
<GroupPollIcon size="xs" />
|
||||
|
|
|
@ -43,9 +43,7 @@ export function PageHeader({
|
|||
className?: string;
|
||||
variant?: "default" | "ghost";
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("flex items-center gap-x-4", className)}>{children}</div>
|
||||
);
|
||||
return <div className={cn(className)}>{children}</div>;
|
||||
}
|
||||
|
||||
export function PageSection({ children }: { children?: React.ReactNode }) {
|
||||
|
|
|
@ -18,8 +18,8 @@ export function TabMenuItem({
|
|||
className={cn(
|
||||
"flex h-9 min-w-0 grow items-center gap-x-2.5 rounded-md px-2.5 text-sm font-medium",
|
||||
pathname === href
|
||||
? "text-foreground bg-gray-200"
|
||||
: "hover:text-foreground focus:text-foreground border-transparent text-gray-500 focus:bg-gray-200",
|
||||
? "text-foreground bg-gray-100"
|
||||
: "hover:text-foreground focus:text-foreground border-transparent text-gray-500 hover:bg-gray-100 focus:bg-gray-200",
|
||||
)}
|
||||
href={href}
|
||||
>
|
||||
|
@ -30,5 +30,7 @@ export function TabMenuItem({
|
|||
}
|
||||
|
||||
export function TabMenu({ children }: { children: React.ReactNode }) {
|
||||
return <ul className="flex gap-2.5">{children}</ul>;
|
||||
return (
|
||||
<ul className="scrollbar-none flex gap-1 overflow-auto">{children}</ul>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export function GridCardHeader({
|
|||
|
||||
export const GridCard = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<div className="relative rounded-lg border bg-white p-3 shadow-sm">
|
||||
<div className="relative flex h-48 flex-col rounded-lg bg-gray-100 p-4">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -12,8 +12,6 @@ import {
|
|||
import { Icon } from "@rallly/ui/icon";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@rallly/ui/tooltip";
|
||||
import {
|
||||
BarChart2Icon,
|
||||
CalendarSearchIcon,
|
||||
CheckIcon,
|
||||
Link2Icon,
|
||||
MoreHorizontalIcon,
|
||||
|
@ -25,7 +23,11 @@ import Link from "next/link";
|
|||
import React from "react";
|
||||
import { useCopyToClipboard } from "react-use";
|
||||
|
||||
import { GridCard, GridCardHeader } from "@/components/grid-card";
|
||||
import {
|
||||
GridCard,
|
||||
GridCardFooter,
|
||||
GridCardHeader,
|
||||
} from "@/components/grid-card";
|
||||
import { GroupPollIcon } from "@/components/group-poll-icon";
|
||||
import { Pill, PillList } from "@/components/pill";
|
||||
import { Trans } from "@/components/trans";
|
||||
|
@ -40,6 +42,7 @@ function CopyLinkButton({ link, ...forwardProps }: { link: string }) {
|
|||
<Tooltip open={isCopied ? true : undefined}>
|
||||
<TooltipTrigger onMouseLeave={() => setIsCopied(false)} asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
{...forwardProps}
|
||||
onClick={() => {
|
||||
|
@ -94,36 +97,15 @@ export function GroupPollCard({
|
|||
|
||||
return (
|
||||
<GridCard key={pollId}>
|
||||
<GridCardHeader className="flex flex-col justify-between gap-4 sm:flex-row">
|
||||
<div className="flex items-center gap-2">
|
||||
<div>
|
||||
<GroupPollIcon size="xs" />
|
||||
</div>
|
||||
<h3 className="font-medium">
|
||||
<Link className="truncate hover:underline" href={`/poll/${pollId}`}>
|
||||
{title}
|
||||
</Link>
|
||||
</h3>
|
||||
<GridCardHeader className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<GroupPollIcon size="xs" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CopyLinkButton link={inviteLink} />
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button size="sm" asChild>
|
||||
<Link href={`/poll/${pollId}`}>
|
||||
<Icon>
|
||||
<BarChart2Icon />
|
||||
</Icon>
|
||||
</Link>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<Trans i18nKey="viewResults" defaults="View results" />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="sm">
|
||||
<Button variant="ghost" size="sm">
|
||||
<Icon>
|
||||
<MoreHorizontalIcon />
|
||||
</Icon>
|
||||
|
@ -147,27 +129,34 @@ export function GroupPollCard({
|
|||
</DropdownMenu>
|
||||
</div>
|
||||
</GridCardHeader>
|
||||
<PillList>
|
||||
<Pill>
|
||||
<Icon>
|
||||
<CalendarSearchIcon />
|
||||
</Icon>
|
||||
<div className="grow space-y-1">
|
||||
<h3 className="font-medium">
|
||||
<Link className="truncate hover:underline" href={`/poll/${pollId}`}>
|
||||
{title}
|
||||
</Link>
|
||||
</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{getRange(
|
||||
localizeTime(dateStart, !timeZone).toDate(),
|
||||
localizeTime(dateEnd, !timeZone).toDate(),
|
||||
)}
|
||||
</Pill>
|
||||
<Pill>
|
||||
<Icon>
|
||||
<User2Icon />
|
||||
</Icon>
|
||||
<Trans
|
||||
i18nKey="participantCount"
|
||||
defaults="{count, plural, one {# participant} other {# participants}}"
|
||||
values={{ count: responseCount }}
|
||||
/>
|
||||
</Pill>
|
||||
</PillList>
|
||||
</p>
|
||||
</div>
|
||||
<GridCardFooter>
|
||||
<PillList>
|
||||
<Pill></Pill>
|
||||
<Pill>
|
||||
<Icon>
|
||||
<User2Icon />
|
||||
</Icon>
|
||||
<Trans
|
||||
i18nKey="participantCount"
|
||||
defaults="{count, plural, one {# participant} other {# participants}}"
|
||||
values={{ count: responseCount }}
|
||||
/>
|
||||
</Pill>
|
||||
</PillList>
|
||||
</GridCardFooter>
|
||||
</GridCard>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ export function GroupPollIcon({
|
|||
role="img"
|
||||
aria-label="Group Poll Icon"
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center bg-gradient-to-br from-purple-500 to-violet-500 text-purple-100",
|
||||
"inline-flex items-center justify-center bg-purple-600 text-purple-50",
|
||||
{
|
||||
"size-6 rounded": size === "xs",
|
||||
"size-8 rounded-md": size === "sm",
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
export function PillList({ children }: React.PropsWithChildren) {
|
||||
return <ul className="flex gap-2">{children}</ul>;
|
||||
return <ul className="flex flex-col gap-2">{children}</ul>;
|
||||
}
|
||||
|
||||
export function Pill({ children }: React.PropsWithChildren) {
|
||||
return (
|
||||
<span className="text-muted-foreground inline-flex items-center gap-2 rounded-md border bg-gray-50 p-1 text-sm">
|
||||
<span className="inline-flex items-center gap-2 rounded-md text-sm">
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply text-foreground bg-gray-100;
|
||||
@apply text-foreground bg-white;
|
||||
font-feature-settings:
|
||||
"rlig" 1,
|
||||
"calt" 1;
|
||||
|
|
|
@ -42,7 +42,7 @@ export const dashboard = router({
|
|||
},
|
||||
},
|
||||
},
|
||||
take: 4,
|
||||
take: 3,
|
||||
});
|
||||
|
||||
return polls.map((poll) => {
|
||||
|
|
|
@ -3,15 +3,24 @@ import dayjs from "dayjs";
|
|||
/**
|
||||
* Get a range of dates in a human readable format
|
||||
* If the start and end date are the same, return the start date
|
||||
* If either the end date is in a different year, include the year
|
||||
* @param start The start date
|
||||
* @param end The end date
|
||||
* @returns A human readable range of dates
|
||||
*/
|
||||
export function getRange(start: Date, end: Date) {
|
||||
const startDay = dayjs(start).format("DD MMM");
|
||||
const endDay = dayjs(end).format("DD MMM");
|
||||
const startDay = dayjs(start).format("D MMM");
|
||||
const endDay = dayjs(end).format("D MMM");
|
||||
const startYear = dayjs(start).format("YYYY");
|
||||
const endYear = dayjs(end).format("YYYY");
|
||||
|
||||
if (startDay === endDay) {
|
||||
return startDay;
|
||||
return `${startDay} ${startYear}`;
|
||||
}
|
||||
return `${startDay} - ${endDay}`;
|
||||
|
||||
if (startYear !== endYear) {
|
||||
return `${startDay} ${startYear} - ${endDay} ${endYear}`;
|
||||
}
|
||||
|
||||
return `${startDay} - ${endDay} ${startYear}`;
|
||||
}
|
||||
|
|
|
@ -7,15 +7,15 @@ import { cn } from "./lib/utils";
|
|||
|
||||
const buttonVariants = cva(
|
||||
cn(
|
||||
"inline-flex border transition-colors font-medium disabled:pointer-events-none select-none disabled:opacity-50 items-center justify-center whitespace-nowrap border",
|
||||
"inline-flex border active:shadow-none hover:shadow-sm transition-colors font-medium disabled:pointer-events-none select-none disabled:opacity-50 items-center justify-center whitespace-nowrap border",
|
||||
),
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
primary:
|
||||
"bg-primary disabled:bg-gray-400 disabled:border-transparent text-primary-foreground hover:bg-primary-700 active:bg-primary-800 shadow-sm",
|
||||
"bg-primary disabled:bg-gray-400 disabled:border-transparent text-primary-foreground hover:bg-primary-700 active:bg-primary-800",
|
||||
destructive:
|
||||
"bg-destructive shadow-sm text-destructive-foreground active:bg-destructive border-destructive hover:bg-destructive/90",
|
||||
"bg-destructive text-destructive-foreground active:bg-destructive border-destructive hover:bg-destructive/90",
|
||||
default:
|
||||
"data-[state=open]:bg-gray-100 hover:bg-gray-100 active:bg-gray-200 bg-white",
|
||||
secondary:
|
||||
|
@ -27,7 +27,7 @@ const buttonVariants = cva(
|
|||
size: {
|
||||
default: "h-8 px-2 gap-x-1.5 text-sm rounded-md",
|
||||
sm: "h-7 text-sm px-1.5 gap-x-1.5 rounded-md",
|
||||
lg: "h-11 text-base gap-x-3 px-4 rounded-md",
|
||||
lg: "h-11 text-base gap-x-3 px-4 rounded-xl",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
|
Loading…
Add table
Reference in a new issue