add admin checks for admin pages and run biome formating 🔨
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 11m8s

This commit is contained in:
2025-07-25 13:31:12 +05:00
parent aedf7cdf7d
commit 9b2f2c1528
127 changed files with 6577 additions and 6334 deletions

View File

@ -1,8 +1,5 @@
{ {
"extends": [ "extends": ["next/core-web-vitals", "next/typescript"],
"next/core-web-vitals",
"next/typescript"
],
"rules": { "rules": {
"@typescript-eslint/no-explicit-any": "error" "@typescript-eslint/no-explicit-any": "error"
} }

View File

@ -8,7 +8,7 @@ import type {
ApiResponse, ApiResponse,
NewPayment, NewPayment,
Payment, Payment,
Topup Topup,
} from "@/lib/backend-types"; } from "@/lib/backend-types";
import type { TopupResponse } from "@/lib/types"; import type { TopupResponse } from "@/lib/types";
import { handleApiResponse } from "@/utils/tryCatch"; import { handleApiResponse } from "@/utils/tryCatch";
@ -24,7 +24,8 @@ export async function createPayment(data: NewPayment) {
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
console.log("data", data); console.log("data", data);
const response = await fetch( const response = await fetch(
`${process.env.SARLINK_API_BASE_URL // }); `${
process.env.SARLINK_API_BASE_URL // });
}/api/billing/payment/`, }/api/billing/payment/`,
{ {
method: "POST", method: "POST",
@ -93,11 +94,17 @@ type GetPaymentProps = {
[key: string]: string | number | undefined; // Allow additional properties for flexibility [key: string]: string | number | undefined; // Allow additional properties for flexibility
}; };
export async function getPayments(params: GetPaymentProps, allPayments = false) { export async function getPayments(
params: GetPaymentProps,
allPayments = false,
) {
// Build query string from all defined params // Build query string from all defined params
const query = Object.entries(params) const query = Object.entries(params)
.filter(([_, value]) => value !== undefined && value !== "") .filter(([_, value]) => value !== undefined && value !== "")
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) .map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`,
)
.join("&"); .join("&");
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
const response = await fetch( const response = await fetch(
@ -122,12 +129,17 @@ export async function getPayments(params: GetPaymentProps, allPayments = false)
return data; return data;
} }
export async function getTopups(params: GenericGetResponseProps, all_topups = false) { export async function getTopups(
params: GenericGetResponseProps,
all_topups = false,
) {
// Build query string from all defined params // Build query string from all defined params
const query = Object.entries(params) const query = Object.entries(params)
.filter(([_, value]) => value !== undefined && value !== "") .filter(([_, value]) => value !== undefined && value !== "")
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) .map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`,
)
.join("&"); .join("&");
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
@ -223,17 +235,17 @@ export type VerifyDevicePaymentState = {
success: boolean; success: boolean;
fieldErrors: Record<string, string>; fieldErrors: Record<string, string>;
payload?: FormData; payload?: FormData;
} };
export async function verifyDevicePayment( export async function verifyDevicePayment(
_prevState: VerifyDevicePaymentState, _prevState: VerifyDevicePaymentState,
formData: FormData formData: FormData,
): Promise<VerifyDevicePaymentState> { ): Promise<VerifyDevicePaymentState> {
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
// Get the payment ID and method from the form data // Get the payment ID and method from the form data
const paymentId = formData.get('paymentId') as string; const paymentId = formData.get("paymentId") as string;
const method = formData.get('method') as "TRANSFER" | "WALLET"; const method = formData.get("method") as "TRANSFER" | "WALLET";
if (!paymentId) { if (!paymentId) {
return { return {
@ -266,12 +278,16 @@ export async function verifyDevicePayment(
}, },
); );
const result = await handleApiResponse<Payment>(response, "verifyDevicePayment"); const result = await handleApiResponse<Payment>(
response,
"verifyDevicePayment",
);
revalidatePath("/payments/[paymentId]", "page"); revalidatePath("/payments/[paymentId]", "page");
return { return {
message: method === "WALLET" message:
method === "WALLET"
? "Payment completed successfully using wallet!" ? "Payment completed successfully using wallet!"
: "Payment verification successful!", : "Payment verification successful!",
success: true, success: true,
@ -281,7 +297,8 @@ export async function verifyDevicePayment(
} catch (error: unknown) { } catch (error: unknown) {
if (error instanceof Error) { if (error instanceof Error) {
return { return {
message: error.message || "Payment verification failed. Please try again.", message:
error.message || "Payment verification failed. Please try again.",
success: false, success: false,
fieldErrors: {}, fieldErrors: {},
}; };
@ -295,7 +312,6 @@ export async function verifyDevicePayment(
} }
} }
export type VerifyTopupPaymentState = { export type VerifyTopupPaymentState = {
transaction?: { transaction?: {
sourceBank: string; sourceBank: string;
@ -305,15 +321,15 @@ export type VerifyTopupPaymentState = {
success: boolean; success: boolean;
fieldErrors: Record<string, string>; fieldErrors: Record<string, string>;
payload?: FormData; payload?: FormData;
} };
export async function verifyTopupPayment( export async function verifyTopupPayment(
_prevState: VerifyTopupPaymentState, _prevState: VerifyTopupPaymentState,
formData: FormData formData: FormData,
): Promise<VerifyTopupPaymentState> { ): Promise<VerifyTopupPaymentState> {
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
// Get the topup ID from the form data or use a hidden input // Get the topup ID from the form data or use a hidden input
const topupId = formData.get('topupId') as string; const topupId = formData.get("topupId") as string;
if (!topupId) { if (!topupId) {
return { return {
@ -335,7 +351,10 @@ export async function verifyTopupPayment(
}, },
); );
const result = await handleApiResponse<TopupResponse>(response, "verifyTopupPayment"); const result = await handleApiResponse<TopupResponse>(
response,
"verifyTopupPayment",
);
revalidatePath("/top-ups/[topupId]", "page"); revalidatePath("/top-ups/[topupId]", "page");
@ -348,7 +367,8 @@ export async function verifyTopupPayment(
} catch (error: unknown) { } catch (error: unknown) {
if (error instanceof Error) { if (error instanceof Error) {
return { return {
message: error.message || "Please check your payment details and try again.", message:
error.message || "Please check your payment details and try again.",
success: false, success: false,
fieldErrors: {}, fieldErrors: {},
}; };
@ -361,5 +381,3 @@ export async function verifyTopupPayment(
} }
} }
} }

View File

@ -9,37 +9,45 @@ import type { ApiError } from "@/lib/backend-types";
import type { User } from "@/lib/types/user"; import type { User } from "@/lib/types/user";
import { handleApiResponse } from "@/utils/tryCatch"; import { handleApiResponse } from "@/utils/tryCatch";
type VerifyUserResponse = { type VerifyUserResponse =
"ok": boolean, | {
"mismatch_fields": string[] | null, ok: boolean;
"error": string | null, mismatch_fields: string[] | null;
"detail": string | null error: string | null;
} | { detail: string | null;
"message": boolean, }
| {
message: boolean;
}; };
export async function verifyUser(userId: string) { export async function verifyUser(userId: string) {
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
if (!session?.apiToken) { if (!session?.apiToken) {
return { ok: false, error: 'Not authenticated' } as const; return { ok: false, error: "Not authenticated" } as const;
} }
try { try {
const r = await fetch( const r = await fetch(
`${process.env.SARLINK_API_BASE_URL}/api/auth/users/${userId}/verify/`, `${process.env.SARLINK_API_BASE_URL}/api/auth/users/${userId}/verify/`,
{ {
method: 'PUT', method: "PUT",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
Authorization: `Token ${session.apiToken}`, Authorization: `Token ${session.apiToken}`,
}, },
}, },
); );
const body = (await r.json().catch(() => ({}))) as VerifyUserResponse & const body = (await r.json().catch(() => ({}))) as VerifyUserResponse & {
{ message?: string; detail?: string }; message?: string;
detail?: string;
};
if (!r.ok) { if (!r.ok) {
const msg = body?.message || body?.detail || 'User verification failed'; const msg = body?.message || body?.detail || "User verification failed";
return { ok: false, error: msg, mismatch_fields: body?.mismatch_fields || null } as const; return {
ok: false,
error: msg,
mismatch_fields: body?.mismatch_fields || null,
} as const;
} }
return { ok: true, data: body } as const; return { ok: true, data: body } as const;
@ -66,7 +74,7 @@ export async function getProfile() {
export async function rejectUser( export async function rejectUser(
_prevState: RejectUserFormState, _prevState: RejectUserFormState,
formData: FormData formData: FormData,
): Promise<RejectUserFormState> { ): Promise<RejectUserFormState> {
const userId = formData.get("userId") as string; const userId = formData.get("userId") as string;
const rejection_details = formData.get("rejection_details") as string; const rejection_details = formData.get("rejection_details") as string;
@ -85,7 +93,9 @@ export async function rejectUser(
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json();
throw new Error(errorData.message || errorData.detail || "Failed to reject user"); throw new Error(
errorData.message || errorData.detail || "Failed to reject user",
);
} }
// Handle 204 No Content response (successful deletion) // Handle 204 No Content response (successful deletion)
@ -95,11 +105,14 @@ export async function rejectUser(
} }
revalidatePath("/users"); revalidatePath("/users");
const error = await response.json() const error = await response.json();
return { return {
message: (error as ApiError).message || (error as ApiError).detail || "An unexpected error occurred.", message:
(error as ApiError).message ||
(error as ApiError).detail ||
"An unexpected error occurred.",
fieldErrors: {}, fieldErrors: {},
payload: formData payload: formData,
}; };
} }
@ -116,10 +129,9 @@ export type UpdateUserFormState = {
payload?: FormData; payload?: FormData;
}; };
export async function updateUser( export async function updateUser(
_prevState: UpdateUserFormState, _prevState: UpdateUserFormState,
formData: FormData formData: FormData,
): Promise<UpdateUserFormState> { ): Promise<UpdateUserFormState> {
const userId = formData.get("userId") as string; const userId = formData.get("userId") as string;
const data: Record<string, string | number | boolean> = {}; const data: Record<string, string | number | boolean> = {};
@ -128,7 +140,7 @@ export async function updateUser(
data[key] = typeof value === "number" ? value : String(value); data[key] = typeof value === "number" ? value : String(value);
} }
} }
console.log("data in update user action", data) console.log("data in update user action", data);
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
const response = await fetch( const response = await fetch(
@ -142,18 +154,21 @@ export async function updateUser(
body: JSON.stringify(data), body: JSON.stringify(data),
}, },
); );
console.log("response in update user action", response) console.log("response in update user action", response);
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json();
return { return {
message: errorData.message || errorData.detail || "An error occurred while updating the user.", message:
errorData.message ||
errorData.detail ||
"An error occurred while updating the user.",
fieldErrors: errorData.field_errors || {}, fieldErrors: errorData.field_errors || {},
payload: formData, payload: formData,
} };
} }
const updatedUser = await response.json() as User; const updatedUser = (await response.json()) as User;
revalidatePath("/users/[userId]/update", "page"); revalidatePath("/users/[userId]/update", "page");
revalidatePath("/users/[userId]/verify", "page"); revalidatePath("/users/[userId]/verify", "page");
return { return {
@ -164,7 +179,7 @@ export async function updateUser(
export async function updateUserAgreement( export async function updateUserAgreement(
_prevState: UpdateUserFormState, _prevState: UpdateUserFormState,
formData: FormData formData: FormData,
): Promise<UpdateUserFormState> { ): Promise<UpdateUserFormState> {
const userId = formData.get("userId") as string; const userId = formData.get("userId") as string;
// Remove userId from formData before sending to API // Remove userId from formData before sending to API
@ -174,7 +189,7 @@ export async function updateUserAgreement(
apiFormData.append(key, value); apiFormData.append(key, value);
} }
} }
console.log({ apiFormData }) console.log({ apiFormData });
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
const response = await fetch( const response = await fetch(
`${process.env.SARLINK_API_BASE_URL}/api/auth/users/${userId}/agreement/`, `${process.env.SARLINK_API_BASE_URL}/api/auth/users/${userId}/agreement/`,
@ -186,17 +201,20 @@ export async function updateUserAgreement(
body: apiFormData, body: apiFormData,
}, },
); );
console.log("response in update user agreement action", response) console.log("response in update user agreement action", response);
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json();
return { return {
message: errorData.message || errorData.detail || "An error occurred while updating the user agreement.", message:
errorData.message ||
errorData.detail ||
"An error occurred while updating the user agreement.",
fieldErrors: errorData.field_errors || {}, fieldErrors: errorData.field_errors || {},
payload: formData, payload: formData,
} };
} }
const updatedUserAgreement = await response.json() as { agreement: string }; const updatedUserAgreement = (await response.json()) as { agreement: string };
revalidatePath("/users/[userId]/update", "page"); revalidatePath("/users/[userId]/update", "page");
revalidatePath("/users/[userId]/verify", "page"); revalidatePath("/users/[userId]/verify", "page");
revalidatePath("/users/[userId]/agreement", "page"); revalidatePath("/users/[userId]/agreement", "page");

View File

@ -1,7 +1,8 @@
export default function AuthLayout({ export default function AuthLayout({
children, children,
}: { children: React.ReactNode }) { }: {
children: React.ReactNode;
}) {
return ( return (
<div className="bg-gray-100 dark:bg-black w-full h-screen flex items-center justify-center font-sans"> <div className="bg-gray-100 dark:bg-black w-full h-screen flex items-center justify-center font-sans">
{children} {children}

View File

@ -11,7 +11,7 @@ export default async function VerifyRegistrationOTP({
}: { }: {
searchParams: Promise<{ phone_number: string }>; searchParams: Promise<{ phone_number: string }>;
}) { }) {
const session = await getServerSession(authOptions) const session = await getServerSession(authOptions);
if (session) { if (session) {
// If the user is already logged in, redirect them to the home page // If the user is already logged in, redirect them to the home page
return redirect("/"); return redirect("/");

View File

@ -1,13 +1,11 @@
import React from 'react' import React from "react";
export default function Agreements() { export default function Agreements() {
return ( return (
<div> <div>
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4"> <div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
<h3 className="text-sarLinkOrange text-2xl"> <h3 className="text-sarLinkOrange text-2xl">Agreements</h3>
Agreements
</h3>
</div> </div>
</div> </div>
) );
} }

View File

@ -1,8 +1,6 @@
import FullPageLoader from '@/components/full-page-loader' import FullPageLoader from "@/components/full-page-loader";
import React from 'react' import React from "react";
export default function Loading() { export default function Loading() {
return ( return <FullPageLoader />;
<FullPageLoader />
)
} }

View File

@ -49,7 +49,7 @@ export default async function Devices({
label: "Vendor", label: "Vendor",
type: "string", type: "string",
placeholder: "Enter vendor name", placeholder: "Enter vendor name",
} },
]} ]}
/> />
</div> </div>

View File

@ -6,9 +6,9 @@ export default function DashboardLayout({
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return <ApplicationLayout> return (
<QueryProvider> <ApplicationLayout>
{children} <QueryProvider>{children}</QueryProvider>
</QueryProvider> </ApplicationLayout>
</ApplicationLayout>; );
} }

View File

@ -11,7 +11,6 @@ export default async function ParentalControl({
status: string; status: string;
}>; }>;
}) { }) {
const parentalControlFilters = { const parentalControlFilters = {
is_active: "true", is_active: "true",
has_a_pending_payment: "false", has_a_pending_payment: "false",
@ -48,9 +47,10 @@ export default async function ParentalControl({
label: "Vendor", label: "Vendor",
type: "string", type: "string",
placeholder: "Enter vendor name", placeholder: "Enter vendor name",
} },
]} ]}
/> </div> />{" "}
</div>
<Suspense key={(await searchParams).page} fallback={"loading...."}> <Suspense key={(await searchParams).page} fallback={"loading...."}>
<DevicesTable <DevicesTable
parentalControl={true} parentalControl={true}

View File

@ -31,7 +31,9 @@ export default async function PaymentPage({
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-4 mb-4 mx-2"> <div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-4 mb-4 mx-2">
<h3 className="text-sarLinkOrange text-2xl">Payment</h3> <h3 className="text-sarLinkOrange text-2xl">Payment</h3>
<div className="flex flex-col gap-4 items-end w-full"> <div className="flex flex-col gap-4 items-end w-full">
{!payment.is_expired && payment.paid && payment.status !== "PENDING" && ( {!payment.is_expired &&
payment.paid &&
payment.status !== "PENDING" && (
<Button <Button
disabled disabled
className={cn( className={cn(

View File

@ -1,8 +1,6 @@
import PriceCalculator from '@/components/price-calculator' import PriceCalculator from "@/components/price-calculator";
import React from 'react' import React from "react";
export default function Pricing() { export default function Pricing() {
return ( return <PriceCalculator />;
<PriceCalculator />
)
} }

View File

@ -17,7 +17,7 @@ export default async function UserDevices({
}) { }) {
const query = (await searchParams)?.query || ""; const query = (await searchParams)?.query || "";
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
if (session?.user?.is_admin !== true) redirect("/devices?page=1"); if (!session?.user?.is_admin) redirect("/devices?page=1");
return ( return (
<div> <div>
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4"> <div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
@ -55,7 +55,7 @@ export default async function UserDevices({
label: "Device User", label: "Device User",
type: "string", type: "string",
placeholder: "User name or id card", placeholder: "User name or id card",
} },
]} ]}
/> />
</div> </div>

View File

@ -1,4 +1,7 @@
import { redirect } from "next/navigation";
import { getServerSession } from "next-auth";
import { Suspense } from "react"; import { Suspense } from "react";
import { authOptions } from "@/app/auth";
import { UsersPaymentsTable } from "@/components/admin/user-payments-table"; import { UsersPaymentsTable } from "@/components/admin/user-payments-table";
import DynamicFilter from "@/components/generic-filter"; import DynamicFilter from "@/components/generic-filter";
@ -13,7 +16,9 @@ export default async function UserPayments({
}>; }>;
}) { }) {
const query = (await searchParams)?.query || ""; const query = (await searchParams)?.query || "";
// const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
if (!session?.user?.is_admin) redirect("/payments?page=1");
return ( return (
<div> <div>
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4"> <div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
@ -70,9 +75,8 @@ export default async function UserPayments({
{ label: "All", value: "" }, { label: "All", value: "" },
{ label: "Wallet", value: "WALLET" }, { label: "Wallet", value: "WALLET" },
{ label: "Transfer", value: "TRANSFER" }, { label: "Transfer", value: "TRANSFER" },
] ],
}, },
]} ]}
/> />
<Suspense key={query} fallback={"loading...."}> <Suspense key={query} fallback={"loading...."}>

View File

@ -1,4 +1,7 @@
import { redirect } from "next/navigation";
import { getServerSession } from "next-auth";
import { Suspense } from "react"; import { Suspense } from "react";
import { authOptions } from "@/app/auth";
import { AdminTopupsTable } from "@/components/admin/admin-topup-table"; import { AdminTopupsTable } from "@/components/admin/admin-topup-table";
import DynamicFilter from "@/components/generic-filter"; import DynamicFilter from "@/components/generic-filter";
@ -10,7 +13,8 @@ export default async function UserTopups({
}>; }>;
}) { }) {
const query = (await searchParams)?.query || ""; const query = (await searchParams)?.query || "";
// const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
if (!session?.user?.is_admin) redirect("/top-ups?page=1");
return ( return (
<div> <div>
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4"> <div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">

View File

@ -24,10 +24,9 @@ export default async function UserUpdate({
}) { }) {
const { userId } = await params; const { userId } = await params;
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
if (!session?.user?.is_admin) return null if (!session?.user?.is_admin) return redirect("/devices?page=1");
const [error, user] = await tryCatch(getProfileById(userId)); const [error, user] = await tryCatch(getProfileById(userId));
if (error) { if (error) {
if (error.message === "UNAUTHORIZED") { if (error.message === "UNAUTHORIZED") {
redirect("/auth/signin"); redirect("/auth/signin");
@ -39,7 +38,9 @@ export default async function UserUpdate({
return ( return (
<div> <div>
<div className="flex items-center justify-between text-gray-500 text-2xl font-bold title-bg py-4 px-2 mb-4"> <div className="flex items-center justify-between text-gray-500 text-2xl font-bold title-bg py-4 px-2 mb-4">
<h3 className="text-sarLinkOrange text-2xl">Upload user user agreement</h3> <h3 className="text-sarLinkOrange text-2xl">
Upload user user agreement
</h3>
</div> </div>
<UserAgreementForm user={user} /> <UserAgreementForm user={user} />
</div> </div>

View File

@ -24,10 +24,9 @@ export default async function UserUpdate({
}) { }) {
const { userId } = await params; const { userId } = await params;
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
if (!session?.user?.is_admin) return null if (!session?.user?.is_admin) return redirect("/devices?page=1");
const [error, user] = await tryCatch(getProfileById(userId)); const [error, user] = await tryCatch(getProfileById(userId));
if (error) { if (error) {
if (error.message === "UNAUTHORIZED") { if (error.message === "UNAUTHORIZED") {
redirect("/auth/signin"); redirect("/auth/signin");

View File

@ -22,7 +22,9 @@ export default async function VerifyUserPage({
const userId = (await params).userId; const userId = (await params).userId;
const [error, dbUser] = await tryCatch(getProfileById(userId)); const [error, dbUser] = await tryCatch(getProfileById(userId));
const [nationalDataEror, nationalData] = await tryCatch(getNationalPerson({ idCard: dbUser?.id_card ?? "" })) const [nationalDataEror, nationalData] = await tryCatch(
getNationalPerson({ idCard: dbUser?.id_card ?? "" }),
);
if (nationalDataEror) { if (nationalDataEror) {
console.warn("Error fetching national data:", nationalDataEror); console.warn("Error fetching national data:", nationalDataEror);
} }
@ -47,19 +49,23 @@ export default async function VerifyUserPage({
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{dbUser && !dbUser?.verified && <UserVerifyDialog user={dbUser} />} {dbUser && !dbUser?.verified && <UserVerifyDialog user={dbUser} />}
{dbUser && !dbUser?.verified && <UserRejectDialog user={dbUser} />} {dbUser && !dbUser?.verified && <UserRejectDialog user={dbUser} />}
<Link href={'update'}> <Link href={"update"}>
<Button className="hover:cursor-pointer"> <Button className="hover:cursor-pointer">
<PencilIcon /> <PencilIcon />
Update User Update User
</Button> </Button>
</Link> </Link>
<Link href={'agreement'}> <Link href={"agreement"}>
<Button className="hover:cursor-pointer"> <Button className="hover:cursor-pointer">
<FileTextIcon /> <FileTextIcon />
Update Agreement Update Agreement
</Button> </Button>
</Link> </Link>
<Link href={dbUser?.agreement || "#"} target="_blank" rel="noopener noreferrer"> <Link
href={dbUser?.agreement || "#"}
target="_blank"
rel="noopener noreferrer"
>
<Button className="hover:cursor-pointer"> <Button className="hover:cursor-pointer">
<EyeIcon /> <EyeIcon />
View Agreement View Agreement
@ -114,9 +120,7 @@ export default async function VerifyUserPage({
<InputReadOnly <InputReadOnly
showCheck showCheck
checkTrue={ checkTrue={dbUserDob === nationalDob}
dbUserDob === nationalDob
}
labelClassName="text-sarLinkOrange" labelClassName="text-sarLinkOrange"
label="DOB" label="DOB"
value={new Date(dbUser?.dob ?? "").toLocaleDateString("en-US", { value={new Date(dbUser?.dob ?? "").toLocaleDateString("en-US", {
@ -134,7 +138,7 @@ export default async function VerifyUserPage({
/> />
</div> </div>
</div> </div>
{( {
<div id="national-information"> <div id="national-information">
<h4 className="p-2 rounded font-semibold">National Information</h4> <h4 className="p-2 rounded font-semibold">National Information</h4>
<div className="bg-green-800/10 shadow p-2 rounded-lg border border-dashed border-green-800 space-y-1 my-2 grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="bg-green-800/10 shadow p-2 rounded-lg border border-dashed border-green-800 space-y-1 my-2 grid grid-cols-1 md:grid-cols-2 gap-2">
@ -198,7 +202,7 @@ export default async function VerifyUserPage({
</div> </div>
</div> </div>
</div> </div>
)} }
</div> </div>
</div> </div>
); );

View File

@ -1,18 +1,19 @@
import { redirect } from "next/navigation";
import { getServerSession } from "next-auth";
import { Suspense } from "react"; import { Suspense } from "react";
import { authOptions } from "@/app/auth";
import DynamicFilter from "@/components/generic-filter"; import DynamicFilter from "@/components/generic-filter";
import { UsersTable } from "@/components/user-table"; import { UsersTable } from "@/components/user-table";
export default async function AdminUsers({ export default async function AdminUsers({
searchParams, searchParams,
}: { }: {
searchParams: Promise<{ searchParams: Promise<{
query: string; [key: string]: string;
page: number;
sortBy: string;
status: string;
}>; }>;
}) { }) {
const session = await getServerSession(authOptions);
if (!session?.user?.is_admin) redirect("/devices?page=1");
return ( return (
<div> <div>
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4"> <div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
@ -73,8 +74,9 @@ export default async function AdminUsers({
{ {
label: "Unverified", label: "Unverified",
value: "false", value: "false",
}] },
} ],
},
]} ]}
/> />
</div> </div>

View File

@ -1,13 +1,11 @@
import React from 'react' import React from "react";
export default function UserWallet() { export default function UserWallet() {
return ( return (
<div> <div>
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4"> <div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
<h3 className="text-sarLinkOrange text-2xl"> <h3 className="text-sarLinkOrange text-2xl">My Wallet</h3>
My Wallet
</h3>
</div> </div>
</div> </div>
) );
} }

View File

@ -1,4 +1,4 @@
@import 'tailwindcss'; @import "tailwindcss";
@plugin "tailwindcss-animate"; @plugin "tailwindcss-animate";
@plugin "@pyncz/tailwind-mask-image"; @plugin "@pyncz/tailwind-mask-image";
@plugin "tailwindcss-motion"; @plugin "tailwindcss-motion";
@ -117,7 +117,6 @@ background-image: url("data:image/svg+xml,%3Csvg width='6' height='6' viewBox='0
--sidebar-accent-foreground: oklch(0.205 0 0); --sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0); --sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0); --sidebar-ring: oklch(0.708 0 0);
} }
.dark { .dark {
@ -152,7 +151,6 @@ background-image: url("data:image/svg+xml,%3Csvg width='6' height='6' viewBox='0
--sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%); --sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0); --sidebar-ring: oklch(0.556 0 0);
} }
@layer base { @layer base {

View File

@ -35,7 +35,9 @@ export default async function RootLayout({
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
return ( return (
<html lang="en" suppressHydrationWarning> <html lang="en" suppressHydrationWarning>
<body className={`${barlow.variable} ${bokor.variable} antialiased font-sans bg-gray-100 dark:bg-black`}> <body
className={`${barlow.variable} ${bokor.variable} antialiased font-sans bg-gray-100 dark:bg-black`}
>
<AuthProvider session={session || undefined}> <AuthProvider session={session || undefined}>
<Provider> <Provider>
<NextTopLoader color="#f49d1b" showSpinner={false} zIndex={9999} /> <NextTopLoader color="#f49d1b" showSpinner={false} zIndex={9999} />
@ -46,14 +48,11 @@ export default async function RootLayout({
enableSystem enableSystem
disableTransitionOnChange disableTransitionOnChange
> >
<QueryProvider> <QueryProvider>{children}</QueryProvider>
{children}
</QueryProvider>
</ThemeProvider> </ThemeProvider>
</Provider> </Provider>
</AuthProvider> </AuthProvider>
</body> </body>
</html> </html>
); );
} }

View File

@ -1,4 +1,4 @@
"use client" "use client";
import { Clipboard, ClipboardCheck } from "lucide-react"; import { Clipboard, ClipboardCheck } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";

View File

@ -45,9 +45,7 @@ export async function AdminDevicesTable({
apiParams.limit = limit; apiParams.limit = limit;
apiParams.offset = offset; apiParams.offset = offset;
const [error, devices] = await tryCatch( const [error, devices] = await tryCatch(getDevices(apiParams, true));
getDevices(apiParams, true),
);
if (error) { if (error) {
if (error.message === "UNAUTHORIZED") { if (error.message === "UNAUTHORIZED") {
redirect("/auth/signin"); redirect("/auth/signin");
@ -78,9 +76,7 @@ export async function AdminDevicesTable({
</TableHeader> </TableHeader>
<TableBody className="overflow-scroll"> <TableBody className="overflow-scroll">
{data?.map((device) => ( {data?.map((device) => (
<TableRow <TableRow key={device.id}>
key={device.id}
>
<TableCell> <TableCell>
<div className="flex flex-col items-start"> <div className="flex flex-col items-start">
<Link <Link
@ -96,18 +92,19 @@ export async function AdminDevicesTable({
<div className="text-muted-foreground"> <div className="text-muted-foreground">
Active until{" "} Active until{" "}
<span className="font-semibold"> <span className="font-semibold">
{new Date(device.expiry_date || "").toLocaleDateString( {new Date(
"en-US", device.expiry_date || "",
{ ).toLocaleDateString("en-US", {
month: "short", month: "short",
day: "2-digit", day: "2-digit",
year: "numeric", year: "numeric",
}, })}
)}
</span> </span>
</div> </div>
) : ( ) : (
<p className="text-muted-foreground">Device Inactive</p> <p className="text-muted-foreground">
Device Inactive
</p>
)} )}
{device.has_a_pending_payment && ( {device.has_a_pending_payment && (
<Link href={`/payments/${device.pending_payment_id}`}> <Link href={`/payments/${device.pending_payment_id}`}>
@ -121,7 +118,9 @@ export async function AdminDevicesTable({
{device.blocked_by === "ADMIN" && device.blocked && ( {device.blocked_by === "ADMIN" && device.blocked && (
<div className="p-2 rounded border my-2 bg-white dark:bg-neutral-800 shadow"> <div className="p-2 rounded border my-2 bg-white dark:bg-neutral-800 shadow">
<span className="font-semibold">Comment</span> <span className="font-semibold">Comment</span>
<p className="text-neutral-400">{device?.reason_for_blocking}</p> <p className="text-neutral-400">
{device?.reason_for_blocking}
</p>
</div> </div>
)} )}
</div> </div>
@ -129,12 +128,15 @@ export async function AdminDevicesTable({
<TableCell className="font-medium"> <TableCell className="font-medium">
<div className="flex flex-col items-start"> <div className="flex flex-col items-start">
{device?.user?.name} {device?.user?.name}
<span className="text-muted-foreground">{device?.user?.id_card}</span> <span className="text-muted-foreground">
{device?.user?.id_card}
</span>
</div> </div>
</TableCell> </TableCell>
<TableCell className="font-medium">{device.mac}</TableCell> <TableCell className="font-medium">{device.mac}</TableCell>
<TableCell className="font-medium">{device?.vendor}</TableCell> <TableCell className="font-medium">
{device?.vendor}
</TableCell>
<TableCell> <TableCell>
{!device.has_a_pending_payment && ( {!device.has_a_pending_payment && (
<BlockDeviceDialog <BlockDeviceDialog
@ -151,9 +153,7 @@ export async function AdminDevicesTable({
<TableRow> <TableRow>
<TableCell colSpan={5} className="text-muted-foreground"> <TableCell colSpan={5} className="text-muted-foreground">
{meta?.total === 1 ? ( {meta?.total === 1 ? (
<p className="text-center"> <p className="text-center">Total {meta?.total} device.</p>
Total {meta?.total} device.
</p>
) : ( ) : (
<p className="text-center"> <p className="text-center">
Total {meta?.total} devices. Total {meta?.total} devices.
@ -170,8 +170,7 @@ export async function AdminDevicesTable({
currentPage={meta?.current_page} currentPage={meta?.current_page}
/> />
</> </>
) )}
}
</div> </div>
); );
} }

View File

@ -1,4 +1,3 @@
import { Calendar } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { getTopups } from "@/actions/payment"; import { getTopups } from "@/actions/payment";
@ -12,8 +11,6 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import type { Topup } from "@/lib/backend-types";
import { cn } from "@/lib/utils";
import { tryCatch } from "@/utils/tryCatch"; import { tryCatch } from "@/utils/tryCatch";
import Pagination from "../pagination"; import Pagination from "../pagination";
import { Badge } from "../ui/badge"; import { Badge } from "../ui/badge";
@ -57,7 +54,7 @@ export async function AdminTopupsTable({
</div> </div>
) : ( ) : (
<> <>
<div className="hidden sm:block"> <div>
<Table className="overflow-scroll"> <Table className="overflow-scroll">
<TableCaption>Table of all topups.</TableCaption> <TableCaption>Table of all topups.</TableCaption>
<TableHeader> <TableHeader>
@ -71,11 +68,12 @@ export async function AdminTopupsTable({
<TableBody className="overflow-scroll"> <TableBody className="overflow-scroll">
{topups?.data?.map((topup) => ( {topups?.data?.map((topup) => (
<TableRow key={topup.id}> <TableRow key={topup.id}>
<TableCell> <TableCell>
<div className="flex flex-col items-start"> <div className="flex flex-col items-start">
{topup?.user?.name} {topup?.user?.name}
<span className="text-muted-foreground">{topup?.user?.id_card}</span> <span className="text-muted-foreground">
{topup?.user?.id_card}
</span>
</div> </div>
</TableCell> </TableCell>
<TableCell> <TableCell>
@ -101,8 +99,7 @@ export async function AdminTopupsTable({
MVR MVR
</TableCell> </TableCell>
<TableCell> <TableCell>
<div <div>
>
<div className="flex items-center gap-2 mt-2"> <div className="flex items-center gap-2 mt-2">
<Link <Link
className="font-medium hover:underline" className="font-medium hover:underline"
@ -131,11 +128,6 @@ export async function AdminTopupsTable({
</TableFooter> </TableFooter>
</Table> </Table>
</div> </div>
<div className="sm:hidden block">
{data.map((topup) => (
<MobileTopupDetails key={topup.id} topup={topup} />
))}
</div>
<Pagination <Pagination
totalPages={meta?.last_page} totalPages={meta?.last_page}
currentPage={meta?.current_page} currentPage={meta?.current_page}
@ -145,61 +137,3 @@ export async function AdminTopupsTable({
</div> </div>
); );
} }
function MobileTopupDetails({ topup }: { topup: Topup }) {
return (
<div
className={cn(
"flex flex-col items-start border rounded p-2 my-2",
topup?.paid
? "bg-green-500/10 border-dashed border-green=500"
: "bg-yellow-500/10 border-dashed border-yellow-500 dark:border-yellow-500/50",
)}
>
<div className="flex items-center gap-2">
<Calendar size={16} opacity={0.5} />
<span className="text-muted-foreground text-sm">
{new Date(topup.created_at).toLocaleDateString("en-US", {
month: "short",
day: "2-digit",
year: "numeric",
})}
</span>
</div>
<div className="flex flex-col items-start text-sm border rounded p-2 mt-2 w-full bg-white dark:bg-black">
{topup?.user?.name}
<span className="text-muted-foreground">{topup?.user?.id_card}</span>
</div>
<div className="flex items-center gap-2 mt-2">
<Link
className="font-medium hover:underline"
href={`/top-ups/${topup.id}`}
>
<Button size={"sm"} variant="outline">
View Details
</Button>
</Link>
</div>
<div className="bg-white dark:bg-black p-2 rounded mt-2 w-full border flex justify-between items-center">
<div className="block sm:hidden">
<h3 className="text-sm font-medium">Amount</h3>
<span className="text-sm text-muted-foreground">
{topup.amount.toFixed(2)} MVR
</span>
</div>
<span className="font-semibold pr-2">
{topup.paid ? (
<Badge className="bg-green-100 dark:bg-green-700" variant="outline">
{topup.status}
</Badge>
) : topup.is_expired ? (
<Badge>Expired</Badge>
) : (
<Badge variant="secondary">{topup.status}</Badge>
)}
</span>
</div>
</div>
);
}

View File

@ -54,7 +54,6 @@ export function AccountPopover() {
</Button> </Button>
</Link> </Link>
</div> </div>
</div> </div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>

View File

@ -1,6 +1,6 @@
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { getServerSession } from "next-auth"; import { getServerSession } from "next-auth";
import { NuqsAdapter } from 'nuqs/adapters/next/app' import { NuqsAdapter } from "nuqs/adapters/next/app";
import { getProfile } from "@/actions/user-actions"; import { getProfile } from "@/actions/user-actions";
import { authOptions } from "@/app/auth"; import { authOptions } from "@/app/auth";
import { DeviceCartDrawer } from "@/components/device-cart"; import { DeviceCartDrawer } from "@/components/device-cart";
@ -19,7 +19,9 @@ import { AccountPopover } from "./account-popver";
export async function ApplicationLayout({ export async function ApplicationLayout({
children, children,
}: { children: React.ReactNode }) { }: {
children: React.ReactNode;
}) {
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
if (!session) return redirect("/auth/signin"); if (!session) return redirect("/auth/signin");
@ -50,9 +52,7 @@ export async function ApplicationLayout({
/> />
<DeviceCartDrawer /> <DeviceCartDrawer />
<div className="p-4 flex flex-col flex-1 rounded-lg bg-background"> <div className="p-4 flex flex-col flex-1 rounded-lg bg-background">
<NuqsAdapter> <NuqsAdapter>{children}</NuqsAdapter>
{children}
</NuqsAdapter>
</div> </div>
</SidebarInset> </SidebarInset>
</SidebarProvider> </SidebarProvider>

View File

@ -59,7 +59,8 @@ export default function SignUpForm() {
<Link href="login" className="underline"> <Link href="login" className="underline">
login login
</Link> </Link>
</div>- </div>
-
</> </>
); );
} }
@ -72,9 +73,17 @@ export default function SignUpForm() {
{/* Logo */} {/* Logo */}
<div className="mb-8"> <div className="mb-8">
<div className="w-20 h-20 bg-transparent backdrop-blur-sm rounded-2xl flex items-center justify-center mx-auto mb-4"> <div className="w-20 h-20 bg-transparent backdrop-blur-sm rounded-2xl flex items-center justify-center mx-auto mb-4">
<Image src="/logo.png" alt="Company Logo" height={1080} width={1080} className="w-12 h-12 text-white" /> <Image
src="/logo.png"
alt="Company Logo"
height={1080}
width={1080}
className="w-12 h-12 text-white"
/>
</div> </div>
<h3 className="text-xl font-semibold dark:text-orange-100">SAR Link Portal</h3> <h3 className="text-xl font-semibold dark:text-orange-100">
SAR Link Portal
</h3>
</div> </div>
<h1 className="text-4xl font-bold mb-6">Welcome to Our Platform</h1> <h1 className="text-4xl font-bold mb-6">Welcome to Our Platform</h1>
@ -92,7 +101,10 @@ export default function SignUpForm() {
<div className="max-w-sm shadow-2xl shadow-sarLinkOrange/20 h-fit my-auto mt-2 w-full bg-white dark:bg-transparent rounded-lg m-auto backdrop-blur-lg self-center border-2 border-sarLinkOrange/10 dark:border-sarLinkOrange/50"> <div className="max-w-sm shadow-2xl shadow-sarLinkOrange/20 h-fit my-auto mt-2 w-full bg-white dark:bg-transparent rounded-lg m-auto backdrop-blur-lg self-center border-2 border-sarLinkOrange/10 dark:border-sarLinkOrange/50">
<div className="py-2 px-4 my-2 space-y-2"> <div className="py-2 px-4 my-2 space-y-2">
<div className="space-y-2"> <div className="space-y-2">
<label htmlFor="name" className="text-sm font-medium text-slate-700 dark:text-slate-300"> <label
htmlFor="name"
className="text-sm font-medium text-slate-700 dark:text-slate-300"
>
Full Name Full Name
</label> </label>
@ -105,7 +117,9 @@ export default function SignUpForm() {
name="name" name="name"
type="text" type="text"
disabled={isPending} disabled={isPending}
defaultValue={(actionState?.payload?.get("name") || "") as string} defaultValue={
(actionState?.payload?.get("name") || "") as string
}
placeholder="Full Name" placeholder="Full Name"
/> />
{actionState?.errors?.fieldErrors.name && ( {actionState?.errors?.fieldErrors.name && (
@ -115,7 +129,10 @@ export default function SignUpForm() {
)} )}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label htmlFor="id_card" className="text-sm font-medium text-slate-700 dark:text-slate-300"> <label
htmlFor="id_card"
className="text-sm font-medium text-slate-700 dark:text-slate-300"
>
ID Card ID Card
</label> </label>
<Input <Input
@ -146,7 +163,10 @@ export default function SignUpForm() {
</div> </div>
<div> <div>
<div className="space-y-2"> <div className="space-y-2">
<label htmlFor="atoll" className="text-sm font-medium text-slate-700 dark:text-slate-300"> <label
htmlFor="atoll"
className="text-sm font-medium text-slate-700 dark:text-slate-300"
>
Atoll Atoll
</label> </label>
<Select <Select
@ -154,7 +174,9 @@ export default function SignUpForm() {
onValueChange={(v) => { onValueChange={(v) => {
console.log({ v }); console.log({ v });
setAtoll( setAtoll(
atolls?.data.find((atoll) => atoll.id === Number.parseInt(v)), atolls?.data.find(
(atoll) => atoll.id === Number.parseInt(v),
),
); );
}} }}
name="atoll_id" name="atoll_id"
@ -181,7 +203,10 @@ export default function SignUpForm() {
</Select> </Select>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label htmlFor="island" className="text-sm font-medium text-slate-700 dark:text-slate-300"> <label
htmlFor="island"
className="text-sm font-medium text-slate-700 dark:text-slate-300"
>
Island Island
</label> </label>
<Select disabled={isPending} name="island_id"> <Select disabled={isPending} name="island_id">
@ -192,7 +217,10 @@ export default function SignUpForm() {
<SelectGroup> <SelectGroup>
<SelectLabel>Islands</SelectLabel> <SelectLabel>Islands</SelectLabel>
{atoll?.islands?.map((island) => ( {atoll?.islands?.map((island) => (
<SelectItem key={island.id} value={island.id.toString()}> <SelectItem
key={island.id}
value={island.id.toString()}
>
{island.name} {island.name}
</SelectItem> </SelectItem>
))} ))}
@ -208,7 +236,10 @@ export default function SignUpForm() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label htmlFor="address" className="text-sm font-medium text-slate-700 dark:text-slate-300"> <label
htmlFor="address"
className="text-sm font-medium text-slate-700 dark:text-slate-300"
>
Address Address
</label> </label>
<Input <Input
@ -233,7 +264,10 @@ export default function SignUpForm() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label htmlFor="dob" className="text-sm font-medium text-slate-700 dark:text-slate-300"> <label
htmlFor="dob"
className="text-sm font-medium text-slate-700 dark:text-slate-300"
>
Date of Birth Date of Birth
</label> </label>
<Input <Input
@ -244,7 +278,9 @@ export default function SignUpForm() {
)} )}
name="dob" name="dob"
disabled={isPending} disabled={isPending}
defaultValue={(actionState?.payload?.get("dob") || "") as string} defaultValue={
(actionState?.payload?.get("dob") || "") as string
}
type="date" type="date"
placeholder="Date of birth" placeholder="Date of birth"
/> />
@ -260,7 +296,10 @@ export default function SignUpForm() {
)} )}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label htmlFor="accNo" className="text-sm font-medium text-slate-700 dark:text-slate-300"> <label
htmlFor="accNo"
className="text-sm font-medium text-slate-700 dark:text-slate-300"
>
Account Number Account Number
</label> </label>
@ -273,7 +312,9 @@ export default function SignUpForm() {
name="accNo" name="accNo"
type="number" type="number"
disabled={isPending} disabled={isPending}
defaultValue={(actionState?.payload?.get("accNo") || "") as string} defaultValue={
(actionState?.payload?.get("accNo") || "") as string
}
placeholder="Account no" placeholder="Account no"
/> />
{actionState?.errors?.fieldErrors.accNo && ( {actionState?.errors?.fieldErrors.accNo && (
@ -283,7 +324,10 @@ export default function SignUpForm() {
)} )}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label htmlFor="phone_number" className="text-sm font-medium text-slate-700 dark:text-slate-300"> <label
htmlFor="phone_number"
className="text-sm font-medium text-slate-700 dark:text-slate-300"
>
Phone Number Phone Number
</label> </label>
<Input <Input
@ -316,7 +360,8 @@ export default function SignUpForm() {
<input <input
type="checkbox" type="checkbox"
defaultChecked={ defaultChecked={
((actionState?.payload?.get("terms") || "") as string) === "on" ((actionState?.payload?.get("terms") || "") as string) ===
"on"
} }
name="terms" name="terms"
id="terms" id="terms"
@ -340,7 +385,8 @@ export default function SignUpForm() {
<input <input
type="checkbox" type="checkbox"
defaultChecked={ defaultChecked={
((actionState?.payload?.get("policy") || "") as string) === "on" ((actionState?.payload?.get("policy") || "") as string) ===
"on"
} }
name="policy" name="policy"
id="terms" id="terms"
@ -372,9 +418,7 @@ export default function SignUpForm() {
</Link> </Link>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
); );
} }

View File

@ -20,7 +20,9 @@ const OTPSchema = z.object({
export default function VerifyOTPForm({ export default function VerifyOTPForm({
phone_number, phone_number,
}: { phone_number: string }) { }: {
phone_number: string;
}) {
const [isPending, startTransition] = useTransition(); const [isPending, startTransition] = useTransition();
const router = useRouter(); const router = useRouter();
console.log("verification in OTP form", phone_number); console.log("verification in OTP form", phone_number);

View File

@ -12,7 +12,9 @@ import { useActionState } from "react";
export default function VerifyRegistrationOTPForm({ export default function VerifyRegistrationOTPForm({
phone_number, phone_number,
}: { phone_number: string }) { }: {
phone_number: string;
}) {
console.log("verification in OTP form", phone_number); console.log("verification in OTP form", phone_number);
const searchParams = useSearchParams(); const searchParams = useSearchParams();

View File

@ -10,14 +10,18 @@ import { Button } from "../ui/button";
export default function CancelPaymentButton({ export default function CancelPaymentButton({
paymentId, paymentId,
}: { paymentId: string }) { }: {
paymentId: string;
}) {
const router = useRouter(); const router = useRouter();
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
return ( return (
<Button <Button
onClick={async () => { onClick={async () => {
setLoading(true); setLoading(true);
const [error, payment] = await tryCatch(cancelPayment({ id: paymentId })); const [error, payment] = await tryCatch(
cancelPayment({ id: paymentId }),
);
console.log(payment); console.log(payment);
if (error) { if (error) {
toast.error(error.message); toast.error(error.message);

View File

@ -8,9 +8,7 @@ import { cancelTopup } from "@/actions/payment";
import { tryCatch } from "@/utils/tryCatch"; import { tryCatch } from "@/utils/tryCatch";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
export default function CancelTopupButton({ export default function CancelTopupButton({ topupId }: { topupId: string }) {
topupId,
}: { topupId: string }) {
const router = useRouter(); const router = useRouter();
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
return ( return (
@ -25,7 +23,7 @@ export default function CancelTopupButton({
toast.success("Topup cancelled successfully!", { toast.success("Topup cancelled successfully!", {
description: `Your topup of ${topup?.amount} MVR has been cancelled.`, description: `Your topup of ${topup?.amount} MVR has been cancelled.`,
closeButton: true, closeButton: true,
}) });
router.replace("/top-ups"); router.replace("/top-ups");
} }
}} }}

View File

@ -1,57 +1,67 @@
'use client' "use client";
import { usePathname, useRouter } from 'next/navigation' import { usePathname, useRouter } from "next/navigation";
import { useEffect, useState } from 'react' import { useEffect, useState } from "react";
import { Progress } from '@/components/ui/progress' import { Progress } from "@/components/ui/progress";
const calculateTimeLeft = (expiresAt: string) => { const calculateTimeLeft = (expiresAt: string) => {
const now = Date.now() const now = Date.now();
const expirationTime = new Date(expiresAt).getTime() const expirationTime = new Date(expiresAt).getTime();
return Math.max(0, Math.floor((expirationTime - now) / 1000)) return Math.max(0, Math.floor((expirationTime - now) / 1000));
} };
const HumanizeTimeLeft = (seconds: number) => { const HumanizeTimeLeft = (seconds: number) => {
const minutes = Math.floor(seconds / 60) const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60 const remainingSeconds = seconds % 60;
return `${minutes}m ${remainingSeconds}s` return `${minutes}m ${remainingSeconds}s`;
} };
export default function ExpiryCountDown({ expiresAt, expiryLabel }: { expiresAt: string, expiryLabel: string }) { export default function ExpiryCountDown({
const [timeLeft, setTimeLeft] = useState(calculateTimeLeft(expiresAt)) expiresAt,
const [mounted, setMounted] = useState(false) expiryLabel,
const router = useRouter() }: {
const pathname = usePathname() expiresAt: string;
expiryLabel: string;
}) {
const [timeLeft, setTimeLeft] = useState(calculateTimeLeft(expiresAt));
const [mounted, setMounted] = useState(false);
const router = useRouter();
const pathname = usePathname();
useEffect(() => { useEffect(() => {
setMounted(true) setMounted(true);
}, []) }, []);
useEffect(() => { useEffect(() => {
const timer = setInterval(() => { const timer = setInterval(() => {
setTimeLeft(calculateTimeLeft(expiresAt)) setTimeLeft(calculateTimeLeft(expiresAt));
}, 1000) }, 1000);
return () => clearInterval(timer) return () => clearInterval(timer);
}, [expiresAt]) }, [expiresAt]);
useEffect(() => { useEffect(() => {
if (timeLeft <= 0) { if (timeLeft <= 0) {
router.replace(pathname) router.replace(pathname);
} }
}, [timeLeft, router, pathname]) }, [timeLeft, router, pathname]);
if (!mounted) { if (!mounted) {
return null return null;
} }
return ( return (
<div className='overflow-clip relative mx-2 p-4 rounded-md border border-dashed flex items-center justify-between text-muted-foreground'> <div className="overflow-clip relative mx-2 p-4 rounded-md border border-dashed flex items-center justify-between text-muted-foreground">
<div className='absolute inset-0 title-bg mask-b-from-0' /> <div className="absolute inset-0 title-bg mask-b-from-0" />
{timeLeft ? ( {timeLeft ? (
<span>Time left: {HumanizeTimeLeft(timeLeft)}</span> <span>Time left: {HumanizeTimeLeft(timeLeft)}</span>
) : ( ) : (
<span>{expiryLabel} has expired.</span> <span>{expiryLabel} has expired.</span>
)} )}
{timeLeft > 0 && ( {timeLeft > 0 && (
<Progress className='absolute bottom-0 left-0 right-0' color='#f49b5b' value={timeLeft / 600 * 100} /> <Progress
className="absolute bottom-0 left-0 right-0"
color="#f49b5b"
value={(timeLeft / 600) * 100}
/>
)} )}
</div> </div>
) );
} }

View File

@ -46,7 +46,10 @@ export default function BlockDeviceDialog({
parentalControl?: boolean; parentalControl?: boolean;
}) { }) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [state, formAction, isPending] = useActionState(blockDeviceAction, initialState); const [state, formAction, isPending] = useActionState(
blockDeviceAction,
initialState,
);
const [isTransitioning, startTransition] = useTransition(); const [isTransitioning, startTransition] = useTransition();
const handleSimpleBlock = () => { const handleSimpleBlock = () => {
@ -99,7 +102,11 @@ export default function BlockDeviceDialog({
// If device is not blocked and user is not admin, show simple block button // If device is not blocked and user is not admin, show simple block button
if (!device.blocked && parentalControl) { if (!device.blocked && parentalControl) {
return ( return (
<Button onClick={handleSimpleBlock} disabled={isLoading} variant="destructive"> <Button
onClick={handleSimpleBlock}
disabled={isLoading}
variant="destructive"
>
<ShieldBan /> <ShieldBan />
{isLoading ? <TextShimmer>Blocking</TextShimmer> : "Block"} {isLoading ? <TextShimmer>Blocking</TextShimmer> : "Block"}
</Button> </Button>
@ -137,10 +144,13 @@ export default function BlockDeviceDialog({
rows={10} rows={10}
name="reason_for_blocking" name="reason_for_blocking"
id="reason_for_blocking" id="reason_for_blocking"
defaultValue={(state?.payload?.get("reason_for_blocking") || "") as string} defaultValue={
(state?.payload?.get("reason_for_blocking") || "") as string
}
className={cn( className={cn(
"col-span-5 mt-2", "col-span-5 mt-2",
(state.fieldErrors?.reason_for_blocking) && "ring-2 ring-red-500", state.fieldErrors?.reason_for_blocking &&
"ring-2 ring-red-500",
)} )}
/> />
<span className="text-sm text-red-500"> <span className="text-sm text-red-500">

View File

@ -12,7 +12,11 @@ export default function ClickableRow({
device, device,
parentalControl, parentalControl,
admin = false, admin = false,
}: { device: Device; parentalControl?: boolean; admin?: boolean }) { }: {
device: Device;
parentalControl?: boolean;
admin?: boolean;
}) {
const [devices, setDeviceCart] = useAtom(deviceCartAtom); const [devices, setDeviceCart] = useAtom(deviceCartAtom);
return ( return (

View File

@ -12,7 +12,11 @@ import { Badge } from "./ui/badge";
export default function DeviceCard({ export default function DeviceCard({
device, device,
parentalControl, parentalControl,
}: { device: Device; parentalControl?: boolean, isAdmin?: boolean }) { }: {
device: Device;
parentalControl?: boolean;
isAdmin?: boolean;
}) {
const [devices, setDeviceCart] = useAtom(deviceCartAtom); const [devices, setDeviceCart] = useAtom(deviceCartAtom);
const isChecked = devices.some((d) => d.id === device.id); const isChecked = devices.some((d) => d.id === device.id);
@ -46,7 +50,10 @@ export default function DeviceCard({
<div className=""> <div className="">
<div className="font-semibold flex flex-col items-start gap-2 mb-2"> <div className="font-semibold flex flex-col items-start gap-2 mb-2">
<Link <Link
className={cn("font-medium hover:underline ml-0.5", device.is_active ? "text-green-600" : "")} className={cn(
"font-medium hover:underline ml-0.5",
device.is_active ? "text-green-600" : "",
)}
href={`/devices/${device.id}`} href={`/devices/${device.id}`}
> >
{device.name} {device.name}
@ -59,7 +66,6 @@ export default function DeviceCard({
</Badge> </Badge>
</div> </div>
{device.is_active ? ( {device.is_active ? (
<div className="text-muted-foreground ml-0.5"> <div className="text-muted-foreground ml-0.5">
Active until{" "} Active until{" "}

View File

@ -24,7 +24,8 @@ export function DeviceCartDrawer() {
variant="secondary" variant="secondary"
> >
<MonitorSmartphone /> <MonitorSmartphone />
Pay {devices.length > 0 && `(${devices.length})`} {devices.length > 1 ? "devices" : "device"} Pay {devices.length > 0 && `(${devices.length})`}{" "}
{devices.length > 1 ? "devices" : "device"}
</Button> </Button>
<Button <Button
variant={"destructive"} variant={"destructive"}

View File

@ -52,9 +52,7 @@ export async function DevicesTable({
} }
apiParams.limit = limit; apiParams.limit = limit;
apiParams.offset = offset; apiParams.offset = offset;
const [error, devices] = await tryCatch( const [error, devices] = await tryCatch(getDevices(apiParams));
getDevices(apiParams),
);
if (error) { if (error) {
if (error.message === "UNAUTHORIZED") { if (error.message === "UNAUTHORIZED") {
redirect("/auth/signin"); redirect("/auth/signin");
@ -96,9 +94,7 @@ export async function DevicesTable({
<TableRow> <TableRow>
<TableCell colSpan={4} className="text-muted-foreground"> <TableCell colSpan={4} className="text-muted-foreground">
{meta?.total === 1 ? ( {meta?.total === 1 ? (
<p className="text-center"> <p className="text-center">Total {meta?.total} device.</p>
Total {meta?.total} device.
</p>
) : ( ) : (
<p className="text-center"> <p className="text-center">
Total {meta?.total} devices. Total {meta?.total} devices.
@ -123,8 +119,7 @@ export async function DevicesTable({
currentPage={meta?.current_page} currentPage={meta?.current_page}
/> />
</> </>
) )}
}
</div> </div>
); );
} }

View File

@ -1,12 +1,11 @@
"use client"; "use client";
import { import { BadgeDollarSign, Loader2, Wallet } from "lucide-react";
BadgeDollarSign,
Loader2,
Wallet
} from "lucide-react";
import { useActionState, useEffect } from "react"; import { useActionState, useEffect } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { type VerifyDevicePaymentState, verifyDevicePayment } from "@/actions/payment"; import {
type VerifyDevicePaymentState,
verifyDevicePayment,
} from "@/actions/payment";
import { import {
Table, Table,
TableBody, TableBody,
@ -29,9 +28,16 @@ const initialState: VerifyDevicePaymentState = {
export default function DevicesToPay({ export default function DevicesToPay({
payment, payment,
user, user,
disabled disabled,
}: { payment?: Payment; user?: User, disabled?: boolean }) { }: {
const [state, formAction, isPending] = useActionState(verifyDevicePayment, initialState); payment?: Payment;
user?: User;
disabled?: boolean;
}) {
const [state, formAction, isPending] = useActionState(
verifyDevicePayment,
initialState,
);
// Handle toast notifications based on state changes // Handle toast notifications based on state changes
useEffect(() => { useEffect(() => {
@ -40,7 +46,11 @@ export default function DevicesToPay({
closeButton: true, closeButton: true,
description: state.message, description: state.message,
}); });
} else if (!state.success && state.message && state.message !== initialState.message) { } else if (
!state.success &&
state.message &&
state.message !== initialState.message
) {
toast.error("Payment Verification Failed", { toast.error("Payment Verification Failed", {
closeButton: true, closeButton: true,
description: state.message, description: state.message,
@ -101,7 +111,11 @@ export default function DevicesToPay({
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{isWalletPayVisible && ( {isWalletPayVisible && (
<form action={formAction}> <form action={formAction}>
<input type="hidden" name="paymentId" value={payment?.id ?? ""} /> <input
type="hidden"
name="paymentId"
value={payment?.id ?? ""}
/>
<input type="hidden" name="method" value="WALLET" /> <input type="hidden" name="method" value="WALLET" />
<Button <Button
disabled={isPending} disabled={isPending}
@ -109,13 +123,19 @@ export default function DevicesToPay({
variant={"secondary"} variant={"secondary"}
size={"lg"} size={"lg"}
> >
{isPending ? "Processing payment..." : "Pay with wallet"} {isPending
? "Processing payment..."
: "Pay with wallet"}
<Wallet /> <Wallet />
</Button> </Button>
</form> </form>
)} )}
<form action={formAction}> <form action={formAction}>
<input type="hidden" name="paymentId" value={payment?.id ?? ""} /> <input
type="hidden"
name="paymentId"
value={payment?.id ?? ""}
/>
<input type="hidden" name="method" value="TRANSFER" /> <input type="hidden" name="method" value="TRANSFER" />
<Button <Button
disabled={isPending || disabled} disabled={isPending || disabled}
@ -139,14 +159,17 @@ export default function DevicesToPay({
<TableRow> <TableRow>
<TableCell>Payment created</TableCell> <TableCell>Payment created</TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
{new Date(payment?.created_at ?? "").toLocaleDateString("en-US", { {new Date(payment?.created_at ?? "").toLocaleDateString(
"en-US",
{
month: "short", month: "short",
day: "2-digit", day: "2-digit",
year: "numeric", year: "numeric",
minute: "2-digit", minute: "2-digit",
hour: "2-digit", hour: "2-digit",
second: "2-digit", second: "2-digit",
})} },
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
@ -160,7 +183,6 @@ export default function DevicesToPay({
<TableCell className="text-right text-xl"> <TableCell className="text-right text-xl">
{payment?.number_of_months} Months {payment?.number_of_months} Months
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>
<TableFooter> <TableFooter>
@ -176,5 +198,3 @@ export default function DevicesToPay({
</div> </div>
); );
} }

View File

@ -1,13 +1,22 @@
"use client" "use client";
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer"; import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ListFilter, Loader2, X } from "lucide-react"; import { ListFilter, Loader2, X } from "lucide-react";
import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useQueryState } from "nuqs"; import { useQueryState } from "nuqs";
import { useState, useTransition } from 'react'; import { useState, useTransition } from "react";
export default function DeviceFilter() { export default function DeviceFilter() {
const { replace } = useRouter(); const { replace } = useRouter();
@ -17,16 +26,15 @@ export default function DeviceFilter() {
const pathname = usePathname(); const pathname = usePathname();
const params = new URLSearchParams(searchParams.toString()); const params = new URLSearchParams(searchParams.toString());
const [urlInputName] = useQueryState("name", { const [urlInputName] = useQueryState("name", {
clearOnDefault: true clearOnDefault: true,
}) });
const [urlInputMac] = useQueryState("mac", { const [urlInputMac] = useQueryState("mac", {
clearOnDefault: true clearOnDefault: true,
}) });
const [urlInputVendor] = useQueryState("vendor", { const [urlInputVendor] = useQueryState("vendor", {
clearOnDefault: true clearOnDefault: true,
}) });
// Local state for input fields // Local state for input fields
const [inputName, setInputName] = useState(urlInputName ?? ""); const [inputName, setInputName] = useState(urlInputName ?? "");
@ -34,16 +42,29 @@ export default function DeviceFilter() {
const [inputVendor, setInputVendor] = useState(urlInputVendor ?? ""); const [inputVendor, setInputVendor] = useState(urlInputVendor ?? "");
// Map filter keys to their state setters // Map filter keys to their state setters
const filterSetters: Record<string, React.Dispatch<React.SetStateAction<string>>> = { const filterSetters: Record<
string,
React.Dispatch<React.SetStateAction<string>>
> = {
name: setInputName, name: setInputName,
mac: setInputMac, mac: setInputMac,
vendor: setInputVendor, vendor: setInputVendor,
}; };
function handleSearch({ name, mac, vendor }: { name: string; mac: string; vendor: string }) { function handleSearch({
name,
if (name) params.set("name", name); else params.delete("name"); mac,
if (mac) params.set("mac", mac); else params.delete("mac"); vendor,
if (vendor) params.set("vendor", vendor); else params.delete("vendor"); }: {
name: string;
mac: string;
vendor: string;
}) {
if (name) params.set("name", name);
else params.delete("name");
if (mac) params.set("mac", mac);
else params.delete("mac");
if (vendor) params.set("vendor", vendor);
else params.delete("vendor");
params.set("page", "1"); params.set("page", "1");
startTransition(() => { startTransition(() => {
@ -54,12 +75,16 @@ export default function DeviceFilter() {
const appliedFilters = searchParams const appliedFilters = searchParams
.toString() .toString()
.split("&") .split("&")
.filter((filter) => !filter.startsWith("page=") && filter) .filter((filter) => !filter.startsWith("page=") && filter);
return ( return (
<div className="flex flex-col items-start justify-start gap-2 w-full"> <div className="flex flex-col items-start justify-start gap-2 w-full">
<Drawer open={isOpen} onOpenChange={setIsOpen}> <Drawer open={isOpen} onOpenChange={setIsOpen}>
<DrawerTrigger asChild> <DrawerTrigger asChild>
<Button className="w-full sm:w-48 flex items-end justify-between" onClick={() => setIsOpen(!isOpen)} variant="outline"> <Button
className="w-full sm:w-48 flex items-end justify-between"
onClick={() => setIsOpen(!isOpen)}
variant="outline"
>
Filter Filter
<ListFilter /> <ListFilter />
</Button> </Button>
@ -69,9 +94,7 @@ export default function DeviceFilter() {
<DrawerHeader> <DrawerHeader>
<DrawerTitle>Device Filters</DrawerTitle> <DrawerTitle>Device Filters</DrawerTitle>
<DrawerDescription asChild> <DrawerDescription asChild>
<div> <div>Select your desired filters here</div>
Select your desired filters here
</div>
</DrawerDescription> </DrawerDescription>
</DrawerHeader> </DrawerHeader>
@ -79,17 +102,17 @@ export default function DeviceFilter() {
<Input <Input
placeholder="Device name ..." placeholder="Device name ..."
value={inputName || searchParams.get("name") || ""} value={inputName || searchParams.get("name") || ""}
onChange={e => setInputName(e.target.value)} onChange={(e) => setInputName(e.target.value)}
/> />
<Input <Input
placeholder="Device Mac address ..." placeholder="Device Mac address ..."
value={inputMac || searchParams.get("mac") || ""} value={inputMac || searchParams.get("mac") || ""}
onChange={e => setInputMac(e.target.value)} onChange={(e) => setInputMac(e.target.value)}
/> />
<Input <Input
placeholder="Device vendor ..." placeholder="Device vendor ..."
value={inputVendor || searchParams.get("vendor") || ""} value={inputVendor || searchParams.get("vendor") || ""}
onChange={e => setInputVendor(e.target.value)} onChange={(e) => setInputVendor(e.target.value)}
/> />
</div> </div>
<DrawerFooter className="max-w-sm mx-auto"> <DrawerFooter className="max-w-sm mx-auto">
@ -107,17 +130,18 @@ export default function DeviceFilter() {
<Loader2 className="ml-2 animate-spin" /> <Loader2 className="ml-2 animate-spin" />
</> </>
) : ( ) : (
<> <>Apply Filters</>
Apply Filters
</>
)} )}
</Button> </Button>
<Button variant="secondary" onClick={() => { <Button
variant="secondary"
onClick={() => {
setInputName(""); setInputName("");
setInputMac(""); setInputMac("");
setInputVendor(""); setInputVendor("");
replace(pathname) replace(pathname);
}}> }}
>
Clear Filters Clear Filters
</Button> </Button>
@ -133,11 +157,13 @@ export default function DeviceFilter() {
<Badge <Badge
aria-disabled={disabled} aria-disabled={disabled}
variant={"outline"} variant={"outline"}
className={cn("flex p-2 gap-2 items-center justify-between", { "opacity-50 pointer-events-none": disabled })} className={cn("flex p-2 gap-2 items-center justify-between", {
key={filter}> "opacity-50 pointer-events-none": disabled,
})}
key={filter}
>
<span className="text-md">{prettyPrintFilter(filter)}</span> <span className="text-md">{prettyPrintFilter(filter)}</span>
{ {disabled ? (
disabled ? (
<Loader2 className="animate-spin" size={16} /> <Loader2 className="animate-spin" size={16} />
) : ( ) : (
<X <X
@ -157,8 +183,7 @@ export default function DeviceFilter() {
> >
Remove Remove
</X> </X>
) )}
}
</Badge> </Badge>
))} ))}
</div> </div>
@ -166,18 +191,28 @@ export default function DeviceFilter() {
); );
} }
function prettyPrintFilter(filter: string) { function prettyPrintFilter(filter: string) {
const [key, value] = filter.split("="); const [key, value] = filter.split("=");
switch (key) { switch (key) {
case "name": case "name":
return <p>Device Name: <span className="text-muted-foreground">{value}</span></p>; return (
<p>
Device Name: <span className="text-muted-foreground">{value}</span>
</p>
);
case "mac": case "mac":
return <p>MAC Address: <span className="text-muted-foreground">{value}</span></p>; return (
<p>
MAC Address: <span className="text-muted-foreground">{value}</span>
</p>
);
case "vendor": case "vendor":
return <p>Vendor: <span className="text-muted-foreground">{value}</span></p>; return (
<p>
Vendor: <span className="text-muted-foreground">{value}</span>
</p>
);
default: default:
return filter; return filter;
} }
} }

View File

@ -1,9 +1,9 @@
import React from 'react' import React from "react";
import { Loader2 } from 'lucide-react' import { Loader2 } from "lucide-react";
export default function FullPageLoader() { export default function FullPageLoader() {
return ( return (
<div className='flex items-center justify-center h-screen'> <div className="flex items-center justify-center h-screen">
<Loader2 className='animate-spin' /> <Loader2 className="animate-spin" />
</div> </div>
) );
} }

View File

@ -181,11 +181,10 @@ export default function DynamicFilter<
const maxValue = searchParams.get(`${input.name}_max`); const maxValue = searchParams.get(`${input.name}_max`);
const parsedMin = minValue ? Number(minValue) : input.min; const parsedMin = minValue ? Number(minValue) : input.min;
const parsedMax = maxValue ? Number(maxValue) : input.max; const parsedMax = maxValue ? Number(maxValue) : input.max;
(initialState as FilterValues<TFilterKeys, TInputs>)[input.name] = (initialState as FilterValues<TFilterKeys, TInputs>)[input.name] = [
[parsedMin, parsedMax] as FilterValues< parsedMin,
TFilterKeys, parsedMax,
TInputs ] as FilterValues<TFilterKeys, TInputs>[typeof input.name];
>[typeof input.name];
} else { } else {
(initialState as FilterValues<TFilterKeys, TInputs>)[input.name] = (initialState as FilterValues<TFilterKeys, TInputs>)[input.name] =
(urlValue || "") as FilterValues< (urlValue || "") as FilterValues<
@ -227,8 +226,7 @@ export default function DynamicFilter<
}; };
// Handler for dual range slider changes // Handler for dual range slider changes
const handleDualRangeChange = const handleDualRangeChange = (name: TFilterKeys) => (values: number[]) => {
(name: TFilterKeys) => (values: number[]) => {
setInputValues((prev) => ({ setInputValues((prev) => ({
...prev, ...prev,
[name]: values, [name]: values,
@ -253,7 +251,10 @@ export default function DynamicFilter<
} else if (input.type === "dual-range-slider") { } else if (input.type === "dual-range-slider") {
const rangeValues = value as number[]; const rangeValues = value as number[];
// Only set params if values are different from the default min/max // Only set params if values are different from the default min/max
if (rangeValues.length === 2 && (rangeValues[0] !== input.min || rangeValues[1] !== input.max)) { if (
rangeValues.length === 2 &&
(rangeValues[0] !== input.min || rangeValues[1] !== input.max)
) {
newParams.set(`${input.name}_min`, rangeValues[0].toString()); newParams.set(`${input.name}_min`, rangeValues[0].toString());
newParams.set(`${input.name}_max`, rangeValues[1].toString()); newParams.set(`${input.name}_max`, rangeValues[1].toString());
} else { } else {
@ -290,8 +291,15 @@ export default function DynamicFilter<
} else if (input.type === "dual-range-slider") { } else if (input.type === "dual-range-slider") {
(clearedInputState as FilterValues<TFilterKeys, TInputs>)[ (clearedInputState as FilterValues<TFilterKeys, TInputs>)[
input.name as TFilterKeys input.name as TFilterKeys
] = [input.min, input.max] as FilterValues<TFilterKeys, TInputs>[typeof input.name]; ] = [input.min, input.max] as FilterValues<
} else if (input.type === "radio-group" || input.type === "string" || input.type === "number") { TFilterKeys,
TInputs
>[typeof input.name];
} else if (
input.type === "radio-group" ||
input.type === "string" ||
input.type === "number"
) {
(clearedInputState as FilterValues<TFilterKeys, TInputs>)[ (clearedInputState as FilterValues<TFilterKeys, TInputs>)[
input.name as TFilterKeys input.name as TFilterKeys
] = "" as FilterValues<TFilterKeys, TInputs>[typeof input.name]; ] = "" as FilterValues<TFilterKeys, TInputs>[typeof input.name];
@ -378,7 +386,9 @@ export default function DynamicFilter<
} }
if (config.type === "dual-range-slider") { if (config.type === "dual-range-slider") {
const rangeValues = value as number[]; const rangeValues = value as number[];
const formatLabel = config.formatLabel || ((val: number | undefined) => val?.toString() || ""); const formatLabel =
config.formatLabel ||
((val: number | undefined) => val?.toString() || "");
return ( return (
<p> <p>
{config.label}:{" "} {config.label}:{" "}
@ -396,14 +406,16 @@ export default function DynamicFilter<
const displayValue = option?.label || stringValue; const displayValue = option?.label || stringValue;
return ( return (
<p> <p>
{config.label}: <span className="text-muted-foreground">{displayValue}</span> {config.label}:{" "}
<span className="text-muted-foreground">{displayValue}</span>
</p> </p>
); );
} }
// For other values, display as normal // For other values, display as normal
return ( return (
<p> <p>
{config.label}: <span className="text-muted-foreground">{stringValue}</span> {config.label}:{" "}
<span className="text-muted-foreground">{stringValue}</span>
</p> </p>
); );
} }
@ -501,7 +513,12 @@ export default function DynamicFilter<
</Label> </Label>
<div className="px-3 py-2 mt-5"> <div className="px-3 py-2 mt-5">
<DualRangeSlider <DualRangeSlider
label={(value) => <span className="text-xs">{value}{input.sliderLabel}</span>} label={(value) => (
<span className="text-xs">
{value}
{input.sliderLabel}
</span>
)}
value={inputValues[input.name] as number[]} value={inputValues[input.name] as number[]}
onValueChange={handleDualRangeChange(input.name)} onValueChange={handleDualRangeChange(input.name)}
min={input.min} min={input.min}

View File

@ -1,21 +1,16 @@
import { PhoneIcon } from "lucide-react" import { PhoneIcon } from "lucide-react";
import Link from "next/link" import Link from "next/link";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
} from "@/components/ui/accordion" } from "@/components/ui/accordion";
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button";
export function GetMacAccordion() { export function GetMacAccordion() {
return ( return (
<Accordion <Accordion type="single" collapsible className="w-full">
type="single"
collapsible
className="w-full"
>
<AccordionItem value="item-1"> <AccordionItem value="item-1">
<AccordionTrigger>How do I find my MAC Address?</AccordionTrigger> <AccordionTrigger>How do I find my MAC Address?</AccordionTrigger>
<AccordionContent className="flex flex-col gap-4 text-start"> <AccordionContent className="flex flex-col gap-4 text-start">
@ -46,19 +41,15 @@ export function GetMacAccordion() {
<AccordionItem value="windows"> <AccordionItem value="windows">
<AccordionTrigger>Windows Laptop</AccordionTrigger> <AccordionTrigger>Windows Laptop</AccordionTrigger>
<AccordionContent> <AccordionContent>
Settings Network and Internet Wi-Fi Hardware Settings Network and Internet Wi-Fi Hardware Properties
Properties Physical address (MAC): Physical address (MAC):
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
<AccordionItem value="other"> <AccordionItem value="other">
<AccordionTrigger>Other Device</AccordionTrigger> <AccordionTrigger>Other Device</AccordionTrigger>
<AccordionContent> <AccordionContent>
<p> <p>Please contact SAR Link for assistance.</p>
Please contact SAR Link for assistance. <Link href="tel:+9609198026">
</p>
<Link
href="tel:+9609198026"
>
<Button className="mt-4"> <Button className="mt-4">
<PhoneIcon className="mr-2" /> <PhoneIcon className="mr-2" />
9198026 9198026
@ -70,5 +61,5 @@ export function GetMacAccordion() {
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
) );
} }

View File

@ -1,19 +1,33 @@
import { CheckCheck, X } from 'lucide-react'; import { CheckCheck, X } from "lucide-react";
import { cn } from '@/lib/utils'; import { cn } from "@/lib/utils";
export default function InputReadOnly({ label, value, labelClassName, className, showCheck = true, checkTrue = false }: { export default function InputReadOnly({
label,
value,
labelClassName,
className,
showCheck = true,
checkTrue = false,
}: {
label: string; label: string;
value: string; value: string;
labelClassName?: string; labelClassName?: string;
className?: string; className?: string;
showCheck?: boolean; showCheck?: boolean;
checkTrue?: boolean checkTrue?: boolean;
}) { }) {
return ( return (
<div className={cn("relative flex items-center justify-between rounded-lg border border-input bg-background shadow-sm shadow-black/5 transition-shadow focus-within:border-ring focus-within:outline-none focus-within:ring-[3px] focus-within:ring-ring/20 has-disabled:cursor-not-allowed has-disabled:opacity-80 [&:has(input:is(:disabled))_*]:pointer-events-none col-span-2 sm:col-span-1", className)}> <div
className={cn(
"relative flex items-center justify-between rounded-lg border border-input bg-background shadow-sm shadow-black/5 transition-shadow focus-within:border-ring focus-within:outline-none focus-within:ring-[3px] focus-within:ring-ring/20 has-disabled:cursor-not-allowed has-disabled:opacity-80 [&:has(input:is(:disabled))_*]:pointer-events-none col-span-2 sm:col-span-1",
className,
)}
>
<div> <div>
<label
<label htmlFor="input-33" className={cn("block px-3 pt-2 text-xs font-medium", labelClassName)}> htmlFor="input-33"
className={cn("block px-3 pt-2 text-xs font-medium", labelClassName)}
>
{label} {label}
</label> </label>
<input <input
@ -29,13 +43,12 @@ export default function InputReadOnly({ label, value, labelClassName, className,
{showCheck && ( {showCheck && (
<div> <div>
{checkTrue ? ( {checkTrue ? (
<CheckCheck className='mx-4 text-green-500' /> <CheckCheck className="mx-4 text-green-500" />
) : ( ) : (
<X className='mx-4 text-red-500' /> <X className="mx-4 text-red-500" />
)} )}
</div> </div>
)} )}
</div> </div>
);
)
} }

View File

@ -9,7 +9,6 @@ import {
NumberField, NumberField,
} from "react-aria-components"; } from "react-aria-components";
export default function NumberInput({ export default function NumberInput({
maxAllowed, maxAllowed,
label, label,
@ -17,7 +16,14 @@ export default function NumberInput({
onChange, onChange,
className, className,
isDisabled, isDisabled,
}: { maxAllowed?: number, label: string; value: number; onChange: (value: number) => void, className?: string, isDisabled?: boolean }) { }: {
maxAllowed?: number;
label: string;
value: number;
onChange: (value: number) => void;
className?: string;
isDisabled?: boolean;
}) {
useEffect(() => { useEffect(() => {
if (maxAllowed) { if (maxAllowed) {
if (value > maxAllowed) { if (value > maxAllowed) {
@ -26,7 +32,13 @@ export default function NumberInput({
} }
}, [maxAllowed, value, onChange]); }, [maxAllowed, value, onChange]);
return ( return (
<NumberField isDisabled={isDisabled} className={cn(className)} value={value} minValue={0} onChange={onChange}> <NumberField
isDisabled={isDisabled}
className={cn(className)}
value={value}
minValue={0}
onChange={onChange}
>
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-sm font-medium text-foreground">{label}</Label> <Label className="text-sm font-medium text-foreground">{label}</Label>
<Group className="relative inline-flex h-9 w-full items-center overflow-hidden whitespace-nowrap rounded-lg border border-input text-sm shadow-sm shadow-black/5 transition-shadow data-[focus-within]:border-ring data-disabled:opacity-50 data-focus-within:outline-none data-focus-within:ring-[3px] data-[focus-within]:ring-ring/20"> <Group className="relative inline-flex h-9 w-full items-center overflow-hidden whitespace-nowrap rounded-lg border border-input text-sm shadow-sm shadow-black/5 transition-shadow data-[focus-within]:border-ring data-disabled:opacity-50 data-focus-within:outline-none data-focus-within:ring-[3px] data-[focus-within]:ring-ring/20">

View File

@ -189,7 +189,13 @@ export async function PaymentsTable({
); );
} }
export function MobilePaymentDetails({ payment, isAdmin = false }: { payment: Payment, isAdmin?: boolean }) { export function MobilePaymentDetails({
payment,
isAdmin = false,
}: {
payment: Payment;
isAdmin?: boolean;
}) {
return ( return (
<div <div
className={cn( className={cn(
@ -267,7 +273,9 @@ export function MobilePaymentDetails({ payment, isAdmin = false }: { payment: Pa
{isAdmin && ( {isAdmin && (
<div className="my-2 text-primary flex flex-col items-start text-sm border rounded p-2 mt-2 w-full bg-white dark:bg-black"> <div className="my-2 text-primary flex flex-col items-start text-sm border rounded p-2 mt-2 w-full bg-white dark:bg-black">
{payment?.user?.name} {payment?.user?.name}
<span className="text-muted-foreground">{payment?.user?.id_card}</span> <span className="text-muted-foreground">
{payment?.user?.id_card}
</span>
</div> </div>
)} )}
</div> </div>

View File

@ -10,7 +10,6 @@ import { useAtom } from "jotai";
import { useEffect } from "react"; import { useEffect } from "react";
import NumberInput from "./number-input"; import NumberInput from "./number-input";
export default function PriceCalculator() { export default function PriceCalculator() {
const [initialPrice, setInitialPrice] = useAtom(initialPriceAtom); const [initialPrice, setInitialPrice] = useAtom(initialPriceAtom);
const [discountPercentage, setDiscountPercentage] = useAtom( const [discountPercentage, setDiscountPercentage] = useAtom(
@ -36,9 +35,7 @@ export default function PriceCalculator() {
return ( return (
<div className="border p-2 rounded-xl"> <div className="border p-2 rounded-xl">
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4"> <div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
<h3 className="text-sarLinkOrange text-2xl"> <h3 className="text-sarLinkOrange text-2xl">Price Calculator</h3>
Price Calculator
</h3>
</div> </div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2"> <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
{/* Initial Price Input */} {/* Initial Price Input */}
@ -87,4 +84,3 @@ export default function PriceCalculator() {
</div> </div>
); );
} }

View File

@ -1,8 +1,5 @@
"use client"; "use client";
import { import { BadgeDollarSign, Loader2 } from "lucide-react";
BadgeDollarSign,
Loader2
} from "lucide-react";
import { useActionState, useEffect } from "react"; import { useActionState, useEffect } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { import {
@ -154,5 +151,3 @@ export default function TopupToPay({
</div> </div>
); );
} }

View File

@ -1,15 +1,15 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Accordion as AccordionPrimitive } from "radix-ui" import { Accordion as AccordionPrimitive } from "radix-ui";
import { ChevronDownIcon } from "lucide-react" import { ChevronDownIcon } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function Accordion({ function Accordion({
...props ...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) { }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return <AccordionPrimitive.Root data-slot="accordion" {...props} /> return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
} }
function AccordionItem({ function AccordionItem({
@ -22,7 +22,7 @@ function AccordionItem({
className={cn("border-b last:border-b-0", className)} className={cn("border-b last:border-b-0", className)}
{...props} {...props}
/> />
) );
} }
function AccordionTrigger({ function AccordionTrigger({
@ -36,7 +36,7 @@ function AccordionTrigger({
data-slot="accordion-trigger" data-slot="accordion-trigger"
className={cn( className={cn(
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180", "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
className className,
)} )}
{...props} {...props}
> >
@ -44,7 +44,7 @@ function AccordionTrigger({
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" /> <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
</AccordionPrimitive.Trigger> </AccordionPrimitive.Trigger>
</AccordionPrimitive.Header> </AccordionPrimitive.Header>
) );
} }
function AccordionContent({ function AccordionContent({
@ -60,7 +60,7 @@ function AccordionContent({
> >
<div className={cn("pt-0 pb-4", className)}>{children}</div> <div className={cn("pt-0 pb-4", className)}>{children}</div>
</AccordionPrimitive.Content> </AccordionPrimitive.Content>
) );
} }
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View File

@ -1,14 +1,14 @@
"use client" "use client";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import * as React from "react" import * as React from "react";
import { buttonVariants } from "@/components/ui/button" import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function AlertDialog({ function AlertDialog({
...props ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) { }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} /> return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
} }
function AlertDialogTrigger({ function AlertDialogTrigger({
@ -16,7 +16,7 @@ function AlertDialogTrigger({
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) { }: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
return ( return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} /> <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
) );
} }
function AlertDialogPortal({ function AlertDialogPortal({
@ -24,7 +24,7 @@ function AlertDialogPortal({
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) { }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return ( return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} /> <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
) );
} }
function AlertDialogOverlay({ function AlertDialogOverlay({
@ -36,11 +36,11 @@ function AlertDialogOverlay({
data-slot="alert-dialog-overlay" data-slot="alert-dialog-overlay"
className={cn( className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function AlertDialogContent({ function AlertDialogContent({
@ -54,12 +54,12 @@ function AlertDialogContent({
data-slot="alert-dialog-content" data-slot="alert-dialog-content"
className={cn( className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className className,
)} )}
{...props} {...props}
/> />
</AlertDialogPortal> </AlertDialogPortal>
) );
} }
function AlertDialogHeader({ function AlertDialogHeader({
@ -72,7 +72,7 @@ function AlertDialogHeader({
className={cn("flex flex-col gap-2 text-center sm:text-left", className)} className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props} {...props}
/> />
) );
} }
function AlertDialogFooter({ function AlertDialogFooter({
@ -84,11 +84,11 @@ function AlertDialogFooter({
data-slot="alert-dialog-footer" data-slot="alert-dialog-footer"
className={cn( className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function AlertDialogTitle({ function AlertDialogTitle({
@ -101,7 +101,7 @@ function AlertDialogTitle({
className={cn("text-lg font-semibold", className)} className={cn("text-lg font-semibold", className)}
{...props} {...props}
/> />
) );
} }
function AlertDialogDescription({ function AlertDialogDescription({
@ -114,7 +114,7 @@ function AlertDialogDescription({
className={cn("text-muted-foreground text-sm", className)} className={cn("text-muted-foreground text-sm", className)}
{...props} {...props}
/> />
) );
} }
function AlertDialogAction({ function AlertDialogAction({
@ -126,7 +126,7 @@ function AlertDialogAction({
className={cn(buttonVariants(), className)} className={cn(buttonVariants(), className)}
{...props} {...props}
/> />
) );
} }
function AlertDialogCancel({ function AlertDialogCancel({
@ -138,7 +138,7 @@ function AlertDialogCancel({
className={cn(buttonVariants({ variant: "outline" }), className)} className={cn(buttonVariants({ variant: "outline" }), className)}
{...props} {...props}
/> />
) );
} }
export { export {
@ -153,4 +153,4 @@ export {
AlertDialogDescription, AlertDialogDescription,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
} };

View File

@ -1,7 +1,7 @@
import * as React from "react" import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const alertVariants = cva( const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
@ -16,8 +16,8 @@ const alertVariants = cva(
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
}, },
} },
) );
const Alert = React.forwardRef< const Alert = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -29,8 +29,8 @@ const Alert = React.forwardRef<
className={cn(alertVariants({ variant }), className)} className={cn(alertVariants({ variant }), className)}
{...props} {...props}
/> />
)) ));
Alert.displayName = "Alert" Alert.displayName = "Alert";
const AlertTitle = React.forwardRef< const AlertTitle = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@ -41,8 +41,8 @@ const AlertTitle = React.forwardRef<
className={cn("mb-1 font-medium leading-none tracking-tight", className)} className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props} {...props}
/> />
)) ));
AlertTitle.displayName = "AlertTitle" AlertTitle.displayName = "AlertTitle";
const AlertDescription = React.forwardRef< const AlertDescription = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@ -53,7 +53,7 @@ const AlertDescription = React.forwardRef<
className={cn("text-sm [&_p]:leading-relaxed", className)} className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props} {...props}
/> />
)) ));
AlertDescription.displayName = "AlertDescription" AlertDescription.displayName = "AlertDescription";
export { Alert, AlertTitle, AlertDescription } export { Alert, AlertTitle, AlertDescription };

View File

@ -146,17 +146,19 @@ export async function AppSidebar({
} else { } else {
// Filter out ADMIN CONTROL category for non-admin users // Filter out ADMIN CONTROL category for non-admin users
const nonAdminCategories = categories.filter( const nonAdminCategories = categories.filter(
(category) => category.id !== "ADMIN CONTROL" (category) => category.id !== "ADMIN CONTROL",
); );
const filteredCategories = nonAdminCategories.map((category) => { const filteredCategories = nonAdminCategories.map((category) => {
const filteredChildren = category.children.filter((child) => { const filteredChildren = category.children.filter((child) => {
const permIdentifier = child.perm_identifier; const permIdentifier = child.perm_identifier;
return session?.user?.user_permissions?.some((permission: Permission) => { return session?.user?.user_permissions?.some(
(permission: Permission) => {
const permissionParts = permission.name.split(" "); const permissionParts = permission.name.split(" ");
const modelNameFromPermission = permissionParts.slice(2).join(" "); const modelNameFromPermission = permissionParts.slice(2).join(" ");
return modelNameFromPermission === permIdentifier; return modelNameFromPermission === permIdentifier;
}); },
);
}); });
return { ...category, children: filteredChildren }; return { ...category, children: filteredChildren };

View File

@ -1,7 +1,7 @@
"use client" "use client";
import { AspectRatio as AspectRatioPrimitive } from "radix-ui" import { AspectRatio as AspectRatioPrimitive } from "radix-ui";
const AspectRatio = AspectRatioPrimitive.Root const AspectRatio = AspectRatioPrimitive.Root;
export { AspectRatio } export { AspectRatio };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Avatar as AvatarPrimitive } from "radix-ui" import { Avatar as AvatarPrimitive } from "radix-ui";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Avatar = React.forwardRef< const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>, React.ElementRef<typeof AvatarPrimitive.Root>,
@ -13,12 +13,12 @@ const Avatar = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className className,
)} )}
{...props} {...props}
/> />
)) ));
Avatar.displayName = AvatarPrimitive.Root.displayName Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef< const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>, React.ElementRef<typeof AvatarPrimitive.Image>,
@ -29,8 +29,8 @@ const AvatarImage = React.forwardRef<
className={cn("aspect-square h-full w-full", className)} className={cn("aspect-square h-full w-full", className)}
{...props} {...props}
/> />
)) ));
AvatarImage.displayName = AvatarPrimitive.Image.displayName AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef< const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>, React.ElementRef<typeof AvatarPrimitive.Fallback>,
@ -40,11 +40,11 @@ const AvatarFallback = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted", "flex h-full w-full items-center justify-center rounded-full bg-muted",
className className,
)} )}
{...props} {...props}
/> />
)) ));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
export { Avatar, AvatarImage, AvatarFallback } export { Avatar, AvatarImage, AvatarFallback };

View File

@ -1,7 +1,7 @@
import { type VariantProps, cva } from "class-variance-authority" import { type VariantProps, cva } from "class-variance-authority";
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
@ -20,8 +20,8 @@ const badgeVariants = cva(
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
}, },
} },
) );
export interface BadgeProps export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>, extends React.HTMLAttributes<HTMLDivElement>,
@ -30,7 +30,7 @@ export interface BadgeProps
function Badge({ className, variant, ...props }: BadgeProps) { function Badge({ className, variant, ...props }: BadgeProps) {
return ( return (
<div className={cn(badgeVariants({ variant }), className)} {...props} /> <div className={cn(badgeVariants({ variant }), className)} {...props} />
) );
} }
export { Badge, badgeVariants } export { Badge, badgeVariants };

View File

@ -1,16 +1,16 @@
import * as React from "react" import * as React from "react";
import { Slot as SlotPrimitive } from "radix-ui" import { Slot as SlotPrimitive } from "radix-ui";
import { ChevronRight, MoreHorizontal } from "lucide-react" import { ChevronRight, MoreHorizontal } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Breadcrumb = React.forwardRef< const Breadcrumb = React.forwardRef<
HTMLElement, HTMLElement,
React.ComponentPropsWithoutRef<"nav"> & { React.ComponentPropsWithoutRef<"nav"> & {
separator?: React.ReactNode separator?: React.ReactNode;
} }
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />) >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
Breadcrumb.displayName = "Breadcrumb" Breadcrumb.displayName = "Breadcrumb";
const BreadcrumbList = React.forwardRef< const BreadcrumbList = React.forwardRef<
HTMLOListElement, HTMLOListElement,
@ -20,12 +20,12 @@ const BreadcrumbList = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5", "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
className className,
)} )}
{...props} {...props}
/> />
)) ));
BreadcrumbList.displayName = "BreadcrumbList" BreadcrumbList.displayName = "BreadcrumbList";
const BreadcrumbItem = React.forwardRef< const BreadcrumbItem = React.forwardRef<
HTMLLIElement, HTMLLIElement,
@ -36,16 +36,16 @@ const BreadcrumbItem = React.forwardRef<
className={cn("inline-flex items-center gap-1.5", className)} className={cn("inline-flex items-center gap-1.5", className)}
{...props} {...props}
/> />
)) ));
BreadcrumbItem.displayName = "BreadcrumbItem" BreadcrumbItem.displayName = "BreadcrumbItem";
const BreadcrumbLink = React.forwardRef< const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement, HTMLAnchorElement,
React.ComponentPropsWithoutRef<"a"> & { React.ComponentPropsWithoutRef<"a"> & {
asChild?: boolean asChild?: boolean;
} }
>(({ asChild, className, ...props }, ref) => { >(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? SlotPrimitive.Slot : "a" const Comp = asChild ? SlotPrimitive.Slot : "a";
return ( return (
<Comp <Comp
@ -53,9 +53,9 @@ const BreadcrumbLink = React.forwardRef<
className={cn("transition-colors hover:text-foreground", className)} className={cn("transition-colors hover:text-foreground", className)}
{...props} {...props}
/> />
) );
}) });
BreadcrumbLink.displayName = "BreadcrumbLink" BreadcrumbLink.displayName = "BreadcrumbLink";
const BreadcrumbPage = React.forwardRef< const BreadcrumbPage = React.forwardRef<
HTMLSpanElement, HTMLSpanElement,
@ -69,8 +69,8 @@ const BreadcrumbPage = React.forwardRef<
className={cn("font-normal text-foreground", className)} className={cn("font-normal text-foreground", className)}
{...props} {...props}
/> />
)) ));
BreadcrumbPage.displayName = "BreadcrumbPage" BreadcrumbPage.displayName = "BreadcrumbPage";
const BreadcrumbSeparator = ({ const BreadcrumbSeparator = ({
children, children,
@ -85,8 +85,8 @@ const BreadcrumbSeparator = ({
> >
{children ?? <ChevronRight />} {children ?? <ChevronRight />}
</li> </li>
) );
BreadcrumbSeparator.displayName = "BreadcrumbSeparator" BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
const BreadcrumbEllipsis = ({ const BreadcrumbEllipsis = ({
className, className,
@ -101,8 +101,8 @@ const BreadcrumbEllipsis = ({
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span> <span className="sr-only">More</span>
</span> </span>
) );
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis" BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
export { export {
Breadcrumb, Breadcrumb,
@ -112,4 +112,4 @@ export {
BreadcrumbPage, BreadcrumbPage,
BreadcrumbSeparator, BreadcrumbSeparator,
BreadcrumbEllipsis, BreadcrumbEllipsis,
} };

View File

@ -1,8 +1,8 @@
import { Slot as SlotPrimitive } from "radix-ui" import { Slot as SlotPrimitive } from "radix-ui";
import { type VariantProps, cva } from "class-variance-authority" import { type VariantProps, cva } from "class-variance-authority";
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
@ -32,8 +32,8 @@ const buttonVariants = cva(
variant: "default", variant: "default",
size: "default", size: "default",
}, },
} },
) );
function Button({ function Button({
className, className,
@ -43,9 +43,9 @@ function Button({
...props ...props
}: React.ComponentProps<"button"> & }: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & { VariantProps<typeof buttonVariants> & {
asChild?: boolean asChild?: boolean;
}) { }) {
const Comp = asChild ? SlotPrimitive.Slot : "button" const Comp = asChild ? SlotPrimitive.Slot : "button";
return ( return (
<Comp <Comp
@ -53,7 +53,7 @@ function Button({
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
{...props} {...props}
/> />
) );
} }
export { Button, buttonVariants } export { Button, buttonVariants };

View File

@ -1,15 +1,15 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { import {
ChevronDownIcon, ChevronDownIcon,
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon, ChevronRightIcon,
} from "lucide-react" } from "lucide-react";
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker" import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
import { Button, buttonVariants } from "@/components/ui/button" import { Button, buttonVariants } from "@/components/ui/button";
function Calendar({ function Calendar({
className, className,
@ -21,9 +21,9 @@ function Calendar({
components, components,
...props ...props
}: React.ComponentProps<typeof DayPicker> & { }: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"] buttonVariant?: React.ComponentProps<typeof Button>["variant"];
}) { }) {
const defaultClassNames = getDefaultClassNames() const defaultClassNames = getDefaultClassNames();
return ( return (
<DayPicker <DayPicker
@ -32,7 +32,7 @@ function Calendar({
"bg-background group/calendar p-3 [--cell-size:2rem] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent", "bg-background group/calendar p-3 [--cell-size:2rem] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className className,
)} )}
captionLayout={captionLayout} captionLayout={captionLayout}
formatters={{ formatters={{
@ -44,34 +44,34 @@ function Calendar({
root: cn("w-fit", defaultClassNames.root), root: cn("w-fit", defaultClassNames.root),
months: cn( months: cn(
"relative flex flex-col gap-4 md:flex-row", "relative flex flex-col gap-4 md:flex-row",
defaultClassNames.months defaultClassNames.months,
), ),
month: cn("flex w-full flex-col gap-4", defaultClassNames.month), month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
nav: cn( nav: cn(
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1", "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
defaultClassNames.nav defaultClassNames.nav,
), ),
button_previous: cn( button_previous: cn(
buttonVariants({ variant: buttonVariant }), buttonVariants({ variant: buttonVariant }),
"h-(--cell-size) w-(--cell-size) select-none p-0 aria-disabled:opacity-50", "h-(--cell-size) w-(--cell-size) select-none p-0 aria-disabled:opacity-50",
defaultClassNames.button_previous defaultClassNames.button_previous,
), ),
button_next: cn( button_next: cn(
buttonVariants({ variant: buttonVariant }), buttonVariants({ variant: buttonVariant }),
"h-(--cell-size) w-(--cell-size) select-none p-0 aria-disabled:opacity-50", "h-(--cell-size) w-(--cell-size) select-none p-0 aria-disabled:opacity-50",
defaultClassNames.button_next defaultClassNames.button_next,
), ),
month_caption: cn( month_caption: cn(
"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)", "flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
defaultClassNames.month_caption defaultClassNames.month_caption,
), ),
dropdowns: cn( dropdowns: cn(
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium", "flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
defaultClassNames.dropdowns defaultClassNames.dropdowns,
), ),
dropdown_root: cn( dropdown_root: cn(
"has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border", "has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
defaultClassNames.dropdown_root defaultClassNames.dropdown_root,
), ),
dropdown: cn("absolute inset-0 opacity-0", defaultClassNames.dropdown), dropdown: cn("absolute inset-0 opacity-0", defaultClassNames.dropdown),
caption_label: cn( caption_label: cn(
@ -79,44 +79,44 @@ function Calendar({
captionLayout === "label" captionLayout === "label"
? "text-sm" ? "text-sm"
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5", : "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
defaultClassNames.caption_label defaultClassNames.caption_label,
), ),
table: "w-full border-collapse", table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays), weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn( weekday: cn(
"text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal", "text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
defaultClassNames.weekday defaultClassNames.weekday,
), ),
week: cn("mt-2 flex w-full", defaultClassNames.week), week: cn("mt-2 flex w-full", defaultClassNames.week),
week_number_header: cn( week_number_header: cn(
"w-(--cell-size) select-none", "w-(--cell-size) select-none",
defaultClassNames.week_number_header defaultClassNames.week_number_header,
), ),
week_number: cn( week_number: cn(
"text-muted-foreground select-none text-[0.8rem]", "text-muted-foreground select-none text-[0.8rem]",
defaultClassNames.week_number defaultClassNames.week_number,
), ),
day: cn( day: cn(
"group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md", "group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
defaultClassNames.day defaultClassNames.day,
), ),
range_start: cn( range_start: cn(
"bg-accent rounded-l-md", "bg-accent rounded-l-md",
defaultClassNames.range_start defaultClassNames.range_start,
), ),
range_middle: cn("rounded-none", defaultClassNames.range_middle), range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end), range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
today: cn( today: cn(
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
defaultClassNames.today defaultClassNames.today,
), ),
outside: cn( outside: cn(
"text-muted-foreground aria-selected:text-muted-foreground", "text-muted-foreground aria-selected:text-muted-foreground",
defaultClassNames.outside defaultClassNames.outside,
), ),
disabled: cn( disabled: cn(
"text-muted-foreground opacity-50", "text-muted-foreground opacity-50",
defaultClassNames.disabled defaultClassNames.disabled,
), ),
hidden: cn("invisible", defaultClassNames.hidden), hidden: cn("invisible", defaultClassNames.hidden),
...classNames, ...classNames,
@ -130,13 +130,13 @@ function Calendar({
className={cn(className)} className={cn(className)}
{...props} {...props}
/> />
) );
}, },
Chevron: ({ className, orientation, ...props }) => { Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") { if (orientation === "left") {
return ( return (
<ChevronLeftIcon className={cn("size-4", className)} {...props} /> <ChevronLeftIcon className={cn("size-4", className)} {...props} />
) );
} }
if (orientation === "right") { if (orientation === "right") {
@ -145,12 +145,12 @@ function Calendar({
className={cn("size-4", className)} className={cn("size-4", className)}
{...props} {...props}
/> />
) );
} }
return ( return (
<ChevronDownIcon className={cn("size-4", className)} {...props} /> <ChevronDownIcon className={cn("size-4", className)} {...props} />
) );
}, },
DayButton: CalendarDayButton, DayButton: CalendarDayButton,
WeekNumber: ({ children, ...props }) => { WeekNumber: ({ children, ...props }) => {
@ -160,13 +160,13 @@ function Calendar({
{children} {children}
</div> </div>
</td> </td>
) );
}, },
...components, ...components,
}} }}
{...props} {...props}
/> />
) );
} }
function CalendarDayButton({ function CalendarDayButton({
@ -175,12 +175,12 @@ function CalendarDayButton({
modifiers, modifiers,
...props ...props
}: React.ComponentProps<typeof DayButton>) { }: React.ComponentProps<typeof DayButton>) {
const defaultClassNames = getDefaultClassNames() const defaultClassNames = getDefaultClassNames();
const ref = React.useRef<HTMLButtonElement>(null) const ref = React.useRef<HTMLButtonElement>(null);
React.useEffect(() => { React.useEffect(() => {
if (modifiers.focused) ref.current?.focus() if (modifiers.focused) ref.current?.focus();
}, [modifiers.focused]) }, [modifiers.focused]);
return ( return (
<Button <Button
@ -200,11 +200,11 @@ function CalendarDayButton({
className={cn( className={cn(
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-(--cell-size) flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70", "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-(--cell-size) flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
defaultClassNames.day, defaultClassNames.day,
className className,
)} )}
{...props} {...props}
/> />
) );
} }
export { Calendar, CalendarDayButton } export { Calendar, CalendarDayButton };

View File

@ -1,6 +1,6 @@
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Card = React.forwardRef< const Card = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -10,12 +10,12 @@ const Card = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"rounded-xl border bg-card text-card-foreground shadow", "rounded-xl border bg-card text-card-foreground shadow",
className className,
)} )}
{...props} {...props}
/> />
)) ));
Card.displayName = "Card" Card.displayName = "Card";
const CardHeader = React.forwardRef< const CardHeader = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -26,8 +26,8 @@ const CardHeader = React.forwardRef<
className={cn("flex flex-col space-y-1.5 p-6", className)} className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props} {...props}
/> />
)) ));
CardHeader.displayName = "CardHeader" CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef< const CardTitle = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -38,8 +38,8 @@ const CardTitle = React.forwardRef<
className={cn("font-semibold leading-none tracking-tight", className)} className={cn("font-semibold leading-none tracking-tight", className)}
{...props} {...props}
/> />
)) ));
CardTitle.displayName = "CardTitle" CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef< const CardDescription = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -50,16 +50,16 @@ const CardDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
CardDescription.displayName = "CardDescription" CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef< const CardContent = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
)) ));
CardContent.displayName = "CardContent" CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef< const CardFooter = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -70,7 +70,14 @@ const CardFooter = React.forwardRef<
className={cn("flex items-center p-6 pt-0", className)} className={cn("flex items-center p-6 pt-0", className)}
{...props} {...props}
/> />
)) ));
CardFooter.displayName = "CardFooter" CardFooter.displayName = "CardFooter";
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};

View File

@ -1,45 +1,45 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import useEmblaCarousel, { import useEmblaCarousel, {
type UseEmblaCarouselType, type UseEmblaCarouselType,
} from "embla-carousel-react" } from "embla-carousel-react";
import { ArrowLeft, ArrowRight } from "lucide-react" import { ArrowLeft, ArrowRight } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button";
type CarouselApi = UseEmblaCarouselType[1] type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel> type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0] type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1] type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps = { type CarouselProps = {
opts?: CarouselOptions opts?: CarouselOptions;
plugins?: CarouselPlugin plugins?: CarouselPlugin;
orientation?: "horizontal" | "vertical" orientation?: "horizontal" | "vertical";
setApi?: (api: CarouselApi) => void setApi?: (api: CarouselApi) => void;
} };
type CarouselContextProps = { type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0] carouselRef: ReturnType<typeof useEmblaCarousel>[0];
api: ReturnType<typeof useEmblaCarousel>[1] api: ReturnType<typeof useEmblaCarousel>[1];
scrollPrev: () => void scrollPrev: () => void;
scrollNext: () => void scrollNext: () => void;
canScrollPrev: boolean canScrollPrev: boolean;
canScrollNext: boolean canScrollNext: boolean;
} & CarouselProps } & CarouselProps;
const CarouselContext = React.createContext<CarouselContextProps | null>(null) const CarouselContext = React.createContext<CarouselContextProps | null>(null);
function useCarousel() { function useCarousel() {
const context = React.useContext(CarouselContext) const context = React.useContext(CarouselContext);
if (!context) { if (!context) {
throw new Error("useCarousel must be used within a <Carousel />") throw new Error("useCarousel must be used within a <Carousel />");
} }
return context return context;
} }
const Carousel = React.forwardRef< const Carousel = React.forwardRef<
@ -56,69 +56,69 @@ const Carousel = React.forwardRef<
children, children,
...props ...props
}, },
ref ref,
) => { ) => {
const [carouselRef, api] = useEmblaCarousel( const [carouselRef, api] = useEmblaCarousel(
{ {
...opts, ...opts,
axis: orientation === "horizontal" ? "x" : "y", axis: orientation === "horizontal" ? "x" : "y",
}, },
plugins plugins,
) );
const [canScrollPrev, setCanScrollPrev] = React.useState(false) const [canScrollPrev, setCanScrollPrev] = React.useState(false);
const [canScrollNext, setCanScrollNext] = React.useState(false) const [canScrollNext, setCanScrollNext] = React.useState(false);
const onSelect = React.useCallback((api: CarouselApi) => { const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) { if (!api) {
return return;
} }
setCanScrollPrev(api.canScrollPrev()) setCanScrollPrev(api.canScrollPrev());
setCanScrollNext(api.canScrollNext()) setCanScrollNext(api.canScrollNext());
}, []) }, []);
const scrollPrev = React.useCallback(() => { const scrollPrev = React.useCallback(() => {
api?.scrollPrev() api?.scrollPrev();
}, [api]) }, [api]);
const scrollNext = React.useCallback(() => { const scrollNext = React.useCallback(() => {
api?.scrollNext() api?.scrollNext();
}, [api]) }, [api]);
const handleKeyDown = React.useCallback( const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => { (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") { if (event.key === "ArrowLeft") {
event.preventDefault() event.preventDefault();
scrollPrev() scrollPrev();
} else if (event.key === "ArrowRight") { } else if (event.key === "ArrowRight") {
event.preventDefault() event.preventDefault();
scrollNext() scrollNext();
} }
}, },
[scrollPrev, scrollNext] [scrollPrev, scrollNext],
) );
React.useEffect(() => { React.useEffect(() => {
if (!api || !setApi) { if (!api || !setApi) {
return return;
} }
setApi(api) setApi(api);
}, [api, setApi]) }, [api, setApi]);
React.useEffect(() => { React.useEffect(() => {
if (!api) { if (!api) {
return return;
} }
onSelect(api) onSelect(api);
api.on("reInit", onSelect) api.on("reInit", onSelect);
api.on("select", onSelect) api.on("select", onSelect);
return () => { return () => {
api?.off("select", onSelect) api?.off("select", onSelect);
} };
}, [api, onSelect]) }, [api, onSelect]);
return ( return (
<CarouselContext.Provider <CarouselContext.Provider
@ -145,16 +145,16 @@ const Carousel = React.forwardRef<
{children} {children}
</div> </div>
</CarouselContext.Provider> </CarouselContext.Provider>
) );
} },
) );
Carousel.displayName = "Carousel" Carousel.displayName = "Carousel";
const CarouselContent = React.forwardRef< const CarouselContent = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel() const { carouselRef, orientation } = useCarousel();
return ( return (
<div ref={carouselRef} className="overflow-hidden"> <div ref={carouselRef} className="overflow-hidden">
@ -163,20 +163,20 @@ const CarouselContent = React.forwardRef<
className={cn( className={cn(
"flex", "flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className className,
)} )}
{...props} {...props}
/> />
</div> </div>
) );
}) });
CarouselContent.displayName = "CarouselContent" CarouselContent.displayName = "CarouselContent";
const CarouselItem = React.forwardRef< const CarouselItem = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { orientation } = useCarousel() const { orientation } = useCarousel();
return ( return (
<div <div
@ -186,19 +186,19 @@ const CarouselItem = React.forwardRef<
className={cn( className={cn(
"min-w-0 shrink-0 grow-0 basis-full", "min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4", orientation === "horizontal" ? "pl-4" : "pt-4",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
CarouselItem.displayName = "CarouselItem" CarouselItem.displayName = "CarouselItem";
const CarouselPrevious = React.forwardRef< const CarouselPrevious = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
React.ComponentProps<typeof Button> React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => { >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel() const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return ( return (
<Button <Button
@ -210,7 +210,7 @@ const CarouselPrevious = React.forwardRef<
orientation === "horizontal" orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2" ? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90", : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className className,
)} )}
disabled={!canScrollPrev} disabled={!canScrollPrev}
onClick={scrollPrev} onClick={scrollPrev}
@ -219,15 +219,15 @@ const CarouselPrevious = React.forwardRef<
<ArrowLeft className="h-4 w-4" /> <ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span> <span className="sr-only">Previous slide</span>
</Button> </Button>
) );
}) });
CarouselPrevious.displayName = "CarouselPrevious" CarouselPrevious.displayName = "CarouselPrevious";
const CarouselNext = React.forwardRef< const CarouselNext = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
React.ComponentProps<typeof Button> React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => { >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel() const { orientation, scrollNext, canScrollNext } = useCarousel();
return ( return (
<Button <Button
@ -239,7 +239,7 @@ const CarouselNext = React.forwardRef<
orientation === "horizontal" orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2" ? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className className,
)} )}
disabled={!canScrollNext} disabled={!canScrollNext}
onClick={scrollNext} onClick={scrollNext}
@ -248,9 +248,9 @@ const CarouselNext = React.forwardRef<
<ArrowRight className="h-4 w-4" /> <ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span> <span className="sr-only">Next slide</span>
</Button> </Button>
) );
}) });
CarouselNext.displayName = "CarouselNext" CarouselNext.displayName = "CarouselNext";
export { export {
type CarouselApi, type CarouselApi,
@ -259,4 +259,4 @@ export {
CarouselItem, CarouselItem,
CarouselPrevious, CarouselPrevious,
CarouselNext, CarouselNext,
} };

View File

@ -1,10 +1,10 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Checkbox as CheckboxPrimitive } from "radix-ui" import { Checkbox as CheckboxPrimitive } from "radix-ui";
import { Check } from "lucide-react" import { Check } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Checkbox = React.forwardRef< const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>, React.ElementRef<typeof CheckboxPrimitive.Root>,
@ -14,7 +14,7 @@ const Checkbox = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className className,
)} )}
{...props} {...props}
> >
@ -24,7 +24,7 @@ const Checkbox = React.forwardRef<
<Check className="h-4 w-4" /> <Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
)) ));
Checkbox.displayName = CheckboxPrimitive.Root.displayName Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox } export { Checkbox };

View File

@ -1,11 +1,11 @@
"use client" "use client";
import { Collapsible as CollapsiblePrimitive } from "radix-ui" import { Collapsible as CollapsiblePrimitive } from "radix-ui";
const Collapsible = CollapsiblePrimitive.Root const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent } export { Collapsible, CollapsibleTrigger, CollapsibleContent };

View File

@ -1,17 +1,17 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Command as CommandPrimitive } from "cmdk" import { Command as CommandPrimitive } from "cmdk";
import { SearchIcon } from "lucide-react" import { SearchIcon } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@/components/ui/dialog" } from "@/components/ui/dialog";
function Command({ function Command({
className, className,
@ -22,11 +22,11 @@ function Command({
data-slot="command" data-slot="command"
className={cn( className={cn(
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md", "bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function CommandDialog({ function CommandDialog({
@ -37,10 +37,10 @@ function CommandDialog({
showCloseButton = true, showCloseButton = true,
...props ...props
}: React.ComponentProps<typeof Dialog> & { }: React.ComponentProps<typeof Dialog> & {
title?: string title?: string;
description?: string description?: string;
className?: string className?: string;
showCloseButton?: boolean showCloseButton?: boolean;
}) { }) {
return ( return (
<Dialog {...props}> <Dialog {...props}>
@ -57,7 +57,7 @@ function CommandDialog({
</Command> </Command>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
) );
} }
function CommandInput({ function CommandInput({
@ -74,12 +74,12 @@ function CommandInput({
data-slot="command-input" data-slot="command-input"
className={cn( className={cn(
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50", "placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
className className,
)} )}
{...props} {...props}
/> />
</div> </div>
) );
} }
function CommandList({ function CommandList({
@ -91,11 +91,11 @@ function CommandList({
data-slot="command-list" data-slot="command-list"
className={cn( className={cn(
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", "max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function CommandEmpty({ function CommandEmpty({
@ -107,7 +107,7 @@ function CommandEmpty({
className="py-6 text-center text-sm" className="py-6 text-center text-sm"
{...props} {...props}
/> />
) );
} }
function CommandGroup({ function CommandGroup({
@ -119,11 +119,11 @@ function CommandGroup({
data-slot="command-group" data-slot="command-group"
className={cn( className={cn(
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium", "text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function CommandSeparator({ function CommandSeparator({
@ -136,7 +136,7 @@ function CommandSeparator({
className={cn("bg-border -mx-1 h-px", className)} className={cn("bg-border -mx-1 h-px", className)}
{...props} {...props}
/> />
) );
} }
function CommandItem({ function CommandItem({
@ -148,11 +148,11 @@ function CommandItem({
data-slot="command-item" data-slot="command-item"
className={cn( className={cn(
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function CommandShortcut({ function CommandShortcut({
@ -164,11 +164,11 @@ function CommandShortcut({
data-slot="command-shortcut" data-slot="command-shortcut"
className={cn( className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest", "text-muted-foreground ml-auto text-xs tracking-widest",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
export { export {
@ -181,4 +181,4 @@ export {
CommandItem, CommandItem,
CommandShortcut, CommandShortcut,
CommandSeparator, CommandSeparator,
} };

View File

@ -1,27 +1,27 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { ContextMenu as ContextMenuPrimitive } from "radix-ui" import { ContextMenu as ContextMenuPrimitive } from "radix-ui";
import { Check, ChevronRight, Circle } from "lucide-react" import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const ContextMenu = ContextMenuPrimitive.Root const ContextMenu = ContextMenuPrimitive.Root;
const ContextMenuTrigger = ContextMenuPrimitive.Trigger const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
const ContextMenuGroup = ContextMenuPrimitive.Group const ContextMenuGroup = ContextMenuPrimitive.Group;
const ContextMenuPortal = ContextMenuPrimitive.Portal const ContextMenuPortal = ContextMenuPrimitive.Portal;
const ContextMenuSub = ContextMenuPrimitive.Sub const ContextMenuSub = ContextMenuPrimitive.Sub;
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
const ContextMenuSubTrigger = React.forwardRef< const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>, React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & { React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, children, ...props }, ref) => ( >(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger <ContextMenuPrimitive.SubTrigger
@ -29,15 +29,15 @@ const ContextMenuSubTrigger = React.forwardRef<
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRight className="ml-auto h-4 w-4" /> <ChevronRight className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger> </ContextMenuPrimitive.SubTrigger>
)) ));
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
const ContextMenuSubContent = React.forwardRef< const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>, React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
@ -47,12 +47,12 @@ const ContextMenuSubContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-context-menu-content-transform-origin)", "z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-context-menu-content-transform-origin)",
className className,
)} )}
{...props} {...props}
/> />
)) ));
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
const ContextMenuContent = React.forwardRef< const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>, React.ElementRef<typeof ContextMenuPrimitive.Content>,
@ -63,18 +63,18 @@ const ContextMenuContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 max-h-(--radix-context-menu-content-available-height) min-w-32 overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-context-menu-content-transform-origin)", "z-50 max-h-(--radix-context-menu-content-available-height) min-w-32 overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-context-menu-content-transform-origin)",
className className,
)} )}
{...props} {...props}
/> />
</ContextMenuPrimitive.Portal> </ContextMenuPrimitive.Portal>
)) ));
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
const ContextMenuItem = React.forwardRef< const ContextMenuItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Item>, React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & { React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item <ContextMenuPrimitive.Item
@ -82,12 +82,12 @@ const ContextMenuItem = React.forwardRef<
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50", "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
const ContextMenuCheckboxItem = React.forwardRef< const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>, React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
@ -97,7 +97,7 @@ const ContextMenuCheckboxItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
className className,
)} )}
checked={checked} checked={checked}
{...props} {...props}
@ -109,9 +109,9 @@ const ContextMenuCheckboxItem = React.forwardRef<
</span> </span>
{children} {children}
</ContextMenuPrimitive.CheckboxItem> </ContextMenuPrimitive.CheckboxItem>
)) ));
ContextMenuCheckboxItem.displayName = ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName ContextMenuPrimitive.CheckboxItem.displayName;
const ContextMenuRadioItem = React.forwardRef< const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>, React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
@ -121,7 +121,7 @@ const ContextMenuRadioItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
className className,
)} )}
{...props} {...props}
> >
@ -132,13 +132,13 @@ const ContextMenuRadioItem = React.forwardRef<
</span> </span>
{children} {children}
</ContextMenuPrimitive.RadioItem> </ContextMenuPrimitive.RadioItem>
)) ));
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
const ContextMenuLabel = React.forwardRef< const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>, React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & { React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label <ContextMenuPrimitive.Label
@ -146,12 +146,12 @@ const ContextMenuLabel = React.forwardRef<
className={cn( className={cn(
"px-2 py-1.5 text-sm font-semibold text-foreground", "px-2 py-1.5 text-sm font-semibold text-foreground",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
const ContextMenuSeparator = React.forwardRef< const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>, React.ElementRef<typeof ContextMenuPrimitive.Separator>,
@ -162,8 +162,8 @@ const ContextMenuSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-border", className)} className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props} {...props}
/> />
)) ));
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
const ContextMenuShortcut = ({ const ContextMenuShortcut = ({
className, className,
@ -173,13 +173,13 @@ const ContextMenuShortcut = ({
<span <span
className={cn( className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground", "ml-auto text-xs tracking-widest text-muted-foreground",
className className,
)} )}
{...props} {...props}
/> />
) );
} };
ContextMenuShortcut.displayName = "ContextMenuShortcut" ContextMenuShortcut.displayName = "ContextMenuShortcut";
export { export {
ContextMenu, ContextMenu,
@ -197,4 +197,4 @@ export {
ContextMenuSubContent, ContextMenuSubContent,
ContextMenuSubTrigger, ContextMenuSubTrigger,
ContextMenuRadioGroup, ContextMenuRadioGroup,
} };

View File

@ -1,75 +1,82 @@
import { eachMonthOfInterval, endOfYear, format, startOfYear } from "date-fns" import { eachMonthOfInterval, endOfYear, format, startOfYear } from "date-fns";
import { Calendar as CalendarIcon } from "lucide-react" import { Calendar as CalendarIcon } from "lucide-react";
import * as React from "react" import * as React from "react";
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar" import { Calendar } from "@/components/ui/calendar";
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover" } from "@/components/ui/popover";
import { import {
Select, Select,
SelectContent, SelectContent,
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select" } from "@/components/ui/select";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
interface DatePickerProps { interface DatePickerProps {
date: Date | undefined date: Date | undefined;
setDate: (date: Date | undefined) => void setDate: (date: Date | undefined) => void;
} }
export function DatePicker({ date, setDate }: DatePickerProps) { export function DatePicker({ date, setDate }: DatePickerProps) {
const [month, setMonth] = React.useState<number>(date ? date.getMonth() : new Date().getMonth()) const [month, setMonth] = React.useState<number>(
const [year, setYear] = React.useState<number>(date ? date.getFullYear() : new Date().getFullYear()) date ? date.getMonth() : new Date().getMonth(),
);
const [year, setYear] = React.useState<number>(
date ? date.getFullYear() : new Date().getFullYear(),
);
const years = React.useMemo(() => { const years = React.useMemo(() => {
const currentYear = new Date().getFullYear() const currentYear = new Date().getFullYear();
return Array.from({ length: currentYear - 1900 + 1 }, (_, i) => currentYear - i) return Array.from(
}, []) { length: currentYear - 1900 + 1 },
(_, i) => currentYear - i,
);
}, []);
const months = React.useMemo(() => { const months = React.useMemo(() => {
if (year) { if (year) {
return eachMonthOfInterval({ return eachMonthOfInterval({
start: startOfYear(new Date(year, 0, 1)), start: startOfYear(new Date(year, 0, 1)),
end: endOfYear(new Date(year, 0, 1)) end: endOfYear(new Date(year, 0, 1)),
}) });
} }
return [] return [];
}, [year]) }, [year]);
React.useEffect(() => { React.useEffect(() => {
if (date) { if (date) {
setMonth(date.getMonth()) setMonth(date.getMonth());
setYear(date.getFullYear()) setYear(date.getFullYear());
} }
}, [date]) }, [date]);
const handleYearChange = (selectedYear: string) => { const handleYearChange = (selectedYear: string) => {
const newYear = Number.parseInt(selectedYear, 10) const newYear = Number.parseInt(selectedYear, 10);
setYear(newYear) setYear(newYear);
if (date) { if (date) {
const newDate = new Date(date) const newDate = new Date(date);
newDate.setFullYear(newYear) newDate.setFullYear(newYear);
setDate(newDate) setDate(newDate);
}
} }
};
const handleMonthChange = (selectedMonth: string) => { const handleMonthChange = (selectedMonth: string) => {
const newMonth = Number.parseInt(selectedMonth, 10) const newMonth = Number.parseInt(selectedMonth, 10);
setMonth(newMonth) setMonth(newMonth);
if (date) { if (date) {
const newDate = new Date(date) const newDate = new Date(date);
newDate.setMonth(newMonth) newDate.setMonth(newMonth);
setDate(newDate) setDate(newDate);
} else { } else {
setDate(new Date(year, newMonth, 1)) setDate(new Date(year, newMonth, 1));
}
} }
};
return ( return (
<Popover> <Popover>
@ -78,7 +85,7 @@ export function DatePicker({ date, setDate }: DatePickerProps) {
variant={"outline"} variant={"outline"}
className={cn( className={cn(
"w-full justify-start text-left font-normal", "w-full justify-start text-left font-normal",
!date && "text-muted-foreground" !date && "text-muted-foreground",
)} )}
> >
<CalendarIcon className="mr-2 h-4 w-4" /> <CalendarIcon className="mr-2 h-4 w-4" />
@ -118,12 +125,12 @@ export function DatePicker({ date, setDate }: DatePickerProps) {
onSelect={setDate} onSelect={setDate}
month={new Date(year, month)} month={new Date(year, month)}
onMonthChange={(newMonth) => { onMonthChange={(newMonth) => {
setMonth(newMonth.getMonth()) setMonth(newMonth.getMonth());
setYear(newMonth.getFullYear()) setYear(newMonth.getFullYear());
}} }}
initialFocus initialFocus
/> />
</PopoverContent> </PopoverContent>
</Popover> </Popover>
) );
} }

View File

@ -1,33 +1,33 @@
"use client" "use client";
import { XIcon } from "lucide-react" import { XIcon } from "lucide-react";
import { Dialog as DialogPrimitive } from "radix-ui" import { Dialog as DialogPrimitive } from "radix-ui";
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function Dialog({ function Dialog({
...props ...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) { }: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} /> return <DialogPrimitive.Root data-slot="dialog" {...props} />;
} }
function DialogTrigger({ function DialogTrigger({
...props ...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) { }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} /> return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
} }
function DialogPortal({ function DialogPortal({
...props ...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) { }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} /> return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
} }
function DialogClose({ function DialogClose({
...props ...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) { }: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} /> return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
} }
function DialogOverlay({ function DialogOverlay({
@ -39,11 +39,11 @@ function DialogOverlay({
data-slot="dialog-overlay" data-slot="dialog-overlay"
className={cn( className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function DialogContent({ function DialogContent({
@ -52,7 +52,7 @@ function DialogContent({
showCloseButton = true, showCloseButton = true,
...props ...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & { }: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean showCloseButton?: boolean;
}) { }) {
return ( return (
<DialogPortal data-slot="dialog-portal"> <DialogPortal data-slot="dialog-portal">
@ -61,7 +61,7 @@ function DialogContent({
data-slot="dialog-content" data-slot="dialog-content"
className={cn( className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className className,
)} )}
{...props} {...props}
> >
@ -77,7 +77,7 @@ function DialogContent({
)} )}
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPortal> </DialogPortal>
) );
} }
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
@ -87,7 +87,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
className={cn("flex flex-col gap-2 text-center sm:text-left", className)} className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props} {...props}
/> />
) );
} }
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
@ -96,11 +96,11 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
data-slot="dialog-footer" data-slot="dialog-footer"
className={cn( className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function DialogTitle({ function DialogTitle({
@ -113,7 +113,7 @@ function DialogTitle({
className={cn("text-lg leading-none font-semibold", className)} className={cn("text-lg leading-none font-semibold", className)}
{...props} {...props}
/> />
) );
} }
function DialogDescription({ function DialogDescription({
@ -126,7 +126,7 @@ function DialogDescription({
className={cn("text-start text-muted-foreground text-sm", className)} className={cn("text-start text-muted-foreground text-sm", className)}
{...props} {...props}
/> />
) );
} }
export { export {
@ -140,4 +140,4 @@ export {
DialogPortal, DialogPortal,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Drawer as DrawerPrimitive } from "vaul" import { Drawer as DrawerPrimitive } from "vaul";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Drawer = ({ const Drawer = ({
shouldScaleBackground = true, shouldScaleBackground = true,
@ -13,14 +13,14 @@ const Drawer = ({
shouldScaleBackground={shouldScaleBackground} shouldScaleBackground={shouldScaleBackground}
{...props} {...props}
/> />
) );
Drawer.displayName = "Drawer" Drawer.displayName = "Drawer";
const DrawerTrigger = DrawerPrimitive.Trigger const DrawerTrigger = DrawerPrimitive.Trigger;
const DrawerPortal = DrawerPrimitive.Portal const DrawerPortal = DrawerPrimitive.Portal;
const DrawerClose = DrawerPrimitive.Close const DrawerClose = DrawerPrimitive.Close;
const DrawerOverlay = React.forwardRef< const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>, React.ElementRef<typeof DrawerPrimitive.Overlay>,
@ -31,8 +31,8 @@ const DrawerOverlay = React.forwardRef<
className={cn("fixed inset-0 z-50 bg-black/80", className)} className={cn("fixed inset-0 z-50 bg-black/80", className)}
{...props} {...props}
/> />
)) ));
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
const DrawerContent = React.forwardRef< const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>, React.ElementRef<typeof DrawerPrimitive.Content>,
@ -44,7 +44,7 @@ const DrawerContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background", "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
className className,
)} )}
{...props} {...props}
> >
@ -52,8 +52,8 @@ const DrawerContent = React.forwardRef<
{children} {children}
</DrawerPrimitive.Content> </DrawerPrimitive.Content>
</DrawerPortal> </DrawerPortal>
)) ));
DrawerContent.displayName = "DrawerContent" DrawerContent.displayName = "DrawerContent";
const DrawerHeader = ({ const DrawerHeader = ({
className, className,
@ -63,8 +63,8 @@ const DrawerHeader = ({
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)} className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
{...props} {...props}
/> />
) );
DrawerHeader.displayName = "DrawerHeader" DrawerHeader.displayName = "DrawerHeader";
const DrawerFooter = ({ const DrawerFooter = ({
className, className,
@ -74,8 +74,8 @@ const DrawerFooter = ({
className={cn("mt-auto flex flex-col gap-2 p-4", className)} className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props} {...props}
/> />
) );
DrawerFooter.displayName = "DrawerFooter" DrawerFooter.displayName = "DrawerFooter";
const DrawerTitle = React.forwardRef< const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>, React.ElementRef<typeof DrawerPrimitive.Title>,
@ -85,12 +85,12 @@ const DrawerTitle = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"text-lg font-semibold leading-none tracking-tight", "text-lg font-semibold leading-none tracking-tight",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DrawerTitle.displayName = DrawerPrimitive.Title.displayName DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
const DrawerDescription = React.forwardRef< const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>, React.ElementRef<typeof DrawerPrimitive.Description>,
@ -101,8 +101,8 @@ const DrawerDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
DrawerDescription.displayName = DrawerPrimitive.Description.displayName DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
export { export {
Drawer, Drawer,
@ -115,4 +115,4 @@ export {
DrawerFooter, DrawerFooter,
DrawerTitle, DrawerTitle,
DrawerDescription, DrawerDescription,
} };

View File

@ -1,27 +1,27 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui" import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
import { Check, ChevronRight, Circle } from "lucide-react" import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const DropdownMenu = DropdownMenuPrimitive.Root const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef< const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, children, ...props }, ref) => ( >(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
@ -29,16 +29,16 @@ const DropdownMenuSubTrigger = React.forwardRef<
className={cn( className={cn(
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRight className="ml-auto" /> <ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>
)) ));
DropdownMenuSubTrigger.displayName = DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef< const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
@ -48,13 +48,13 @@ const DropdownMenuSubContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-dropdown-menu-content-transform-origin)", "z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-dropdown-menu-content-transform-origin)",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuSubContent.displayName = DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef< const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>, React.ElementRef<typeof DropdownMenuPrimitive.Content>,
@ -67,18 +67,18 @@ const DropdownMenuContent = React.forwardRef<
className={cn( className={cn(
"z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-32 overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md", "z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-32 overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-dropdown-menu-content-transform-origin)", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-dropdown-menu-content-transform-origin)",
className className,
)} )}
{...props} {...props}
/> />
</DropdownMenuPrimitive.Portal> </DropdownMenuPrimitive.Portal>
)) ));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef< const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>, React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
@ -86,12 +86,12 @@ const DropdownMenuItem = React.forwardRef<
className={cn( className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0", "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef< const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
@ -101,7 +101,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
className className,
)} )}
checked={checked} checked={checked}
{...props} {...props}
@ -113,9 +113,9 @@ const DropdownMenuCheckboxItem = React.forwardRef<
</span> </span>
{children} {children}
</DropdownMenuPrimitive.CheckboxItem> </DropdownMenuPrimitive.CheckboxItem>
)) ));
DropdownMenuCheckboxItem.displayName = DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef< const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
@ -125,7 +125,7 @@ const DropdownMenuRadioItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
className className,
)} )}
{...props} {...props}
> >
@ -136,13 +136,13 @@ const DropdownMenuRadioItem = React.forwardRef<
</span> </span>
{children} {children}
</DropdownMenuPrimitive.RadioItem> </DropdownMenuPrimitive.RadioItem>
)) ));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef< const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>, React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
@ -150,12 +150,12 @@ const DropdownMenuLabel = React.forwardRef<
className={cn( className={cn(
"px-2 py-1.5 text-sm font-semibold", "px-2 py-1.5 text-sm font-semibold",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef< const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>, React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
@ -166,8 +166,8 @@ const DropdownMenuSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
)) ));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ const DropdownMenuShortcut = ({
className, className,
@ -178,9 +178,9 @@ const DropdownMenuShortcut = ({
className={cn("ml-auto text-xs tracking-widest opacity-60", className)} className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props} {...props}
/> />
) );
} };
DropdownMenuShortcut.displayName = "DropdownMenuShortcut" DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export { export {
DropdownMenu, DropdownMenu,
@ -198,4 +198,4 @@ export {
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuRadioGroup, DropdownMenuRadioGroup,
} };

View File

@ -1,25 +1,31 @@
'use client'; "use client";
import * as SliderPrimitive from '@radix-ui/react-slider'; import * as SliderPrimitive from "@radix-ui/react-slider";
import * as React from 'react'; import * as React from "react";
import { cn } from '@/lib/utils'; import { cn } from "@/lib/utils";
interface DualRangeSliderProps extends React.ComponentProps<typeof SliderPrimitive.Root> { interface DualRangeSliderProps
labelPosition?: 'top' | 'bottom'; extends React.ComponentProps<typeof SliderPrimitive.Root> {
labelPosition?: "top" | "bottom";
label?: (value: number | undefined) => React.ReactNode; label?: (value: number | undefined) => React.ReactNode;
} }
const DualRangeSlider = React.forwardRef< const DualRangeSlider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>, React.ElementRef<typeof SliderPrimitive.Root>,
DualRangeSliderProps DualRangeSliderProps
>(({ className, label, labelPosition = 'top', ...props }, ref) => { >(({ className, label, labelPosition = "top", ...props }, ref) => {
const initialValue = Array.isArray(props.value) ? props.value : [props.min, props.max]; const initialValue = Array.isArray(props.value)
? props.value
: [props.min, props.max];
return ( return (
<SliderPrimitive.Root <SliderPrimitive.Root
ref={ref} ref={ref}
className={cn('relative flex w-full touch-none select-none items-center', className)} className={cn(
"relative flex w-full touch-none select-none items-center",
className,
)}
{...props} {...props}
> >
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary"> <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
@ -31,9 +37,9 @@ const DualRangeSlider = React.forwardRef<
{label && ( {label && (
<span <span
className={cn( className={cn(
'absolute flex w-full justify-center', "absolute flex w-full justify-center",
labelPosition === 'top' && '-top-7', labelPosition === "top" && "-top-7",
labelPosition === 'bottom' && 'top-4', labelPosition === "bottom" && "top-4",
)} )}
> >
{label(value)} {label(value)}
@ -45,6 +51,6 @@ const DualRangeSlider = React.forwardRef<
</SliderPrimitive.Root> </SliderPrimitive.Root>
); );
}); });
DualRangeSlider.displayName = 'DualRangeSlider'; DualRangeSlider.displayName = "DualRangeSlider";
export { DualRangeSlider }; export { DualRangeSlider };

View File

@ -1,14 +1,22 @@
import * as React from 'react'; import * as React from "react";
import { Input } from '@/components/ui/input'; import { Input } from "@/components/ui/input";
import { Label } from '@/components/ui/label'; import { Label } from "@/components/ui/label";
import { cn } from '@/lib/utils'; import { cn } from "@/lib/utils";
const FloatingInput = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>( const FloatingInput = React.forwardRef<
({ className, ...props }, ref) => { HTMLInputElement,
return <Input placeholder=" " className={cn('peer', className)} ref={ref} {...props} />; React.InputHTMLAttributes<HTMLInputElement>
}, >(({ className, ...props }, ref) => {
return (
<Input
placeholder=" "
className={cn("peer", className)}
ref={ref}
{...props}
/>
); );
FloatingInput.displayName = 'FloatingInput'; });
FloatingInput.displayName = "FloatingInput";
const FloatingLabel = React.forwardRef< const FloatingLabel = React.forwardRef<
React.ElementRef<typeof Label>, React.ElementRef<typeof Label>,
@ -17,7 +25,7 @@ const FloatingLabel = React.forwardRef<
return ( return (
<Label <Label
className={cn( className={cn(
'peer-focus:secondary peer-focus:dark:secondary absolute start-2 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-background px-2 text-sm text-gray-500 duration-300 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 dark:bg-background rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4 cursor-text', "peer-focus:secondary peer-focus:dark:secondary absolute start-2 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-background px-2 text-sm text-gray-500 duration-300 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 dark:bg-background rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4 cursor-text",
className, className,
)} )}
ref={ref} ref={ref}
@ -25,9 +33,11 @@ const FloatingLabel = React.forwardRef<
/> />
); );
}); });
FloatingLabel.displayName = 'FloatingLabel'; FloatingLabel.displayName = "FloatingLabel";
type FloatingLabelInputProps = React.InputHTMLAttributes<HTMLInputElement> & { label?: string }; type FloatingLabelInputProps = React.InputHTMLAttributes<HTMLInputElement> & {
label?: string;
};
const FloatingLabelInput = React.forwardRef< const FloatingLabelInput = React.forwardRef<
React.ElementRef<typeof FloatingInput>, React.ElementRef<typeof FloatingInput>,
@ -40,6 +50,6 @@ const FloatingLabelInput = React.forwardRef<
</div> </div>
); );
}); });
FloatingLabelInput.displayName = 'FloatingLabelInput'; FloatingLabelInput.displayName = "FloatingLabelInput";
export { FloatingInput, FloatingLabel, FloatingLabelInput }; export { FloatingInput, FloatingLabel, FloatingLabelInput };

View File

@ -1,7 +1,7 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Label as LabelPrimitive, Slot as SlotPrimitive } from "radix-ui" import { Label as LabelPrimitive, Slot as SlotPrimitive } from "radix-ui";
import { import {
Controller, Controller,
@ -10,27 +10,27 @@ import {
type ControllerProps, type ControllerProps,
type FieldPath, type FieldPath,
type FieldValues, type FieldValues,
} from "react-hook-form" } from "react-hook-form";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label";
const Form = FormProvider const Form = FormProvider;
type FormFieldContextValue< type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = { > = {
name: TName name: TName;
} };
const FormFieldContext = React.createContext<FormFieldContextValue>( const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue {} as FormFieldContextValue,
) );
const FormField = < const FormField = <
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({ >({
...props ...props
}: ControllerProps<TFieldValues, TName>) => { }: ControllerProps<TFieldValues, TName>) => {
@ -38,21 +38,21 @@ const FormField = <
<FormFieldContext.Provider value={{ name: props.name }}> <FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} /> <Controller {...props} />
</FormFieldContext.Provider> </FormFieldContext.Provider>
) );
} };
const useFormField = () => { const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext) const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext) const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext() const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState) const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) { if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>") throw new Error("useFormField should be used within <FormField>");
} }
const { id } = itemContext const { id } = itemContext;
return { return {
id, id,
@ -61,36 +61,36 @@ const useFormField = () => {
formDescriptionId: `${id}-form-item-description`, formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`, formMessageId: `${id}-form-item-message`,
...fieldState, ...fieldState,
} };
} };
type FormItemContextValue = { type FormItemContextValue = {
id: string id: string;
} };
const FormItemContext = React.createContext<FormItemContextValue>( const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue {} as FormItemContextValue,
) );
const FormItem = React.forwardRef< const FormItem = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const id = React.useId() const id = React.useId();
return ( return (
<FormItemContext.Provider value={{ id }}> <FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} /> <div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider> </FormItemContext.Provider>
) );
}) });
FormItem.displayName = "FormItem" FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef< const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>, React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField() const { error, formItemId } = useFormField();
return ( return (
<Label <Label
@ -99,15 +99,16 @@ const FormLabel = React.forwardRef<
htmlFor={formItemId} htmlFor={formItemId}
{...props} {...props}
/> />
) );
}) });
FormLabel.displayName = "FormLabel" FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef< const FormControl = React.forwardRef<
React.ElementRef<typeof SlotPrimitive.Slot>, React.ElementRef<typeof SlotPrimitive.Slot>,
React.ComponentPropsWithoutRef<typeof SlotPrimitive.Slot> React.ComponentPropsWithoutRef<typeof SlotPrimitive.Slot>
>(({ ...props }, ref) => { >(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField() const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return ( return (
<SlotPrimitive.Slot <SlotPrimitive.Slot
@ -121,15 +122,15 @@ const FormControl = React.forwardRef<
aria-invalid={!!error} aria-invalid={!!error}
{...props} {...props}
/> />
) );
}) });
FormControl.displayName = "FormControl" FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef< const FormDescription = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement> React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField() const { formDescriptionId } = useFormField();
return ( return (
<p <p
@ -138,19 +139,19 @@ const FormDescription = React.forwardRef<
className={cn("text-[0.8rem] text-muted-foreground", className)} className={cn("text-[0.8rem] text-muted-foreground", className)}
{...props} {...props}
/> />
) );
}) });
FormDescription.displayName = "FormDescription" FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef< const FormMessage = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement> React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => { >(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField() const { error, formMessageId } = useFormField();
const body = error ? String(error?.message ?? "") : children const body = error ? String(error?.message ?? "") : children;
if (!body) { if (!body) {
return null return null;
} }
return ( return (
@ -162,9 +163,9 @@ const FormMessage = React.forwardRef<
> >
{body} {body}
</p> </p>
) );
}) });
FormMessage.displayName = "FormMessage" FormMessage.displayName = "FormMessage";
export { export {
useFormField, useFormField,
@ -175,4 +176,4 @@ export {
FormDescription, FormDescription,
FormMessage, FormMessage,
FormField, FormField,
} };

View File

@ -1,13 +1,13 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { HoverCard as HoverCardPrimitive } from "radix-ui" import { HoverCard as HoverCardPrimitive } from "radix-ui";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const HoverCard = HoverCardPrimitive.Root const HoverCard = HoverCardPrimitive.Root;
const HoverCardTrigger = HoverCardPrimitive.Trigger const HoverCardTrigger = HoverCardPrimitive.Trigger;
const HoverCardContent = React.forwardRef< const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>, React.ElementRef<typeof HoverCardPrimitive.Content>,
@ -19,11 +19,11 @@ const HoverCardContent = React.forwardRef<
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-hover-card-content-transform-origin)", "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-hover-card-content-transform-origin)",
className className,
)} )}
{...props} {...props}
/> />
)) ));
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
export { HoverCard, HoverCardTrigger, HoverCardContent } export { HoverCard, HoverCardTrigger, HoverCardContent };

View File

@ -1,10 +1,10 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { OTPInput, OTPInputContext } from "input-otp" import { OTPInput, OTPInputContext } from "input-otp";
import { Minus } from "lucide-react" import { Minus } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const InputOTP = React.forwardRef< const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>, React.ElementRef<typeof OTPInput>,
@ -14,28 +14,28 @@ const InputOTP = React.forwardRef<
ref={ref} ref={ref}
containerClassName={cn( containerClassName={cn(
"flex items-center gap-2 has-disabled:opacity-50", "flex items-center gap-2 has-disabled:opacity-50",
containerClassName containerClassName,
)} )}
className={cn("disabled:cursor-not-allowed", className)} className={cn("disabled:cursor-not-allowed", className)}
{...props} {...props}
/> />
)) ));
InputOTP.displayName = "InputOTP" InputOTP.displayName = "InputOTP";
const InputOTPGroup = React.forwardRef< const InputOTPGroup = React.forwardRef<
React.ElementRef<"div">, React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div"> React.ComponentPropsWithoutRef<"div">
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center", className)} {...props} /> <div ref={ref} className={cn("flex items-center", className)} {...props} />
)) ));
InputOTPGroup.displayName = "InputOTPGroup" InputOTPGroup.displayName = "InputOTPGroup";
const InputOTPSlot = React.forwardRef< const InputOTPSlot = React.forwardRef<
React.ElementRef<"div">, React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div"> & { index: number } React.ComponentPropsWithoutRef<"div"> & { index: number }
>(({ index, className, ...props }, ref) => { >(({ index, className, ...props }, ref) => {
const inputOTPContext = React.useContext(OTPInputContext) const inputOTPContext = React.useContext(OTPInputContext);
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index] const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
return ( return (
<div <div
@ -43,7 +43,7 @@ const InputOTPSlot = React.forwardRef<
className={cn( className={cn(
"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md", "relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
isActive && "z-10 ring-1 ring-ring", isActive && "z-10 ring-1 ring-ring",
className className,
)} )}
{...props} {...props}
> >
@ -54,9 +54,9 @@ const InputOTPSlot = React.forwardRef<
</div> </div>
)} )}
</div> </div>
) );
}) });
InputOTPSlot.displayName = "InputOTPSlot" InputOTPSlot.displayName = "InputOTPSlot";
const InputOTPSeparator = React.forwardRef< const InputOTPSeparator = React.forwardRef<
React.ElementRef<"div">, React.ElementRef<"div">,
@ -65,7 +65,7 @@ const InputOTPSeparator = React.forwardRef<
<div ref={ref} role="separator" {...props}> <div ref={ref} role="separator" {...props}>
<Minus /> <Minus />
</div> </div>
)) ));
InputOTPSeparator.displayName = "InputOTPSeparator" InputOTPSeparator.displayName = "InputOTPSeparator";
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };

View File

@ -1,6 +1,6 @@
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function Input({ className, type, ...props }: React.ComponentProps<"input">) { function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return ( return (
@ -11,11 +11,11 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
export { Input } export { Input };

View File

@ -1,14 +1,14 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Label as LabelPrimitive } from "radix-ui" import { Label as LabelPrimitive } from "radix-ui";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const labelVariants = cva( const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
) );
const Label = React.forwardRef< const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>, React.ElementRef<typeof LabelPrimitive.Root>,
@ -20,7 +20,7 @@ const Label = React.forwardRef<
className={cn(labelVariants(), className)} className={cn(labelVariants(), className)}
{...props} {...props}
/> />
)) ));
Label.displayName = LabelPrimitive.Root.displayName Label.displayName = LabelPrimitive.Root.displayName;
export { Label } export { Label };

View File

@ -1,39 +1,39 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Menubar as MenubarPrimitive } from "radix-ui" import { Menubar as MenubarPrimitive } from "radix-ui";
import { Check, ChevronRight, Circle } from "lucide-react" import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function MenubarMenu({ function MenubarMenu({
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.Menu>) { }: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
return <MenubarPrimitive.Menu {...props} /> return <MenubarPrimitive.Menu {...props} />;
} }
function MenubarGroup({ function MenubarGroup({
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.Group>) { }: React.ComponentProps<typeof MenubarPrimitive.Group>) {
return <MenubarPrimitive.Group {...props} /> return <MenubarPrimitive.Group {...props} />;
} }
function MenubarPortal({ function MenubarPortal({
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.Portal>) { }: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
return <MenubarPrimitive.Portal {...props} /> return <MenubarPrimitive.Portal {...props} />;
} }
function MenubarRadioGroup({ function MenubarRadioGroup({
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) { }: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
return <MenubarPrimitive.RadioGroup {...props} /> return <MenubarPrimitive.RadioGroup {...props} />;
} }
function MenubarSub({ function MenubarSub({
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.Sub>) { }: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} /> return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />;
} }
const Menubar = React.forwardRef< const Menubar = React.forwardRef<
@ -44,12 +44,12 @@ const Menubar = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm", "flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm",
className className,
)} )}
{...props} {...props}
/> />
)) ));
Menubar.displayName = MenubarPrimitive.Root.displayName Menubar.displayName = MenubarPrimitive.Root.displayName;
const MenubarTrigger = React.forwardRef< const MenubarTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Trigger>, React.ElementRef<typeof MenubarPrimitive.Trigger>,
@ -59,17 +59,17 @@ const MenubarTrigger = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", "flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
className className,
)} )}
{...props} {...props}
/> />
)) ));
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;
const MenubarSubTrigger = React.forwardRef< const MenubarSubTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubTrigger>, React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & { React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, children, ...props }, ref) => ( >(({ className, inset, children, ...props }, ref) => (
<MenubarPrimitive.SubTrigger <MenubarPrimitive.SubTrigger
@ -77,15 +77,15 @@ const MenubarSubTrigger = React.forwardRef<
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRight className="ml-auto h-4 w-4" /> <ChevronRight className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger> </MenubarPrimitive.SubTrigger>
)) ));
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;
const MenubarSubContent = React.forwardRef< const MenubarSubContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubContent>, React.ElementRef<typeof MenubarPrimitive.SubContent>,
@ -95,12 +95,12 @@ const MenubarSubContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-menubar-content-transform-origin)", "z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-menubar-content-transform-origin)",
className className,
)} )}
{...props} {...props}
/> />
)) ));
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;
const MenubarContent = React.forwardRef< const MenubarContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Content>, React.ElementRef<typeof MenubarPrimitive.Content>,
@ -108,7 +108,7 @@ const MenubarContent = React.forwardRef<
>( >(
( (
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props }, { className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
ref ref,
) => ( ) => (
<MenubarPrimitive.Portal> <MenubarPrimitive.Portal>
<MenubarPrimitive.Content <MenubarPrimitive.Content
@ -118,19 +118,19 @@ const MenubarContent = React.forwardRef<
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 min-w-48 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-menubar-content-transform-origin)", "z-50 min-w-48 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-menubar-content-transform-origin)",
className className,
)} )}
{...props} {...props}
/> />
</MenubarPrimitive.Portal> </MenubarPrimitive.Portal>
) ),
) );
MenubarContent.displayName = MenubarPrimitive.Content.displayName MenubarContent.displayName = MenubarPrimitive.Content.displayName;
const MenubarItem = React.forwardRef< const MenubarItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Item>, React.ElementRef<typeof MenubarPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & { React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Item <MenubarPrimitive.Item
@ -138,12 +138,12 @@ const MenubarItem = React.forwardRef<
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50", "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
MenubarItem.displayName = MenubarPrimitive.Item.displayName MenubarItem.displayName = MenubarPrimitive.Item.displayName;
const MenubarCheckboxItem = React.forwardRef< const MenubarCheckboxItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>, React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
@ -153,7 +153,7 @@ const MenubarCheckboxItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
className className,
)} )}
checked={checked} checked={checked}
{...props} {...props}
@ -165,8 +165,8 @@ const MenubarCheckboxItem = React.forwardRef<
</span> </span>
{children} {children}
</MenubarPrimitive.CheckboxItem> </MenubarPrimitive.CheckboxItem>
)) ));
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;
const MenubarRadioItem = React.forwardRef< const MenubarRadioItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.RadioItem>, React.ElementRef<typeof MenubarPrimitive.RadioItem>,
@ -176,7 +176,7 @@ const MenubarRadioItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
className className,
)} )}
{...props} {...props}
> >
@ -187,13 +187,13 @@ const MenubarRadioItem = React.forwardRef<
</span> </span>
{children} {children}
</MenubarPrimitive.RadioItem> </MenubarPrimitive.RadioItem>
)) ));
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;
const MenubarLabel = React.forwardRef< const MenubarLabel = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Label>, React.ElementRef<typeof MenubarPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & { React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label <MenubarPrimitive.Label
@ -201,12 +201,12 @@ const MenubarLabel = React.forwardRef<
className={cn( className={cn(
"px-2 py-1.5 text-sm font-semibold", "px-2 py-1.5 text-sm font-semibold",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
MenubarLabel.displayName = MenubarPrimitive.Label.displayName MenubarLabel.displayName = MenubarPrimitive.Label.displayName;
const MenubarSeparator = React.forwardRef< const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>, React.ElementRef<typeof MenubarPrimitive.Separator>,
@ -217,8 +217,8 @@ const MenubarSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
)) ));
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
const MenubarShortcut = ({ const MenubarShortcut = ({
className, className,
@ -228,13 +228,13 @@ const MenubarShortcut = ({
<span <span
className={cn( className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground", "ml-auto text-xs tracking-widest text-muted-foreground",
className className,
)} )}
{...props} {...props}
/> />
) );
} };
MenubarShortcut.displayname = "MenubarShortcut" MenubarShortcut.displayname = "MenubarShortcut";
export { export {
Menubar, Menubar,
@ -253,4 +253,4 @@ export {
MenubarGroup, MenubarGroup,
MenubarSub, MenubarSub,
MenubarShortcut, MenubarShortcut,
} };

View File

@ -1,9 +1,9 @@
import * as React from "react" import * as React from "react";
import { NavigationMenu as NavigationMenuPrimitive } from "radix-ui" import { NavigationMenu as NavigationMenuPrimitive } from "radix-ui";
import { cva } from "class-variance-authority" import { cva } from "class-variance-authority";
import { ChevronDown } from "lucide-react" import { ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const NavigationMenu = React.forwardRef< const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>, React.ElementRef<typeof NavigationMenuPrimitive.Root>,
@ -13,15 +13,15 @@ const NavigationMenu = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative z-10 flex max-w-max flex-1 items-center justify-center", "relative z-10 flex max-w-max flex-1 items-center justify-center",
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<NavigationMenuViewport /> <NavigationMenuViewport />
</NavigationMenuPrimitive.Root> </NavigationMenuPrimitive.Root>
)) ));
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
const NavigationMenuList = React.forwardRef< const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>, React.ElementRef<typeof NavigationMenuPrimitive.List>,
@ -31,18 +31,18 @@ const NavigationMenuList = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1", "group flex flex-1 list-none items-center justify-center space-x-1",
className className,
)} )}
{...props} {...props}
/> />
)) ));
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
const NavigationMenuItem = NavigationMenuPrimitive.Item const NavigationMenuItem = NavigationMenuPrimitive.Item;
const navigationMenuTriggerStyle = cva( const navigationMenuTriggerStyle = cva(
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-accent-foreground data-[state=open]:bg-accent/50 data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent" "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-accent-foreground data-[state=open]:bg-accent/50 data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent",
) );
const NavigationMenuTrigger = React.forwardRef< const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>, React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
@ -59,8 +59,8 @@ const NavigationMenuTrigger = React.forwardRef<
aria-hidden="true" aria-hidden="true"
/> />
</NavigationMenuPrimitive.Trigger> </NavigationMenuPrimitive.Trigger>
)) ));
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
const NavigationMenuContent = React.forwardRef< const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>, React.ElementRef<typeof NavigationMenuPrimitive.Content>,
@ -70,14 +70,14 @@ const NavigationMenuContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ", "left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
className className,
)} )}
{...props} {...props}
/> />
)) ));
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
const NavigationMenuLink = NavigationMenuPrimitive.Link const NavigationMenuLink = NavigationMenuPrimitive.Link;
const NavigationMenuViewport = React.forwardRef< const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>, React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
@ -87,15 +87,15 @@ const NavigationMenuViewport = React.forwardRef<
<NavigationMenuPrimitive.Viewport <NavigationMenuPrimitive.Viewport
className={cn( className={cn(
"origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-(--radix-navigation-menu-viewport-width)", "origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-(--radix-navigation-menu-viewport-width)",
className className,
)} )}
ref={ref} ref={ref}
{...props} {...props}
/> />
</div> </div>
)) ));
NavigationMenuViewport.displayName = NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName NavigationMenuPrimitive.Viewport.displayName;
const NavigationMenuIndicator = React.forwardRef< const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>, React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
@ -105,15 +105,15 @@ const NavigationMenuIndicator = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"top-full z-1 flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in", "top-full z-1 flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className className,
)} )}
{...props} {...props}
> >
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" /> <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator> </NavigationMenuPrimitive.Indicator>
)) ));
NavigationMenuIndicator.displayName = NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName NavigationMenuPrimitive.Indicator.displayName;
export { export {
navigationMenuTriggerStyle, navigationMenuTriggerStyle,
@ -125,4 +125,4 @@ export {
NavigationMenuLink, NavigationMenuLink,
NavigationMenuIndicator, NavigationMenuIndicator,
NavigationMenuViewport, NavigationMenuViewport,
} };

View File

@ -61,7 +61,11 @@ const InputComponent = React.forwardRef<
HTMLInputElement, HTMLInputElement,
React.ComponentProps<"input"> React.ComponentProps<"input">
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<Input className={cn("mx-2 bg-white/10 backdrop-blur-md", className)} {...props} ref={ref} /> <Input
className={cn("mx-2 bg-white/10 backdrop-blur-md", className)}
{...props}
ref={ref}
/>
)); ));
InputComponent.displayName = "InputComponent"; InputComponent.displayName = "InputComponent";

View File

@ -1,15 +1,15 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Popover as PopoverPrimitive } from "radix-ui" import { Popover as PopoverPrimitive } from "radix-ui";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Popover = PopoverPrimitive.Root const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverAnchor = PopoverPrimitive.Anchor const PopoverAnchor = PopoverPrimitive.Anchor;
const PopoverContent = React.forwardRef< const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>, React.ElementRef<typeof PopoverPrimitive.Content>,
@ -22,12 +22,12 @@ const PopoverContent = React.forwardRef<
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-popover-content-transform-origin)", "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-popover-content-transform-origin)",
className className,
)} )}
{...props} {...props}
/> />
</PopoverPrimitive.Portal> </PopoverPrimitive.Portal>
)) ));
PopoverContent.displayName = PopoverPrimitive.Content.displayName PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import { Progress as ProgressPrimitive } from "radix-ui" import { Progress as ProgressPrimitive } from "radix-ui";
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Progress = React.forwardRef< const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>, React.ElementRef<typeof ProgressPrimitive.Root>,
@ -13,16 +13,20 @@ const Progress = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative h-1 w-full overflow-hidden rounded-full bg-primary/20", "relative h-1 w-full overflow-hidden rounded-full bg-primary/20",
className className,
)} )}
{...props} {...props}
> >
<ProgressPrimitive.Indicator <ProgressPrimitive.Indicator
className={cn("h-full w-full flex-1 transition-all", className, (value ?? 0) < 20 ? "bg-red-500" : "bg-sarLinkOrange")} className={cn(
"h-full w-full flex-1 transition-all",
className,
(value ?? 0) < 20 ? "bg-red-500" : "bg-sarLinkOrange",
)}
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/> />
</ProgressPrimitive.Root> </ProgressPrimitive.Root>
)) ));
Progress.displayName = ProgressPrimitive.Root.displayName Progress.displayName = ProgressPrimitive.Root.displayName;
export { Progress } export { Progress };

View File

@ -1,10 +1,10 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { RadioGroup as RadioGroupPrimitive } from "radix-ui" import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
import { Circle } from "lucide-react" import { Circle } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const RadioGroup = React.forwardRef< const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>, React.ElementRef<typeof RadioGroupPrimitive.Root>,
@ -16,9 +16,9 @@ const RadioGroup = React.forwardRef<
{...props} {...props}
ref={ref} ref={ref}
/> />
) );
}) });
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
const RadioGroupItem = React.forwardRef< const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>, React.ElementRef<typeof RadioGroupPrimitive.Item>,
@ -29,7 +29,7 @@ const RadioGroupItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", "aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className className,
)} )}
{...props} {...props}
> >
@ -37,8 +37,8 @@ const RadioGroupItem = React.forwardRef<
<Circle className="h-3.5 w-3.5 fill-primary" /> <Circle className="h-3.5 w-3.5 fill-primary" />
</RadioGroupPrimitive.Indicator> </RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item> </RadioGroupPrimitive.Item>
) );
}) });
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
export { RadioGroup, RadioGroupItem } export { RadioGroup, RadioGroupItem };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import { GripVertical } from "lucide-react" import { GripVertical } from "lucide-react";
import * as ResizablePrimitive from "react-resizable-panels" import * as ResizablePrimitive from "react-resizable-panels";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const ResizablePanelGroup = ({ const ResizablePanelGroup = ({
className, className,
@ -12,25 +12,25 @@ const ResizablePanelGroup = ({
<ResizablePrimitive.PanelGroup <ResizablePrimitive.PanelGroup
className={cn( className={cn(
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col", "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
className className,
)} )}
{...props} {...props}
/> />
) );
const ResizablePanel = ResizablePrimitive.Panel const ResizablePanel = ResizablePrimitive.Panel;
const ResizableHandle = ({ const ResizableHandle = ({
withHandle, withHandle,
className, className,
...props ...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & { }: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
withHandle?: boolean withHandle?: boolean;
}) => ( }) => (
<ResizablePrimitive.PanelResizeHandle <ResizablePrimitive.PanelResizeHandle
className={cn( className={cn(
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90", "relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
className className,
)} )}
{...props} {...props}
> >
@ -40,6 +40,6 @@ const ResizableHandle = ({
</div> </div>
)} )}
</ResizablePrimitive.PanelResizeHandle> </ResizablePrimitive.PanelResizeHandle>
) );
export { ResizablePanelGroup, ResizablePanel, ResizableHandle } export { ResizablePanelGroup, ResizablePanel, ResizableHandle };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui" import { ScrollArea as ScrollAreaPrimitive } from "radix-ui";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const ScrollArea = React.forwardRef< const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>, React.ElementRef<typeof ScrollAreaPrimitive.Root>,
@ -20,8 +20,8 @@ const ScrollArea = React.forwardRef<
<ScrollBar /> <ScrollBar />
<ScrollAreaPrimitive.Corner /> <ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root> </ScrollAreaPrimitive.Root>
)) ));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollBar = React.forwardRef< const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
@ -36,13 +36,13 @@ const ScrollBar = React.forwardRef<
"h-full w-2.5 border-l border-l-transparent p-px", "h-full w-2.5 border-l border-l-transparent p-px",
orientation === "horizontal" && orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-px", "h-2.5 flex-col border-t border-t-transparent p-px",
className className,
)} )}
{...props} {...props}
> >
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar> </ScrollAreaPrimitive.ScrollAreaScrollbar>
)) ));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
export { ScrollArea, ScrollBar } export { ScrollArea, ScrollBar };

View File

@ -1,16 +1,16 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Select as SelectPrimitive } from "radix-ui" import { Select as SelectPrimitive } from "radix-ui";
import { Check, ChevronDown, ChevronUp } from "lucide-react" import { Check, ChevronDown, ChevronUp } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Select = SelectPrimitive.Root const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef< const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>, React.ElementRef<typeof SelectPrimitive.Trigger>,
@ -20,7 +20,7 @@ const SelectTrigger = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className className,
)} )}
{...props} {...props}
> >
@ -29,8 +29,8 @@ const SelectTrigger = React.forwardRef<
<ChevronDown className="h-4 w-4 opacity-50" /> <ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon> </SelectPrimitive.Icon>
</SelectPrimitive.Trigger> </SelectPrimitive.Trigger>
)) ));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef< const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>, React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
@ -40,14 +40,14 @@ const SelectScrollUpButton = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default items-center justify-center py-1", "flex cursor-default items-center justify-center py-1",
className className,
)} )}
{...props} {...props}
> >
<ChevronUp className="h-4 w-4" /> <ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton> </SelectPrimitive.ScrollUpButton>
)) ));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef< const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>, React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
@ -57,15 +57,15 @@ const SelectScrollDownButton = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default items-center justify-center py-1", "flex cursor-default items-center justify-center py-1",
className className,
)} )}
{...props} {...props}
> >
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton> </SelectPrimitive.ScrollDownButton>
)) ));
SelectScrollDownButton.displayName = SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef< const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>, React.ElementRef<typeof SelectPrimitive.Content>,
@ -78,7 +78,7 @@ const SelectContent = React.forwardRef<
"relative z-50 max-h-(--radix-select-content-available-height) min-w-32 overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-select-content-transform-origin)", "relative z-50 max-h-(--radix-select-content-available-height) min-w-32 overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-select-content-transform-origin)",
position === "popper" && position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className className,
)} )}
position={position} position={position}
{...props} {...props}
@ -88,7 +88,7 @@ const SelectContent = React.forwardRef<
className={cn( className={cn(
"p-1", "p-1",
position === "popper" && position === "popper" &&
"h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)" "h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)",
)} )}
> >
{children} {children}
@ -96,8 +96,8 @@ const SelectContent = React.forwardRef<
<SelectScrollDownButton /> <SelectScrollDownButton />
</SelectPrimitive.Content> </SelectPrimitive.Content>
</SelectPrimitive.Portal> </SelectPrimitive.Portal>
)) ));
SelectContent.displayName = SelectPrimitive.Content.displayName SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef< const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>, React.ElementRef<typeof SelectPrimitive.Label>,
@ -108,8 +108,8 @@ const SelectLabel = React.forwardRef<
className={cn("px-2 py-1.5 text-sm font-semibold", className)} className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props} {...props}
/> />
)) ));
SelectLabel.displayName = SelectPrimitive.Label.displayName SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef< const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>, React.ElementRef<typeof SelectPrimitive.Item>,
@ -119,7 +119,7 @@ const SelectItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50", "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
className className,
)} )}
{...props} {...props}
> >
@ -130,8 +130,8 @@ const SelectItem = React.forwardRef<
</span> </span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item> </SelectPrimitive.Item>
)) ));
SelectItem.displayName = SelectPrimitive.Item.displayName SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef< const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>, React.ElementRef<typeof SelectPrimitive.Separator>,
@ -142,8 +142,8 @@ const SelectSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
)) ));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export { export {
Select, Select,
@ -156,4 +156,4 @@ export {
SelectSeparator, SelectSeparator,
SelectScrollUpButton, SelectScrollUpButton,
SelectScrollDownButton, SelectScrollDownButton,
} };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Separator as SeparatorPrimitive } from "radix-ui" import { Separator as SeparatorPrimitive } from "radix-ui";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Separator = React.forwardRef< const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>, React.ElementRef<typeof SeparatorPrimitive.Root>,
@ -11,7 +11,7 @@ const Separator = React.forwardRef<
>( >(
( (
{ className, orientation = "horizontal", decorative = true, ...props }, { className, orientation = "horizontal", decorative = true, ...props },
ref ref,
) => ( ) => (
<SeparatorPrimitive.Root <SeparatorPrimitive.Root
ref={ref} ref={ref}
@ -20,12 +20,12 @@ const Separator = React.forwardRef<
className={cn( className={cn(
"shrink-0 bg-border", "shrink-0 bg-border",
orientation === "horizontal" ? "h-px w-full" : "h-full w-px", orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
className className,
)} )}
{...props} {...props}
/> />
) ),
) );
Separator.displayName = SeparatorPrimitive.Root.displayName Separator.displayName = SeparatorPrimitive.Root.displayName;
export { Separator } export { Separator };

View File

@ -1,19 +1,19 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Dialog as SheetPrimitive } from "radix-ui" import { Dialog as SheetPrimitive } from "radix-ui";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react" import { X } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Sheet = SheetPrimitive.Root const Sheet = SheetPrimitive.Root;
const SheetTrigger = SheetPrimitive.Trigger const SheetTrigger = SheetPrimitive.Trigger;
const SheetClose = SheetPrimitive.Close const SheetClose = SheetPrimitive.Close;
const SheetPortal = SheetPrimitive.Portal const SheetPortal = SheetPrimitive.Portal;
const SheetOverlay = React.forwardRef< const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>, React.ElementRef<typeof SheetPrimitive.Overlay>,
@ -22,13 +22,13 @@ const SheetOverlay = React.forwardRef<
<SheetPrimitive.Overlay <SheetPrimitive.Overlay
className={cn( 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", "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 className,
)} )}
{...props} {...props}
ref={ref} ref={ref}
/> />
)) ));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva( const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out", "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
@ -46,8 +46,8 @@ const sheetVariants = cva(
defaultVariants: { defaultVariants: {
side: "right", side: "right",
}, },
} },
) );
interface SheetContentProps interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
@ -71,8 +71,8 @@ const SheetContent = React.forwardRef<
{children} {children}
</SheetPrimitive.Content> </SheetPrimitive.Content>
</SheetPortal> </SheetPortal>
)) ));
SheetContent.displayName = SheetPrimitive.Content.displayName SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({ const SheetHeader = ({
className, className,
@ -81,12 +81,12 @@ const SheetHeader = ({
<div <div
className={cn( className={cn(
"flex flex-col space-y-2 text-center sm:text-left", "flex flex-col space-y-2 text-center sm:text-left",
className className,
)} )}
{...props} {...props}
/> />
) );
SheetHeader.displayName = "SheetHeader" SheetHeader.displayName = "SheetHeader";
const SheetFooter = ({ const SheetFooter = ({
className, className,
@ -95,12 +95,12 @@ const SheetFooter = ({
<div <div
className={cn( className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className className,
)} )}
{...props} {...props}
/> />
) );
SheetFooter.displayName = "SheetFooter" SheetFooter.displayName = "SheetFooter";
const SheetTitle = React.forwardRef< const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>, React.ElementRef<typeof SheetPrimitive.Title>,
@ -111,8 +111,8 @@ const SheetTitle = React.forwardRef<
className={cn("text-lg font-semibold text-foreground", className)} className={cn("text-lg font-semibold text-foreground", className)}
{...props} {...props}
/> />
)) ));
SheetTitle.displayName = SheetPrimitive.Title.displayName SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef< const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>, React.ElementRef<typeof SheetPrimitive.Description>,
@ -123,8 +123,8 @@ const SheetDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
SheetDescription.displayName = SheetPrimitive.Description.displayName SheetDescription.displayName = SheetPrimitive.Description.displayName;
export { export {
Sheet, Sheet,
@ -137,4 +137,4 @@ export {
SheetFooter, SheetFooter,
SheetTitle, SheetTitle,
SheetDescription, SheetDescription,
} };

View File

@ -1,64 +1,64 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Slot as SlotPrimitive } from "radix-ui" import { Slot as SlotPrimitive } from "radix-ui";
import { VariantProps, cva } from "class-variance-authority" import { VariantProps, cva } from "class-variance-authority";
import { PanelLeft } from "lucide-react" import { PanelLeft } from "lucide-react";
import { useIsMobile } from "@/hooks/use-mobile" import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator" import { Separator } from "@/components/ui/separator";
import { import {
Sheet, Sheet,
SheetContent, SheetContent,
SheetDescription, SheetDescription,
SheetHeader, SheetHeader,
SheetTitle, SheetTitle,
} from "@/components/ui/sheet" } from "@/components/ui/sheet";
import { Skeleton } from "@/components/ui/skeleton" import { Skeleton } from "@/components/ui/skeleton";
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip" } from "@/components/ui/tooltip";
const SIDEBAR_COOKIE_NAME = "sidebar_state" const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "16rem" const SIDEBAR_WIDTH = "16rem";
const SIDEBAR_WIDTH_MOBILE = "18rem" const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem" const SIDEBAR_WIDTH_ICON = "3rem";
const SIDEBAR_KEYBOARD_SHORTCUT = "b" const SIDEBAR_KEYBOARD_SHORTCUT = "b";
type SidebarContextProps = { type SidebarContextProps = {
state: "expanded" | "collapsed" state: "expanded" | "collapsed";
open: boolean open: boolean;
setOpen: (open: boolean) => void setOpen: (open: boolean) => void;
openMobile: boolean openMobile: boolean;
setOpenMobile: (open: boolean) => void setOpenMobile: (open: boolean) => void;
isMobile: boolean isMobile: boolean;
toggleSidebar: () => void toggleSidebar: () => void;
} };
const SidebarContext = React.createContext<SidebarContextProps | null>(null) const SidebarContext = React.createContext<SidebarContextProps | null>(null);
function useSidebar() { function useSidebar() {
const context = React.useContext(SidebarContext) const context = React.useContext(SidebarContext);
if (!context) { if (!context) {
throw new Error("useSidebar must be used within a SidebarProvider.") throw new Error("useSidebar must be used within a SidebarProvider.");
} }
return context return context;
} }
const SidebarProvider = React.forwardRef< const SidebarProvider = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.ComponentProps<"div"> & { React.ComponentProps<"div"> & {
defaultOpen?: boolean defaultOpen?: boolean;
open?: boolean open?: boolean;
onOpenChange?: (open: boolean) => void onOpenChange?: (open: boolean) => void;
} }
>( >(
( (
@ -71,36 +71,36 @@ const SidebarProvider = React.forwardRef<
children, children,
...props ...props
}, },
ref ref,
) => { ) => {
const isMobile = useIsMobile() const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = React.useState(false) const [openMobile, setOpenMobile] = React.useState(false);
// This is the internal state of the sidebar. // This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component. // We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen) const [_open, _setOpen] = React.useState(defaultOpen);
const open = openProp ?? _open const open = openProp ?? _open;
const setOpen = React.useCallback( const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => { (value: boolean | ((value: boolean) => boolean)) => {
const openState = typeof value === "function" ? value(open) : value const openState = typeof value === "function" ? value(open) : value;
if (setOpenProp) { if (setOpenProp) {
setOpenProp(openState) setOpenProp(openState);
} else { } else {
_setOpen(openState) _setOpen(openState);
} }
// This sets the cookie to keep the sidebar state. // This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
}, },
[setOpenProp, open] [setOpenProp, open],
) );
// Helper to toggle the sidebar. // Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => { const toggleSidebar = React.useCallback(() => {
return isMobile return isMobile
? setOpenMobile((open) => !open) ? setOpenMobile((open) => !open)
: setOpen((open) => !open) : setOpen((open) => !open);
}, [isMobile, setOpen, setOpenMobile]) }, [isMobile, setOpen, setOpenMobile]);
// Adds a keyboard shortcut to toggle the sidebar. // Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => { React.useEffect(() => {
@ -109,18 +109,18 @@ const SidebarProvider = React.forwardRef<
event.key === SIDEBAR_KEYBOARD_SHORTCUT && event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey) (event.metaKey || event.ctrlKey)
) { ) {
event.preventDefault() event.preventDefault();
toggleSidebar() toggleSidebar();
}
} }
};
window.addEventListener("keydown", handleKeyDown) window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown) return () => window.removeEventListener("keydown", handleKeyDown);
}, [toggleSidebar]) }, [toggleSidebar]);
// We add a state so that we can do data-state="expanded" or "collapsed". // We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes. // This makes it easier to style the sidebar with Tailwind classes.
const state = open ? "expanded" : "collapsed" const state = open ? "expanded" : "collapsed";
const contextValue = React.useMemo<SidebarContextProps>( const contextValue = React.useMemo<SidebarContextProps>(
() => ({ () => ({
@ -132,8 +132,16 @@ const SidebarProvider = React.forwardRef<
setOpenMobile, setOpenMobile,
toggleSidebar, toggleSidebar,
}), }),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] [
) state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
],
);
return ( return (
<SidebarContext.Provider value={contextValue}> <SidebarContext.Provider value={contextValue}>
@ -148,7 +156,7 @@ const SidebarProvider = React.forwardRef<
} }
className={cn( className={cn(
"group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar", "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
className className,
)} )}
ref={ref} ref={ref}
{...props} {...props}
@ -157,17 +165,17 @@ const SidebarProvider = React.forwardRef<
</div> </div>
</TooltipProvider> </TooltipProvider>
</SidebarContext.Provider> </SidebarContext.Provider>
) );
} },
) );
SidebarProvider.displayName = "SidebarProvider" SidebarProvider.displayName = "SidebarProvider";
const Sidebar = React.forwardRef< const Sidebar = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.ComponentProps<"div"> & { React.ComponentProps<"div"> & {
side?: "left" | "right" side?: "left" | "right";
variant?: "sidebar" | "floating" | "inset" variant?: "sidebar" | "floating" | "inset";
collapsible?: "offcanvas" | "icon" | "none" collapsible?: "offcanvas" | "icon" | "none";
} }
>( >(
( (
@ -179,23 +187,23 @@ const Sidebar = React.forwardRef<
children, children,
...props ...props
}, },
ref ref,
) => { ) => {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar() const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
if (collapsible === "none") { if (collapsible === "none") {
return ( return (
<div <div
className={cn( className={cn(
"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground", "flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
className className,
)} )}
ref={ref} ref={ref}
{...props} {...props}
> >
{children} {children}
</div> </div>
) );
} }
if (isMobile) { if (isMobile) {
@ -219,7 +227,7 @@ const Sidebar = React.forwardRef<
<div className="flex h-full w-full flex-col">{children}</div> <div className="flex h-full w-full flex-col">{children}</div>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
) );
} }
return ( return (
@ -239,7 +247,7 @@ const Sidebar = React.forwardRef<
"group-data-[side=right]:rotate-180", "group-data-[side=right]:rotate-180",
variant === "floating" || variant === "inset" variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
)} )}
/> />
<div <div
@ -252,7 +260,7 @@ const Sidebar = React.forwardRef<
variant === "floating" || variant === "inset" variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l", : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
className className,
)} )}
{...props} {...props}
> >
@ -264,16 +272,16 @@ const Sidebar = React.forwardRef<
</div> </div>
</div> </div>
</div> </div>
) );
} },
) );
Sidebar.displayName = "Sidebar" Sidebar.displayName = "Sidebar";
const SidebarTrigger = React.forwardRef< const SidebarTrigger = React.forwardRef<
React.ElementRef<typeof Button>, React.ElementRef<typeof Button>,
React.ComponentProps<typeof Button> React.ComponentProps<typeof Button>
>(({ className, onClick, ...props }, ref) => { >(({ className, onClick, ...props }, ref) => {
const { toggleSidebar } = useSidebar() const { toggleSidebar } = useSidebar();
return ( return (
<Button <Button
@ -283,23 +291,23 @@ const SidebarTrigger = React.forwardRef<
size="icon" size="icon"
className={cn("h-7 w-7", className)} className={cn("h-7 w-7", className)}
onClick={(event) => { onClick={(event) => {
onClick?.(event) onClick?.(event);
toggleSidebar() toggleSidebar();
}} }}
{...props} {...props}
> >
<PanelLeft /> <PanelLeft />
<span className="sr-only">Toggle Sidebar</span> <span className="sr-only">Toggle Sidebar</span>
</Button> </Button>
) );
}) });
SidebarTrigger.displayName = "SidebarTrigger" SidebarTrigger.displayName = "SidebarTrigger";
const SidebarRail = React.forwardRef< const SidebarRail = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
React.ComponentProps<"button"> React.ComponentProps<"button">
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { toggleSidebar } = useSidebar() const { toggleSidebar } = useSidebar();
return ( return (
<button <button
@ -316,13 +324,13 @@ const SidebarRail = React.forwardRef<
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar", "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2", "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2", "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
SidebarRail.displayName = "SidebarRail" SidebarRail.displayName = "SidebarRail";
const SidebarInset = React.forwardRef< const SidebarInset = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -334,13 +342,13 @@ const SidebarInset = React.forwardRef<
className={cn( className={cn(
"relative flex w-full flex-1 flex-col bg-background", "relative flex w-full flex-1 flex-col bg-background",
"md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow", "md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
SidebarInset.displayName = "SidebarInset" SidebarInset.displayName = "SidebarInset";
const SidebarInput = React.forwardRef< const SidebarInput = React.forwardRef<
React.ElementRef<typeof Input>, React.ElementRef<typeof Input>,
@ -352,13 +360,13 @@ const SidebarInput = React.forwardRef<
data-sidebar="input" data-sidebar="input"
className={cn( className={cn(
"h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring", "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
SidebarInput.displayName = "SidebarInput" SidebarInput.displayName = "SidebarInput";
const SidebarHeader = React.forwardRef< const SidebarHeader = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -371,9 +379,9 @@ const SidebarHeader = React.forwardRef<
className={cn("flex flex-col gap-2 p-2", className)} className={cn("flex flex-col gap-2 p-2", className)}
{...props} {...props}
/> />
) );
}) });
SidebarHeader.displayName = "SidebarHeader" SidebarHeader.displayName = "SidebarHeader";
const SidebarFooter = React.forwardRef< const SidebarFooter = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -386,9 +394,9 @@ const SidebarFooter = React.forwardRef<
className={cn("flex flex-col gap-2 p-2", className)} className={cn("flex flex-col gap-2 p-2", className)}
{...props} {...props}
/> />
) );
}) });
SidebarFooter.displayName = "SidebarFooter" SidebarFooter.displayName = "SidebarFooter";
const SidebarSeparator = React.forwardRef< const SidebarSeparator = React.forwardRef<
React.ElementRef<typeof Separator>, React.ElementRef<typeof Separator>,
@ -401,9 +409,9 @@ const SidebarSeparator = React.forwardRef<
className={cn("mx-2 w-auto bg-sidebar-border", className)} className={cn("mx-2 w-auto bg-sidebar-border", className)}
{...props} {...props}
/> />
) );
}) });
SidebarSeparator.displayName = "SidebarSeparator" SidebarSeparator.displayName = "SidebarSeparator";
const SidebarContent = React.forwardRef< const SidebarContent = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -415,13 +423,13 @@ const SidebarContent = React.forwardRef<
data-sidebar="content" data-sidebar="content"
className={cn( className={cn(
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
SidebarContent.displayName = "SidebarContent" SidebarContent.displayName = "SidebarContent";
const SidebarGroup = React.forwardRef< const SidebarGroup = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -434,15 +442,15 @@ const SidebarGroup = React.forwardRef<
className={cn("relative flex w-full min-w-0 flex-col p-2", className)} className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
{...props} {...props}
/> />
) );
}) });
SidebarGroup.displayName = "SidebarGroup" SidebarGroup.displayName = "SidebarGroup";
const SidebarGroupLabel = React.forwardRef< const SidebarGroupLabel = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.ComponentProps<"div"> & { asChild?: boolean } React.ComponentProps<"div"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => { >(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? SlotPrimitive.Slot : "div" const Comp = asChild ? SlotPrimitive.Slot : "div";
return ( return (
<Comp <Comp
@ -451,19 +459,19 @@ const SidebarGroupLabel = React.forwardRef<
className={cn( className={cn(
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
SidebarGroupLabel.displayName = "SidebarGroupLabel" SidebarGroupLabel.displayName = "SidebarGroupLabel";
const SidebarGroupAction = React.forwardRef< const SidebarGroupAction = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
React.ComponentProps<"button"> & { asChild?: boolean } React.ComponentProps<"button"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => { >(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? SlotPrimitive.Slot : "button" const Comp = asChild ? SlotPrimitive.Slot : "button";
return ( return (
<Comp <Comp
@ -474,13 +482,13 @@ const SidebarGroupAction = React.forwardRef<
// Increases the hit area of the button on mobile. // Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 after:md:hidden", "after:absolute after:-inset-2 after:md:hidden",
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
SidebarGroupAction.displayName = "SidebarGroupAction" SidebarGroupAction.displayName = "SidebarGroupAction";
const SidebarGroupContent = React.forwardRef< const SidebarGroupContent = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -492,8 +500,8 @@ const SidebarGroupContent = React.forwardRef<
className={cn("w-full text-sm", className)} className={cn("w-full text-sm", className)}
{...props} {...props}
/> />
)) ));
SidebarGroupContent.displayName = "SidebarGroupContent" SidebarGroupContent.displayName = "SidebarGroupContent";
const SidebarMenu = React.forwardRef< const SidebarMenu = React.forwardRef<
HTMLUListElement, HTMLUListElement,
@ -505,8 +513,8 @@ const SidebarMenu = React.forwardRef<
className={cn("flex w-full min-w-0 flex-col gap-1", className)} className={cn("flex w-full min-w-0 flex-col gap-1", className)}
{...props} {...props}
/> />
)) ));
SidebarMenu.displayName = "SidebarMenu" SidebarMenu.displayName = "SidebarMenu";
const SidebarMenuItem = React.forwardRef< const SidebarMenuItem = React.forwardRef<
HTMLLIElement, HTMLLIElement,
@ -518,8 +526,8 @@ const SidebarMenuItem = React.forwardRef<
className={cn("group/menu-item relative", className)} className={cn("group/menu-item relative", className)}
{...props} {...props}
/> />
)) ));
SidebarMenuItem.displayName = "SidebarMenuItem" SidebarMenuItem.displayName = "SidebarMenuItem";
const sidebarMenuButtonVariants = cva( const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
@ -540,15 +548,15 @@ const sidebarMenuButtonVariants = cva(
variant: "default", variant: "default",
size: "default", size: "default",
}, },
} },
) );
const SidebarMenuButton = React.forwardRef< const SidebarMenuButton = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
React.ComponentProps<"button"> & { React.ComponentProps<"button"> & {
asChild?: boolean asChild?: boolean;
isActive?: boolean isActive?: boolean;
tooltip?: string | React.ComponentProps<typeof TooltipContent> tooltip?: string | React.ComponentProps<typeof TooltipContent>;
} & VariantProps<typeof sidebarMenuButtonVariants> } & VariantProps<typeof sidebarMenuButtonVariants>
>( >(
( (
@ -561,10 +569,10 @@ const SidebarMenuButton = React.forwardRef<
className, className,
...props ...props
}, },
ref ref,
) => { ) => {
const Comp = asChild ? SlotPrimitive.Slot : "button" const Comp = asChild ? SlotPrimitive.Slot : "button";
const { isMobile, state } = useSidebar() const { isMobile, state } = useSidebar();
const button = ( const button = (
<Comp <Comp
@ -575,16 +583,16 @@ const SidebarMenuButton = React.forwardRef<
className={cn(sidebarMenuButtonVariants({ variant, size }), className)} className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props} {...props}
/> />
) );
if (!tooltip) { if (!tooltip) {
return button return button;
} }
if (typeof tooltip === "string") { if (typeof tooltip === "string") {
tooltip = { tooltip = {
children: tooltip, children: tooltip,
} };
} }
return ( return (
@ -597,19 +605,19 @@ const SidebarMenuButton = React.forwardRef<
{...tooltip} {...tooltip}
/> />
</Tooltip> </Tooltip>
) );
} },
) );
SidebarMenuButton.displayName = "SidebarMenuButton" SidebarMenuButton.displayName = "SidebarMenuButton";
const SidebarMenuAction = React.forwardRef< const SidebarMenuAction = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
React.ComponentProps<"button"> & { React.ComponentProps<"button"> & {
asChild?: boolean asChild?: boolean;
showOnHover?: boolean showOnHover?: boolean;
} }
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => { >(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
const Comp = asChild ? SlotPrimitive.Slot : "button" const Comp = asChild ? SlotPrimitive.Slot : "button";
return ( return (
<Comp <Comp
@ -625,13 +633,13 @@ const SidebarMenuAction = React.forwardRef<
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
showOnHover && showOnHover &&
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0", "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
SidebarMenuAction.displayName = "SidebarMenuAction" SidebarMenuAction.displayName = "SidebarMenuAction";
const SidebarMenuBadge = React.forwardRef< const SidebarMenuBadge = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -647,23 +655,23 @@ const SidebarMenuBadge = React.forwardRef<
"peer-data-[size=default]/menu-button:top-1.5", "peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5", "peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
className className,
)} )}
{...props} {...props}
/> />
)) ));
SidebarMenuBadge.displayName = "SidebarMenuBadge" SidebarMenuBadge.displayName = "SidebarMenuBadge";
const SidebarMenuSkeleton = React.forwardRef< const SidebarMenuSkeleton = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.ComponentProps<"div"> & { React.ComponentProps<"div"> & {
showIcon?: boolean showIcon?: boolean;
} }
>(({ className, showIcon = false, ...props }, ref) => { >(({ className, showIcon = false, ...props }, ref) => {
// Random width between 50 to 90%. // Random width between 50 to 90%.
const width = React.useMemo(() => { const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%` return `${Math.floor(Math.random() * 40) + 50}%`;
}, []) }, []);
return ( return (
<div <div
@ -688,9 +696,9 @@ const SidebarMenuSkeleton = React.forwardRef<
} }
/> />
</div> </div>
) );
}) });
SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton" SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton";
const SidebarMenuSub = React.forwardRef< const SidebarMenuSub = React.forwardRef<
HTMLUListElement, HTMLUListElement,
@ -702,28 +710,28 @@ const SidebarMenuSub = React.forwardRef<
className={cn( className={cn(
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5", "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
className className,
)} )}
{...props} {...props}
/> />
)) ));
SidebarMenuSub.displayName = "SidebarMenuSub" SidebarMenuSub.displayName = "SidebarMenuSub";
const SidebarMenuSubItem = React.forwardRef< const SidebarMenuSubItem = React.forwardRef<
HTMLLIElement, HTMLLIElement,
React.ComponentProps<"li"> React.ComponentProps<"li">
>(({ ...props }, ref) => <li ref={ref} {...props} />) >(({ ...props }, ref) => <li ref={ref} {...props} />);
SidebarMenuSubItem.displayName = "SidebarMenuSubItem" SidebarMenuSubItem.displayName = "SidebarMenuSubItem";
const SidebarMenuSubButton = React.forwardRef< const SidebarMenuSubButton = React.forwardRef<
HTMLAnchorElement, HTMLAnchorElement,
React.ComponentProps<"a"> & { React.ComponentProps<"a"> & {
asChild?: boolean asChild?: boolean;
size?: "sm" | "md" size?: "sm" | "md";
isActive?: boolean isActive?: boolean;
} }
>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => { >(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
const Comp = asChild ? SlotPrimitive.Slot : "a" const Comp = asChild ? SlotPrimitive.Slot : "a";
return ( return (
<Comp <Comp
@ -737,13 +745,13 @@ const SidebarMenuSubButton = React.forwardRef<
size === "sm" && "text-xs", size === "sm" && "text-xs",
size === "md" && "text-sm", size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
SidebarMenuSubButton.displayName = "SidebarMenuSubButton" SidebarMenuSubButton.displayName = "SidebarMenuSubButton";
export { export {
Sidebar, Sidebar,
@ -770,4 +778,4 @@ export {
SidebarSeparator, SidebarSeparator,
SidebarTrigger, SidebarTrigger,
useSidebar, useSidebar,
} };

View File

@ -1,4 +1,4 @@
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function Skeleton({ function Skeleton({
className, className,
@ -9,7 +9,7 @@ function Skeleton({
className={cn("animate-pulse rounded-md bg-primary/10", className)} className={cn("animate-pulse rounded-md bg-primary/10", className)}
{...props} {...props}
/> />
) );
} }
export { Skeleton } export { Skeleton };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Slider as SliderPrimitive } from "radix-ui" import { Slider as SliderPrimitive } from "radix-ui";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Slider = React.forwardRef< const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>, React.ElementRef<typeof SliderPrimitive.Root>,
@ -13,7 +13,7 @@ const Slider = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex w-full touch-none select-none items-center", "relative flex w-full touch-none select-none items-center",
className className,
)} )}
{...props} {...props}
> >
@ -22,7 +22,7 @@ const Slider = React.forwardRef<
</SliderPrimitive.Track> </SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" /> <SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root> </SliderPrimitive.Root>
)) ));
Slider.displayName = SliderPrimitive.Root.displayName Slider.displayName = SliderPrimitive.Root.displayName;
export { Slider } export { Slider };

View File

@ -1,12 +1,12 @@
"use client" "use client";
import { useTheme } from "next-themes" import { useTheme } from "next-themes";
import { Toaster as Sonner } from "sonner" import { Toaster as Sonner } from "sonner";
type ToasterProps = React.ComponentProps<typeof Sonner> type ToasterProps = React.ComponentProps<typeof Sonner>;
const Toaster = ({ ...props }: ToasterProps) => { const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme() const { theme = "system" } = useTheme();
return ( return (
<Sonner <Sonner
@ -25,7 +25,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
}} }}
{...props} {...props}
/> />
) );
} };
export { Toaster } export { Toaster };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Switch as SwitchPrimitives } from "radix-ui" import { Switch as SwitchPrimitives } from "radix-ui";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Switch = React.forwardRef< const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>, React.ElementRef<typeof SwitchPrimitives.Root>,
@ -12,18 +12,18 @@ const Switch = React.forwardRef<
<SwitchPrimitives.Root <SwitchPrimitives.Root
className={cn( className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className className,
)} )}
{...props} {...props}
ref={ref} ref={ref}
> >
<SwitchPrimitives.Thumb <SwitchPrimitives.Thumb
className={cn( className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0" "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
)} )}
/> />
</SwitchPrimitives.Root> </SwitchPrimitives.Root>
)) ));
Switch.displayName = SwitchPrimitives.Root.displayName Switch.displayName = SwitchPrimitives.Root.displayName;
export { Switch } export { Switch };

View File

@ -1,6 +1,6 @@
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Table = React.forwardRef< const Table = React.forwardRef<
HTMLTableElement, HTMLTableElement,
@ -13,16 +13,16 @@ const Table = React.forwardRef<
{...props} {...props}
/> />
</div> </div>
)) ));
Table.displayName = "Table" Table.displayName = "Table";
const TableHeader = React.forwardRef< const TableHeader = React.forwardRef<
HTMLTableSectionElement, HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement> React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} /> <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
)) ));
TableHeader.displayName = "TableHeader" TableHeader.displayName = "TableHeader";
const TableBody = React.forwardRef< const TableBody = React.forwardRef<
HTMLTableSectionElement, HTMLTableSectionElement,
@ -33,8 +33,8 @@ const TableBody = React.forwardRef<
className={cn("[&_tr:last-child]:border-0", className)} className={cn("[&_tr:last-child]:border-0", className)}
{...props} {...props}
/> />
)) ));
TableBody.displayName = "TableBody" TableBody.displayName = "TableBody";
const TableFooter = React.forwardRef< const TableFooter = React.forwardRef<
HTMLTableSectionElement, HTMLTableSectionElement,
@ -44,12 +44,12 @@ const TableFooter = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TableFooter.displayName = "TableFooter" TableFooter.displayName = "TableFooter";
const TableRow = React.forwardRef< const TableRow = React.forwardRef<
HTMLTableRowElement, HTMLTableRowElement,
@ -59,12 +59,12 @@ const TableRow = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TableRow.displayName = "TableRow" TableRow.displayName = "TableRow";
const TableHead = React.forwardRef< const TableHead = React.forwardRef<
HTMLTableCellElement, HTMLTableCellElement,
@ -74,12 +74,12 @@ const TableHead = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", "h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TableHead.displayName = "TableHead" TableHead.displayName = "TableHead";
const TableCell = React.forwardRef< const TableCell = React.forwardRef<
HTMLTableCellElement, HTMLTableCellElement,
@ -89,12 +89,12 @@ const TableCell = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TableCell.displayName = "TableCell" TableCell.displayName = "TableCell";
const TableCaption = React.forwardRef< const TableCaption = React.forwardRef<
HTMLTableCaptionElement, HTMLTableCaptionElement,
@ -105,8 +105,8 @@ const TableCaption = React.forwardRef<
className={cn("mt-4 text-sm text-muted-foreground", className)} className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
TableCaption.displayName = "TableCaption" TableCaption.displayName = "TableCaption";
export { export {
Table, Table,
@ -117,4 +117,4 @@ export {
TableRow, TableRow,
TableCell, TableCell,
TableCaption, TableCaption,
} };

View File

@ -1,11 +1,11 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Tabs as TabsPrimitive } from "radix-ui" import { Tabs as TabsPrimitive } from "radix-ui";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Tabs = TabsPrimitive.Root const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef< const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>, React.ElementRef<typeof TabsPrimitive.List>,
@ -15,12 +15,12 @@ const TabsList = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", "inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TabsList.displayName = TabsPrimitive.List.displayName TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef< const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>, React.ElementRef<typeof TabsPrimitive.Trigger>,
@ -30,12 +30,12 @@ const TabsTrigger = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow", "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef< const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>, React.ElementRef<typeof TabsPrimitive.Content>,
@ -45,11 +45,11 @@ const TabsContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TabsContent.displayName = TabsPrimitive.Content.displayName TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent } export { Tabs, TabsList, TabsTrigger, TabsContent };

View File

@ -1,7 +1,7 @@
'use client'; "use client";
import React, { useMemo, type JSX } from 'react'; import React, { useMemo, type JSX } from "react";
import { motion } from 'motion/react'; import { motion } from "motion/react";
import { cn } from '@/lib/utils'; import { cn } from "@/lib/utils";
interface TextShimmerProps { interface TextShimmerProps {
children: string; children: string;
@ -13,13 +13,13 @@ interface TextShimmerProps {
export function TextShimmer({ export function TextShimmer({
children, children,
as: Component = 'p', as: Component = "p",
className, className,
duration = 2, duration = 2,
spread = 2, spread = 2,
}: TextShimmerProps) { }: TextShimmerProps) {
const MotionComponent = motion.create( const MotionComponent = motion.create(
Component as keyof JSX.IntrinsicElements Component as keyof JSX.IntrinsicElements,
); );
const dynamicSpread = useMemo(() => { const dynamicSpread = useMemo(() => {
@ -29,22 +29,22 @@ export function TextShimmer({
return ( return (
<MotionComponent <MotionComponent
className={cn( className={cn(
'relative inline-block bg-size-[250%_100%,auto] bg-clip-text', "relative inline-block bg-size-[250%_100%,auto] bg-clip-text",
'text-transparent [--base-color:#a1a1aa] [--base-gradient-color:#000]', "text-transparent [--base-color:#a1a1aa] [--base-gradient-color:#000]",
'[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--base-gradient-color),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]', "[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--base-gradient-color),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]",
'dark:[--base-color:#71717a] dark:[--base-gradient-color:#ffffff] dark:[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--base-gradient-color),#0000_calc(50%+var(--spread)))]', "dark:[--base-color:#71717a] dark:[--base-gradient-color:#ffffff] dark:[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--base-gradient-color),#0000_calc(50%+var(--spread)))]",
className className,
)} )}
initial={{ backgroundPosition: '100% center' }} initial={{ backgroundPosition: "100% center" }}
animate={{ backgroundPosition: '0% center' }} animate={{ backgroundPosition: "0% center" }}
transition={{ transition={{
repeat: Infinity, repeat: Infinity,
duration, duration,
ease: 'linear', ease: "linear",
}} }}
style={ style={
{ {
'--spread': `${dynamicSpread}px`, "--spread": `${dynamicSpread}px`,
backgroundImage: `var(--bg), linear-gradient(var(--base-color), var(--base-color))`, backgroundImage: `var(--bg), linear-gradient(var(--base-color), var(--base-color))`,
} as React.CSSProperties } as React.CSSProperties
} }

View File

@ -1,6 +1,6 @@
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return ( return (
@ -8,11 +8,11 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
data-slot="textarea" data-slot="textarea"
className={cn( className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
export { Textarea } export { Textarea };

View File

@ -1,18 +1,18 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui" import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui";
import { type VariantProps } from "class-variance-authority" import { type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
import { toggleVariants } from "@/components/ui/toggle" import { toggleVariants } from "@/components/ui/toggle";
const ToggleGroupContext = React.createContext< const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants> VariantProps<typeof toggleVariants>
>({ >({
size: "default", size: "default",
variant: "default", variant: "default",
}) });
const ToggleGroup = React.forwardRef< const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>, React.ElementRef<typeof ToggleGroupPrimitive.Root>,
@ -28,16 +28,16 @@ const ToggleGroup = React.forwardRef<
{children} {children}
</ToggleGroupContext.Provider> </ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root> </ToggleGroupPrimitive.Root>
)) ));
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
const ToggleGroupItem = React.forwardRef< const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>, React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> & React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants> VariantProps<typeof toggleVariants>
>(({ className, children, variant, size, ...props }, ref) => { >(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext) const context = React.useContext(ToggleGroupContext);
return ( return (
<ToggleGroupPrimitive.Item <ToggleGroupPrimitive.Item
@ -47,15 +47,15 @@ const ToggleGroupItem = React.forwardRef<
variant: context.variant || variant, variant: context.variant || variant,
size: context.size || size, size: context.size || size,
}), }),
className className,
)} )}
{...props} {...props}
> >
{children} {children}
</ToggleGroupPrimitive.Item> </ToggleGroupPrimitive.Item>
) );
}) });
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
export { ToggleGroup, ToggleGroupItem } export { ToggleGroup, ToggleGroupItem };

View File

@ -1,10 +1,10 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Toggle as TogglePrimitive } from "radix-ui" import { Toggle as TogglePrimitive } from "radix-ui";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const toggleVariants = cva( const toggleVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
@ -25,8 +25,8 @@ const toggleVariants = cva(
variant: "default", variant: "default",
size: "default", size: "default",
}, },
} },
) );
const Toggle = React.forwardRef< const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>, React.ElementRef<typeof TogglePrimitive.Root>,
@ -38,8 +38,8 @@ const Toggle = React.forwardRef<
className={cn(toggleVariants({ variant, size, className }))} className={cn(toggleVariants({ variant, size, className }))}
{...props} {...props}
/> />
)) ));
Toggle.displayName = TogglePrimitive.Root.displayName Toggle.displayName = TogglePrimitive.Root.displayName;
export { Toggle, toggleVariants } export { Toggle, toggleVariants };

View File

@ -1,15 +1,15 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Tooltip as TooltipPrimitive } from "radix-ui" import { Tooltip as TooltipPrimitive } from "radix-ui";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef< const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>, React.ElementRef<typeof TooltipPrimitive.Content>,
@ -21,12 +21,12 @@ const TooltipContent = React.forwardRef<
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-tooltip-content-transform-origin)", "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-tooltip-content-transform-origin)",
className className,
)} )}
{...props} {...props}
/> />
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
)) ));
TooltipContent.displayName = TooltipPrimitive.Content.displayName TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

View File

@ -92,7 +92,9 @@ export async function UsersTable({
className={`${user.verified && "title-bg dark:bg-black"}`} className={`${user.verified && "title-bg dark:bg-black"}`}
key={user.id} key={user.id}
> >
<TableCell className="font-medium">{user.first_name} {user.last_name}</TableCell> <TableCell className="font-medium">
{user.first_name} {user.last_name}
</TableCell>
<TableCell className="font-medium">{user.id_card}</TableCell> <TableCell className="font-medium">{user.id_card}</TableCell>
<TableCell>{user.atoll?.name}</TableCell> <TableCell>{user.atoll?.name}</TableCell>
<TableCell>{user.island?.name}</TableCell> <TableCell>{user.island?.name}</TableCell>
@ -144,8 +146,10 @@ export async function UsersTable({
</TableRow> </TableRow>
</TableFooter> </TableFooter>
</Table> </Table>
<Pagination totalPages={meta?.last_page} <Pagination
currentPage={meta?.current_page} /> totalPages={meta?.last_page}
currentPage={meta?.current_page}
/>
</> </>
)} )}
</div> </div>

View File

@ -4,7 +4,15 @@ import { Loader2, Plus } from "lucide-react";
import { useActionState, useEffect, useState } from "react"; // Import useActionState import { useActionState, useEffect, useState } from "react"; // Import useActionState
import { toast } from "sonner"; import { toast } from "sonner";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { addDeviceAction } from "@/queries/devices"; import { addDeviceAction } from "@/queries/devices";
@ -19,7 +27,6 @@ export type AddDeviceFormState = {
payload?: FormData; payload?: FormData;
}; };
export const initialState: AddDeviceFormState = { export const initialState: AddDeviceFormState = {
message: "", message: "",
fieldErrors: {}, fieldErrors: {},
@ -30,7 +37,7 @@ export default function AddDeviceDialogForm({ user_id }: { user_id?: string }) {
const [state, formAction, pending] = useActionState( const [state, formAction, pending] = useActionState(
addDeviceAction, addDeviceAction,
initialState initialState,
); );
useEffect(() => { useEffect(() => {
@ -53,7 +60,11 @@ export default function AddDeviceDialogForm({ user_id }: { user_id?: string }) {
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button className="gap-2 items-center" disabled={pending} variant="default"> <Button
className="gap-2 items-center"
disabled={pending}
variant="default"
>
Add Device Add Device
<Plus size={16} /> <Plus size={16} />
</Button> </Button>
@ -71,9 +82,7 @@ export default function AddDeviceDialogForm({ user_id }: { user_id?: string }) {
<div className="grid gap-4 py-4"> <div className="grid gap-4 py-4">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div> <div>
<Label htmlFor="device_name"> <Label htmlFor="device_name">Device Name</Label>
Device Name
</Label>
<Input <Input
placeholder="eg: iPhone X" placeholder="eg: iPhone X"
type="text" type="text"
@ -90,13 +99,13 @@ export default function AddDeviceDialogForm({ user_id }: { user_id?: string }) {
</div> </div>
<div> <div>
<Label htmlFor="mac_address"> <Label htmlFor="mac_address">Mac Address</Label>
Mac Address
</Label>
<Input <Input
placeholder="Mac address of your device" placeholder="Mac address of your device"
name="mac_address" name="mac_address"
defaultValue={(state?.payload?.get("mac_address") || "") as string} defaultValue={
(state?.payload?.get("mac_address") || "") as string
}
id="mac_address" id="mac_address"
className="col-span-3" className="col-span-3"
/> />

Some files were not shown because too many files have changed in this diff Show More