add theme switcher and improve org switcher

This commit is contained in:
Milo Schwartz 2024-12-22 20:16:52 -05:00
parent af2d78cbfb
commit b1afba191e
No known key found for this signature in database
4 changed files with 106 additions and 49 deletions

View file

@ -448,11 +448,11 @@ authRouter.post(
verifySessionMiddleware,
auth.requestEmailVerificationCode
);
authRouter.post(
"/change-password",
verifySessionUserMiddleware,
auth.changePassword
);
// authRouter.post(
// "/change-password",
// verifySessionUserMiddleware,
// auth.changePassword
// );
authRouter.post("/reset-password/request", auth.requestPasswordReset);
authRouter.post("/reset-password/", auth.resetPassword);

View file

@ -10,6 +10,7 @@ import {
CommandInput,
CommandItem,
CommandList,
CommandSeparator
} from "@app/components/ui/command";
import {
DropdownMenu,
@ -18,12 +19,12 @@ import {
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
DropdownMenuTrigger
} from "@app/components/ui/dropdown-menu";
import {
Popover,
PopoverContent,
PopoverTrigger,
PopoverTrigger
} from "@app/components/ui/popover";
import {
Select,
@ -31,13 +32,23 @@ import {
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
SelectValue
} from "@app/components/ui/select";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useToast } from "@app/hooks/useToast";
import { cn, formatAxiosError } from "@app/lib/utils";
import { ListOrgsResponse } from "@server/routers/org";
import { Check, ChevronsUpDown, Plus } from "lucide-react";
import {
Check,
ChevronsUpDown,
Laptop,
LogOut,
Moon,
Plus,
Sun,
User
} from "lucide-react";
import { useTheme } from "next-themes";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
@ -51,8 +62,12 @@ type HeaderProps = {
export default function Header({ email, orgId, name, orgs }: HeaderProps) {
const { toast } = useToast();
const { setTheme, theme } = useTheme();
const [open, setOpen] = useState(false);
const [userTheme, setUserTheme] = useState<"light" | "dark" | "system">(
theme as "light" | "dark" | "system"
);
const router = useRouter();
@ -72,7 +87,7 @@ export default function Header({ email, orgId, name, orgs }: HeaderProps) {
console.error("Error logging out", e);
toast({
title: "Error logging out",
description: formatAxiosError(e, "Error logging out"),
description: formatAxiosError(e, "Error logging out")
});
})
.then(() => {
@ -80,6 +95,11 @@ export default function Header({ email, orgId, name, orgs }: HeaderProps) {
});
}
function handleThemeChange(theme: "light" | "dark" | "system") {
setUserTheme(theme);
setTheme(theme);
}
return (
<>
<div className="flex items-center justify-between">
@ -104,22 +124,54 @@ export default function Header({ email, orgId, name, orgs }: HeaderProps) {
>
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
{name && (
<p className="text-sm font-medium leading-none truncate">
{name}
</p>
)}
<p className="text-xs leading-none text-muted-foreground truncate">
<p className="text-sm font-medium leading-none">
Signed in as
</p>
<p className="text-xs leading-none text-muted-foreground">
{email}
</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem onClick={logout}>
Logout
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuItem>
<User className="mr-2 h-4 w-4" />
<span>User Settings</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel>Theme</DropdownMenuLabel>
{(["light", "dark", "system"] as const).map(
(themeOption) => (
<DropdownMenuItem
key={themeOption}
onClick={() =>
handleThemeChange(themeOption)
}
>
{themeOption === "light" && (
<Sun className="mr-2 h-4 w-4" />
)}
{themeOption === "dark" && (
<Moon className="mr-2 h-4 w-4" />
)}
{themeOption === "system" && (
<Laptop className="mr-2 h-4 w-4" />
)}
<span className="capitalize">
{themeOption}
</span>
{userTheme === themeOption && (
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<span className="h-2 w-2 rounded-full bg-primary"></span>
</span>
)}
</DropdownMenuItem>
)
)}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => logout()}>
<LogOut className="mr-2 h-4 w-4" />
<span>Log out</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<span className="truncate max-w-[150px] md:max-w-none font-medium">
@ -163,7 +215,7 @@ export default function Header({ email, orgId, name, orgs }: HeaderProps) {
{orgId
? orgs.find(
(org) =>
org.orgId === orgId,
org.orgId === orgId
)?.name
: "Select organization..."}
</span>
@ -176,25 +228,30 @@ export default function Header({ email, orgId, name, orgs }: HeaderProps) {
<Command>
<CommandInput placeholder="Search..." />
<CommandEmpty>
No organization found.
No organizations found.
</CommandEmpty>
<CommandGroup className="[50px]">
<CommandGroup heading="Create">
<CommandList>
<CommandItem
className="flex items-center border border-input mb-2 cursor-pointer"
className="flex items-center cursor-pointer"
onSelect={(currentValue) => {
router.push("/setup");
}}
>
<Plus className="mr-2 h-4 w-4"/>
<Plus className="mr-2 h-4 w-4" />
New Organization
</CommandItem>
</CommandList>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Organizations">
<CommandList>
{orgs.map((org) => (
<CommandItem
key={org.orgId}
onSelect={(currentValue) => {
router.push(
`/${org.orgId}/settings`,
`/${org.orgId}/settings`
);
}}
>
@ -203,7 +260,7 @@ export default function Header({ email, orgId, name, orgs }: HeaderProps) {
"mr-2 h-4 w-4",
orgId === org.orgId
? "opacity-100"
: "opacity-0",
: "opacity-0"
)}
/>
{org.name}

View file

@ -110,27 +110,6 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
</div>
<div className="container mx-auto sm:px-0 px-3 pt-[165px]">{children}</div>
<footer className="w-full mt-6 py-3">
<div className="container mx-auto flex justify-end items-center px-3 sm:px-0 text-sm text-neutral-300 dark:text-neutral-700 space-x-3 select-none">
<div>Built by Fossorial</div>
<a
href="https://github.com/fosrl/pangolin"
target="_blank"
rel="noopener noreferrer"
aria-label="GitHub"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="w-4 h-4"
>
<path d="M12 0C5.37 0 0 5.373 0 12c0 5.303 3.438 9.8 8.207 11.385.6.11.82-.26.82-.577v-2.17c-3.338.726-4.042-1.61-4.042-1.61-.546-1.385-1.333-1.755-1.333-1.755-1.09-.744.082-.73.082-.73 1.205.085 1.84 1.24 1.84 1.24 1.07 1.835 2.807 1.305 3.492.997.107-.775.42-1.305.763-1.605-2.665-.305-5.467-1.335-5.467-5.93 0-1.31.468-2.382 1.236-3.22-.123-.303-.535-1.523.117-3.176 0 0 1.008-.322 3.3 1.23a11.52 11.52 0 013.006-.403c1.02.005 2.045.137 3.006.403 2.29-1.552 3.295-1.23 3.295-1.23.654 1.653.242 2.873.12 3.176.77.838 1.235 1.91 1.235 3.22 0 4.605-2.805 5.623-5.475 5.92.43.37.814 1.1.814 2.22v3.293c0 .32.217.693.825.576C20.565 21.795 24 17.298 24 12 24 5.373 18.627 0 12 0z" />
</svg>
</a>
</div>
</footer>
</>
);
}

View file

@ -38,6 +38,27 @@ export default async function RootLayout({
}}
>
{children}
<footer className="w-full mt-6 py-3">
<div className="container mx-auto flex justify-center items-center px-3 sm:px-0 text-sm text-neutral-300 dark:text-neutral-700 space-x-3 select-none">
<div>Built by Fossorial</div>
<a
href="https://github.com/fosrl/pangolin"
target="_blank"
rel="noopener noreferrer"
aria-label="GitHub"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="w-4 h-4"
>
<path d="M12 0C5.37 0 0 5.373 0 12c0 5.303 3.438 9.8 8.207 11.385.6.11.82-.26.82-.577v-2.17c-3.338.726-4.042-1.61-4.042-1.61-.546-1.385-1.333-1.755-1.333-1.755-1.09-.744.082-.73.082-.73 1.205.085 1.84 1.24 1.84 1.24 1.07 1.835 2.807 1.305 3.492.997.107-.775.42-1.305.763-1.605-2.665-.305-5.467-1.335-5.467-5.93 0-1.31.468-2.382 1.236-3.22-.123-.303-.535-1.523.117-3.176 0 0 1.008-.322 3.3 1.23a11.52 11.52 0 013.006-.403c1.02.005 2.045.137 3.006.403 2.29-1.552 3.295-1.23 3.295-1.23.654 1.653.242 2.873.12 3.176.77.838 1.235 1.91 1.235 3.22 0 4.605-2.805 5.623-5.475 5.92.43.37.814 1.1.814 2.22v3.293c0 .32.217.693.825.576C20.565 21.795 24 17.298 24 12 24 5.373 18.627 0 12 0z" />
</svg>
</a>
</div>
</footer>
</EnvProvider>
<Toaster />
</ThemeProvider>