mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e8048dd20 | ||
|
|
97255dd882 |
No files matched your search
@@ -4,6 +4,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- "feature/**"
|
- "feature/**"
|
||||||
- main
|
- main
|
||||||
|
- debug
|
||||||
tags:
|
tags:
|
||||||
- "**"
|
- "**"
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -55,7 +56,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm
|
platforms: linux/amd64,linux/arm64,linux/arm
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@@ -57,8 +57,6 @@ import { getOperatingSystem } from "@/hooks/useOperatingSystem";
|
|||||||
import { OperatingSystem } from "@/interfaces/OperatingSystem";
|
import { OperatingSystem } from "@/interfaces/OperatingSystem";
|
||||||
import type { Peer } from "@/interfaces/Peer";
|
import type { Peer } from "@/interfaces/Peer";
|
||||||
import PageContainer from "@/layouts/PageContainer";
|
import PageContainer from "@/layouts/PageContainer";
|
||||||
import { AddExitNodeButton } from "@/modules/exit-node/AddExitNodeButton";
|
|
||||||
import { useHasExitNodes } from "@/modules/exit-node/useHasExitNodes";
|
|
||||||
import useGroupHelper from "@/modules/groups/useGroupHelper";
|
import useGroupHelper from "@/modules/groups/useGroupHelper";
|
||||||
import AddRouteDropdownButton from "@/modules/peer/AddRouteDropdownButton";
|
import AddRouteDropdownButton from "@/modules/peer/AddRouteDropdownButton";
|
||||||
import PeerRoutesTable from "@/modules/peer/PeerRoutesTable";
|
import PeerRoutesTable from "@/modules/peer/PeerRoutesTable";
|
||||||
@@ -129,7 +127,6 @@ function PeerOverview() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { isUser } = useLoggedInUser();
|
const { isUser } = useLoggedInUser();
|
||||||
const hasExitNodes = useHasExitNodes(peer);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
@@ -345,8 +342,7 @@ function PeerOverview() {
|
|||||||
</Paragraph>
|
</Paragraph>
|
||||||
</div>
|
</div>
|
||||||
<div className={"inline-flex gap-4 justify-end"}>
|
<div className={"inline-flex gap-4 justify-end"}>
|
||||||
<div className={"gap-4 flex"}>
|
<div>
|
||||||
<AddExitNodeButton peer={peer} firstTime={!hasExitNodes} />
|
|
||||||
<AddRouteDropdownButton />
|
<AddRouteDropdownButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import SkeletonTable from "@components/skeletons/SkeletonTable";
|
|||||||
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
|
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
|
||||||
import useFetchApi from "@utils/api";
|
import useFetchApi from "@utils/api";
|
||||||
import { ExternalLinkIcon } from "lucide-react";
|
import { ExternalLinkIcon } from "lucide-react";
|
||||||
import React, { lazy, Suspense, useMemo } from "react";
|
import React, { lazy, Suspense } from "react";
|
||||||
import SetupKeysIcon from "@/assets/icons/SetupKeysIcon";
|
import SetupKeysIcon from "@/assets/icons/SetupKeysIcon";
|
||||||
import { useGroups } from "@/contexts/GroupsProvider";
|
import { useGroups } from "@/contexts/GroupsProvider";
|
||||||
import { Group } from "@/interfaces/Group";
|
import { Group } from "@/interfaces/Group";
|
||||||
@@ -22,21 +22,16 @@ export default function SetupKeys() {
|
|||||||
const { data: setupKeys, isLoading } = useFetchApi<SetupKey[]>("/setup-keys");
|
const { data: setupKeys, isLoading } = useFetchApi<SetupKey[]>("/setup-keys");
|
||||||
const { groups } = useGroups();
|
const { groups } = useGroups();
|
||||||
|
|
||||||
const setupKeysWithGroups = useMemo(() => {
|
const setupKeysWithGroups = setupKeys?.map((setupKey) => {
|
||||||
if (!setupKeys) return [];
|
if (!setupKey.auto_groups) return setupKey;
|
||||||
return setupKeys?.map((setupKey) => {
|
if (!groups) return setupKey;
|
||||||
if (!setupKey.auto_groups) return setupKey;
|
return {
|
||||||
if (!groups) return setupKey;
|
...setupKey,
|
||||||
return {
|
groups: setupKey.auto_groups.map((group) => {
|
||||||
...setupKey,
|
return groups.find((g) => g.id === group) || undefined;
|
||||||
groups: setupKey.auto_groups
|
}) as Group[] | undefined,
|
||||||
?.map((group) => {
|
};
|
||||||
return groups.find((g) => g.id === group) || undefined;
|
});
|
||||||
})
|
|
||||||
.filter((group) => group !== undefined) as Group[],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [setupKeys, groups]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
|
|||||||
+2
-34
@@ -1,41 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import FullScreenLoading from "@components/ui/FullScreenLoading";
|
import FullScreenLoading from "@components/ui/FullScreenLoading";
|
||||||
import { useLocalStorage } from "@hooks/useLocalStorage";
|
|
||||||
import { useRedirect } from "@hooks/useRedirect";
|
import { useRedirect } from "@hooks/useRedirect";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
url: string;
|
|
||||||
queryParams?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [mounted, setMounted] = useState(false);
|
useRedirect("/peers");
|
||||||
const [tempQueryParams, setTempQueryParams] = useLocalStorage(
|
|
||||||
"netbird-query-params",
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
const [queryParams, setQueryParams] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setQueryParams(tempQueryParams);
|
|
||||||
setTempQueryParams("");
|
|
||||||
setMounted(true);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return mounted ? (
|
|
||||||
<Redirect
|
|
||||||
url={window?.location?.pathname || "/"}
|
|
||||||
queryParams={queryParams}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<FullScreenLoading />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Redirect = ({ url, queryParams }: Props) => {
|
|
||||||
useRedirect(url == "/" ? "/peers" : url + (queryParams && `?${queryParams}`));
|
|
||||||
return <FullScreenLoading />;
|
return <FullScreenLoading />;
|
||||||
};
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import * as React from "react";
|
||||||
|
import deIcon from "@/assets/countries/de.svg";
|
||||||
|
|
||||||
|
export const CountryDERounded = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
"w-5 h-5 overflow-hidden rounded-full relative shadow-2xl border border-nb-gray-600 flex items-center justify-center"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={deIcon}
|
||||||
|
alt={"de"}
|
||||||
|
fill={true}
|
||||||
|
className={"object-cover object-center"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import * as React from "react";
|
||||||
|
import euIcon from "@/assets/countries/eu.svg";
|
||||||
|
|
||||||
|
export const CountryEURounded = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
"w-5 h-5 overflow-hidden rounded-full relative shadow-2xl border border-nb-gray-600 flex items-center justify-center"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={euIcon}
|
||||||
|
alt={"eu"}
|
||||||
|
fill={true}
|
||||||
|
className={"object-cover object-center shrink-0"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import * as React from "react";
|
||||||
|
import jpIcon from "@/assets/countries/jp.svg";
|
||||||
|
|
||||||
|
export const CountryJPRounded = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
"w-5 h-5 overflow-hidden rounded-full relative shadow-2xl border border-nb-gray-600 flex items-center justify-center"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={jpIcon}
|
||||||
|
alt={"eu"}
|
||||||
|
fill={true}
|
||||||
|
className={"object-cover object-center"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import * as React from "react";
|
||||||
|
import usIcon from "@/assets/countries/us.svg";
|
||||||
|
|
||||||
|
export const CountryUSRounded = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
"w-5 h-5 overflow-hidden rounded-full relative shadow-2xl border border-nb-gray-600 flex items-center justify-center"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={usIcon}
|
||||||
|
alt={"us"}
|
||||||
|
fill={true}
|
||||||
|
className={"object-cover object-center"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="600" viewBox="0 0 5 3">
|
||||||
|
<desc>Flag of Germany</desc>
|
||||||
|
<rect id="black_stripe" width="5" height="3" y="0" x="0" fill="#000"/>
|
||||||
|
<rect id="red_stripe" width="5" height="2" y="1" x="0" fill="#D00"/>
|
||||||
|
<rect id="gold_stripe" width="5" height="1" y="2" x="0" fill="#FFCE00"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 493 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 810 540"><defs><g id="d"><g id="b"><path id="a" d="M0 0v1h.5z" transform="rotate(18 3.157 -.5)"/><use xlink:href="#a" transform="scale(-1 1)"/></g><g id="c"><use xlink:href="#b" transform="rotate(72)"/><use xlink:href="#b" transform="rotate(144)"/></g><use xlink:href="#c" transform="scale(-1 1)"/></g></defs><path fill="#039" d="M0 0h810v540H0z"/><g fill="#fc0" transform="matrix(30 0 0 30 405 270)"><use xlink:href="#d" y="-6"/><use xlink:href="#d" y="6"/><g id="e"><use xlink:href="#d" x="-6"/><use xlink:href="#d" transform="rotate(-144 -2.344 -2.11)"/><use xlink:href="#d" transform="rotate(144 -2.11 -2.344)"/><use xlink:href="#d" transform="rotate(72 -4.663 -2.076)"/><use xlink:href="#d" transform="rotate(72 -5.076 .534)"/></g><use xlink:href="#e" transform="scale(-1 1)"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 888 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 600">
|
||||||
|
<rect fill="#fff" height="600" width="900"/>
|
||||||
|
<circle fill="#bc002d" cx="450" cy="300" r="180"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 166 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 7410 3900"><path fill="#b22234" d="M0 0h7410v3900H0z"/><path d="M0 450h7410m0 600H0m0 600h7410m0 600H0m0 600h7410m0 600H0" stroke="#fff" stroke-width="300"/><path fill="#3c3b6e" d="M0 0h2964v2100H0z"/><g fill="#fff"><g id="d"><g id="c"><g id="e"><g id="b"><path id="a" d="M247 90l70.534 217.082-184.66-134.164h228.253L176.466 307.082z"/><use xlink:href="#a" y="420"/><use xlink:href="#a" y="840"/><use xlink:href="#a" y="1260"/></g><use xlink:href="#a" y="1680"/></g><use xlink:href="#b" x="247" y="210"/></g><use xlink:href="#c" x="494"/></g><use xlink:href="#d" x="988"/><use xlink:href="#c" x="1976"/><use xlink:href="#e" x="2470"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 741 B |
Binary file not shown.
@@ -2,7 +2,7 @@ import { useOidc, useOidcUser } from "@axa-fr/react-oidc";
|
|||||||
import Button from "@components/Button";
|
import Button from "@components/Button";
|
||||||
import Paragraph from "@components/Paragraph";
|
import Paragraph from "@components/Paragraph";
|
||||||
import loadConfig from "@utils/config";
|
import loadConfig from "@utils/config";
|
||||||
import { ArrowRightIcon } from "lucide-react";
|
import { ArrowRightIcon, LogOut } from "lucide-react";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
@@ -55,7 +55,7 @@ export const OIDCError = () => {
|
|||||||
variant={"primary"}
|
variant={"primary"}
|
||||||
size={"sm"}
|
size={"sm"}
|
||||||
className={"mt-5"}
|
className={"mt-5"}
|
||||||
onClick={() => logout("/", { client_id: config.clientId })}
|
onClick={() => login("/", { client_id: config.clientId })}
|
||||||
>
|
>
|
||||||
Continue
|
Continue
|
||||||
<ArrowRightIcon size={16} />
|
<ArrowRightIcon size={16} />
|
||||||
@@ -83,6 +83,7 @@ export const OIDCError = () => {
|
|||||||
onClick={() => logout("/", { client_id: config.clientId })}
|
onClick={() => logout("/", { client_id: config.clientId })}
|
||||||
>
|
>
|
||||||
Logout
|
Logout
|
||||||
|
<LogOut size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
OidcConfiguration,
|
OidcConfiguration,
|
||||||
} from "@axa-fr/react-oidc/dist/vanilla/oidc";
|
} from "@axa-fr/react-oidc/dist/vanilla/oidc";
|
||||||
import FullScreenLoading from "@components/ui/FullScreenLoading";
|
import FullScreenLoading from "@components/ui/FullScreenLoading";
|
||||||
import { useLocalStorage } from "@hooks/useLocalStorage";
|
|
||||||
import { useRedirect } from "@hooks/useRedirect";
|
import { useRedirect } from "@hooks/useRedirect";
|
||||||
import loadConfig, { buildExtras } from "@utils/config";
|
import loadConfig, { buildExtras } from "@utils/config";
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
@@ -35,7 +34,7 @@ const auth0AuthorityConfig: AuthorityConfiguration = {
|
|||||||
|
|
||||||
const onEvent = (configurationName: any, eventName: any, data: any) => {
|
const onEvent = (configurationName: any, eventName: any, data: any) => {
|
||||||
if (process.env.NODE_ENV !== "production") {
|
if (process.env.NODE_ENV !== "production") {
|
||||||
//console.info(`oidc:${configurationName}:${eventName}`, data);
|
console.info(`oidc:${configurationName}:${eventName}`, data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -44,19 +43,6 @@ export default function OIDCProvider({ children }: Props) {
|
|||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const path = usePathname();
|
const path = usePathname();
|
||||||
const params = useSearchParams()?.toString();
|
|
||||||
const [, setQueryParams] = useLocalStorage("netbird-query-params", params);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
params?.includes("tab") ||
|
|
||||||
params?.includes("search") ||
|
|
||||||
params?.includes("id")
|
|
||||||
) {
|
|
||||||
setQueryParams(params);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const withCustomHistory = () => {
|
const withCustomHistory = () => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { OidcSecure, useOidc } from "@axa-fr/react-oidc";
|
import { OidcSecure, useOidc } from "@axa-fr/react-oidc";
|
||||||
|
import loadConfig from "@utils/config";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
@@ -6,13 +7,18 @@ import { useEffect } from "react";
|
|||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
|
||||||
export const SecureProvider = ({ children }: Props) => {
|
export const SecureProvider = ({ children }: Props) => {
|
||||||
const { isAuthenticated, login } = useOidc();
|
const { isAuthenticated, login } = useOidc();
|
||||||
const currentPath = usePathname();
|
const currentPath = usePathname();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log("isAuthenticated", isAuthenticated);
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
login(currentPath);
|
console.info("Not authenticated, logging in...");
|
||||||
|
login(currentPath, { client_id: config.clientId });
|
||||||
}
|
}
|
||||||
}, [currentPath, isAuthenticated, login]);
|
}, [currentPath, isAuthenticated, login]);
|
||||||
|
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ export default function HelpText({
|
|||||||
className,
|
className,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<span
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-[.8rem] dark:text-nb-gray-300 block font-light tracking-wide",
|
"text-[.8rem] dark:text-nb-gray-300",
|
||||||
margin && "mb-2",
|
margin && "mb-2",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { IconCircleX } from "@tabler/icons-react";
|
|
||||||
import type { ErrorResponse } from "@utils/api";
|
import type { ErrorResponse } from "@utils/api";
|
||||||
import { cn } from "@utils/helpers";
|
import { cn } from "@utils/helpers";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
@@ -89,7 +88,7 @@ export default function Notification<T>({
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<Loader2 size={14} className={"animate-spin"} />
|
<Loader2 size={14} className={"animate-spin"} />
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<IconCircleX size={24} />
|
<XIcon size={14} />
|
||||||
) : (
|
) : (
|
||||||
icon || <CheckIcon size={14} />
|
icon || <CheckIcon size={14} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export function PeerSelector({
|
|||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
"min-h-[46px] w-full relative items-center group",
|
"min-h-[42px] w-full relative items-center group",
|
||||||
"border border-neutral-200 dark:border-nb-gray-700 justify-between py-2 px-3",
|
"border border-neutral-200 dark:border-nb-gray-700 justify-between py-2 px-3",
|
||||||
"rounded-md bg-white text-sm dark:bg-nb-gray-900/40 flex dark:text-neutral-400/70 text-neutral-500 cursor-pointer enabled:hover:dark:bg-nb-gray-900/50",
|
"rounded-md bg-white text-sm dark:bg-nb-gray-900/40 flex dark:text-neutral-400/70 text-neutral-500 cursor-pointer enabled:hover:dark:bg-nb-gray-900/50",
|
||||||
"disabled:opacity-40 disabled:cursor-default",
|
"disabled:opacity-40 disabled:cursor-default",
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const iconVariant = cva(
|
|||||||
green: "bg-green-950 border-green-500 text-green-500",
|
green: "bg-green-950 border-green-500 text-green-500",
|
||||||
purple: "bg-purple-950 border-purple-500 text-purple-500",
|
purple: "bg-purple-950 border-purple-500 text-purple-500",
|
||||||
indigo: "bg-indigo-950 border-indigo-500 text-indigo-500",
|
indigo: "bg-indigo-950 border-indigo-500 text-indigo-500",
|
||||||
yellow: "bg-yellow-950 border-yellow-400 text-yellow-400",
|
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
small: "w-8 h-8",
|
small: "w-8 h-8",
|
||||||
|
|||||||
Loaded 20 of 53 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user