disable eslint - new colors, and slimmer buttons/inputs??

This commit is contained in:
Milo Schwartz 2024-11-22 22:09:40 -05:00
parent bf04deb038
commit 5388c5d5b4
No known key found for this signature in database
20 changed files with 788 additions and 553 deletions

View file

@ -1,6 +0,0 @@
{
"extends": [
"next/core-web-vitals",
"next/typescript"
]
}

View file

@ -88,8 +88,6 @@
"drizzle-kit": "0.24.2",
"esbuild": "0.20.1",
"esbuild-node-externals": "1.13.0",
"eslint": "^8",
"eslint-config-next": "15.0.1",
"postcss": "^8",
"react-email": "3.0.1",
"tailwindcss": "^3.4.1",

View file

@ -90,7 +90,7 @@ export async function verifyResourceSession(
return allowed(res);
}
const redirectUrl = `${config.app.base_url}/auth/resource/${resource.resourceId}/login?redirect=${originalRequestURL}`;
const redirectUrl = `${config.app.base_url}/${resource.orgId}/auth/resource/${resource.resourceId}?redirect=${originalRequestURL}`;
if (sso && sessions.session) {
const { session, user } = await validateSessionToken(

View file

@ -15,7 +15,7 @@ const setResourceAuthMethodsParamsSchema = z.object({
const setResourceAuthMethodsBodySchema = z
.object({
password: z.string().nullish(),
password: z.string().min(4).max(100).nullable(),
})
.strict();

View file

@ -0,0 +1,205 @@
"use client";
import { useState } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
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,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { LockIcon, KeyIcon, UserIcon, Binary, Key, User } from "lucide-react";
const pinSchema = z.object({
pin: z
.string()
.length(6, { message: "PIN must be exactly 6 digits" })
.regex(/^\d+$/, { message: "PIN must only contain numbers" }),
});
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" }),
});
export default function ResourceAuthPortal() {
const [activeTab, setActiveTab] = useState("pin");
const pinForm = useForm<z.infer<typeof pinSchema>>({
resolver: zodResolver(pinSchema),
defaultValues: {
pin: "",
},
});
const passwordForm = useForm<z.infer<typeof passwordSchema>>({
resolver: zodResolver(passwordSchema),
defaultValues: {
email: "",
password: "",
},
});
const onPinSubmit = (values: z.infer<typeof pinSchema>) => {
console.log("PIN authentication", values);
// Implement PIN authentication logic here
};
const onPasswordSubmit = (values: z.infer<typeof passwordSchema>) => {
console.log("Password authentication", values);
// Implement password authentication logic here
};
const handleSSOAuth = () => {
console.log("SSO authentication");
// Implement SSO authentication logic here
};
return (
<div className="w-full max-w-md mx-auto">
<Card>
<CardHeader>
<CardTitle>Welcome Back</CardTitle>
<CardDescription>
Choose your preferred login method
</CardDescription>
</CardHeader>
<CardContent>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="pin">
<Binary className="w-4 h-4 mr-1" /> PIN
</TabsTrigger>
<TabsTrigger value="password">
<Key className="w-4 h-4 mr-1" /> Password
</TabsTrigger>
<TabsTrigger value="sso">
<User className="w-4 h-4 mr-1" /> SSO
</TabsTrigger>
</TabsList>
<TabsContent value="pin">
<Form {...pinForm}>
<form
onSubmit={pinForm.handleSubmit(onPinSubmit)}
className="space-y-4"
>
<FormField
control={pinForm.control}
name="pin"
render={({ field }) => (
<FormItem>
<FormLabel>Enter PIN</FormLabel>
<FormControl>
<Input
placeholder="Enter your 6-digit PIN"
type="password"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
<KeyIcon className="w-4 h-4 mr-2" />
Login with PIN
</Button>
</form>
</Form>
</TabsContent>
<TabsContent value="password">
<Form {...passwordForm}>
<form
onSubmit={passwordForm.handleSubmit(
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"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
placeholder="Enter your password"
type="password"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
<LockIcon className="w-4 h-4 mr-2" />
Login with Password
</Button>
</form>
</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"
>
<UserIcon className="w-4 h-4 mr-2" />
Login with SSO
</Button>
</div>
</TabsContent>
</Tabs>
</CardContent>
<CardFooter className="flex justify-center">
<p className="text-sm text-muted-foreground">
Don't have an account?{" "}
<a href="#" className="underline">
Sign up
</a>
</p>
</CardFooter>
</Card>
</div>
);
}

View file

@ -0,0 +1,17 @@
import ResourceAuthPortal from "./components/ResourceAuthPortal";
export default async function ResourceAuthPage(props: {
params: Promise<{ resourceId: number; orgId: string }>;
}) {
const params = await props.params;
console.log(params);
return (
<>
<div className="p-3 md:mt-32">
<ResourceAuthPortal />
</div>
</>
);
}

View file

@ -32,22 +32,22 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { ListUsersResponse } from "@server/routers/user";
import { Switch } from "@app/components/ui/switch";
import { Label } from "@app/components/ui/label";
import { Input } from "@app/components/ui/input";
import { ShieldCheck } from "lucide-react";
import SetResourcePasswordForm from "./components/SetResourcePasswordForm";
import { Separator } from "@app/components/ui/separator";
const UsersRolesFormSchema = z.object({
roles: z.array(
z.object({
id: z.string(),
text: z.string(),
})
}),
),
users: z.array(
z.object({
id: z.string(),
text: z.string(),
})
}),
),
});
@ -60,10 +60,10 @@ export default function ResourceAuthenticationPage() {
const [pageLoading, setPageLoading] = useState(true);
const [allRoles, setAllRoles] = useState<{ id: string; text: string }[]>(
[]
[],
);
const [allUsers, setAllUsers] = useState<{ id: string; text: string }[]>(
[]
[],
);
const [activeRolesTagIndex, setActiveRolesTagIndex] = useState<
number | null
@ -73,7 +73,7 @@ export default function ResourceAuthenticationPage() {
>(null);
const [ssoEnabled, setSsoEnabled] = useState(resource.sso);
const [blockAccess, setBlockAccess] = useState(resource.blockAccess);
// const [blockAccess, setBlockAccess] = useState(resource.blockAccess);
const [loadingSaveUsersRoles, setLoadingSaveUsersRoles] = useState(false);
const [loadingRemoveResourcePassword, setLoadingRemoveResourcePassword] =
@ -96,16 +96,16 @@ export default function ResourceAuthenticationPage() {
resourceUsersResponse,
] = await Promise.all([
api.get<AxiosResponse<ListRolesResponse>>(
`/org/${org?.org.orgId}/roles`
`/org/${org?.org.orgId}/roles`,
),
api.get<AxiosResponse<ListResourceRolesResponse>>(
`/resource/${resource.resourceId}/roles`
`/resource/${resource.resourceId}/roles`,
),
api.get<AxiosResponse<ListUsersResponse>>(
`/org/${org?.org.orgId}/users`
`/org/${org?.org.orgId}/users`,
),
api.get<AxiosResponse<ListResourceUsersResponse>>(
`/resource/${resource.resourceId}/users`
`/resource/${resource.resourceId}/users`,
),
]);
@ -115,7 +115,7 @@ export default function ResourceAuthenticationPage() {
id: role.roleId.toString(),
text: role.name,
}))
.filter((role) => role.text !== "Admin")
.filter((role) => role.text !== "Admin"),
);
usersRolesForm.setValue(
@ -125,14 +125,14 @@ export default function ResourceAuthenticationPage() {
id: i.roleId.toString(),
text: i.name,
}))
.filter((role) => role.text !== "Admin")
.filter((role) => role.text !== "Admin"),
);
setAllUsers(
usersResponse.data.data.users.map((user) => ({
id: user.id.toString(),
text: user.email,
}))
})),
);
usersRolesForm.setValue(
@ -140,7 +140,7 @@ export default function ResourceAuthenticationPage() {
resourceUsersResponse.data.data.users.map((i) => ({
id: i.userId.toString(),
text: i.email,
}))
})),
);
setPageLoading(false);
@ -151,7 +151,7 @@ export default function ResourceAuthenticationPage() {
title: "Failed to fetch data",
description: formatAxiosError(
e,
"An error occurred while fetching the data"
"An error occurred while fetching the data",
),
});
}
@ -161,7 +161,7 @@ export default function ResourceAuthenticationPage() {
}, []);
async function onSubmitUsersRoles(
data: z.infer<typeof UsersRolesFormSchema>
data: z.infer<typeof UsersRolesFormSchema>,
) {
try {
setLoadingSaveUsersRoles(true);
@ -175,7 +175,6 @@ export default function ResourceAuthenticationPage() {
}),
api.post(`/resource/${resource.resourceId}`, {
sso: ssoEnabled,
blockAccess,
}),
];
@ -200,7 +199,7 @@ export default function ResourceAuthenticationPage() {
title: "Failed to set roles",
description: formatAxiosError(
e,
"An error occurred while setting the roles"
"An error occurred while setting the roles",
),
});
} finally {
@ -231,7 +230,7 @@ export default function ResourceAuthenticationPage() {
title: "Error removing resource password",
description: formatAxiosError(
e,
"An error occurred while removing the resource password"
"An error occurred while removing the resource password",
),
});
})
@ -258,22 +257,8 @@ export default function ResourceAuthenticationPage() {
/>
)}
<div className="space-y-6 lg:max-w-2xl">
{/* <div>
<div className="flex items-center space-x-2 mb-2">
<Switch
id="block-toggle"
defaultChecked={resource.blockAccess}
onCheckedChange={(val) => setBlockAccess(val)}
/>
<Label htmlFor="block-toggle">Block Access</Label>
</div>
<span className="text-muted-foreground text-sm">
When enabled, this will prevent anyone from accessing
the resource including SSO users.
</span>
</div> */}
<div className="space-y-12 lg:max-w-2xl">
<section className="space-y-6">
<SettingsSectionTitle
title="Users & Roles"
description="Configure who can visit this resource (only applicable if SSO is used)"
@ -300,7 +285,7 @@ export default function ResourceAuthenticationPage() {
<Form {...usersRolesForm}>
<form
onSubmit={usersRolesForm.handleSubmit(
onSubmitUsersRoles
onSubmitUsersRoles,
)}
className="space-y-6"
>
@ -313,18 +298,24 @@ export default function ResourceAuthenticationPage() {
<FormControl>
<TagInput
{...field}
activeTagIndex={activeRolesTagIndex}
activeTagIndex={
activeRolesTagIndex
}
setActiveTagIndex={
setActiveRolesTagIndex
}
placeholder="Enter a role"
tags={
usersRolesForm.getValues().roles
usersRolesForm.getValues()
.roles
}
setTags={(newRoles) => {
usersRolesForm.setValue(
"roles",
newRoles as [Tag, ...Tag[]]
newRoles as [
Tag,
...Tag[],
],
);
}}
enableAutocomplete={true}
@ -336,7 +327,7 @@ export default function ResourceAuthenticationPage() {
sortTags={true}
styleClasses={{
tag: {
body: "bg-muted hover:bg-accent text-foreground p-2",
body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full",
},
input: "border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
inlineTagsContainer:
@ -345,9 +336,9 @@ export default function ResourceAuthenticationPage() {
/>
</FormControl>
<FormDescription>
Users with these roles will be able to
access this resource. Admins can always
access this resource.
Users with these roles will be able
to access this resource. Admins can
always access this resource.
</FormDescription>
<FormMessage />
</FormItem>
@ -362,18 +353,24 @@ export default function ResourceAuthenticationPage() {
<FormControl>
<TagInput
{...field}
activeTagIndex={activeUsersTagIndex}
activeTagIndex={
activeUsersTagIndex
}
setActiveTagIndex={
setActiveUsersTagIndex
}
placeholder="Enter a user"
tags={
usersRolesForm.getValues().users
usersRolesForm.getValues()
.users
}
setTags={(newUsers) => {
usersRolesForm.setValue(
"users",
newUsers as [Tag, ...Tag[]]
newUsers as [
Tag,
...Tag[],
],
);
}}
enableAutocomplete={true}
@ -385,7 +382,7 @@ export default function ResourceAuthenticationPage() {
sortTags={true}
styleClasses={{
tag: {
body: "bg-muted hover:bg-accent text-foreground p-2",
body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full",
},
input: "border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
inlineTagsContainer:
@ -394,10 +391,11 @@ export default function ResourceAuthenticationPage() {
/>
</FormControl>
<FormDescription>
Users added here will be able to access
this resource. A user will always have
access to a resource if they have a role
that has access to it.
Users added here will be able to
access this resource. A user will
always have access to a resource if
they have a role that has access to
it.
</FormDescription>
<FormMessage />
</FormItem>
@ -412,14 +410,17 @@ export default function ResourceAuthenticationPage() {
</Button>
</form>
</Form>
</section>
<Separator />
<section className="space-y-6">
<SettingsSectionTitle
title="Authentication Methods"
description="You can also allow users to access the resource via the below methods"
size="1xl"
/>
<div>
{authInfo?.password ? (
<div className="flex items-center space-x-4">
<div className="flex items-center text-green-500 space-x-2">
@ -447,7 +448,7 @@ export default function ResourceAuthenticationPage() {
</Button>
</div>
)}
</div>
</section>
</div>
</>
);

View file

@ -39,17 +39,38 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
};
return (
<Card>
<Card className="shadow-none">
<Alert>
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">
Resource Information
</AlertTitle>
<AlertDescription className="mt-3">
<p className="mb-2">
The current full URL for this resource is:
</p>
<div className="flex items-center space-x-2 bg-muted p-2 rounded-md">
<div className="space-y-3">
<div>
{authInfo.password ||
authInfo.pincode ||
authInfo.sso ? (
<div className="flex items-center space-x-2 text-green-500">
<ShieldCheck />
<span>
This resource is protected with at least
one auth method.
</span>
</div>
) : (
<div className="flex items-center space-x-2 text-yellow-500">
<ShieldOff />
<span>
This resource is not protected with any
auth method. Anyone can access this
resource.
</span>
</div>
)}
</div>
<div className="flex items-center space-x-2 bg-muted p-1 pl-3 rounded-md">
<LinkIcon className="h-4 w-4" />
<a
href={fullUrl}
@ -87,27 +108,6 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
</Link>{" "}
to this resource
</p> */}
<div className="mt-3">
{authInfo.password ||
authInfo.pincode ||
authInfo.sso ? (
<div className="flex items-center space-x-2 text-green-500">
<ShieldCheck />
<span>
This resource is protected with at least one
auth method
</span>
</div>
) : (
<div className="flex items-center space-x-2 text-yellow-500">
<ShieldOff />
<span>
This resource is not protected with any auth
method. Anyone can access this resource.
</span>
</div>
)}
</div>
</AlertDescription>
</Alert>

View file

@ -51,6 +51,7 @@ import { useResourceContext } from "@app/hooks/useResourceContext";
import { ArrayElement } from "@server/types/ArrayElement";
import { Dot } from "lucide-react";
import { formatAxiosError } from "@app/lib/utils";
import { Separator } from "@radix-ui/react-separator";
const addTargetSchema = z.object({
ip: z.string().ip(),
@ -104,7 +105,7 @@ export default function ReverseProxyTargets(props: {
const fetchSites = async () => {
try {
const res = await api.get<AxiosResponse<ListTargetsResponse>>(
`/resource/${params.resourceId}/targets`
`/resource/${params.resourceId}/targets`,
);
if (res.status === 200) {
@ -117,7 +118,7 @@ export default function ReverseProxyTargets(props: {
title: "Failed to fetch targets",
description: formatAxiosError(
err,
"An error occurred while fetching targets"
"An error occurred while fetching targets",
),
});
} finally {
@ -155,8 +156,8 @@ export default function ReverseProxyTargets(props: {
targets.map((target) =>
target.targetId === targetId
? { ...target, ...data, updated: true }
: target
)
: target,
),
);
}
@ -186,7 +187,7 @@ export default function ReverseProxyTargets(props: {
} else if (target.updated) {
const res = await api.post(
`/target/${target.targetId}`,
data
data,
);
}
@ -204,7 +205,7 @@ export default function ReverseProxyTargets(props: {
for (const targetId of targetsToRemove) {
await api.delete(`/target/${targetId}`);
setTargets(
targets.filter((target) => target.targetId !== targetId)
targets.filter((target) => target.targetId !== targetId),
);
}
@ -221,7 +222,7 @@ export default function ReverseProxyTargets(props: {
title: "Operation failed",
description: formatAxiosError(
err,
"An error occurred during the save operation"
"An error occurred during the save operation",
),
});
}
@ -346,8 +347,9 @@ export default function ReverseProxyTargets(props: {
}
return (
<div>
<div className="space-y-6">
<>
<div className="space-y-12">
<section className="space-y-6 lg:max-w-2xl">
<SettingsSectionTitle
title="SSL"
description="Setup SSL to secure your connections with LetsEncrypt certificates"
@ -362,16 +364,23 @@ export default function ReverseProxyTargets(props: {
/>
<Label htmlFor="ssl-toggle">Enable SSL (https)</Label>
</div>
</section>
<hr className="lg:max-w-2xl" />
<section className="space-y-6">
<SettingsSectionTitle
title="Targets"
description="Setup targets to route traffic to your services"
size="1xl"
/>
<div className="space-y-6">
<Form {...addTargetForm}>
<form
onSubmit={addTargetForm.handleSubmit(addTarget as any)}
onSubmit={addTargetForm.handleSubmit(
addTarget as any,
)}
>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<FormField
@ -379,12 +388,15 @@ export default function ReverseProxyTargets(props: {
name="ip"
render={({ field }) => (
<FormItem>
<FormLabel>IP Address</FormLabel>
<FormLabel>
IP Address
</FormLabel>
<FormControl>
<Input id="ip" {...field} />
</FormControl>
<FormDescription>
Enter the IP address of the target
Enter the IP address of the
target.
</FormDescription>
<FormMessage />
</FormItem>
@ -399,10 +411,12 @@ export default function ReverseProxyTargets(props: {
<FormControl>
<Select
{...field}
onValueChange={(value) => {
onValueChange={(
value,
) => {
addTargetForm.setValue(
"method",
value
value,
);
}}
>
@ -420,8 +434,8 @@ export default function ReverseProxyTargets(props: {
</Select>
</FormControl>
<FormDescription>
Choose the method for how the target
is accessed
Choose the method for how
the target is accessed.
</FormDescription>
<FormMessage />
</FormItem>
@ -442,8 +456,8 @@ export default function ReverseProxyTargets(props: {
/>
</FormControl>
<FormDescription>
Specify the port number for the
target
Specify the port number for
the target.
</FormDescription>
<FormMessage />
</FormItem>
@ -496,19 +510,27 @@ export default function ReverseProxyTargets(props: {
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
{table
.getHeaderGroups()
.map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{headerGroup.headers.map(
(header) => (
<TableHead
key={header.id}
>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef
header
.column
.columnDef
.header,
header.getContext()
header.getContext(),
)}
</TableHead>
))}
),
)}
</TableRow>
))}
</TableHeader>
@ -516,11 +538,17 @@ export default function ReverseProxyTargets(props: {
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{row
.getVisibleCells()
.map((cell) => (
<TableCell
key={cell.id}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
cell.column
.columnDef
.cell,
cell.getContext(),
)}
</TableCell>
))}
@ -532,7 +560,8 @@ export default function ReverseProxyTargets(props: {
colSpan={columns.length}
className="h-24 text-center"
>
No targets. Add a target using the form.
No targets. Add a target using
the form.
</TableCell>
</TableRow>
)}
@ -540,10 +569,16 @@ export default function ReverseProxyTargets(props: {
</Table>
</div>
<Button onClick={saveAll} loading={loading} disabled={loading}>
<Button
onClick={saveAll}
loading={loading}
disabled={loading}
>
Save Changes
</Button>
</div>
</section>
</div>
</>
);
}

View file

@ -118,7 +118,8 @@ export default function GeneralForm() {
return (
<>
<div className="lg:max-w-2xl space-y-6">
<div className="lg:max-w-2xl space-y-12">
<section className="space-y-6">
<SettingsSectionTitle
title="General Settings"
description="Configure the general settings for this resource"
@ -140,19 +141,14 @@ export default function GeneralForm() {
<Input {...field} />
</FormControl>
<FormDescription>
This is the display name of the resource
This is the display name of the
resource.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<SettingsSectionTitle
title="Domain"
description="Define the domain that users will use to access this resource"
size="1xl"
/>
<FormField
control={form.control}
name="subdomain"
@ -172,10 +168,10 @@ export default function GeneralForm() {
}
/>
</FormControl>
{/* <FormDescription>
This is the subdomain that will be used
to access the resource
</FormDescription> */}
<FormDescription>
This is the subdomain that will be
used to access the resource.
</FormDescription>
<FormMessage />
</FormItem>
)}
@ -266,6 +262,7 @@ export default function GeneralForm() {
</Button>
</form>
</Form>
</section>
</div>
</>
);

View file

@ -116,7 +116,7 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
sidebarNavItems={sidebarNavItems}
limitWidth={false}
>
<div className="mb-8">
<div className="mb-8 lg:max-w-2xl">
<ResourceInfoBox />
</div>
{children}

View file

@ -206,6 +206,7 @@ sh get-docker.sh`;
<FormLabel>Name</FormLabel>
<FormControl>
<Input
autoComplete="off"
placeholder="Site name"
{...field}
/>

View file

@ -2,83 +2,65 @@
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 211.58 18.45% 20.2%;
--primary: 14.59 24.83% 29.22%;
--primary-foreground: 0 0% 100%;
--card: 20 15.79% 96.27%;
--card-foreground: 0 0% 0%;
--foreground: 20 14.3% 4.1%;
--card: 0 0% 100%;
--card-foreground: 20 14.3% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 30 28.57% 2.75%;
--secondary: 25 16.07% 43.92%;
--secondary-foreground: 0 0% 100%;
--muted: 20 15.79% 96.27%;
--muted-foreground: 0 0% 34.12%;
--accent: 0 0% 86.67%;
--accent-foreground: 24 23.81% 4.12%;
--popover-foreground: 20 14.3% 4.1%;
--primary: 24.6 95% 53.1%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--muted: 60 4.8% 95.9%;
--muted-foreground: 25 5.3% 44.7%;
--accent: 60 4.8% 95.9%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 12 6.67% 85.29%;
--input: 12 6.67% 85.29%;
--ring: 24.71 31.29% 31.96%;
--chart-1: 23.64 23.74% 27.25%;
--chart-2: 23.57 14.43% 38.04%;
--chart-3: 22.86 8.71% 52.75%;
--chart-4: 23.33 8.82% 60%;
--chart-5: 24 8.98% 67.25%;
--radius: 0.35rem;
--destructive-foreground: 60 9.1% 97.8%;
--border: 20 5.9% 90%;
--input: 20 5.9% 90%;
--ring: 24.6 95% 53.1%;
--radius: 0.75rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--background: 0 0% 11.76%;
--foreground: 204 6.67% 85.29%;
--primary: 14.21 25.68% 29.02%;
--primary-foreground: 228 13.51% 92.75%;
--card: 0 0% 9.41%;
--card-foreground: 204 6.67% 85.29%;
--popover: 0 0% 11.76%;
--popover-foreground: 24 9.09% 89.22%;
--secondary: 12.63 15.97% 23.33%;
--secondary-foreground: 0 0% 100%;
--muted: 0 0% 9.41%;
--muted-foreground: 212.73 5.31% 59.41%;
--accent: 0 2.17% 18.04%;
--accent-foreground: 24 9.09% 89.22%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 240 2.86% 27.45%;
--input: 240 2.86% 27.45%;
--ring: 23.64 23.74% 27.25%;
--chart-1: 23.64 23.74% 27.25%;
--chart-2: 23.57 23.73% 23.14%;
--chart-3: 24.55 24.44% 17.65%;
--chart-4: 23.33 23.68% 14.9%;
--chart-5: 24 23.81% 12.35%;
--radius: 0.35rem;
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
--popover: 20 14.3% 4.1%;
--popover-foreground: 60 9.1% 97.8%;
--primary: 20.5 90.2% 48.2%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 12 6.5% 15.1%;
--secondary-foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 15.1%;
--muted-foreground: 24 5.4% 63.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 60 9.1% 97.8%;
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--ring: 20.5 90.2% 48.2%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;

View file

@ -1,6 +1,6 @@
import type { Metadata } from "next";
import "./globals.css";
import { Fira_Sans, Inter, Noto_Sans_Mono, Roboto_Mono } from "next/font/google";
import { IBM_Plex_Sans, Work_Sans } from "next/font/google";
import { Toaster } from "@/components/ui/toaster";
import { ThemeProvider } from "@app/providers/ThemeProvider";
@ -9,8 +9,11 @@ export const metadata: Metadata = {
description: "",
};
const font = Inter({ subsets: ["latin"] });
// const font = Inter({ subsets: ["latin"] });
// const font = Noto_Sans_Mono({ subsets: ["latin"] });
const font = Work_Sans({ subsets: ["latin"] });
// const font = Space_Grotesk({subsets: ["latin"]})
// const font = IBM_Plex_Sans({subsets: ["latin"], weight: "400"})
export default async function RootLayout({
children,

View file

@ -10,7 +10,9 @@ export default function SettingsSectionTitle({
size,
}: SettingsSectionTitleProps) {
return (
<div className="space-y-0.5 select-none mb-6">
<div
className={`space-y-0.5 select-none ${!size || size === "2xl" ? "mb-12" : ""}`}
>
<h2
className={`text-${
size ? size : "2xl"

View file

@ -81,7 +81,7 @@ export function SidebarNav({
</div>
<nav
className={cn(
"hidden lg:flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1",
"hidden lg:flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-3",
disabled && "opacity-50 pointer-events-none",
className
)}

View file

@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
"relative w-full rounded-lg p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
{
variants: {
variant: {

View file

@ -23,10 +23,10 @@ const buttonVariants = cva(
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {

View file

@ -10,7 +10,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}

View file

@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
"flex h-9 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}