small visual enhancements to icons

This commit is contained in:
Milo Schwartz 2024-11-22 23:06:12 -05:00
parent 5388c5d5b4
commit 45e1bff2e0
No known key found for this signature in database
28 changed files with 299 additions and 138 deletions

View file

@ -15,7 +15,6 @@ import {
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { import {
Form, Form,
FormControl, FormControl,
@ -24,7 +23,12 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "@/components/ui/form"; } 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({ const pinSchema = z.object({
pin: z pin: z
@ -34,12 +38,16 @@ const pinSchema = z.object({
}); });
const passwordSchema = z.object({ const passwordSchema = z.object({
email: z.string().email({ message: "Please enter a valid email address" }),
password: z password: z
.string() .string()
.min(8, { message: "Password must be at least 8 characters long" }), .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() { export default function ResourceAuthPortal() {
const [activeTab, setActiveTab] = useState("pin"); const [activeTab, setActiveTab] = useState("pin");
@ -52,6 +60,13 @@ export default function ResourceAuthPortal() {
const passwordForm = useForm<z.infer<typeof passwordSchema>>({ const passwordForm = useForm<z.infer<typeof passwordSchema>>({
resolver: zodResolver(passwordSchema), resolver: zodResolver(passwordSchema),
defaultValues: {
password: "",
},
});
const userForm = useForm<z.infer<typeof userSchema>>({
resolver: zodResolver(userSchema),
defaultValues: { defaultValues: {
email: "", email: "",
password: "", password: "",
@ -77,9 +92,9 @@ export default function ResourceAuthPortal() {
<div className="w-full max-w-md mx-auto"> <div className="w-full max-w-md mx-auto">
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Welcome Back</CardTitle> <CardTitle>Authentication Required</CardTitle>
<CardDescription> <CardDescription>
Choose your preferred login method Choose your preferred method
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@ -92,7 +107,7 @@ export default function ResourceAuthPortal() {
<Key className="w-4 h-4 mr-1" /> Password <Key className="w-4 h-4 mr-1" /> Password
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="sso"> <TabsTrigger value="sso">
<User className="w-4 h-4 mr-1" /> SSO <User className="w-4 h-4 mr-1" /> User
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="pin"> <TabsContent value="pin">
@ -106,20 +121,44 @@ export default function ResourceAuthPortal() {
name="pin" name="pin"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Enter PIN</FormLabel> <FormLabel>
Enter 6-digit PIN
</FormLabel>
<FormControl> <FormControl>
<Input <div className="flex justify-center">
placeholder="Enter your 6-digit PIN" <InputOTP
type="password" maxLength={6}
{...field} {...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> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<Button type="submit" className="w-full"> <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 Login with PIN
</Button> </Button>
</form> </form>
@ -129,27 +168,10 @@ export default function ResourceAuthPortal() {
<Form {...passwordForm}> <Form {...passwordForm}>
<form <form
onSubmit={passwordForm.handleSubmit( onSubmit={passwordForm.handleSubmit(
onPasswordSubmit onPasswordSubmit,
)} )}
className="space-y-4" 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 <FormField
control={passwordForm.control} control={passwordForm.control}
name="password" name="password"
@ -158,7 +180,7 @@ export default function ResourceAuthPortal() {
<FormLabel>Password</FormLabel> <FormLabel>Password</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="Enter your password" placeholder="Enter password"
type="password" type="password"
{...field} {...field}
/> />
@ -175,31 +197,73 @@ export default function ResourceAuthPortal() {
</Form> </Form>
</TabsContent> </TabsContent>
<TabsContent value="sso"> <TabsContent value="sso">
<div className="space-y-4"> <Form {...userForm}>
<p className="text-sm text-muted-foreground"> <form
Click the button below to login with your onSubmit={userForm.handleSubmit(
organization's SSO provider. (values) => {
</p> console.log(
<Button "User authentication",
onClick={handleSSOAuth} values,
className="w-full" );
// Implement user authentication logic here
},
)}
className="space-y-4"
> >
<UserIcon className="w-4 h-4 mr-2" /> <FormField
Login with SSO control={userForm.control}
</Button> name="email"
</div> 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> </TabsContent>
</Tabs> </Tabs>
</CardContent> </CardContent>
<CardFooter className="flex justify-center"> </Card>
{activeTab === "sso" && (
<div className="flex justify-center mt-4">
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Don't have an account?{" "} Don't have an account?{" "}
<a href="#" className="underline"> <a href="#" className="underline">
Sign up Sign up
</a> </a>
</p> </p>
</CardFooter> </div>
</Card> )}
</div> </div>
); );
} }

View file

@ -123,7 +123,7 @@ export default function CreateRoleForm({
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6" className="space-y-8:w"
id="create-role-form" id="create-role-form"
> >
<FormField <FormField

View file

@ -155,7 +155,7 @@ export default function DeleteRoleForm({
</CredenzaDescription> </CredenzaDescription>
</CredenzaHeader> </CredenzaHeader>
<CredenzaBody> <CredenzaBody>
<div className="space-y-6"> <div className="space-y-8">
<div className="space-y-4"> <div className="space-y-4">
<p> <p>
You're about to delete the{" "} You're about to delete the{" "}
@ -170,7 +170,7 @@ export default function DeleteRoleForm({
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6" className="space-y-8"
id="remove-role-form" id="remove-role-form"
> >
<FormField <FormField

View file

@ -110,7 +110,7 @@ export default function AccessControlsPage() {
return ( return (
<> <>
<div className="space-y-6"> <div className="space-y-8">
<SettingsSectionTitle <SettingsSectionTitle
title="Access Controls" title="Access Controls"
description="Manage what this user can access and do in the organization" description="Manage what this user can access and do in the organization"
@ -120,7 +120,7 @@ export default function AccessControlsPage() {
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6" className="space-y-8"
> >
<FormField <FormField
control={form.control} control={form.control}

View file

@ -14,7 +14,6 @@ import {
BreadcrumbSeparator, BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"; } from "@/components/ui/breadcrumb";
import Link from "next/link"; import Link from "next/link";
import { ArrowLeft } from "lucide-react";
interface UserLayoutProps { interface UserLayoutProps {
children: React.ReactNode; children: React.ReactNode;
@ -30,7 +29,7 @@ export default async function UserLayoutProps(props: UserLayoutProps) {
try { try {
const res = await internal.get<AxiosResponse<GetOrgUserResponse>>( const res = await internal.get<AxiosResponse<GetOrgUserResponse>>(
`/org/${params.orgId}/user/${params.userId}`, `/org/${params.orgId}/user/${params.userId}`,
await authCookieHeader() await authCookieHeader(),
); );
user = res.data.data; user = res.data.data;
} catch { } catch {
@ -48,15 +47,17 @@ export default async function UserLayoutProps(props: UserLayoutProps) {
<> <>
<OrgUserProvider orgUser={user}> <OrgUserProvider orgUser={user}>
<div className="mb-4"> <div className="mb-4">
<Link <Breadcrumb>
href="../../" <BreadcrumbList>
className="text-muted-foreground hover:underline" <BreadcrumbItem>
> <Link href="../../">Users</Link>
<div className="flex flex-row items-center gap-1"> </BreadcrumbItem>
<ArrowLeft className="w-4 h-4" />{" "} <BreadcrumbSeparator />
<span>All Users</span> <BreadcrumbItem>
</div> <BreadcrumbPage>{user.email}</BreadcrumbPage>
</Link> </BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div> </div>
<div className="space-y-0.5 select-none mb-6"> <div className="space-y-0.5 select-none mb-6">

View file

@ -171,7 +171,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) {
</CredenzaDescription> </CredenzaDescription>
</CredenzaHeader> </CredenzaHeader>
<CredenzaBody> <CredenzaBody>
<div className="space-y-6"> <div className="space-y-8">
{!inviteLink && ( {!inviteLink && (
<Form {...form}> <Form {...form}>
<form <form

View file

@ -3,6 +3,14 @@
import api from "@app/api"; import api from "@app/api";
import { Avatar, AvatarFallback } from "@app/components/ui/avatar"; import { Avatar, AvatarFallback } from "@app/components/ui/avatar";
import { Button } from "@app/components/ui/button"; import { Button } from "@app/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@app/components/ui/command";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@ -12,6 +20,11 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@app/components/ui/dropdown-menu"; } from "@app/components/ui/dropdown-menu";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@app/components/ui/popover";
import { import {
Select, Select,
SelectContent, SelectContent,
@ -21,10 +34,12 @@ import {
SelectValue, SelectValue,
} from "@app/components/ui/select"; } from "@app/components/ui/select";
import { useToast } from "@app/hooks/useToast"; 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 { ListOrgsResponse } from "@server/routers/org";
import { Check, ChevronsUpDown } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useState } from "react";
type HeaderProps = { type HeaderProps = {
name?: string; name?: string;
@ -36,6 +51,8 @@ type HeaderProps = {
export default function Header({ email, orgName, name, orgs }: HeaderProps) { export default function Header({ email, orgName, name, orgs }: HeaderProps) {
const { toast } = useToast(); const { toast } = useToast();
const [open, setOpen] = useState(false);
const router = useRouter(); const router = useRouter();
function getInitials() { function getInitials() {
@ -125,7 +142,68 @@ export default function Header({ email, orgName, name, orgs }: HeaderProps) {
</div> </div>
</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} defaultValue={orgName}
onValueChange={(val) => { onValueChange={(val) => {
router.push(`/${val}/settings`); router.push(`/${val}/settings`);
@ -146,7 +224,7 @@ export default function Header({ email, orgName, name, orgs }: HeaderProps) {
))} ))}
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select> */}
</div> </div>
</div> </div>
</> </>

View file

@ -46,7 +46,7 @@ export default function GeneralPage() {
title="Delete organization" title="Delete organization"
/> />
<div className="space-y-6"> <div className="space-y-8">
{orgUser.isOwner ? ( {orgUser.isOwner ? (
<Button onClick={() => setIsDeleteModalOpen(true)}> <Button onClick={() => setIsDeleteModalOpen(true)}>
Delete Organization Delete Organization

View file

@ -22,22 +22,22 @@ const topNavItems = [
{ {
title: "Sites", title: "Sites",
href: "/{orgId}/settings/sites", href: "/{orgId}/settings/sites",
icon: <Combine className="h-5 w-5" />, icon: <Combine className="h-4 w-4" />,
}, },
{ {
title: "Resources", title: "Resources",
href: "/{orgId}/settings/resources", href: "/{orgId}/settings/resources",
icon: <Waypoints className="h-5 w-5" />, icon: <Waypoints className="h-4 w-4" />,
}, },
{ {
title: "Access", title: "Access",
href: "/{orgId}/settings/access", href: "/{orgId}/settings/access",
icon: <Users className="h-5 w-5" />, icon: <Users className="h-4 w-4" />,
}, },
{ {
title: "General", title: "General",
href: "/{orgId}/settings/general", href: "/{orgId}/settings/general",
icon: <Settings className="h-5 w-5" />, icon: <Settings className="h-4 w-4" />,
}, },
]; ];

View file

@ -257,11 +257,11 @@ export default function ResourceAuthenticationPage() {
/> />
)} )}
<div className="space-y-12 lg:max-w-2xl"> <div className="space-y-12">
<section className="space-y-6"> <section className="space-y-8">
<SettingsSectionTitle <SettingsSectionTitle
title="Users & Roles" 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" size="1xl"
/> />
@ -275,9 +275,7 @@ export default function ResourceAuthenticationPage() {
<Label htmlFor="sso-toggle">Allow SSO</Label> <Label htmlFor="sso-toggle">Allow SSO</Label>
</div> </div>
<span className="text-muted-foreground text-sm"> <span className="text-muted-foreground text-sm">
Users will be able to access the resource if they're Existing users will only have to login once for all
logged into the dashboard and have access to the
resource. Users will only have to login once for all
resources that have SSO enabled. resources that have SSO enabled.
</span> </span>
</div> </div>
@ -287,7 +285,7 @@ export default function ResourceAuthenticationPage() {
onSubmit={usersRolesForm.handleSubmit( onSubmit={usersRolesForm.handleSubmit(
onSubmitUsersRoles, onSubmitUsersRoles,
)} )}
className="space-y-6" className="space-y-8"
> >
<FormField <FormField
control={usersRolesForm.control} control={usersRolesForm.control}
@ -336,9 +334,9 @@ export default function ResourceAuthenticationPage() {
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription>
Users with these roles will be able These roles will be able to access
to access this resource. Admins can this resource. Admins can always
always access this resource. access this resource.
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -414,10 +412,10 @@ export default function ResourceAuthenticationPage() {
<Separator /> <Separator />
<section className="space-y-6"> <section className="space-y-8">
<SettingsSectionTitle <SettingsSectionTitle
title="Authentication Methods" 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" size="1xl"
/> />

View file

@ -349,7 +349,7 @@ export default function ReverseProxyTargets(props: {
return ( return (
<> <>
<div className="space-y-12"> <div className="space-y-12">
<section className="space-y-6 lg:max-w-2xl"> <section className="space-y-8">
<SettingsSectionTitle <SettingsSectionTitle
title="SSL" title="SSL"
description="Setup SSL to secure your connections with LetsEncrypt certificates" description="Setup SSL to secure your connections with LetsEncrypt certificates"
@ -366,23 +366,24 @@ export default function ReverseProxyTargets(props: {
</div> </div>
</section> </section>
<hr className="lg:max-w-2xl" /> <hr />
<section className="space-y-6"> <section className="space-y-8">
<SettingsSectionTitle <SettingsSectionTitle
title="Targets" title="Targets"
description="Setup targets to route traffic to your services" description="Setup targets to route traffic to your services"
size="1xl" size="1xl"
/> />
<div className="space-y-6"> <div className="space-y-8">
<Form {...addTargetForm}> <Form {...addTargetForm}>
<form <form
onSubmit={addTargetForm.handleSubmit( onSubmit={addTargetForm.handleSubmit(
addTarget as any, 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 <FormField
control={addTargetForm.control} control={addTargetForm.control}
name="ip" name="ip"

View file

@ -118,8 +118,8 @@ export default function GeneralForm() {
return ( return (
<> <>
<div className="lg:max-w-2xl space-y-12"> <div className="space-y-12 lg:max-w-2xl">
<section className="space-y-6"> <section className="space-y-8">
<SettingsSectionTitle <SettingsSectionTitle
title="General Settings" title="General Settings"
description="Configure the general settings for this resource" description="Configure the general settings for this resource"
@ -129,7 +129,7 @@ export default function GeneralForm() {
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6" className="space-y-8"
> >
<FormField <FormField
control={form.control} control={form.control}

View file

@ -8,13 +8,21 @@ import { AxiosResponse } from "axios";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { authCookieHeader } from "@app/api/cookies"; import { authCookieHeader } from "@app/api/cookies";
import { SidebarSettings } from "@app/components/SidebarSettings"; import { SidebarSettings } from "@app/components/SidebarSettings";
import Link from "next/link"; import { Cloud, Settings, Shield } from "lucide-react";
import { ArrowLeft, Cloud, Settings, Shield } from "lucide-react";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { GetOrgResponse } from "@server/routers/org"; import { GetOrgResponse } from "@server/routers/org";
import OrgProvider from "@app/providers/OrgProvider"; import OrgProvider from "@app/providers/OrgProvider";
import { cache } from "react"; import { cache } from "react";
import ResourceInfoBox from "./components/ResourceInfoBox"; import ResourceInfoBox from "./components/ResourceInfoBox";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@app/components/ui/breadcrumb";
import Link from "next/link";
interface ResourceLayoutProps { interface ResourceLayoutProps {
children: React.ReactNode; children: React.ReactNode;
@ -31,7 +39,7 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
try { try {
const res = await internal.get<AxiosResponse<GetResourceResponse>>( const res = await internal.get<AxiosResponse<GetResourceResponse>>(
`/resource/${params.resourceId}`, `/resource/${params.resourceId}`,
await authCookieHeader() await authCookieHeader(),
); );
resource = res.data.data; resource = res.data.data;
} catch { } catch {
@ -60,8 +68,8 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
const getOrg = cache(async () => const getOrg = cache(async () =>
internal.get<AxiosResponse<GetOrgResponse>>( internal.get<AxiosResponse<GetOrgResponse>>(
`/org/${params.orgId}`, `/org/${params.orgId}`,
await authCookieHeader() await authCookieHeader(),
) ),
); );
const res = await getOrg(); const res = await getOrg();
org = res.data.data; org = res.data.data;
@ -94,15 +102,17 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
return ( return (
<> <>
<div className="mb-4"> <div className="mb-4">
<Link <Breadcrumb>
href="../../" <BreadcrumbList>
className="text-muted-foreground hover:underline" <BreadcrumbItem>
> <Link href="../">Resources</Link>
<div className="flex flex-row items-center gap-1"> </BreadcrumbItem>
<ArrowLeft className="w-4 h-4" />{" "} <BreadcrumbSeparator />
<span>All Resources</span> <BreadcrumbItem>
</div> <BreadcrumbPage>{resource.name}</BreadcrumbPage>
</Link> </BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div> </div>
<SettingsSectionTitle <SettingsSectionTitle
@ -112,10 +122,7 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
<OrgProvider org={org}> <OrgProvider org={org}>
<ResourceProvider resource={resource} authInfo={authInfo}> <ResourceProvider resource={resource} authInfo={authInfo}>
<SidebarSettings <SidebarSettings sidebarNavItems={sidebarNavItems}>
sidebarNavItems={sidebarNavItems}
limitWidth={false}
>
<div className="mb-8 lg:max-w-2xl"> <div className="mb-8 lg:max-w-2xl">
<ResourceInfoBox /> <ResourceInfoBox />
</div> </div>

View file

@ -64,7 +64,7 @@ export default function GeneralPage() {
return ( return (
<> <>
<div className="space-y-6"> <div className="space-y-8">
<SettingsSectionTitle <SettingsSectionTitle
title="General Settings" title="General Settings"
description="Configure the general settings for this site" description="Configure the general settings for this site"
@ -74,7 +74,7 @@ export default function GeneralPage() {
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6" className="space-y-8"
> >
<FormField <FormField
control={form.control} control={form.control}

View file

@ -8,6 +8,13 @@ import { SidebarSettings } from "@app/components/SidebarSettings";
import Link from "next/link"; import Link from "next/link";
import { ArrowLeft } from "lucide-react"; import { ArrowLeft } from "lucide-react";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@app/components/ui/breadcrumb";
interface SettingsLayoutProps { interface SettingsLayoutProps {
children: React.ReactNode; children: React.ReactNode;
@ -23,7 +30,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
try { try {
const res = await internal.get<AxiosResponse<GetSiteResponse>>( const res = await internal.get<AxiosResponse<GetSiteResponse>>(
`/org/${params.orgId}/site/${params.niceId}`, `/org/${params.orgId}/site/${params.niceId}`,
await authCookieHeader() await authCookieHeader(),
); );
site = res.data.data; site = res.data.data;
} catch { } catch {
@ -39,15 +46,18 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
return ( return (
<> <>
<div className="mb-4"> <div className="mb-4 flex-row">
<Link <Breadcrumb>
href="../../" <BreadcrumbList>
className="text-muted-foreground hover:underline" <BreadcrumbItem>
> <Link href="../../">Sites</Link>
<div className="flex flex-row items-center gap-1"> </BreadcrumbItem>
<ArrowLeft className="w-4 h-4" /> <span>All Sites</span> <BreadcrumbSeparator />
</div> <BreadcrumbItem>
</Link> <BreadcrumbPage>{site.name}</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div> </div>
<SettingsSectionTitle <SettingsSectionTitle

View file

@ -191,11 +191,11 @@ sh get-docker.sh`;
</CredenzaDescription> </CredenzaDescription>
</CredenzaHeader> </CredenzaHeader>
<CredenzaBody> <CredenzaBody>
<div className="space-y-6"> <div className="space-y-8">
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6" className="space-y-8"
id="create-site-form" id="create-site-form"
> >
<FormField <FormField

View file

@ -27,6 +27,7 @@ import { api } from "@app/api";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { formatAxiosError } from "@app/lib/utils"; import { formatAxiosError } from "@app/lib/utils";
import { LockIcon } from "lucide-react";
type LoginFormProps = { type LoginFormProps = {
redirect?: string; redirect?: string;
@ -108,7 +109,7 @@ export default function LoginForm({ redirect }: LoginFormProps) {
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6" className="space-y-8"
> >
<FormField <FormField
control={form.control} control={form.control}
@ -153,6 +154,7 @@ export default function LoginForm({ redirect }: LoginFormProps) {
className="w-full" className="w-full"
loading={loading} loading={loading}
> >
<LockIcon className="w-4 h-4 mr-2" />
Login Login
</Button> </Button>
</form> </form>

View file

@ -111,7 +111,7 @@ export default function SignupForm({ redirect }: SignupFormProps) {
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6" className="space-y-8"
> >
<FormField <FormField
control={form.control} control={form.control}

View file

@ -134,7 +134,7 @@ export default function VerifyEmailForm({
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6" className="space-y-8"
> >
<FormField <FormField
control={form.control} control={form.control}

View file

@ -3,7 +3,7 @@ import { AccountForm } from "./account-form"
export default function SettingsAccountPage() { export default function SettingsAccountPage() {
return ( return (
<div className="space-y-6"> <div className="space-y-8">
<div> <div>
<h3 className="text-lg font-medium">Account</h3> <h3 className="text-lg font-medium">Account</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">

View file

@ -3,7 +3,7 @@ import { AppearanceForm } from "./appearance-form"
export default function SettingsAppearancePage() { export default function SettingsAppearancePage() {
return ( return (
<div className="space-y-6"> <div className="space-y-8">
<div> <div>
<h3 className="text-lg font-medium">Appearance</h3> <h3 className="text-lg font-medium">Appearance</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">

View file

@ -3,7 +3,7 @@ import { DisplayForm } from "./display-form"
export default function SettingsDisplayPage() { export default function SettingsDisplayPage() {
return ( return (
<div className="space-y-6"> <div className="space-y-8">
<div> <div>
<h3 className="text-lg font-medium">Display</h3> <h3 className="text-lg font-medium">Display</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">

View file

@ -56,7 +56,7 @@ export default function SettingsLayout({ children }: SettingsLayoutProps) {
className="hidden dark:block" className="hidden dark:block"
/> />
</div> </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"> <div className="space-y-0.5">
<h2 className="text-2xl font-bold tracking-tight">Settings</h2> <h2 className="text-2xl font-bold tracking-tight">Settings</h2>
<p className="text-muted-foreground"> <p className="text-muted-foreground">

View file

@ -3,7 +3,7 @@ import { NotificationsForm } from "./notifications-form"
export default function SettingsNotificationsPage() { export default function SettingsNotificationsPage() {
return ( return (
<div className="space-y-6"> <div className="space-y-8">
<div> <div>
<h3 className="text-lg font-medium">Notifications</h3> <h3 className="text-lg font-medium">Notifications</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">

View file

@ -3,7 +3,7 @@ import { ProfileForm } from "@app/components/profile-form"
export default function SettingsProfilePage() { export default function SettingsProfilePage() {
return ( return (
<div className="space-y-6"> <div className="space-y-8">
<div> <div>
<h3 className="text-lg font-medium">Profile</h3> <h3 className="text-lg font-medium">Profile</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">

View file

@ -206,7 +206,7 @@ export default function StepperForm() {
</div> </div>
)} )}
{currentStep === "site" && ( {currentStep === "site" && (
<div className="space-y-6"> <div className="space-y-8">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="siteName">Site Name</Label> <Label htmlFor="siteName">Site Name</Label>
<Input <Input
@ -222,7 +222,7 @@ export default function StepperForm() {
</div> </div>
)} )}
{currentStep === "resources" && ( {currentStep === "resources" && (
<div className="space-y-6"> <div className="space-y-8">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="resourceName"> <Label htmlFor="resourceName">
Resource Name Resource Name

View file

@ -11,7 +11,7 @@ export default function SettingsSectionTitle({
}: SettingsSectionTitleProps) { }: SettingsSectionTitleProps) {
return ( return (
<div <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 <h2
className={`text-${ className={`text-${

View file

@ -21,8 +21,8 @@ export function SidebarSettings({
limitWidth, limitWidth,
}: SideBarSettingsProps) { }: SideBarSettingsProps) {
return ( return (
<div className="space-y-6 0 pb-16k"> <div className="space-y-8 0 pb-16k">
<div className="flex flex-col space-y-6 lg:flex-row lg:space-x-32 lg:space-y-0"> <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"> <aside className="-mx-4 lg:w-1/5">
<SidebarNav items={sidebarNavItems} disabled={disabled} /> <SidebarNav items={sidebarNavItems} disabled={disabled} />
</aside> </aside>