diff --git a/apps/web/package.json b/apps/web/package.json index 75e525c5..1dd4a432 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -45,6 +45,7 @@ "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.0.2", + "@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-tooltip": "^1.0.5", "@react-email/button": "0.0.5", "@react-email/container": "0.0.5", @@ -99,6 +100,7 @@ "next-themes": "^0.2.1", "nextjs-cors": "^2.1.2", "nodemailer": "^6.9.1", + "nuqs": "^1.17.8", "octokit": "^2.0.18", "rate-limiter-flexible": "^5.0.3", "react": "18.2.0", diff --git a/apps/web/src/components/DashboardSection.tsx b/apps/web/src/components/DashboardSection.tsx index 58304d13..a0a64aa7 100644 --- a/apps/web/src/components/DashboardSection.tsx +++ b/apps/web/src/components/DashboardSection.tsx @@ -9,7 +9,7 @@ export function DashboardSection({ }) { return (
{children}
diff --git a/apps/web/src/components/Layout.tsx b/apps/web/src/components/Layout.tsx index 9d40fdba..5f02ed82 100644 --- a/apps/web/src/components/Layout.tsx +++ b/apps/web/src/components/Layout.tsx @@ -96,7 +96,7 @@ export const Layout = ({ children }: { children: ReactNode }) => { />
-
{children}
+
{children}
diff --git a/apps/web/src/components/analytics/EventGraph.tsx b/apps/web/src/components/analytics/EventGraph.tsx index 200c2274..5b4456dd 100644 --- a/apps/web/src/components/analytics/EventGraph.tsx +++ b/apps/web/src/components/analytics/EventGraph.tsx @@ -1,7 +1,7 @@ "use client"; import * as React from "react"; -import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"; +import { LineChart, CartesianGrid, XAxis, YAxis, Line } from "recharts"; import { Card, @@ -52,9 +52,6 @@ export function EventGraph({ ) as Record; return { - visitors: { - label: "Visitors", - }, ...variantsConfig, } satisfies ChartConfig; }, [variants]); @@ -72,7 +69,7 @@ export function EventGraph({ config={chartConfig} className="aspect-auto h-[250px] w-full" > - + {variants.map((variant) => ( { return dayjs(value).format(getFormattingByInterval(interval)); }} /> + { return dayjs(value).format(getFormattingByInterval(interval)); }} - content={} + content={} /> {variants.map((variant) => ( - ))} } /> - + diff --git a/apps/web/src/components/ui/tabs.tsx b/apps/web/src/components/ui/tabs.tsx new file mode 100644 index 00000000..0f63bb78 --- /dev/null +++ b/apps/web/src/components/ui/tabs.tsx @@ -0,0 +1,53 @@ +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/apps/web/src/pages/projects/[projectId]/settings.tsx b/apps/web/src/pages/projects/[projectId]/settings.tsx index c3ff540a..ce0a7c9d 100644 --- a/apps/web/src/pages/projects/[projectId]/settings.tsx +++ b/apps/web/src/pages/projects/[projectId]/settings.tsx @@ -13,24 +13,42 @@ import { Progress } from "components/Progress"; import { RemoveUserModal } from "components/RemoveUserModal"; import { Button } from "components/ui/button"; import { Input } from "components/ui/input"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "components/ui/tabs"; import dayjs from "dayjs"; import { getFlagCount } from "lib/flags"; import { getProjectPaidPlan, useAbbyStripe } from "lib/stripe"; import { useTracking } from "lib/tracking"; import type { GetStaticPaths, GetStaticProps } from "next"; import { useSession } from "next-auth/react"; -import Link from "next/link"; import { useRouter } from "next/router"; import type { NextPageWithLayout } from "pages/_app"; import { type FormEvent, useRef, useState } from "react"; import { toast } from "react-hot-toast"; import { getLimitByPlan } from "server/common/plans"; import { trpc } from "utils/trpc"; +import { parseAsStringLiteral, useQueryState } from "nuqs"; +import Link from "next/link"; + +const SETTINGS_TABS = { + General: "general", + Team: "team", + Billing: "billing", + Danger: "danger", +} as const; const SettingsPage: NextPageWithLayout = () => { const [userToRemove, setUserToRemove] = useState(null); const [isShowDeleteModal, setisShowDeleteModal] = useState(false); const inviteEmailRef = useRef(null); + const [currentTab, setCurrentTab] = useQueryState( + "tab", + parseAsStringLiteral([ + SETTINGS_TABS.General, + SETTINGS_TABS.Team, + SETTINGS_TABS.Billing, + SETTINGS_TABS.Danger, + ] as const).withDefault(SETTINGS_TABS.General) + ); const router = useRouter(); const trackEvent = useTracking(); @@ -95,251 +113,278 @@ const SettingsPage: NextPageWithLayout = () => { const isPlanWithStripe = projectPlan !== null && projectPlan !== "BETA"; return ( -
+

Project Settings

{isLoading || isError ? ( ) : ( - <> - - - General Details - -
-
-