mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-01 17:44:52 +02:00
use bottom sheet instead of vaul drawer
This commit is contained in:
parent
91d314c4cc
commit
e6263567a9
13 changed files with 284 additions and 121 deletions
|
@ -4,7 +4,7 @@ WORKDIR /app
|
||||||
|
|
||||||
COPY package.json ./
|
COPY package.json ./
|
||||||
|
|
||||||
RUN npm install --legacy-peer-deps
|
RUN npm install
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ WORKDIR /app
|
||||||
|
|
||||||
COPY package.json ./
|
COPY package.json ./
|
||||||
|
|
||||||
RUN npm install --omit=dev --legacy-peer-deps
|
RUN npm install --omit=dev
|
||||||
|
|
||||||
COPY --from=builder /app/.next ./.next
|
COPY --from=builder /app/.next ./.next
|
||||||
COPY --from=builder /app/dist ./dist
|
COPY --from=builder /app/dist ./dist
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import express, { Request, Response } from "express";
|
import express from "express";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import cookieParser from "cookie-parser";
|
import cookieParser from "cookie-parser";
|
||||||
import config from "@server/config";
|
import config from "@server/config";
|
||||||
|
|
174
server/config.ts
174
server/config.ts
|
@ -14,7 +14,10 @@ const portSchema = z.number().positive().gt(0).lte(65535);
|
||||||
|
|
||||||
const environmentSchema = z.object({
|
const environmentSchema = z.object({
|
||||||
app: z.object({
|
app: z.object({
|
||||||
base_url: z.string().url().transform((url) => url.toLowerCase()),
|
base_url: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.transform((url) => url.toLowerCase()),
|
||||||
log_level: z.enum(["debug", "info", "warn", "error"]),
|
log_level: z.enum(["debug", "info", "warn", "error"]),
|
||||||
save_logs: z.boolean()
|
save_logs: z.boolean()
|
||||||
}),
|
}),
|
||||||
|
@ -76,97 +79,102 @@ const environmentSchema = z.object({
|
||||||
.optional()
|
.optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
const loadConfig = (configPath: string) => {
|
export function getConfig() {
|
||||||
try {
|
const loadConfig = (configPath: string) => {
|
||||||
const yamlContent = fs.readFileSync(configPath, "utf8");
|
|
||||||
const config = yaml.load(yamlContent);
|
|
||||||
return config;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
throw new Error(
|
|
||||||
`Error loading configuration file: ${error.message}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const configFilePath1 = path.join(APP_PATH, "config.yml");
|
|
||||||
const configFilePath2 = path.join(APP_PATH, "config.yaml");
|
|
||||||
|
|
||||||
let environment: any;
|
|
||||||
if (fs.existsSync(configFilePath1)) {
|
|
||||||
environment = loadConfig(configFilePath1);
|
|
||||||
} else if (fs.existsSync(configFilePath2)) {
|
|
||||||
environment = loadConfig(configFilePath2);
|
|
||||||
}
|
|
||||||
if (!environment) {
|
|
||||||
const exampleConfigPath = path.join(__DIRNAME, "config.example.yml");
|
|
||||||
if (fs.existsSync(exampleConfigPath)) {
|
|
||||||
try {
|
try {
|
||||||
const exampleConfigContent = fs.readFileSync(
|
const yamlContent = fs.readFileSync(configPath, "utf8");
|
||||||
exampleConfigPath,
|
const config = yaml.load(yamlContent);
|
||||||
"utf8"
|
return config;
|
||||||
);
|
|
||||||
fs.writeFileSync(configFilePath1, exampleConfigContent, "utf8");
|
|
||||||
environment = loadConfig(configFilePath1);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Error creating configuration file from example: ${error.message}`
|
`Error loading configuration file: ${error.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
} else {
|
};
|
||||||
throw new Error(
|
|
||||||
"No configuration file found and no example configuration available"
|
const configFilePath1 = path.join(APP_PATH, "config.yml");
|
||||||
);
|
const configFilePath2 = path.join(APP_PATH, "config.yaml");
|
||||||
|
|
||||||
|
let environment: any;
|
||||||
|
if (fs.existsSync(configFilePath1)) {
|
||||||
|
environment = loadConfig(configFilePath1);
|
||||||
|
} else if (fs.existsSync(configFilePath2)) {
|
||||||
|
environment = loadConfig(configFilePath2);
|
||||||
}
|
}
|
||||||
}
|
if (!environment) {
|
||||||
|
const exampleConfigPath = path.join(__DIRNAME, "config.example.yml");
|
||||||
if (!environment) {
|
if (fs.existsSync(exampleConfigPath)) {
|
||||||
throw new Error("No configuration file found");
|
try {
|
||||||
}
|
const exampleConfigContent = fs.readFileSync(
|
||||||
|
exampleConfigPath,
|
||||||
const parsedConfig = environmentSchema.safeParse(environment);
|
"utf8"
|
||||||
|
);
|
||||||
if (!parsedConfig.success) {
|
fs.writeFileSync(configFilePath1, exampleConfigContent, "utf8");
|
||||||
const errors = fromError(parsedConfig.error);
|
environment = loadConfig(configFilePath1);
|
||||||
throw new Error(`Invalid configuration file: ${errors}`);
|
} catch (error) {
|
||||||
}
|
if (error instanceof Error) {
|
||||||
|
throw new Error(
|
||||||
const packageJsonPath = path.join(__DIRNAME, "..", "package.json");
|
`Error creating configuration file from example: ${error.message}`
|
||||||
let packageJson: any;
|
);
|
||||||
if (fs.existsSync && fs.existsSync(packageJsonPath)) {
|
}
|
||||||
const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
|
throw error;
|
||||||
packageJson = JSON.parse(packageJsonContent);
|
}
|
||||||
|
} else {
|
||||||
if (packageJson.version) {
|
throw new Error(
|
||||||
process.env.APP_VERSION = packageJson.version;
|
"No configuration file found and no example configuration available"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!environment) {
|
||||||
|
throw new Error("No configuration file found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedConfig = environmentSchema.safeParse(environment);
|
||||||
|
|
||||||
|
if (!parsedConfig.success) {
|
||||||
|
const errors = fromError(parsedConfig.error);
|
||||||
|
throw new Error(`Invalid configuration file: ${errors}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageJsonPath = path.join(__DIRNAME, "..", "package.json");
|
||||||
|
let packageJson: any;
|
||||||
|
if (fs.existsSync && fs.existsSync(packageJsonPath)) {
|
||||||
|
const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
|
||||||
|
packageJson = JSON.parse(packageJsonContent);
|
||||||
|
|
||||||
|
if (packageJson.version) {
|
||||||
|
process.env.APP_VERSION = packageJson.version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
|
||||||
|
process.env.SERVER_EXTERNAL_PORT =
|
||||||
|
parsedConfig.data.server.external_port.toString();
|
||||||
|
process.env.SERVER_INTERNAL_PORT =
|
||||||
|
parsedConfig.data.server.internal_port.toString();
|
||||||
|
process.env.FLAGS_EMAIL_VERIFICATION_REQUIRED = parsedConfig.data.flags
|
||||||
|
?.require_email_verification
|
||||||
|
? "true"
|
||||||
|
: "false";
|
||||||
|
process.env.SESSION_COOKIE_NAME =
|
||||||
|
parsedConfig.data.server.session_cookie_name;
|
||||||
|
process.env.RESOURCE_SESSION_COOKIE_NAME =
|
||||||
|
parsedConfig.data.server.resource_session_cookie_name;
|
||||||
|
process.env.EMAIL_ENABLED = parsedConfig.data.email ? "true" : "false";
|
||||||
|
process.env.DISABLE_SIGNUP_WITHOUT_INVITE = parsedConfig.data.flags
|
||||||
|
?.disable_signup_without_invite
|
||||||
|
? "true"
|
||||||
|
: "false";
|
||||||
|
process.env.DISABLE_USER_CREATE_ORG = parsedConfig.data.flags
|
||||||
|
?.disable_user_create_org
|
||||||
|
? "true"
|
||||||
|
: "false";
|
||||||
|
|
||||||
|
return parsedConfig.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
|
export default getConfig();
|
||||||
process.env.SERVER_EXTERNAL_PORT =
|
|
||||||
parsedConfig.data.server.external_port.toString();
|
|
||||||
process.env.SERVER_INTERNAL_PORT =
|
|
||||||
parsedConfig.data.server.internal_port.toString();
|
|
||||||
process.env.FLAGS_EMAIL_VERIFICATION_REQUIRED = parsedConfig.data.flags
|
|
||||||
?.require_email_verification
|
|
||||||
? "true"
|
|
||||||
: "false";
|
|
||||||
process.env.SESSION_COOKIE_NAME = parsedConfig.data.server.session_cookie_name;
|
|
||||||
process.env.RESOURCE_SESSION_COOKIE_NAME =
|
|
||||||
parsedConfig.data.server.resource_session_cookie_name;
|
|
||||||
process.env.EMAIL_ENABLED = parsedConfig.data.email ? "true" : "false";
|
|
||||||
process.env.DISABLE_SIGNUP_WITHOUT_INVITE = parsedConfig.data.flags
|
|
||||||
?.disable_signup_without_invite
|
|
||||||
? "true"
|
|
||||||
: "false";
|
|
||||||
process.env.DISABLE_USER_CREATE_ORG = parsedConfig.data.flags
|
|
||||||
?.disable_user_create_org
|
|
||||||
? "true"
|
|
||||||
: "false";
|
|
||||||
|
|
||||||
export default parsedConfig.data;
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ export const SendInviteLink = ({
|
||||||
<Section className="text-center my-6">
|
<Section className="text-center my-6">
|
||||||
<Button
|
<Button
|
||||||
href={inviteLink}
|
href={inviteLink}
|
||||||
className="rounded-lg bg-primary px-[12px] py-[9px] text-center font-semibold text-white cursor-pointer"
|
className="rounded-lg bg-primary px-[12px] py-[9px] text-center font-semibold text-white cursor-pointer text-xl"
|
||||||
>
|
>
|
||||||
Accept invitation to {orgName}
|
Accept invitation to {orgName}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -421,6 +421,7 @@ export default function ResourceAuthenticationPage() {
|
||||||
<FormItem className="flex flex-col items-start">
|
<FormItem className="flex flex-col items-start">
|
||||||
<FormLabel>Roles</FormLabel>
|
<FormLabel>Roles</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
{/* @ts-ignore */}
|
||||||
<TagInput
|
<TagInput
|
||||||
{...field}
|
{...field}
|
||||||
activeTagIndex={
|
activeTagIndex={
|
||||||
|
@ -454,9 +455,9 @@ export default function ResourceAuthenticationPage() {
|
||||||
tag: {
|
tag: {
|
||||||
body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full"
|
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",
|
input: "text-base md:text-sm border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
|
||||||
inlineTagsContainer:
|
inlineTagsContainer:
|
||||||
"bg-transparent"
|
"bg-transparent p-2"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -476,6 +477,7 @@ export default function ResourceAuthenticationPage() {
|
||||||
<FormItem className="flex flex-col items-start">
|
<FormItem className="flex flex-col items-start">
|
||||||
<FormLabel>Users</FormLabel>
|
<FormLabel>Users</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
{/* @ts-ignore */}
|
||||||
<TagInput
|
<TagInput
|
||||||
{...field}
|
{...field}
|
||||||
activeTagIndex={
|
activeTagIndex={
|
||||||
|
@ -509,9 +511,9 @@ export default function ResourceAuthenticationPage() {
|
||||||
tag: {
|
tag: {
|
||||||
body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full"
|
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",
|
input: "text-base md:text-sm border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
|
||||||
inlineTagsContainer:
|
inlineTagsContainer:
|
||||||
"bg-transparent"
|
"bg-transparent p-2"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -649,6 +651,7 @@ export default function ResourceAuthenticationPage() {
|
||||||
Whitelisted Emails
|
Whitelisted Emails
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
{/* @ts-ignore */}
|
||||||
<TagInput
|
<TagInput
|
||||||
{...field}
|
{...field}
|
||||||
activeTagIndex={
|
activeTagIndex={
|
||||||
|
@ -691,9 +694,9 @@ export default function ResourceAuthenticationPage() {
|
||||||
tag: {
|
tag: {
|
||||||
body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full"
|
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",
|
input: "text-base md:text-sm border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
|
||||||
inlineTagsContainer:
|
inlineTagsContainer:
|
||||||
"bg-transparent"
|
"bg-transparent p-2"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
|
@ -50,7 +50,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<BreadcrumbList>
|
<BreadcrumbList>
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<Link href="../../">Sites</Link>
|
<Link href="../">Sites</Link>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
<BreadcrumbSeparator />
|
<BreadcrumbSeparator />
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 0 0% 100%;
|
||||||
--foreground: 20 5.0% 10.0%;
|
--foreground: 20 0.0% 10.0%;
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
--card-foreground: 20 5.0% 10.0%;
|
--card-foreground: 20 0.0% 10.0%;
|
||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 20 5.0% 10.0%;
|
--popover-foreground: 20 0.0% 10.0%;
|
||||||
--primary: 24.6 95% 53.1%;
|
--primary: 24.6 95% 53.1%;
|
||||||
--primary-foreground: 60 9.1% 97.8%;
|
--primary-foreground: 60 9.1% 97.8%;
|
||||||
--secondary: 60 4.8% 95.9%;
|
--secondary: 60 4.8% 95.9%;
|
||||||
|
@ -33,11 +33,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 20 5.0% 10.0%;
|
--background: 20 0.0% 10.0%;
|
||||||
--foreground: 60 9.1% 97.8%;
|
--foreground: 60 9.1% 97.8%;
|
||||||
--card: 20 5.0% 10.0%;
|
--card: 20 0.0% 10.0%;
|
||||||
--card-foreground: 60 9.1% 97.8%;
|
--card-foreground: 60 9.1% 97.8%;
|
||||||
--popover: 20 5.0% 10.0%;
|
--popover: 20 0.0% 10.0%;
|
||||||
--popover-foreground: 60 9.1% 97.8%;
|
--popover-foreground: 60 9.1% 97.8%;
|
||||||
--primary: 20.5 90.2% 48.2%;
|
--primary: 20.5 90.2% 48.2%;
|
||||||
--primary-foreground: 60 9.1% 97.8%;
|
--primary-foreground: 60 9.1% 97.8%;
|
||||||
|
|
|
@ -24,6 +24,15 @@ import {
|
||||||
DrawerTitle,
|
DrawerTitle,
|
||||||
DrawerTrigger
|
DrawerTrigger
|
||||||
} from "@/components/ui/drawer";
|
} from "@/components/ui/drawer";
|
||||||
|
import {
|
||||||
|
Sheet,
|
||||||
|
SheetContent,
|
||||||
|
SheetDescription,
|
||||||
|
SheetFooter,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
SheetTrigger
|
||||||
|
} from "./ui/sheet";
|
||||||
|
|
||||||
interface BaseProps {
|
interface BaseProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
@ -44,7 +53,7 @@ const desktop = "(min-width: 768px)";
|
||||||
const Credenza = ({ children, ...props }: RootCredenzaProps) => {
|
const Credenza = ({ children, ...props }: RootCredenzaProps) => {
|
||||||
const isDesktop = useMediaQuery(desktop);
|
const isDesktop = useMediaQuery(desktop);
|
||||||
// const isDesktop = true;
|
// const isDesktop = true;
|
||||||
const Credenza = isDesktop ? Dialog : Drawer;
|
const Credenza = isDesktop ? Dialog : Sheet;
|
||||||
|
|
||||||
return <Credenza {...props}>{children}</Credenza>;
|
return <Credenza {...props}>{children}</Credenza>;
|
||||||
};
|
};
|
||||||
|
@ -53,7 +62,7 @@ const CredenzaTrigger = ({ className, children, ...props }: CredenzaProps) => {
|
||||||
const isDesktop = useMediaQuery(desktop);
|
const isDesktop = useMediaQuery(desktop);
|
||||||
// const isDesktop = true;
|
// const isDesktop = true;
|
||||||
|
|
||||||
const CredenzaTrigger = isDesktop ? DialogTrigger : DrawerTrigger;
|
const CredenzaTrigger = isDesktop ? DialogTrigger : SheetTrigger;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CredenzaTrigger className={className} {...props}>
|
<CredenzaTrigger className={className} {...props}>
|
||||||
|
@ -79,10 +88,14 @@ const CredenzaContent = ({ className, children, ...props }: CredenzaProps) => {
|
||||||
const isDesktop = useMediaQuery(desktop);
|
const isDesktop = useMediaQuery(desktop);
|
||||||
// const isDesktop = true;
|
// const isDesktop = true;
|
||||||
|
|
||||||
const CredenzaContent = isDesktop ? DialogContent : DrawerContent;
|
const CredenzaContent = isDesktop ? DialogContent : SheetContent;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CredenzaContent className={className} {...props}>
|
<CredenzaContent
|
||||||
|
className={cn("overflow-y-auto max-h-screen", className)}
|
||||||
|
{...props}
|
||||||
|
side={"bottom"}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</CredenzaContent>
|
</CredenzaContent>
|
||||||
);
|
);
|
||||||
|
@ -98,7 +111,7 @@ const CredenzaDescription = ({
|
||||||
|
|
||||||
const CredenzaDescription = isDesktop
|
const CredenzaDescription = isDesktop
|
||||||
? DialogDescription
|
? DialogDescription
|
||||||
: DrawerDescription;
|
: SheetDescription;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CredenzaDescription className={className} {...props}>
|
<CredenzaDescription className={className} {...props}>
|
||||||
|
@ -111,7 +124,7 @@ const CredenzaHeader = ({ className, children, ...props }: CredenzaProps) => {
|
||||||
const isDesktop = useMediaQuery(desktop);
|
const isDesktop = useMediaQuery(desktop);
|
||||||
// const isDesktop = true;
|
// const isDesktop = true;
|
||||||
|
|
||||||
const CredenzaHeader = isDesktop ? DialogHeader : DrawerHeader;
|
const CredenzaHeader = isDesktop ? DialogHeader : SheetHeader;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CredenzaHeader className={className} {...props}>
|
<CredenzaHeader className={className} {...props}>
|
||||||
|
@ -124,7 +137,7 @@ const CredenzaTitle = ({ className, children, ...props }: CredenzaProps) => {
|
||||||
const isDesktop = useMediaQuery(desktop);
|
const isDesktop = useMediaQuery(desktop);
|
||||||
// const isDesktop = true;
|
// const isDesktop = true;
|
||||||
|
|
||||||
const CredenzaTitle = isDesktop ? DialogTitle : DrawerTitle;
|
const CredenzaTitle = isDesktop ? DialogTitle : SheetTitle;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CredenzaTitle className={className} {...props}>
|
<CredenzaTitle className={className} {...props}>
|
||||||
|
@ -134,25 +147,24 @@ const CredenzaTitle = ({ className, children, ...props }: CredenzaProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const CredenzaBody = ({ className, children, ...props }: CredenzaProps) => {
|
const CredenzaBody = ({ className, children, ...props }: CredenzaProps) => {
|
||||||
return (
|
// return (
|
||||||
<div className={cn("px-4 md:px-0 mb-4", className)} {...props}>
|
// <div className={cn("px-4 md:px-0 mb-4", className)} {...props}>
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <div className={cn("px-0 mb-4", className)} {...props}>
|
|
||||||
// {children}
|
// {children}
|
||||||
// </div>
|
// </div>
|
||||||
// );
|
// );
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn("px-0 mb-4", className)} {...props}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const CredenzaFooter = ({ className, children, ...props }: CredenzaProps) => {
|
const CredenzaFooter = ({ className, children, ...props }: CredenzaProps) => {
|
||||||
const isDesktop = useMediaQuery(desktop);
|
const isDesktop = useMediaQuery(desktop);
|
||||||
// const isDesktop = true;
|
// const isDesktop = true;
|
||||||
|
|
||||||
const CredenzaFooter = isDesktop ? DialogFooter : DrawerFooter;
|
const CredenzaFooter = isDesktop ? DialogFooter : SheetFooter;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CredenzaFooter className={className} {...props}>
|
<CredenzaFooter className={className} {...props}>
|
||||||
|
|
|
@ -224,7 +224,7 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
|
||||||
<div className="h-[250px] mx-auto flex items-center justify-center">
|
<div className="h-[250px] mx-auto flex items-center justify-center">
|
||||||
<QRCodeCanvas value={secretUri} size={200} />
|
<QRCodeCanvas value={secretUri} size={200} />
|
||||||
</div>
|
</div>
|
||||||
<CopyTextBox text={secretKey} wrapText={false} />
|
<CopyTextBox text={secretUri} wrapText={false} />
|
||||||
|
|
||||||
<Form {...confirmForm}>
|
<Form {...confirmForm}>
|
||||||
<form
|
<form
|
||||||
|
|
140
src/components/ui/sheet.tsx
Normal file
140
src/components/ui/sheet.tsx
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
import { X } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Sheet = SheetPrimitive.Root
|
||||||
|
|
||||||
|
const SheetTrigger = SheetPrimitive.Trigger
|
||||||
|
|
||||||
|
const SheetClose = SheetPrimitive.Close
|
||||||
|
|
||||||
|
const SheetPortal = SheetPrimitive.Portal
|
||||||
|
|
||||||
|
const SheetOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Overlay
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const sheetVariants = cva(
|
||||||
|
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
side: {
|
||||||
|
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
||||||
|
bottom:
|
||||||
|
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||||
|
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
||||||
|
right:
|
||||||
|
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
side: "right",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
interface SheetContentProps
|
||||||
|
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||||
|
VariantProps<typeof sheetVariants> {}
|
||||||
|
|
||||||
|
const SheetContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||||
|
SheetContentProps
|
||||||
|
>(({ side = "right", className, children, ...props }, ref) => (
|
||||||
|
<SheetPortal>
|
||||||
|
<SheetOverlay />
|
||||||
|
<SheetPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(sheetVariants({ side }), className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</SheetPrimitive.Close>
|
||||||
|
</SheetPrimitive.Content>
|
||||||
|
</SheetPortal>
|
||||||
|
))
|
||||||
|
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const SheetHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-2 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
SheetHeader.displayName = "SheetHeader"
|
||||||
|
|
||||||
|
const SheetFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
SheetFooter.displayName = "SheetFooter"
|
||||||
|
|
||||||
|
const SheetTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-lg font-semibold text-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const SheetDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Sheet,
|
||||||
|
SheetPortal,
|
||||||
|
SheetOverlay,
|
||||||
|
SheetTrigger,
|
||||||
|
SheetClose,
|
||||||
|
SheetContent,
|
||||||
|
SheetHeader,
|
||||||
|
SheetFooter,
|
||||||
|
SheetTitle,
|
||||||
|
SheetDescription,
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ const ToastViewport = React.forwardRef<
|
||||||
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
||||||
|
|
||||||
const toastVariants = cva(
|
const toastVariants = cva(
|
||||||
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-3 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import * as React from "react";
|
||||||
import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
|
import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
|
||||||
|
|
||||||
const TOAST_LIMIT = 3;
|
const TOAST_LIMIT = 3;
|
||||||
const TOAST_REMOVE_DELAY = 5 * 1000;
|
const TOAST_REMOVE_DELAY = 1 * 1000;
|
||||||
|
|
||||||
type ToasterToast = ToastProps & {
|
type ToasterToast = ToastProps & {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||||
import { type ThemeProviderProps } from "next-themes/dist/types";
|
|
||||||
|
type ThemeProviderProps = React.ComponentProps<typeof NextThemesProvider>;
|
||||||
|
|
||||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue