mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-05 19:45:04 +02:00
small visual enhancements to icons
This commit is contained in:
parent
5388c5d5b4
commit
45e1bff2e0
28 changed files with 299 additions and 138 deletions
|
@ -15,7 +15,6 @@ import {
|
|||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
|
@ -24,7 +23,12 @@ import {
|
|||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { LockIcon, KeyIcon, UserIcon, Binary, Key, User } from "lucide-react";
|
||||
import { LockIcon, UserIcon, Binary, Key, User } from "lucide-react";
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSlot,
|
||||
} from "@app/components/ui/input-otp";
|
||||
|
||||
const pinSchema = z.object({
|
||||
pin: z
|
||||
|
@ -34,12 +38,16 @@ const pinSchema = z.object({
|
|||
});
|
||||
|
||||
const passwordSchema = z.object({
|
||||
email: z.string().email({ message: "Please enter a valid email address" }),
|
||||
password: z
|
||||
.string()
|
||||
.min(8, { message: "Password must be at least 8 characters long" }),
|
||||
});
|
||||
|
||||
const userSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(1),
|
||||
});
|
||||
|
||||
export default function ResourceAuthPortal() {
|
||||
const [activeTab, setActiveTab] = useState("pin");
|
||||
|
||||
|
@ -52,6 +60,13 @@ export default function ResourceAuthPortal() {
|
|||
|
||||
const passwordForm = useForm<z.infer<typeof passwordSchema>>({
|
||||
resolver: zodResolver(passwordSchema),
|
||||
defaultValues: {
|
||||
password: "",
|
||||
},
|
||||
});
|
||||
|
||||
const userForm = useForm<z.infer<typeof userSchema>>({
|
||||
resolver: zodResolver(userSchema),
|
||||
defaultValues: {
|
||||
email: "",
|
||||
password: "",
|
||||
|
@ -77,9 +92,9 @@ export default function ResourceAuthPortal() {
|
|||
<div className="w-full max-w-md mx-auto">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Welcome Back</CardTitle>
|
||||
<CardTitle>Authentication Required</CardTitle>
|
||||
<CardDescription>
|
||||
Choose your preferred login method
|
||||
Choose your preferred method
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
|
@ -92,7 +107,7 @@ export default function ResourceAuthPortal() {
|
|||
<Key className="w-4 h-4 mr-1" /> Password
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="sso">
|
||||
<User className="w-4 h-4 mr-1" /> SSO
|
||||
<User className="w-4 h-4 mr-1" /> User
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="pin">
|
||||
|
@ -106,20 +121,44 @@ export default function ResourceAuthPortal() {
|
|||
name="pin"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Enter PIN</FormLabel>
|
||||
<FormLabel>
|
||||
Enter 6-digit PIN
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Enter your 6-digit PIN"
|
||||
type="password"
|
||||
{...field}
|
||||
/>
|
||||
<div className="flex justify-center">
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
{...field}
|
||||
>
|
||||
<InputOTPGroup className="flex">
|
||||
<InputOTPSlot
|
||||
index={0}
|
||||
/>
|
||||
<InputOTPSlot
|
||||
index={1}
|
||||
/>
|
||||
<InputOTPSlot
|
||||
index={2}
|
||||
/>
|
||||
<InputOTPSlot
|
||||
index={3}
|
||||
/>
|
||||
<InputOTPSlot
|
||||
index={4}
|
||||
/>
|
||||
<InputOTPSlot
|
||||
index={5}
|
||||
/>
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" className="w-full">
|
||||
<KeyIcon className="w-4 h-4 mr-2" />
|
||||
<LockIcon className="w-4 h-4 mr-2" />
|
||||
Login with PIN
|
||||
</Button>
|
||||
</form>
|
||||
|
@ -129,27 +168,10 @@ export default function ResourceAuthPortal() {
|
|||
<Form {...passwordForm}>
|
||||
<form
|
||||
onSubmit={passwordForm.handleSubmit(
|
||||
onPasswordSubmit
|
||||
onPasswordSubmit,
|
||||
)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<FormField
|
||||
control={passwordForm.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Enter your email"
|
||||
type="email"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={passwordForm.control}
|
||||
name="password"
|
||||
|
@ -158,7 +180,7 @@ export default function ResourceAuthPortal() {
|
|||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Enter your password"
|
||||
placeholder="Enter password"
|
||||
type="password"
|
||||
{...field}
|
||||
/>
|
||||
|
@ -175,31 +197,73 @@ export default function ResourceAuthPortal() {
|
|||
</Form>
|
||||
</TabsContent>
|
||||
<TabsContent value="sso">
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Click the button below to login with your
|
||||
organization's SSO provider.
|
||||
</p>
|
||||
<Button
|
||||
onClick={handleSSOAuth}
|
||||
className="w-full"
|
||||
<Form {...userForm}>
|
||||
<form
|
||||
onSubmit={userForm.handleSubmit(
|
||||
(values) => {
|
||||
console.log(
|
||||
"User authentication",
|
||||
values,
|
||||
);
|
||||
// Implement user authentication logic here
|
||||
},
|
||||
)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<UserIcon className="w-4 h-4 mr-2" />
|
||||
Login with SSO
|
||||
</Button>
|
||||
</div>
|
||||
<FormField
|
||||
control={userForm.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Enter email"
|
||||
type="email"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={userForm.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Enter password"
|
||||
type="password"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" className="w-full">
|
||||
<LockIcon className="w-4 h-4 mr-2" />
|
||||
Login as User
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-center">
|
||||
</Card>
|
||||
{activeTab === "sso" && (
|
||||
<div className="flex justify-center mt-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Don't have an account?{" "}
|
||||
<a href="#" className="underline">
|
||||
Sign up
|
||||
</a>
|
||||
</p>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ export default function CreateRoleForm({
|
|||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6"
|
||||
className="space-y-8:w"
|
||||
id="create-role-form"
|
||||
>
|
||||
<FormField
|
||||
|
|
|
@ -155,7 +155,7 @@ export default function DeleteRoleForm({
|
|||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-4">
|
||||
<p>
|
||||
You're about to delete the{" "}
|
||||
|
@ -170,7 +170,7 @@ export default function DeleteRoleForm({
|
|||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6"
|
||||
className="space-y-8"
|
||||
id="remove-role-form"
|
||||
>
|
||||
<FormField
|
||||
|
|
|
@ -110,7 +110,7 @@ export default function AccessControlsPage() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<SettingsSectionTitle
|
||||
title="Access Controls"
|
||||
description="Manage what this user can access and do in the organization"
|
||||
|
@ -120,7 +120,7 @@ export default function AccessControlsPage() {
|
|||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6"
|
||||
className="space-y-8"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
|
||||
interface UserLayoutProps {
|
||||
children: React.ReactNode;
|
||||
|
@ -30,7 +29,7 @@ export default async function UserLayoutProps(props: UserLayoutProps) {
|
|||
try {
|
||||
const res = await internal.get<AxiosResponse<GetOrgUserResponse>>(
|
||||
`/org/${params.orgId}/user/${params.userId}`,
|
||||
await authCookieHeader()
|
||||
await authCookieHeader(),
|
||||
);
|
||||
user = res.data.data;
|
||||
} catch {
|
||||
|
@ -48,15 +47,17 @@ export default async function UserLayoutProps(props: UserLayoutProps) {
|
|||
<>
|
||||
<OrgUserProvider orgUser={user}>
|
||||
<div className="mb-4">
|
||||
<Link
|
||||
href="../../"
|
||||
className="text-muted-foreground hover:underline"
|
||||
>
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<ArrowLeft className="w-4 h-4" />{" "}
|
||||
<span>All Users</span>
|
||||
</div>
|
||||
</Link>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<Link href="../../">Users</Link>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>{user.email}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
|
||||
<div className="space-y-0.5 select-none mb-6">
|
||||
|
|
|
@ -171,7 +171,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
|
|||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
{!inviteLink && (
|
||||
<Form {...form}>
|
||||
<form
|
||||
|
|
|
@ -3,6 +3,14 @@
|
|||
import api from "@app/api";
|
||||
import { Avatar, AvatarFallback } from "@app/components/ui/avatar";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@app/components/ui/command";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
|
@ -12,6 +20,11 @@ import {
|
|||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@app/components/ui/dropdown-menu";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@app/components/ui/popover";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
@ -21,10 +34,12 @@ import {
|
|||
SelectValue,
|
||||
} from "@app/components/ui/select";
|
||||
import { useToast } from "@app/hooks/useToast";
|
||||
import { formatAxiosError } from "@app/lib/utils";
|
||||
import { cn, formatAxiosError } from "@app/lib/utils";
|
||||
import { ListOrgsResponse } from "@server/routers/org";
|
||||
import { Check, ChevronsUpDown } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
type HeaderProps = {
|
||||
name?: string;
|
||||
|
@ -36,6 +51,8 @@ type HeaderProps = {
|
|||
export default function Header({ email, orgName, name, orgs }: HeaderProps) {
|
||||
const { toast } = useToast();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function getInitials() {
|
||||
|
@ -125,7 +142,68 @@ export default function Header({ email, orgName, name, orgs }: HeaderProps) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-full md:w-[200px] h-12 px-3 py-4"
|
||||
>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex flex-col items-start">
|
||||
<span className="font-bold text-sm">
|
||||
Organization
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{orgName
|
||||
? orgs.find(
|
||||
(org) =>
|
||||
org.name === orgName,
|
||||
)?.name
|
||||
: "Select organization..."}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
|
||||
</div>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="[100px] md:w-[180px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search..." />
|
||||
<CommandEmpty>
|
||||
No organization found.
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandList>
|
||||
{orgs.map((org) => (
|
||||
<CommandItem
|
||||
key={org.orgId}
|
||||
onSelect={(currentValue) => {
|
||||
router.push(
|
||||
`/${org.orgId}/settings`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
orgName === org.name
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{org.name}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandList>
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
{/* <Select
|
||||
defaultValue={orgName}
|
||||
onValueChange={(val) => {
|
||||
router.push(`/${val}/settings`);
|
||||
|
@ -146,7 +224,7 @@ export default function Header({ email, orgName, name, orgs }: HeaderProps) {
|
|||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Select> */}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -46,7 +46,7 @@ export default function GeneralPage() {
|
|||
title="Delete organization"
|
||||
/>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
{orgUser.isOwner ? (
|
||||
<Button onClick={() => setIsDeleteModalOpen(true)}>
|
||||
Delete Organization
|
||||
|
|
|
@ -22,22 +22,22 @@ const topNavItems = [
|
|||
{
|
||||
title: "Sites",
|
||||
href: "/{orgId}/settings/sites",
|
||||
icon: <Combine className="h-5 w-5" />,
|
||||
icon: <Combine className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
title: "Resources",
|
||||
href: "/{orgId}/settings/resources",
|
||||
icon: <Waypoints className="h-5 w-5" />,
|
||||
icon: <Waypoints className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
title: "Access",
|
||||
href: "/{orgId}/settings/access",
|
||||
icon: <Users className="h-5 w-5" />,
|
||||
icon: <Users className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
title: "General",
|
||||
href: "/{orgId}/settings/general",
|
||||
icon: <Settings className="h-5 w-5" />,
|
||||
icon: <Settings className="h-4 w-4" />,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -257,11 +257,11 @@ export default function ResourceAuthenticationPage() {
|
|||
/>
|
||||
)}
|
||||
|
||||
<div className="space-y-12 lg:max-w-2xl">
|
||||
<section className="space-y-6">
|
||||
<div className="space-y-12">
|
||||
<section className="space-y-8">
|
||||
<SettingsSectionTitle
|
||||
title="Users & Roles"
|
||||
description="Configure who can visit this resource (only applicable if SSO is used)"
|
||||
description="Configure which users can access this resource (only applicable if SSO enabled)"
|
||||
size="1xl"
|
||||
/>
|
||||
|
||||
|
@ -275,9 +275,7 @@ export default function ResourceAuthenticationPage() {
|
|||
<Label htmlFor="sso-toggle">Allow SSO</Label>
|
||||
</div>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
Users will be able to access the resource if they're
|
||||
logged into the dashboard and have access to the
|
||||
resource. Users will only have to login once for all
|
||||
Existing users will only have to login once for all
|
||||
resources that have SSO enabled.
|
||||
</span>
|
||||
</div>
|
||||
|
@ -287,7 +285,7 @@ export default function ResourceAuthenticationPage() {
|
|||
onSubmit={usersRolesForm.handleSubmit(
|
||||
onSubmitUsersRoles,
|
||||
)}
|
||||
className="space-y-6"
|
||||
className="space-y-8"
|
||||
>
|
||||
<FormField
|
||||
control={usersRolesForm.control}
|
||||
|
@ -336,9 +334,9 @@ export default function ResourceAuthenticationPage() {
|
|||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Users with these roles will be able
|
||||
to access this resource. Admins can
|
||||
always access this resource.
|
||||
These roles will be able to access
|
||||
this resource. Admins can always
|
||||
access this resource.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
@ -414,10 +412,10 @@ export default function ResourceAuthenticationPage() {
|
|||
|
||||
<Separator />
|
||||
|
||||
<section className="space-y-6">
|
||||
<section className="space-y-8">
|
||||
<SettingsSectionTitle
|
||||
title="Authentication Methods"
|
||||
description="You can also allow users to access the resource via the below methods"
|
||||
description="You can also anyone to access the resource via the below methods"
|
||||
size="1xl"
|
||||
/>
|
||||
|
||||
|
|
|
@ -349,7 +349,7 @@ export default function ReverseProxyTargets(props: {
|
|||
return (
|
||||
<>
|
||||
<div className="space-y-12">
|
||||
<section className="space-y-6 lg:max-w-2xl">
|
||||
<section className="space-y-8">
|
||||
<SettingsSectionTitle
|
||||
title="SSL"
|
||||
description="Setup SSL to secure your connections with LetsEncrypt certificates"
|
||||
|
@ -366,23 +366,24 @@ export default function ReverseProxyTargets(props: {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<hr className="lg:max-w-2xl" />
|
||||
<hr />
|
||||
|
||||
<section className="space-y-6">
|
||||
<section className="space-y-8">
|
||||
<SettingsSectionTitle
|
||||
title="Targets"
|
||||
description="Setup targets to route traffic to your services"
|
||||
size="1xl"
|
||||
/>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<Form {...addTargetForm}>
|
||||
<form
|
||||
onSubmit={addTargetForm.handleSubmit(
|
||||
addTarget as any,
|
||||
)}
|
||||
className="space-y-8"
|
||||
>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<FormField
|
||||
control={addTargetForm.control}
|
||||
name="ip"
|
||||
|
|
|
@ -118,8 +118,8 @@ export default function GeneralForm() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="lg:max-w-2xl space-y-12">
|
||||
<section className="space-y-6">
|
||||
<div className="space-y-12 lg:max-w-2xl">
|
||||
<section className="space-y-8">
|
||||
<SettingsSectionTitle
|
||||
title="General Settings"
|
||||
description="Configure the general settings for this resource"
|
||||
|
@ -129,7 +129,7 @@ export default function GeneralForm() {
|
|||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6"
|
||||
className="space-y-8"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
|
|
@ -8,13 +8,21 @@ import { AxiosResponse } from "axios";
|
|||
import { redirect } from "next/navigation";
|
||||
import { authCookieHeader } from "@app/api/cookies";
|
||||
import { SidebarSettings } from "@app/components/SidebarSettings";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeft, Cloud, Settings, Shield } from "lucide-react";
|
||||
import { Cloud, Settings, Shield } from "lucide-react";
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import { GetOrgResponse } from "@server/routers/org";
|
||||
import OrgProvider from "@app/providers/OrgProvider";
|
||||
import { cache } from "react";
|
||||
import ResourceInfoBox from "./components/ResourceInfoBox";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@app/components/ui/breadcrumb";
|
||||
import Link from "next/link";
|
||||
|
||||
interface ResourceLayoutProps {
|
||||
children: React.ReactNode;
|
||||
|
@ -31,7 +39,7 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
|
|||
try {
|
||||
const res = await internal.get<AxiosResponse<GetResourceResponse>>(
|
||||
`/resource/${params.resourceId}`,
|
||||
await authCookieHeader()
|
||||
await authCookieHeader(),
|
||||
);
|
||||
resource = res.data.data;
|
||||
} catch {
|
||||
|
@ -60,8 +68,8 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
|
|||
const getOrg = cache(async () =>
|
||||
internal.get<AxiosResponse<GetOrgResponse>>(
|
||||
`/org/${params.orgId}`,
|
||||
await authCookieHeader()
|
||||
)
|
||||
await authCookieHeader(),
|
||||
),
|
||||
);
|
||||
const res = await getOrg();
|
||||
org = res.data.data;
|
||||
|
@ -94,15 +102,17 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
|
|||
return (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<Link
|
||||
href="../../"
|
||||
className="text-muted-foreground hover:underline"
|
||||
>
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<ArrowLeft className="w-4 h-4" />{" "}
|
||||
<span>All Resources</span>
|
||||
</div>
|
||||
</Link>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<Link href="../">Resources</Link>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>{resource.name}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
|
||||
<SettingsSectionTitle
|
||||
|
@ -112,10 +122,7 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
|
|||
|
||||
<OrgProvider org={org}>
|
||||
<ResourceProvider resource={resource} authInfo={authInfo}>
|
||||
<SidebarSettings
|
||||
sidebarNavItems={sidebarNavItems}
|
||||
limitWidth={false}
|
||||
>
|
||||
<SidebarSettings sidebarNavItems={sidebarNavItems}>
|
||||
<div className="mb-8 lg:max-w-2xl">
|
||||
<ResourceInfoBox />
|
||||
</div>
|
||||
|
|
|
@ -64,7 +64,7 @@ export default function GeneralPage() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<SettingsSectionTitle
|
||||
title="General Settings"
|
||||
description="Configure the general settings for this site"
|
||||
|
@ -74,7 +74,7 @@ export default function GeneralPage() {
|
|||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6"
|
||||
className="space-y-8"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
|
|
@ -8,6 +8,13 @@ import { SidebarSettings } from "@app/components/SidebarSettings";
|
|||
import Link from "next/link";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@app/components/ui/breadcrumb";
|
||||
|
||||
interface SettingsLayoutProps {
|
||||
children: React.ReactNode;
|
||||
|
@ -23,7 +30,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
|||
try {
|
||||
const res = await internal.get<AxiosResponse<GetSiteResponse>>(
|
||||
`/org/${params.orgId}/site/${params.niceId}`,
|
||||
await authCookieHeader()
|
||||
await authCookieHeader(),
|
||||
);
|
||||
site = res.data.data;
|
||||
} catch {
|
||||
|
@ -39,15 +46,18 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<Link
|
||||
href="../../"
|
||||
className="text-muted-foreground hover:underline"
|
||||
>
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<ArrowLeft className="w-4 h-4" /> <span>All Sites</span>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="mb-4 flex-row">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<Link href="../../">Sites</Link>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>{site.name}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
|
||||
<SettingsSectionTitle
|
||||
|
|
|
@ -191,11 +191,11 @@ sh get-docker.sh`;
|
|||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6"
|
||||
className="space-y-8"
|
||||
id="create-site-form"
|
||||
>
|
||||
<FormField
|
||||
|
|
|
@ -27,6 +27,7 @@ import { api } from "@app/api";
|
|||
import { useRouter } from "next/navigation";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { formatAxiosError } from "@app/lib/utils";
|
||||
import { LockIcon } from "lucide-react";
|
||||
|
||||
type LoginFormProps = {
|
||||
redirect?: string;
|
||||
|
@ -108,7 +109,7 @@ export default function LoginForm({ redirect }: LoginFormProps) {
|
|||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6"
|
||||
className="space-y-8"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
@ -153,6 +154,7 @@ export default function LoginForm({ redirect }: LoginFormProps) {
|
|||
className="w-full"
|
||||
loading={loading}
|
||||
>
|
||||
<LockIcon className="w-4 h-4 mr-2" />
|
||||
Login
|
||||
</Button>
|
||||
</form>
|
||||
|
|
|
@ -111,7 +111,7 @@ export default function SignupForm({ redirect }: SignupFormProps) {
|
|||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6"
|
||||
className="space-y-8"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
|
|
@ -134,7 +134,7 @@ export default function VerifyEmailForm({
|
|||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6"
|
||||
className="space-y-8"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { AccountForm } from "./account-form"
|
|||
|
||||
export default function SettingsAccountPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Account</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
|
|
@ -3,7 +3,7 @@ import { AppearanceForm } from "./appearance-form"
|
|||
|
||||
export default function SettingsAppearancePage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Appearance</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
|
|
@ -3,7 +3,7 @@ import { DisplayForm } from "./display-form"
|
|||
|
||||
export default function SettingsDisplayPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Display</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
|
|
@ -56,7 +56,7 @@ export default function SettingsLayout({ children }: SettingsLayoutProps) {
|
|||
className="hidden dark:block"
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden space-y-6 p-10 pb-16 md:block">
|
||||
<div className="hidden space-y-8 p-10 pb-16 md:block">
|
||||
<div className="space-y-0.5">
|
||||
<h2 className="text-2xl font-bold tracking-tight">Settings</h2>
|
||||
<p className="text-muted-foreground">
|
||||
|
|
|
@ -3,7 +3,7 @@ import { NotificationsForm } from "./notifications-form"
|
|||
|
||||
export default function SettingsNotificationsPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Notifications</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ProfileForm } from "@app/components/profile-form"
|
|||
|
||||
export default function SettingsProfilePage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Profile</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
|
|
@ -206,7 +206,7 @@ export default function StepperForm() {
|
|||
</div>
|
||||
)}
|
||||
{currentStep === "site" && (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="siteName">Site Name</Label>
|
||||
<Input
|
||||
|
@ -222,7 +222,7 @@ export default function StepperForm() {
|
|||
</div>
|
||||
)}
|
||||
{currentStep === "resources" && (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="resourceName">
|
||||
Resource Name
|
||||
|
|
|
@ -11,7 +11,7 @@ export default function SettingsSectionTitle({
|
|||
}: SettingsSectionTitleProps) {
|
||||
return (
|
||||
<div
|
||||
className={`space-y-0.5 select-none ${!size || size === "2xl" ? "mb-12" : ""}`}
|
||||
className={`space-y-0.5 select-none ${!size || size === "2xl" ? "mb-6 md:mb-12" : ""}`}
|
||||
>
|
||||
<h2
|
||||
className={`text-${
|
||||
|
|
|
@ -21,8 +21,8 @@ export function SidebarSettings({
|
|||
limitWidth,
|
||||
}: SideBarSettingsProps) {
|
||||
return (
|
||||
<div className="space-y-6 0 pb-16k">
|
||||
<div className="flex flex-col space-y-6 lg:flex-row lg:space-x-32 lg:space-y-0">
|
||||
<div className="space-y-8 0 pb-16k">
|
||||
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-32 lg:space-y-0">
|
||||
<aside className="-mx-4 lg:w-1/5">
|
||||
<SidebarNav items={sidebarNavItems} disabled={disabled} />
|
||||
</aside>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue