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,
|
verifySessionMiddleware,
|
||||||
auth.requestEmailVerificationCode
|
auth.requestEmailVerificationCode
|
||||||
);
|
);
|
||||||
authRouter.post(
|
// authRouter.post(
|
||||||
"/change-password",
|
// "/change-password",
|
||||||
verifySessionUserMiddleware,
|
// verifySessionUserMiddleware,
|
||||||
auth.changePassword
|
// auth.changePassword
|
||||||
);
|
// );
|
||||||
authRouter.post("/reset-password/request", auth.requestPasswordReset);
|
authRouter.post("/reset-password/request", auth.requestPasswordReset);
|
||||||
authRouter.post("/reset-password/", auth.resetPassword);
|
authRouter.post("/reset-password/", auth.resetPassword);
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
CommandInput,
|
CommandInput,
|
||||||
CommandItem,
|
CommandItem,
|
||||||
CommandList,
|
CommandList,
|
||||||
|
CommandSeparator
|
||||||
} from "@app/components/ui/command";
|
} from "@app/components/ui/command";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
@ -18,12 +19,12 @@ import {
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger
|
||||||
} from "@app/components/ui/dropdown-menu";
|
} from "@app/components/ui/dropdown-menu";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger
|
||||||
} from "@app/components/ui/popover";
|
} from "@app/components/ui/popover";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
|
@ -31,13 +32,23 @@ import {
|
||||||
SelectGroup,
|
SelectGroup,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue
|
||||||
} from "@app/components/ui/select";
|
} from "@app/components/ui/select";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import { useToast } from "@app/hooks/useToast";
|
import { useToast } from "@app/hooks/useToast";
|
||||||
import { cn, formatAxiosError } from "@app/lib/utils";
|
import { cn, formatAxiosError } from "@app/lib/utils";
|
||||||
import { ListOrgsResponse } from "@server/routers/org";
|
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 Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
@ -51,8 +62,12 @@ type HeaderProps = {
|
||||||
|
|
||||||
export default function Header({ email, orgId, name, orgs }: HeaderProps) {
|
export default function Header({ email, orgId, name, orgs }: HeaderProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { setTheme, theme } = useTheme();
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [userTheme, setUserTheme] = useState<"light" | "dark" | "system">(
|
||||||
|
theme as "light" | "dark" | "system"
|
||||||
|
);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -72,7 +87,7 @@ export default function Header({ email, orgId, name, orgs }: HeaderProps) {
|
||||||
console.error("Error logging out", e);
|
console.error("Error logging out", e);
|
||||||
toast({
|
toast({
|
||||||
title: "Error logging out",
|
title: "Error logging out",
|
||||||
description: formatAxiosError(e, "Error logging out"),
|
description: formatAxiosError(e, "Error logging out")
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
|
@ -104,22 +124,54 @@ export default function Header({ email, orgId, name, orgs }: HeaderProps) {
|
||||||
>
|
>
|
||||||
<DropdownMenuLabel className="font-normal">
|
<DropdownMenuLabel className="font-normal">
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
{name && (
|
<p className="text-sm font-medium leading-none">
|
||||||
<p className="text-sm font-medium leading-none truncate">
|
Signed in as
|
||||||
{name}
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
<p className="text-xs leading-none text-muted-foreground">
|
||||||
<p className="text-xs leading-none text-muted-foreground truncate">
|
|
||||||
{email}
|
{email}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={logout}>
|
<User className="mr-2 h-4 w-4" />
|
||||||
Logout
|
<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>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuGroup>
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<span className="truncate max-w-[150px] md:max-w-none font-medium">
|
<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
|
{orgId
|
||||||
? orgs.find(
|
? orgs.find(
|
||||||
(org) =>
|
(org) =>
|
||||||
org.orgId === orgId,
|
org.orgId === orgId
|
||||||
)?.name
|
)?.name
|
||||||
: "Select organization..."}
|
: "Select organization..."}
|
||||||
</span>
|
</span>
|
||||||
|
@ -176,25 +228,30 @@ export default function Header({ email, orgId, name, orgs }: HeaderProps) {
|
||||||
<Command>
|
<Command>
|
||||||
<CommandInput placeholder="Search..." />
|
<CommandInput placeholder="Search..." />
|
||||||
<CommandEmpty>
|
<CommandEmpty>
|
||||||
No organization found.
|
No organizations found.
|
||||||
</CommandEmpty>
|
</CommandEmpty>
|
||||||
<CommandGroup className="[50px]">
|
<CommandGroup heading="Create">
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
className="flex items-center border border-input mb-2 cursor-pointer"
|
className="flex items-center cursor-pointer"
|
||||||
onSelect={(currentValue) => {
|
onSelect={(currentValue) => {
|
||||||
router.push("/setup");
|
router.push("/setup");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Plus className="mr-2 h-4 w-4"/>
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
New Organization
|
New Organization
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
|
</CommandList>
|
||||||
|
</CommandGroup>
|
||||||
|
<CommandSeparator />
|
||||||
|
<CommandGroup heading="Organizations">
|
||||||
|
<CommandList>
|
||||||
{orgs.map((org) => (
|
{orgs.map((org) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={org.orgId}
|
key={org.orgId}
|
||||||
onSelect={(currentValue) => {
|
onSelect={(currentValue) => {
|
||||||
router.push(
|
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",
|
"mr-2 h-4 w-4",
|
||||||
orgId === org.orgId
|
orgId === org.orgId
|
||||||
? "opacity-100"
|
? "opacity-100"
|
||||||
: "opacity-0",
|
: "opacity-0"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{org.name}
|
{org.name}
|
||||||
|
|
|
@ -110,27 +110,6 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="container mx-auto sm:px-0 px-3 pt-[165px]">{children}</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}
|
{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>
|
</EnvProvider>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|
Loading…
Add table
Reference in a new issue