diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index e8f71847..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": [ - "next/core-web-vitals", - "next/typescript" - ] -} \ No newline at end of file diff --git a/package.json b/package.json index 4be29e72..3523f69d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index 8b4aeb1e..80805d9c 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -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( diff --git a/server/routers/resource/setResourcePassword.ts b/server/routers/resource/setResourcePassword.ts index 54fdcdf3..085587f2 100644 --- a/server/routers/resource/setResourcePassword.ts +++ b/server/routers/resource/setResourcePassword.ts @@ -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(); diff --git a/src/app/[orgId]/auth/resource/[resourceId]/components/ResourceAuthPortal.tsx b/src/app/[orgId]/auth/resource/[resourceId]/components/ResourceAuthPortal.tsx new file mode 100644 index 00000000..39311b08 --- /dev/null +++ b/src/app/[orgId]/auth/resource/[resourceId]/components/ResourceAuthPortal.tsx @@ -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>({ + resolver: zodResolver(pinSchema), + defaultValues: { + pin: "", + }, + }); + + const passwordForm = useForm>({ + resolver: zodResolver(passwordSchema), + defaultValues: { + email: "", + password: "", + }, + }); + + const onPinSubmit = (values: z.infer) => { + console.log("PIN authentication", values); + // Implement PIN authentication logic here + }; + + const onPasswordSubmit = (values: z.infer) => { + console.log("Password authentication", values); + // Implement password authentication logic here + }; + + const handleSSOAuth = () => { + console.log("SSO authentication"); + // Implement SSO authentication logic here + }; + + return ( +
+ + + Welcome Back + + Choose your preferred login method + + + + + + + PIN + + + Password + + + SSO + + + +
+ + ( + + Enter PIN + + + + + + )} + /> + + + +
+ +
+ + ( + + Email + + + + + + )} + /> + ( + + Password + + + + + + )} + /> + + + +
+ +
+

+ Click the button below to login with your + organization's SSO provider. +

+ +
+
+
+
+ +

+ Don't have an account?{" "} + + Sign up + +

+
+
+
+ ); +} diff --git a/src/app/[orgId]/auth/resource/[resourceId]/page.tsx b/src/app/[orgId]/auth/resource/[resourceId]/page.tsx new file mode 100644 index 00000000..11e4a02b --- /dev/null +++ b/src/app/[orgId]/auth/resource/[resourceId]/page.tsx @@ -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 ( + <> +
+ +
+ + ); +} diff --git a/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx index c4971345..feafb8ab 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx @@ -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>( - `/org/${org?.org.orgId}/roles` + `/org/${org?.org.orgId}/roles`, ), api.get>( - `/resource/${resource.resourceId}/roles` + `/resource/${resource.resourceId}/roles`, ), api.get>( - `/org/${org?.org.orgId}/users` + `/org/${org?.org.orgId}/users`, ), api.get>( - `/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 + data: z.infer, ) { 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,168 +257,170 @@ export default function ResourceAuthenticationPage() { /> )} -
- {/*
-
- setBlockAccess(val)} - /> - +
+
+ + +
+
+ setSsoEnabled(val)} + /> + +
+ + 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 + resources that have SSO enabled. +
- - When enabled, this will prevent anyone from accessing - the resource including SSO users. - -
*/} - - -
-
- setSsoEnabled(val)} - /> - -
- - 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 - resources that have SSO enabled. - -
- -
- - ( - - Roles - - { - usersRolesForm.setValue( - "roles", - newRoles as [Tag, ...Tag[]] - ); - }} - enableAutocomplete={true} - autocompleteOptions={allRoles} - allowDuplicates={false} - restrictTagsToAutocompleteOptions={ - true - } - sortTags={true} - styleClasses={{ - tag: { - body: "bg-muted hover:bg-accent text-foreground p-2", - }, - input: "border-none bg-transparent text-inherit placeholder:text-inherit shadow-none", - inlineTagsContainer: - "bg-transparent", - }} - /> - - - Users with these roles will be able to - access this resource. Admins can always - access this resource. - - - + + - ( - - Users - - { - usersRolesForm.setValue( - "users", - newUsers as [Tag, ...Tag[]] - ); - }} - enableAutocomplete={true} - autocompleteOptions={allUsers} - allowDuplicates={false} - restrictTagsToAutocompleteOptions={ - true - } - sortTags={true} - styleClasses={{ - tag: { - body: "bg-muted hover:bg-accent text-foreground p-2", - }, - input: "border-none bg-transparent text-inherit placeholder:text-inherit shadow-none", - inlineTagsContainer: - "bg-transparent", - }} - /> - - - 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. - - - - )} - /> - - - + ( + + Roles + + { + usersRolesForm.setValue( + "roles", + newRoles as [ + Tag, + ...Tag[], + ], + ); + }} + enableAutocomplete={true} + autocompleteOptions={allRoles} + allowDuplicates={false} + restrictTagsToAutocompleteOptions={ + true + } + sortTags={true} + styleClasses={{ + tag: { + 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: + "bg-transparent", + }} + /> + + + Users with these roles will be able + to access this resource. Admins can + always access this resource. + + + + )} + /> + ( + + Users + + { + usersRolesForm.setValue( + "users", + newUsers as [ + Tag, + ...Tag[], + ], + ); + }} + enableAutocomplete={true} + autocompleteOptions={allUsers} + allowDuplicates={false} + restrictTagsToAutocompleteOptions={ + true + } + sortTags={true} + styleClasses={{ + tag: { + 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: + "bg-transparent", + }} + /> + + + 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. + + + + )} + /> + + + + - + + +
+ -
{authInfo?.password ? (
@@ -447,7 +448,7 @@ export default function ResourceAuthenticationPage() {
)} -
+
); diff --git a/src/app/[orgId]/settings/resources/[resourceId]/components/ResourceInfoBox.tsx b/src/app/[orgId]/settings/resources/[resourceId]/components/ResourceInfoBox.tsx index 238bfe10..f4accb9f 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/components/ResourceInfoBox.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/components/ResourceInfoBox.tsx @@ -39,45 +39,66 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) { }; return ( - + Resource Information -

- The current full URL for this resource is: -

-
- - - {fullUrl} - - -
+
- {/*

+

+ + + {fullUrl} + + +
+ + {/*

To create a proxy to your private services,{" "} {" "} to this resource

*/} - -
- {authInfo.password || - authInfo.pincode || - authInfo.sso ? ( -
- - - This resource is protected with at least one - auth method - -
- ) : ( -
- - - This resource is not protected with any auth - method. Anyone can access this resource. - -
- )}
diff --git a/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx index 04236872..10da3a47 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx @@ -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>( - `/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,110 +347,123 @@ export default function ReverseProxyTargets(props: { } return ( -
-
- - -
- setSslEnabled(val)} + <> +
+
+ - -
- +
+ setSslEnabled(val)} + /> + +
+ -
- -
- ( - - IP Address - - - - - Enter the IP address of the target - - - +
+ +
+ + +
+ + - ( - - Method - - - - - Choose the method for how the target - is accessed - - - - )} - /> - ( - - Port - - - - - Specify the port number for the - target - - - - )} - /> - {/* +
+ ( + + + IP Address + + + + + + Enter the IP address of the + target. + + + + )} + /> + ( + + Method + + + + + Choose the method for how + the target is accessed. + + + + )} + /> + ( + + Port + + + + + Specify the port number for + the target. + + + + )} + /> + {/* ( @@ -486,64 +500,85 @@ export default function ReverseProxyTargets(props: { )} /> */} -
- - - +
+ + + -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef - .header, - header.getContext() - )} - - ))} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() +
+
+ + {table + .getHeaderGroups() + .map((headerGroup) => ( + + {headerGroup.headers.map( + (header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header + .column + .columnDef + .header, + header.getContext(), + )} + + ), )} - + ))} - - )) - ) : ( - - - No targets. Add a target using the form. - - - )} - -
-
+ + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row + .getVisibleCells() + .map((cell) => ( + + {flexRender( + cell.column + .columnDef + .cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + No targets. Add a target using + the form. + + + )} + + +
- + +
+
-
+ ); } diff --git a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx index 26d09e3a..37090932 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx @@ -118,69 +118,65 @@ export default function GeneralForm() { return ( <> -
- +
+
+ -
- - ( - - Name - - - - - This is the display name of the resource - - - - )} - /> + + + ( + + Name + + + + + This is the display name of the + resource. + + + + )} + /> - - - ( - - Subdomain - - - form.setValue( - "subdomain", - value - ) - } - /> - - {/* - This is the subdomain that will be used - to access the resource - */} - - - )} - /> - {/* ( + + Subdomain + + + form.setValue( + "subdomain", + value + ) + } + /> + + + This is the subdomain that will be + used to access the resource. + + + + )} + /> + {/* ( @@ -257,15 +253,16 @@ export default function GeneralForm() { )} /> */} - - - + + + +
); diff --git a/src/app/[orgId]/settings/resources/[resourceId]/layout.tsx b/src/app/[orgId]/settings/resources/[resourceId]/layout.tsx index 05f9550b..0e2aa012 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/layout.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/layout.tsx @@ -116,7 +116,7 @@ export default async function ResourceLayout(props: ResourceLayoutProps) { sidebarNavItems={sidebarNavItems} limitWidth={false} > -
+
{children} diff --git a/src/app/[orgId]/settings/sites/components/CreateSiteForm.tsx b/src/app/[orgId]/settings/sites/components/CreateSiteForm.tsx index a27201ae..d9af58a1 100644 --- a/src/app/[orgId]/settings/sites/components/CreateSiteForm.tsx +++ b/src/app/[orgId]/settings/sites/components/CreateSiteForm.tsx @@ -206,6 +206,7 @@ sh get-docker.sh`; Name diff --git a/src/app/globals.css b/src/app/globals.css index 05b6167e..fe141f3f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,83 +2,65 @@ @tailwind components; @tailwind utilities; + @layer base { - :root { - --background: 0 0% 100%; - --foreground: 211.58 18.45% 20.2%; + :root { + --background: 0 0% 100%; + --foreground: 20 14.3% 4.1%; + --card: 0 0% 100%; + --card-foreground: 20 14.3% 4.1%; + --popover: 0 0% 100%; + --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: 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%; + } - --primary: 14.59 24.83% 29.22%; - --primary-foreground: 0 0% 100%; - - --card: 20 15.79% 96.27%; - --card-foreground: 0 0% 0%; - - --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%; - - --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; - } - .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; - } + .dark { + --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; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 36bfccba..4a5b3994 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -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, diff --git a/src/components/SettingsSectionTitle.tsx b/src/components/SettingsSectionTitle.tsx index 5280532e..8cd86f31 100644 --- a/src/components/SettingsSectionTitle.tsx +++ b/src/components/SettingsSectionTitle.tsx @@ -10,7 +10,9 @@ export default function SettingsSectionTitle({ size, }: SettingsSectionTitleProps) { return ( -
+