add theme switcher and improve org switcher
This commit is contained in:
parent
af2d78cbfb
commit
b1afba191e
4 changed files with 106 additions and 49 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 className="text-sm font-medium leading-none">
|
||||
Signed in as
|
||||
</p>
|
||||
)}
|
||||
<p className="text-xs leading-none text-muted-foreground truncate">
|
||||
<p className="text-xs leading-none text-muted-foreground">
|
||||
{email}
|
||||
</p>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem onClick={logout}>
|
||||
Logout
|
||||
<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>
|
||||
</DropdownMenuGroup>
|
||||
</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,12 +228,12 @@ 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");
|
||||
}}
|
||||
|
@ -189,12 +241,17 @@ export default function Header({ email, orgId, name, orgs }: HeaderProps) {
|
|||
<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}
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue