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,9 +1,6 @@
{ {
"extends": [ "extends": ["next/core-web-vitals", "next/typescript"],
"next/core-web-vitals", "rules": {
"next/typescript" "@typescript-eslint/no-explicit-any": "error"
], }
"rules": {
"@typescript-eslint/no-explicit-any": "error"
}
} }

View File

@ -1,3 +1,3 @@
{ {
"typescript.tsdk": "node_modules/typescript/lib" "typescript.tsdk": "node_modules/typescript/lib"
} }

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,14 +278,18 @@ 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:
? "Payment completed successfully using wallet!" method === "WALLET"
: "Payment verification successful!", ? "Payment completed successfully using wallet!"
: "Payment verification successful!",
success: true, success: true,
fieldErrors: {}, fieldErrors: {},
payment: result, payment: result,
@ -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 </div>
</h3> </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

@ -3,61 +3,61 @@ import { DevicesTable } from "@/components/devices-table";
import DynamicFilter from "@/components/generic-filter"; import DynamicFilter from "@/components/generic-filter";
export default async function ParentalControl({ export default async function ParentalControl({
searchParams, searchParams,
}: { }: {
searchParams: Promise<{ searchParams: Promise<{
page: number; page: number;
sortBy: string; sortBy: string;
status: string; status: string;
}>; }>;
}) { }) {
const parentalControlFilters = {
is_active: "true",
has_a_pending_payment: "false",
};
const parentalControlFilters = { return (
is_active: "true", <div>
has_a_pending_payment: "false", <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">Parental Control</h3>
</div>
return ( <div
<div> id="user-filters"
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4"> className=" pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start"
<h3 className="text-sarLinkOrange text-2xl">Parental Control</h3> >
</div> <DynamicFilter
description="Filter devices by name, MAC address, or vendor."
<div title="Device Filter"
id="user-filters" inputs={[
className=" pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start" {
> name: "name",
<DynamicFilter label: "Device Name",
description="Filter devices by name, MAC address, or vendor." type: "string",
title="Device Filter" placeholder: "Enter device name",
inputs={[ },
{ {
name: "name", name: "mac",
label: "Device Name", label: "MAC Address",
type: "string", type: "string",
placeholder: "Enter device name", placeholder: "Enter MAC address",
}, },
{ {
name: "mac", name: "vendor",
label: "MAC Address", label: "Vendor",
type: "string", type: "string",
placeholder: "Enter MAC address", placeholder: "Enter vendor name",
}, },
{ ]}
name: "vendor", />{" "}
label: "Vendor", </div>
type: "string", <Suspense key={(await searchParams).page} fallback={"loading...."}>
placeholder: "Enter vendor name", <DevicesTable
} parentalControl={true}
]} searchParams={searchParams}
/> </div> additionalFilters={parentalControlFilters}
<Suspense key={(await searchParams).page} fallback={"loading...."}> />
<DevicesTable </Suspense>
parentalControl={true} </div>
searchParams={searchParams} );
additionalFilters={parentalControlFilters}
/>
</Suspense>
</div>
);
} }

View File

@ -31,19 +31,21 @@ 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 &&
<Button payment.paid &&
disabled payment.status !== "PENDING" && (
className={cn( <Button
"rounded-md opacity-100! uppercase font-semibold", disabled
payment?.paid className={cn(
? "text-green-900 bg-green-500/20" "rounded-md opacity-100! uppercase font-semibold",
: "text-inherit bg-yellow-400", payment?.paid
)} ? "text-green-900 bg-green-500/20"
> : "text-inherit bg-yellow-400",
{payment.status} )}
</Button> >
)} {payment.status}
</Button>
)}
{payment.status === "PENDING" && !payment.is_expired && ( {payment.status === "PENDING" && !payment.is_expired && (
<Button> <Button>
<TextShimmer>Payment Pending</TextShimmer>{" "} <TextShimmer>Payment Pending</TextShimmer>{" "}

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

@ -8,88 +8,88 @@ import { getProfileById } from "@/queries/users";
import { tryCatch } from "@/utils/tryCatch"; import { tryCatch } from "@/utils/tryCatch";
export default async function Profile() { export default async function Profile() {
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
if (!session?.user) return redirect("/auth/signin?callbackUrl=/profile"); if (!session?.user) return redirect("/auth/signin?callbackUrl=/profile");
const [error, profile] = await tryCatch(getProfileById(session?.user.id)); const [error, profile] = await tryCatch(getProfileById(session?.user.id));
if (error) { if (error) {
if (error.message === "Invalid token.") redirect("/auth/signin"); if (error.message === "Invalid token.") redirect("/auth/signin");
return <ClientErrorMessage message={error.message} />; return <ClientErrorMessage message={error.message} />;
} }
return ( return (
<div> <div>
<div className="flex justify-between items-center font-bold border rounded-md border-dashed title-bg py-4 px-2 mb-4"> <div className="flex justify-between items-center font-bold border rounded-md border-dashed title-bg py-4 px-2 mb-4">
<h3 className="text-sarLinkOrange text-2xl">Profile</h3> <h3 className="text-sarLinkOrange text-2xl">Profile</h3>
<div className="text-sarLinkOrange uppercase font-mono text-sm flex flex-col items-center rounded gap-2 py-2 px-4"> <div className="text-sarLinkOrange uppercase font-mono text-sm flex flex-col items-center rounded gap-2 py-2 px-4">
<span>Profile Status</span> <span>Profile Status</span>
{verifiedStatus(profile?.verified ?? false)} {verifiedStatus(profile?.verified ?? false)}
</div> </div>
</div> </div>
<fieldset> <fieldset>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 max-w-4xl"> <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 max-w-4xl">
<FloatingLabelInput <FloatingLabelInput
id="floating-name" id="floating-name"
label="Full Name" label="Full Name"
value={`${profile?.first_name} ${profile?.last_name}`} value={`${profile?.first_name} ${profile?.last_name}`}
readOnly readOnly
/> />
<FloatingLabelInput <FloatingLabelInput
id="floating-id-card" id="floating-id-card"
label="ID Card" label="ID Card"
value={`${profile?.id_card}`} value={`${profile?.id_card}`}
readOnly readOnly
/> />
<FloatingLabelInput <FloatingLabelInput
id="floating-island" id="floating-island"
label="Island" label="Island"
value={`${profile?.atoll.name}. ${profile?.island.name}`} value={`${profile?.atoll.name}. ${profile?.island.name}`}
readOnly readOnly
/> />
<FloatingLabelInput <FloatingLabelInput
id="floating-dob" id="floating-dob"
label="Date of Birth" label="Date of Birth"
value={`${new Date( value={`${new Date(
profile?.dob.toString() ?? "", profile?.dob.toString() ?? "",
).toLocaleDateString("en-US", { ).toLocaleDateString("en-US", {
month: "short", month: "short",
day: "2-digit", day: "2-digit",
year: "numeric", year: "numeric",
})}`} })}`}
readOnly readOnly
/> />
<FloatingLabelInput <FloatingLabelInput
id="floating-address" id="floating-address"
label="Address" label="Address"
value={`${profile?.address}`} value={`${profile?.address}`}
readOnly readOnly
/> />
<FloatingLabelInput <FloatingLabelInput
id="floating-mobile" id="floating-mobile"
label="Phone Number" label="Phone Number"
value={`${profile?.mobile}`} value={`${profile?.mobile}`}
readOnly readOnly
/> />
<FloatingLabelInput <FloatingLabelInput
id="floating-account" id="floating-account"
label="Account Number" label="Account Number"
value={`${profile?.acc_no}`} value={`${profile?.acc_no}`}
readOnly readOnly
/> />
</div> </div>
</fieldset> </fieldset>
{/* <Suspense key={query} fallback={"loading...."}> {/* <Suspense key={query} fallback={"loading...."}>
<TopupsTable searchParams={searchParams} /> <TopupsTable searchParams={searchParams} />
</Suspense> */} </Suspense> */}
</div> </div>
); );
} }
function verifiedStatus(status: boolean) { function verifiedStatus(status: boolean) {
switch (status) { switch (status) {
case true: case true:
return <Badge className="bg-green-500 text-white">Verified</Badge>; return <Badge className="bg-green-500 text-white">Verified</Badge>;
case false: case false:
return <Badge className="bg-red-500 text-white">Not Verified</Badge>; return <Badge className="bg-red-500 text-white">Not Verified</Badge>;
default: default:
return <Badge className="bg-yellow-500 text-white">Unknown</Badge>; return <Badge className="bg-yellow-500 text-white">Unknown</Badge>;
} }
} }

View File

@ -9,72 +9,72 @@ import { TextShimmer } from "@/components/ui/text-shimmer";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { tryCatch } from "@/utils/tryCatch"; import { tryCatch } from "@/utils/tryCatch";
export default async function TopupPage({ export default async function TopupPage({
params, params,
}: { }: {
params: Promise<{ topupId: string }>; params: Promise<{ topupId: string }>;
}) { }) {
const topupId = (await params).topupId; const topupId = (await params).topupId;
const [error, topup] = await tryCatch(getTopup({ id: topupId })); const [error, topup] = await tryCatch(getTopup({ id: topupId }));
if (error) { if (error) {
if (error.message === "Invalid token.") redirect("/auth/signin"); if (error.message === "Invalid token.") redirect("/auth/signin");
return <ClientErrorMessage message={error.message} />; return <ClientErrorMessage message={error.message} />;
} }
return ( return (
<div> <div>
<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">Topup</h3> <h3 className="text-sarLinkOrange text-2xl">Topup</h3>
<div className="flex flex-col gap-4 items-end w-full"> <div className="flex flex-col gap-4 items-end w-full">
{!topup.is_expired && topup.paid && topup.status !== "PENDING" && ( {!topup.is_expired && topup.paid && topup.status !== "PENDING" && (
<Button <Button
disabled disabled
className={cn( className={cn(
"rounded-md opacity-100! uppercase font-semibold", "rounded-md opacity-100! uppercase font-semibold",
topup?.paid topup?.paid
? "text-green-900 bg-green-500/20" ? "text-green-900 bg-green-500/20"
: "text-inherit bg-yellow-400", : "text-inherit bg-yellow-400",
)} )}
> >
{topup.status} {topup.status}
</Button> </Button>
)} )}
{topup.status === "PENDING" && !topup.is_expired && ( {topup.status === "PENDING" && !topup.is_expired && (
<Button> <Button>
<TextShimmer>Payment Pending</TextShimmer>{" "} <TextShimmer>Payment Pending</TextShimmer>{" "}
</Button> </Button>
)} )}
{!topup.paid && {!topup.paid &&
(topup.is_expired ? ( (topup.is_expired ? (
<Button <Button
disabled disabled
className="rounded-md opacity-100! uppercase font-semibold text-red-500 bg-red-500/20" className="rounded-md opacity-100! uppercase font-semibold text-red-500 bg-red-500/20"
> >
Topup Expired Topup Expired
</Button> </Button>
) : topup.status === "PENDING" ? ( ) : topup.status === "PENDING" ? (
<CancelTopupButton topupId={topupId} /> <CancelTopupButton topupId={topupId} />
) : topup.status === "CANCELLED" ? ( ) : topup.status === "CANCELLED" ? (
<Button disabled>Topup Cancelled</Button> <Button disabled>Topup Cancelled</Button>
) : ( ) : (
"" ""
))} ))}
</div> </div>
</div> </div>
{!topup.paid && topup.status === "PENDING" && !topup.is_expired && ( {!topup.paid && topup.status === "PENDING" && !topup.is_expired && (
<ExpiryCountDown expiryLabel="Top up" expiresAt={topup.expires_at} /> <ExpiryCountDown expiryLabel="Top up" expiresAt={topup.expires_at} />
)} )}
<div <div
id="user-topup-details" id="user-topup-details"
className="pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start" className="pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start"
> >
<TopupToPay <TopupToPay
disabled={ disabled={
topup.paid || topup.is_expired || topup.status === "CANCELLED" topup.paid || topup.is_expired || topup.status === "CANCELLED"
} }
topup={topup || undefined} topup={topup || undefined}
/> />
</div> </div>
</div> </div>
); );
} }

View File

@ -3,84 +3,84 @@ import DynamicFilter from "@/components/generic-filter";
import { TopupsTable } from "@/components/topups-table"; import { TopupsTable } from "@/components/topups-table";
export default async function Topups({ export default async function Topups({
searchParams, searchParams,
}: { }: {
searchParams: Promise<{ searchParams: Promise<{
query: string; query: string;
page: number; page: number;
sortBy: string; sortBy: string;
status: string; status: string;
}>; }>;
}) { }) {
const query = (await searchParams)?.query || ""; const query = (await searchParams)?.query || "";
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">My Topups</h3> <h3 className="text-sarLinkOrange text-2xl">My Topups</h3>
</div> </div>
<div <div
id="topup-filters" id="topup-filters"
className=" pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start" className=" pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start"
> >
<DynamicFilter <DynamicFilter
inputs={[ inputs={[
{ {
label: "Status", label: "Status",
name: "status", name: "status",
type: "radio-group", type: "radio-group",
options: [ options: [
{ {
label: "All", label: "All",
value: "", value: "",
}, },
{ {
label: "Pending", label: "Pending",
value: "PENDING", value: "PENDING",
}, },
{ {
label: "Cancelled", label: "Cancelled",
value: "CANCELLED", value: "CANCELLED",
}, },
{ {
label: "Paid", label: "Paid",
value: "PAID", value: "PAID",
}, },
], ],
}, },
{ {
label: "Topup Expiry", label: "Topup Expiry",
name: "is_expired", name: "is_expired",
type: "radio-group", type: "radio-group",
options: [ options: [
{ {
label: "All", label: "All",
value: "", value: "",
}, },
{ {
label: "Expired", label: "Expired",
value: "true", value: "true",
}, },
{ {
label: "Not Expired", label: "Not Expired",
value: "false", value: "false",
}, },
], ],
}, },
{ {
label: "Topup Amount", label: "Topup Amount",
name: "amount", name: "amount",
type: "dual-range-slider", type: "dual-range-slider",
min: 0, min: 0,
max: 1000, max: 1000,
step: 10, step: 10,
}, },
]} ]}
/> />
</div> </div>
<Suspense key={query} fallback={"loading...."}> <Suspense key={query} fallback={"loading...."}>
<TopupsTable searchParams={searchParams} /> <TopupsTable searchParams={searchParams} />
</Suspense> </Suspense>
</div> </div>
); );
} }

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,86 +1,90 @@
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";
export default async function UserTopups({ export default async function UserTopups({
searchParams, searchParams,
}: { }: {
searchParams: Promise<{ searchParams: Promise<{
[key: string]: string; [key: string]: string;
}>; }>;
}) { }) {
const query = (await searchParams)?.query || ""; const query = (await searchParams)?.query || "";
// const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
return ( if (!session?.user?.is_admin) redirect("/top-ups?page=1");
<div> return (
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4"> <div>
<h3 className="text-sarLinkOrange text-2xl">User Topups</h3> <div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
</div> <h3 className="text-sarLinkOrange text-2xl">User Topups</h3>
<DynamicFilter </div>
title="User Topups Filter" <DynamicFilter
description="Filter user topups by status, topup expiry, or amount." title="User Topups Filter"
inputs={[ description="Filter user topups by status, topup expiry, or amount."
{ inputs={[
name: "user", {
label: "User", name: "user",
type: "string", label: "User",
placeholder: "Enter user name", type: "string",
}, placeholder: "Enter user name",
{ },
label: "Status", {
name: "status", label: "Status",
type: "radio-group", name: "status",
options: [ type: "radio-group",
{ options: [
label: "All", {
value: "", label: "All",
}, value: "",
{ },
label: "Pending", {
value: "PENDING", label: "Pending",
}, value: "PENDING",
{ },
label: "Cancelled", {
value: "CANCELLED", label: "Cancelled",
}, value: "CANCELLED",
{ },
label: "Paid", {
value: "PAID", label: "Paid",
}, value: "PAID",
], },
}, ],
{ },
label: "Topup Expiry", {
name: "is_expired", label: "Topup Expiry",
type: "radio-group", name: "is_expired",
options: [ type: "radio-group",
{ options: [
label: "All", {
value: "", label: "All",
}, value: "",
{ },
label: "Expired", {
value: "true", label: "Expired",
}, value: "true",
{ },
label: "Not Expired", {
value: "false", label: "Not Expired",
}, value: "false",
], },
}, ],
{ },
label: "Topup Amount", {
name: "amount", label: "Topup Amount",
type: "dual-range-slider", name: "amount",
min: 0, type: "dual-range-slider",
max: 1000, min: 0,
step: 10, max: 1000,
}, step: 10,
]} },
/> ]}
<Suspense key={query} fallback={"loading...."}> />
<AdminTopupsTable searchParams={searchParams} /> <Suspense key={query} fallback={"loading...."}>
</Suspense> <AdminTopupsTable searchParams={searchParams} />
</div> </Suspense>
); </div>
);
} }

View File

@ -16,32 +16,33 @@ import { tryCatch } from "@/utils/tryCatch";
// } from "@/components/ui/select"; // } from "@/components/ui/select";
export default async function UserUpdate({ export default async function UserUpdate({
params, params,
}: { }: {
params: Promise<{ params: Promise<{
userId: string; userId: string;
}>; }>;
}) { }) {
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.message === "UNAUTHORIZED") {
redirect("/auth/signin");
} else {
return <ClientErrorMessage message={error.message} />;
}
}
if (error) { return (
if (error.message === "UNAUTHORIZED") { <div>
redirect("/auth/signin"); <div className="flex items-center justify-between text-gray-500 text-2xl font-bold title-bg py-4 px-2 mb-4">
} else { <h3 className="text-sarLinkOrange text-2xl">
return <ClientErrorMessage message={error.message} />; Upload user user agreement
} </h3>
} </div>
<UserAgreementForm user={user} />
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">
<h3 className="text-sarLinkOrange text-2xl">Upload user user agreement</h3>
</div>
<UserAgreementForm user={user} />
</div>
);
} }

View File

@ -16,32 +16,31 @@ import { tryCatch } from "@/utils/tryCatch";
// } from "@/components/ui/select"; // } from "@/components/ui/select";
export default async function UserUpdate({ export default async function UserUpdate({
params, params,
}: { }: {
params: Promise<{ params: Promise<{
userId: string; userId: string;
}>; }>;
}) { }) {
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.message === "UNAUTHORIZED") {
redirect("/auth/signin");
} else {
return <ClientErrorMessage message={error.message} />;
}
}
if (error) { return (
if (error.message === "UNAUTHORIZED") { <div>
redirect("/auth/signin"); <div className="flex items-center justify-between text-gray-500 text-2xl font-bold title-bg py-4 px-2 mb-4">
} else { <h3 className="text-sarLinkOrange text-2xl">Verify user</h3>
return <ClientErrorMessage message={error.message} />; </div>
} <UserUpdateForm user={user} />
} </div>
);
return (
<div>
<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">Verify user</h3>
</div>
<UserUpdateForm user={user} />
</div>
);
} }

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 </div>
</h3> </div>
</div> );
</div>
)
} }

File diff suppressed because one or more lines are too long

View File

@ -11,49 +11,48 @@ import QueryProvider from "@/providers/query-provider";
import { getServerSession } from "next-auth"; import { getServerSession } from "next-auth";
import { authOptions } from "./auth"; import { authOptions } from "./auth";
const barlow = Barlow({ const barlow = Barlow({
subsets: ["latin"], subsets: ["latin"],
weight: ["100", "300", "400", "500", "600", "700", "800", "900"], weight: ["100", "300", "400", "500", "600", "700", "800", "900"],
variable: "--font-barlow", variable: "--font-barlow",
}); });
const bokor = Bokor({ const bokor = Bokor({
subsets: ["latin"], subsets: ["latin"],
weight: ["400"], weight: ["400"],
variable: "--font-bokor", variable: "--font-bokor",
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "SAR Link Portal", title: "SAR Link Portal",
description: "Sarlink Portal", description: "Sarlink Portal",
}; };
export default async function RootLayout({ export default async function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
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
<AuthProvider session={session || undefined}> className={`${barlow.variable} ${bokor.variable} antialiased font-sans bg-gray-100 dark:bg-black`}
<Provider> >
<NextTopLoader color="#f49d1b" showSpinner={false} zIndex={9999} /> <AuthProvider session={session || undefined}>
<Toaster richColors /> <Provider>
<ThemeProvider <NextTopLoader color="#f49d1b" showSpinner={false} zIndex={9999} />
attribute="class" <Toaster richColors />
defaultTheme="system" <ThemeProvider
enableSystem attribute="class"
disableTransitionOnChange defaultTheme="system"
> enableSystem
<QueryProvider> disableTransitionOnChange
{children} >
</QueryProvider> <QueryProvider>{children}</QueryProvider>
</ThemeProvider> </ThemeProvider>
</Provider> </Provider>
</AuthProvider> </AuthProvider>
</body> </body>
</html>
</html> );
);
} }

View File

@ -1,5 +1,5 @@
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
export default async function Home() { export default async function Home() {
return redirect("/devices"); return redirect("/devices");
} }

View File

@ -1,21 +1,21 @@
{ {
"$schema": "https://ui.shadcn.com/schema.json", "$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york", "style": "new-york",
"rsc": true, "rsc": true,
"tsx": true, "tsx": true,
"tailwind": { "tailwind": {
"config": "tailwind.config.ts", "config": "tailwind.config.ts",
"css": "app/globals.css", "css": "app/globals.css",
"baseColor": "neutral", "baseColor": "neutral",
"cssVariables": true, "cssVariables": true,
"prefix": "" "prefix": ""
}, },
"aliases": { "aliases": {
"components": "@/components", "components": "@/components",
"utils": "@/lib/utils", "utils": "@/lib/utils",
"ui": "@/components/ui", "ui": "@/components/ui",
"lib": "@/lib", "lib": "@/lib",
"hooks": "@/hooks" "hooks": "@/hooks"
}, },
"iconLibrary": "lucide" "iconLibrary": "lucide"
} }

View File

@ -1,56 +1,56 @@
"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";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
export function AccountInfomation({ export function AccountInfomation({
accountNo, accountNo,
accName, accName,
}: { }: {
accountNo: string; accountNo: string;
accName: string; accName: string;
}) { }) {
const [accNo, setAccNo] = useState(false); const [accNo, setAccNo] = useState(false);
return ( return (
<div className="justify-center items-center border my-4 flex flex-col gap-2 p-2 rounded-md"> <div className="justify-center items-center border my-4 flex flex-col gap-2 p-2 rounded-md">
<h6 className="title-bg uppercase p-2 border rounded w-full font-semibold"> <h6 className="title-bg uppercase p-2 border rounded w-full font-semibold">
Account Information Account Information
</h6> </h6>
<div className="border justify-center flex flex-col items-center bg-white/10 w-full p-2 rounded"> <div className="border justify-center flex flex-col items-center bg-white/10 w-full p-2 rounded">
<div className="text-sm font-semibold">Account Name</div> <div className="text-sm font-semibold">Account Name</div>
<span>{accName}</span> <span>{accName}</span>
</div> </div>
<div className="border flex justify-between items-center gap-2 w-full p-2 rounded"> <div className="border flex justify-between items-center gap-2 w-full p-2 rounded">
<div className="flex flex-col items-center justify-center w-full"> <div className="flex flex-col items-center justify-center w-full">
<p className="text-sm font-semibold">Account No</p> <p className="text-sm font-semibold">Account No</p>
<span>{accountNo}</span> <span>{accountNo}</span>
<Button <Button
onClick={() => { onClick={() => {
setTimeout(() => { setTimeout(() => {
setAccNo(true); setAccNo(true);
navigator.clipboard.writeText(accountNo); navigator.clipboard.writeText(accountNo);
}, 2000); }, 2000);
toast.success("Account number copied!"); toast.success("Account number copied!");
setAccNo((prev) => !prev); setAccNo((prev) => !prev);
}} }}
className="mt-2 w-full" className="mt-2 w-full"
variant={"secondary"} variant={"secondary"}
> >
{accNo ? ( {accNo ? (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span>Copy Account Number</span> <span>Copy Account Number</span>
<Clipboard /> <Clipboard />
</div> </div>
) : ( ) : (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span>Copy Account Number</span> <span>Copy Account Number</span>
<ClipboardCheck color="green" /> <ClipboardCheck color="green" />
</div> </div>
)} )}
</Button> </Button>
</div> </div>
</div> </div>
</div> </div>
); );
} }

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,205 +1,139 @@
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";
import { import {
Table, Table,
TableBody, TableBody,
TableCaption, TableCaption,
TableCell, TableCell,
TableFooter, TableFooter,
TableHead, TableHead,
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";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
export async function AdminTopupsTable({ export async function AdminTopupsTable({
searchParams, searchParams,
}: { }: {
searchParams: Promise<{ searchParams: Promise<{
[key: string]: unknown; [key: string]: unknown;
}>; }>;
}) { }) {
const resolvedParams = await searchParams; const resolvedParams = await searchParams;
const page = Number.parseInt(resolvedParams.page as string) || 1; const page = Number.parseInt(resolvedParams.page as string) || 1;
const limit = 10; const limit = 10;
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
// Build params object // Build params object
const apiParams: Record<string, string | number | undefined> = {}; const apiParams: Record<string, string | number | undefined> = {};
for (const [key, value] of Object.entries(resolvedParams)) { for (const [key, value] of Object.entries(resolvedParams)) {
if (value !== undefined && value !== "") { if (value !== undefined && value !== "") {
apiParams[key] = typeof value === "number" ? value : String(value); apiParams[key] = typeof value === "number" ? value : String(value);
} }
} }
apiParams.limit = limit; apiParams.limit = limit;
apiParams.offset = offset; apiParams.offset = offset;
const [error, topups] = await tryCatch(getTopups(apiParams, true)); const [error, topups] = await tryCatch(getTopups(apiParams, true));
if (error) { if (error) {
if (error.message.includes("Unauthorized")) { if (error.message.includes("Unauthorized")) {
redirect("/auth/signin"); redirect("/auth/signin");
} else { } else {
return <pre>{JSON.stringify(error, null, 2)}</pre>; return <pre>{JSON.stringify(error, null, 2)}</pre>;
} }
} }
const { data, meta } = topups; const { data, meta } = topups;
return ( return (
<div> <div>
{data?.length === 0 ? ( {data?.length === 0 ? (
<div className="h-[calc(100svh-400px)] flex flex-col items-center justify-center my-4"> <div className="h-[calc(100svh-400px)] flex flex-col items-center justify-center my-4">
<h3>No topups yet.</h3> <h3>No topups yet.</h3>
</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>
<TableRow> <TableRow>
<TableHead>User</TableHead> <TableHead>User</TableHead>
<TableHead>Status</TableHead> <TableHead>Status</TableHead>
<TableHead>Amount</TableHead> <TableHead>Amount</TableHead>
<TableHead>Action</TableHead> <TableHead>Action</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<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">
<span className="text-muted-foreground">{topup?.user?.id_card}</span> {topup?.user?.id_card}
</div> </span>
</TableCell> </div>
<TableCell> </TableCell>
<span className="font-semibold pr-2"> <TableCell>
{topup.paid ? ( <span className="font-semibold pr-2">
<Badge {topup.paid ? (
className="bg-green-100 dark:bg-green-700" <Badge
variant="outline" className="bg-green-100 dark:bg-green-700"
> variant="outline"
{topup.status} >
</Badge> {topup.status}
) : topup.is_expired ? ( </Badge>
<Badge>Expired</Badge> ) : topup.is_expired ? (
) : ( <Badge>Expired</Badge>
<Badge variant="outline">{topup.status}</Badge> ) : (
)} <Badge variant="outline">{topup.status}</Badge>
</span> )}
</TableCell> </span>
<TableCell> </TableCell>
<span className="font-semibold pr-2"> <TableCell>
{topup.amount.toFixed(2)} <span className="font-semibold pr-2">
</span> {topup.amount.toFixed(2)}
MVR </span>
</TableCell> MVR
<TableCell> </TableCell>
<div <TableCell>
> <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"
href={`/top-ups/${topup.id}`} href={`/top-ups/${topup.id}`}
> >
<Button size={"sm"} variant="outline"> <Button size={"sm"} variant="outline">
View Details View Details
</Button> </Button>
</Link> </Link>
</div> </div>
</div> </div>
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>
<TableFooter> <TableFooter>
<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">Total {meta?.total} topup.</p> <p className="text-center">Total {meta?.total} topup.</p>
) : ( ) : (
<p className="text-center">Total {meta?.total} topups.</p> <p className="text-center">Total {meta?.total} topups.</p>
)} )}
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableFooter> </TableFooter>
</Table> </Table>
</div> </div>
<div className="sm:hidden block"> <Pagination
{data.map((topup) => ( totalPages={meta?.last_page}
<MobileTopupDetails key={topup.id} topup={topup} /> currentPage={meta?.current_page}
))} />
</div> </>
<Pagination )}
totalPages={meta?.last_page} </div>
currentPage={meta?.current_page} );
/>
</>
)}
</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>
@ -100,12 +112,14 @@ export default function SignUpForm() {
className={cn( className={cn(
"text-base", "text-base",
actionState?.errors?.fieldErrors.name && actionState?.errors?.fieldErrors.name &&
"border-2 border-red-500", "border-2 border-red-500",
)} )}
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
@ -129,7 +146,7 @@ export default function SignUpForm() {
className={cn( className={cn(
"text-base", "text-base",
actionState?.errors?.fieldErrors?.id_card && actionState?.errors?.fieldErrors?.id_card &&
"border-2 border-red-500", "border-2 border-red-500",
)} )}
placeholder="ID Card" placeholder="ID Card"
/> />
@ -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,14 +236,17 @@ 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
className={cn( className={cn(
"text-base", "text-base",
actionState?.errors?.fieldErrors?.address && actionState?.errors?.fieldErrors?.address &&
"border-2 border-red-500", "border-2 border-red-500",
)} )}
disabled={isPending} disabled={isPending}
name="address" name="address"
@ -233,18 +264,23 @@ 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
className={cn( className={cn(
"text-base", "text-base",
actionState?.errors?.fieldErrors?.dob && actionState?.errors?.fieldErrors?.dob &&
"border-2 border-red-500", "border-2 border-red-500",
)} )}
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>
@ -268,12 +307,14 @@ export default function SignUpForm() {
className={cn( className={cn(
"text-base", "text-base",
actionState?.errors?.fieldErrors.accNo && actionState?.errors?.fieldErrors.accNo &&
"border-2 border-red-500", "border-2 border-red-500",
)} )}
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
@ -293,8 +337,8 @@ export default function SignUpForm() {
disabled={isPending} disabled={isPending}
className={cn( className={cn(
!phoneNumberFromUrl && !phoneNumberFromUrl &&
actionState?.errors?.fieldErrors?.phone_number && actionState?.errors?.fieldErrors?.phone_number &&
"border-2 border-red-500 rounded-md", "border-2 border-red-500 rounded-md",
)} )}
defaultValue={NUMBER_WITHOUT_DASH ?? ""} defaultValue={NUMBER_WITHOUT_DASH ?? ""}
readOnly={Boolean(phoneNumberFromUrl)} readOnly={Boolean(phoneNumberFromUrl)}
@ -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,32 +8,30 @@ 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, const router = useRouter();
}: { topupId: string }) { const [loading, setLoading] = React.useState(false);
const router = useRouter(); return (
const [loading, setLoading] = React.useState(false); <Button
return ( onClick={async () => {
<Button setLoading(true);
onClick={async () => { const [error, topup] = await tryCatch(cancelTopup({ id: topupId }));
setLoading(true); if (error) {
const [error, topup] = await tryCatch(cancelTopup({ id: topupId })); toast.error(error.message);
if (error) { setLoading(false);
toast.error(error.message); } else {
setLoading(false); toast.success("Topup cancelled successfully!", {
} else { description: `Your topup of ${topup?.amount} MVR has been cancelled.`,
toast.success("Topup cancelled successfully!", { closeButton: true,
description: `Your topup of ${topup?.amount} MVR has been cancelled.`, });
closeButton: true, router.replace("/top-ups");
}) }
router.replace("/top-ups"); }}
} disabled={loading}
}} variant={"destructive"}
disabled={loading} >
variant={"destructive"} Cancel Topup
> {loading ? <Loader2 className="animate-spin" /> : <Trash2 />}
Cancel Topup </Button>
{loading ? <Loader2 className="animate-spin" /> : <Trash2 />} );
</Button>
);
} }

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;
useEffect(() => { expiryLabel: string;
setMounted(true) }) {
}, []) const [timeLeft, setTimeLeft] = useState(calculateTimeLeft(expiresAt));
const [mounted, setMounted] = useState(false);
useEffect(() => { const router = useRouter();
const timer = setInterval(() => { const pathname = usePathname();
setTimeLeft(calculateTimeLeft(expiresAt)) useEffect(() => {
}, 1000) setMounted(true);
}, []);
return () => clearInterval(timer)
}, [expiresAt]) useEffect(() => {
const timer = setInterval(() => {
useEffect(() => { setTimeLeft(calculateTimeLeft(expiresAt));
if (timeLeft <= 0) { }, 1000);
router.replace(pathname)
} return () => clearInterval(timer);
}, [timeLeft, router, pathname]) }, [expiresAt]);
if (!mounted) { useEffect(() => {
return null if (timeLeft <= 0) {
} router.replace(pathname);
return ( }
<div className='overflow-clip relative mx-2 p-4 rounded-md border border-dashed flex items-center justify-between text-muted-foreground'> }, [timeLeft, router, pathname]);
<div className='absolute inset-0 title-bg mask-b-from-0' />
{timeLeft ? ( if (!mounted) {
<span>Time left: {HumanizeTimeLeft(timeLeft)}</span> return null;
) : ( }
<span>{expiryLabel} has expired.</span> return (
)} <div className="overflow-clip relative mx-2 p-4 rounded-md border border-dashed flex items-center justify-between text-muted-foreground">
{timeLeft > 0 && ( <div className="absolute inset-0 title-bg mask-b-from-0" />
<Progress className='absolute bottom-0 left-0 right-0' color='#f49b5b' value={timeLeft / 600 * 100} /> {timeLeft ? (
)} <span>Time left: {HumanizeTimeLeft(timeLeft)}</span>
</div> ) : (
) <span>{expiryLabel} has expired.</span>
)}
{timeLeft > 0 && (
<Progress
className="absolute bottom-0 left-0 right-0"
color="#f49b5b"
value={(timeLeft / 600) * 100}
/>
)}
</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);
@ -20,7 +24,7 @@ export default function DeviceCard({
return ( return (
// biome-ignore lint/a11y/noStaticElementInteractions: <dw about it> // biome-ignore lint/a11y/noStaticElementInteractions: <dw about it>
<div <div
onKeyUp={() => { }} onKeyUp={() => {}}
onClick={() => { onClick={() => {
if (device.blocked) return; if (device.blocked) return;
if (device.is_active === true) return; if (device.is_active === true) return;
@ -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(
month: "short", "en-US",
day: "2-digit", {
year: "numeric", month: "short",
minute: "2-digit", day: "2-digit",
hour: "2-digit", year: "numeric",
second: "2-digit", minute: "2-digit",
})} hour: "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,183 +1,218 @@
"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();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [disabled, startTransition] = useTransition(); const [disabled, startTransition] = useTransition();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const pathname = usePathname(); const pathname = usePathname();
const params = new URLSearchParams(searchParams.toString()); const params = new URLSearchParams(searchParams.toString());
const [urlInputName] = useQueryState("name", {
clearOnDefault: true,
});
const [urlInputMac] = useQueryState("mac", {
clearOnDefault: true,
});
const [urlInputVendor] = useQueryState("vendor", {
clearOnDefault: true,
});
const [urlInputName] = useQueryState("name", { // Local state for input fields
clearOnDefault: true const [inputName, setInputName] = useState(urlInputName ?? "");
}) const [inputMac, setInputMac] = useState(urlInputMac ?? "");
const [urlInputMac] = useQueryState("mac", { const [inputVendor, setInputVendor] = useState(urlInputVendor ?? "");
clearOnDefault: true
})
const [urlInputVendor] = useQueryState("vendor", {
clearOnDefault: true
})
// Local state for input fields // Map filter keys to their state setters
const [inputName, setInputName] = useState(urlInputName ?? ""); const filterSetters: Record<
const [inputMac, setInputMac] = useState(urlInputMac ?? ""); string,
const [inputVendor, setInputVendor] = useState(urlInputVendor ?? ""); React.Dispatch<React.SetStateAction<string>>
> = {
name: setInputName,
mac: setInputMac,
vendor: setInputVendor,
};
function handleSearch({
name,
mac,
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");
// Map filter keys to their state setters startTransition(() => {
const filterSetters: Record<string, React.Dispatch<React.SetStateAction<string>>> = { replace(`${pathname}?${params.toString()}`);
name: setInputName, });
mac: setInputMac, }
vendor: setInputVendor,
};
function handleSearch({ name, mac, vendor }: { name: string; mac: string; vendor: string }) {
if (name) params.set("name", name); else params.delete("name"); const appliedFilters = searchParams
if (mac) params.set("mac", mac); else params.delete("mac"); .toString()
if (vendor) params.set("vendor", vendor); else params.delete("vendor"); .split("&")
params.set("page", "1"); .filter((filter) => !filter.startsWith("page=") && filter);
return (
<div className="flex flex-col items-start justify-start gap-2 w-full">
<Drawer open={isOpen} onOpenChange={setIsOpen}>
<DrawerTrigger asChild>
<Button
className="w-full sm:w-48 flex items-end justify-between"
onClick={() => setIsOpen(!isOpen)}
variant="outline"
>
Filter
<ListFilter />
</Button>
</DrawerTrigger>
<DrawerContent>
<div className="mx-auto w-full max-w-3xl">
<DrawerHeader>
<DrawerTitle>Device Filters</DrawerTitle>
<DrawerDescription asChild>
<div>Select your desired filters here</div>
</DrawerDescription>
</DrawerHeader>
startTransition(() => { <div className="grid sm:grid-cols-3 gap-4 p-4">
replace(`${pathname}?${params.toString()}`); <Input
}); placeholder="Device name ..."
} value={inputName || searchParams.get("name") || ""}
onChange={(e) => setInputName(e.target.value)}
/>
<Input
placeholder="Device Mac address ..."
value={inputMac || searchParams.get("mac") || ""}
onChange={(e) => setInputMac(e.target.value)}
/>
<Input
placeholder="Device vendor ..."
value={inputVendor || searchParams.get("vendor") || ""}
onChange={(e) => setInputVendor(e.target.value)}
/>
</div>
<DrawerFooter className="max-w-sm mx-auto">
<Button
onClick={() => {
handleSearch({
name: inputName ?? "",
mac: inputMac ?? "",
vendor: inputVendor ?? "",
});
}}
>
{disabled ? (
<>
<Loader2 className="ml-2 animate-spin" />
</>
) : (
<>Apply Filters</>
)}
</Button>
<Button
variant="secondary"
onClick={() => {
setInputName("");
setInputMac("");
setInputVendor("");
replace(pathname);
}}
>
Clear Filters
</Button>
const appliedFilters = searchParams <DrawerClose asChild>
.toString() <Button variant="outline">Cancel</Button>
.split("&") </DrawerClose>
.filter((filter) => !filter.startsWith("page=") && filter) </DrawerFooter>
return ( </div>
<div className="flex flex-col items-start justify-start gap-2 w-full"> </DrawerContent>
<Drawer open={isOpen} onOpenChange={setIsOpen}> </Drawer>
<DrawerTrigger asChild> <div className="flex gap-2 w-fit flex-wrap">
<Button className="w-full sm:w-48 flex items-end justify-between" onClick={() => setIsOpen(!isOpen)} variant="outline"> {appliedFilters.map((filter) => (
Filter <Badge
<ListFilter /> aria-disabled={disabled}
</Button> variant={"outline"}
</DrawerTrigger> className={cn("flex p-2 gap-2 items-center justify-between", {
<DrawerContent> "opacity-50 pointer-events-none": disabled,
<div className="mx-auto w-full max-w-3xl"> })}
<DrawerHeader> key={filter}
<DrawerTitle>Device Filters</DrawerTitle> >
<DrawerDescription asChild> <span className="text-md">{prettyPrintFilter(filter)}</span>
<div> {disabled ? (
Select your desired filters here <Loader2 className="animate-spin" size={16} />
</div> ) : (
</DrawerDescription> <X
</DrawerHeader> className="bg-sarLinkOrange/50 rounded-full p-1 hover:cursor-pointer hover:ring ring-sarLinkOrange"
size={16}
onClick={() => {
const key = filter.split("=")[0];
params.delete(key);
<div className="grid sm:grid-cols-3 gap-4 p-4"> // Use the mapping to clear the correct input state
<Input filterSetters[key]?.("");
placeholder="Device name ..."
value={inputName || searchParams.get("name") || ""}
onChange={e => setInputName(e.target.value)}
/>
<Input
placeholder="Device Mac address ..."
value={inputMac || searchParams.get("mac") || ""}
onChange={e => setInputMac(e.target.value)}
/>
<Input
placeholder="Device vendor ..."
value={inputVendor || searchParams.get("vendor") || ""}
onChange={e => setInputVendor(e.target.value)}
/>
</div>
<DrawerFooter className="max-w-sm mx-auto">
<Button
onClick={() => {
handleSearch({
name: inputName ?? "",
mac: inputMac ?? "",
vendor: inputVendor ?? "",
});
}}
>
{disabled ? (
<>
<Loader2 className="ml-2 animate-spin" />
</>
) : (
<>
Apply Filters
</>
)}
</Button>
<Button variant="secondary" onClick={() => {
setInputName("");
setInputMac("");
setInputVendor("");
replace(pathname)
}}>
Clear Filters
</Button>
<DrawerClose asChild> startTransition(() => {
<Button variant="outline">Cancel</Button> replace(`${pathname}?${params.toString()}`);
</DrawerClose> });
</DrawerFooter> }}
</div> >
</DrawerContent> Remove
</Drawer> </X>
<div className="flex gap-2 w-fit flex-wrap"> )}
{appliedFilters.map((filter) => ( </Badge>
<Badge ))}
aria-disabled={disabled} </div>
variant={"outline"} </div>
className={cn("flex p-2 gap-2 items-center justify-between", { "opacity-50 pointer-events-none": disabled })} );
key={filter}>
<span className="text-md">{prettyPrintFilter(filter)}</span>
{
disabled ? (
<Loader2 className="animate-spin" size={16} />
) : (
<X
className="bg-sarLinkOrange/50 rounded-full p-1 hover:cursor-pointer hover:ring ring-sarLinkOrange"
size={16}
onClick={() => {
const key = filter.split("=")[0];
params.delete(key);
// Use the mapping to clear the correct input state
filterSetters[key]?.("");
startTransition(() => {
replace(`${pathname}?${params.toString()}`);
});
}}
>
Remove
</X>
)
}
</Badge>
))}
</div>
</div>
);
} }
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 (
case "mac": <p>
return <p>MAC Address: <span className="text-muted-foreground">{value}</span></p>; Device Name: <span className="text-muted-foreground">{value}</span>
case "vendor": </p>
return <p>Vendor: <span className="text-muted-foreground">{value}</span></p>; );
default: case "mac":
return filter; return (
} <p>
MAC Address: <span className="text-muted-foreground">{value}</span>
</p>
);
case "vendor":
return (
<p>
Vendor: <span className="text-muted-foreground">{value}</span>
</p>
);
default:
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>
) );
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,74 +1,65 @@
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" <AccordionItem value="item-1">
collapsible <AccordionTrigger>How do I find my MAC Address?</AccordionTrigger>
className="w-full" <AccordionContent className="flex flex-col gap-4 text-start">
> <p>
<AccordionItem value="item-1"> A MAC (Media Access Control) address is a unique identifier assigned
<AccordionTrigger>How do I find my MAC Address?</AccordionTrigger> to a device`&apos;`s network. It is used to identify the device on a
<AccordionContent className="flex flex-col gap-4 text-start"> network, helping to differentiate devices on a network.
<p> </p>
A MAC (Media Access Control) address is a unique identifier assigned <Accordion type="single" collapsible className="w-full">
to a device`&apos;`s network. It is used to identify the device on a <AccordionItem value="iphone">
network, helping to differentiate devices on a network. <AccordionTrigger>iPhone</AccordionTrigger>
</p> <AccordionContent>
<Accordion type="single" collapsible className="w-full"> Settings General About Wi-Fi Address
<AccordionItem value="iphone"> </AccordionContent>
<AccordionTrigger>iPhone</AccordionTrigger> </AccordionItem>
<AccordionContent> <AccordionItem value="redmi">
Settings General About Wi-Fi Address <AccordionTrigger>Redmi</AccordionTrigger>
</AccordionContent> <AccordionContent>
</AccordionItem> Settings About Wi-Fi MAC Address
<AccordionItem value="redmi"> </AccordionContent>
<AccordionTrigger>Redmi</AccordionTrigger> </AccordionItem>
<AccordionContent> <AccordionItem value="samsung">
Settings About Wi-Fi MAC Address <AccordionTrigger>Samsung</AccordionTrigger>
</AccordionContent> <AccordionContent>
</AccordionItem> Settings About phone Status Information Wi-Fi MAC Address
<AccordionItem value="samsung"> </AccordionContent>
<AccordionTrigger>Samsung</AccordionTrigger> </AccordionItem>
<AccordionContent> <AccordionItem value="windows">
Settings About phone Status Information Wi-Fi MAC Address <AccordionTrigger>Windows Laptop</AccordionTrigger>
</AccordionContent> <AccordionContent>
</AccordionItem> Settings Network and Internet Wi-Fi Hardware Properties
<AccordionItem value="windows"> Physical address (MAC):
<AccordionTrigger>Windows Laptop</AccordionTrigger> </AccordionContent>
<AccordionContent> </AccordionItem>
Settings Network and Internet Wi-Fi Hardware <AccordionItem value="other">
Properties Physical address (MAC): <AccordionTrigger>Other Device</AccordionTrigger>
</AccordionContent> <AccordionContent>
</AccordionItem> <p>Please contact SAR Link for assistance.</p>
<AccordionItem value="other"> <Link href="tel:+9609198026">
<AccordionTrigger>Other Device</AccordionTrigger> <Button className="mt-4">
<AccordionContent> <PhoneIcon className="mr-2" />
<p> 9198026
Please contact SAR Link for assistance. </Button>
</p> </Link>
<Link </AccordionContent>
href="tel:+9609198026" </AccordionItem>
> </Accordion>
<Button className="mt-4"> </AccordionContent>
<PhoneIcon className="mr-2" /> </AccordionItem>
9198026 </Accordion>
</Button> );
</Link>
</AccordionContent>
</AccordionItem>
</Accordion>
</AccordionContent>
</AccordionItem>
</Accordion>
)
} }

View File

@ -1,41 +1,54 @@
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: string; label,
value: string; value,
labelClassName?: string; labelClassName,
className?: string; className,
showCheck?: boolean; showCheck = true,
checkTrue?: boolean checkTrue = false,
}: {
label: string;
value: string;
labelClassName?: string;
className?: string;
showCheck?: 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
<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>
<label
htmlFor="input-33"
className={cn("block px-3 pt-2 text-xs font-medium", labelClassName)}
>
{label}
</label>
<input
id="input-33"
className="flex h-10 w-full bg-transparent px-3 pb-2 text-sm text-foreground placeholder:text-muted-foreground/70 focus-visible:outline-none"
placeholder={value}
disabled
value={value}
type="text"
/>
</div>
<label htmlFor="input-33" className={cn("block px-3 pt-2 text-xs font-medium", labelClassName)}> {showCheck && (
{label} <div>
</label> {checkTrue ? (
<input <CheckCheck className="mx-4 text-green-500" />
id="input-33" ) : (
className="flex h-10 w-full bg-transparent px-3 pb-2 text-sm text-foreground placeholder:text-muted-foreground/70 focus-visible:outline-none" <X className="mx-4 text-red-500" />
placeholder={value} )}
disabled </div>
value={value} )}
type="text" </div>
/> );
</div>
{showCheck && (
<div>
{checkTrue ? (
<CheckCheck className='mx-4 text-green-500' />
) : (
<X className='mx-4 text-red-500' />
)}
</div>
)}
</div>
)
} }

View File

@ -2,49 +2,61 @@ import { cn } from "@/lib/utils";
import { Minus, Plus } from "lucide-react"; import { Minus, Plus } from "lucide-react";
import { useEffect } from "react"; import { useEffect } from "react";
import { import {
Button, Button,
Group, Group,
Input, Input,
Label, Label,
NumberField, NumberField,
} from "react-aria-components"; } from "react-aria-components";
export default function NumberInput({ export default function NumberInput({
maxAllowed, maxAllowed,
label, label,
value, value,
onChange, onChange,
className, className,
isDisabled, isDisabled,
}: { maxAllowed?: number, label: string; value: number; onChange: (value: number) => void, className?: string, isDisabled?: boolean }) { }: {
useEffect(() => { maxAllowed?: number;
if (maxAllowed) { label: string;
if (value > maxAllowed) { value: number;
onChange(maxAllowed); onChange: (value: number) => void;
} className?: string;
} isDisabled?: boolean;
}, [maxAllowed, value, onChange]); }) {
return ( useEffect(() => {
<NumberField isDisabled={isDisabled} className={cn(className)} value={value} minValue={0} onChange={onChange}> if (maxAllowed) {
<div className="space-y-2"> if (value > maxAllowed) {
<Label className="text-sm font-medium text-foreground">{label}</Label> onChange(maxAllowed);
<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"> }
<Button }
slot="decrement" }, [maxAllowed, value, onChange]);
className="-ms-px flex aspect-square h-[inherit] items-center justify-center rounded-s-lg border border-input bg-background text-sm text-muted-foreground/80 transition-shadow hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50" return (
> <NumberField
<Minus size={16} strokeWidth={2} aria-hidden="true" /> isDisabled={isDisabled}
</Button> className={cn(className)}
<Input className="w-full grow bg-background px-3 py-2 text-center text-base tabular-nums text-foreground focus:outline-none" /> value={value}
<Button minValue={0}
slot="increment" onChange={onChange}
className="-me-px flex aspect-square h-[inherit] items-center justify-center rounded-e-lg border border-input bg-background text-sm text-muted-foreground/80 transition-shadow hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50" >
> <div className="space-y-2">
<Plus size={16} strokeWidth={2} aria-hidden="true" /> <Label className="text-sm font-medium text-foreground">{label}</Label>
</Button> <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> <Button
</div> slot="decrement"
</NumberField> className="-ms-px flex aspect-square h-[inherit] items-center justify-center rounded-s-lg border border-input bg-background text-sm text-muted-foreground/80 transition-shadow hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50"
); >
<Minus size={16} strokeWidth={2} aria-hidden="true" />
</Button>
<Input className="w-full grow bg-background px-3 py-2 text-center text-base tabular-nums text-foreground focus:outline-none" />
<Button
slot="increment"
className="-me-px flex aspect-square h-[inherit] items-center justify-center rounded-e-lg border border-input bg-background text-sm text-muted-foreground/80 transition-shadow hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50"
>
<Plus size={16} strokeWidth={2} aria-hidden="true" />
</Button>
</Group>
</div>
</NumberField>
);
} }

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

@ -1,90 +1,86 @@
"use client"; "use client";
import { import {
discountPercentageAtom, discountPercentageAtom,
formulaResultAtom, formulaResultAtom,
initialPriceAtom, initialPriceAtom,
numberOfDaysAtom, numberOfDaysAtom,
numberOfDevicesAtom, numberOfDevicesAtom,
} from "@/lib/atoms"; } from "@/lib/atoms";
import { useAtom } from "jotai"; 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(
discountPercentageAtom, discountPercentageAtom,
); );
const [numberOfDevices, setNumberOfDevices] = useAtom(numberOfDevicesAtom); const [numberOfDevices, setNumberOfDevices] = useAtom(numberOfDevicesAtom);
const [numberOfDays, setNumberOfDays] = useAtom(numberOfDaysAtom); const [numberOfDays, setNumberOfDays] = useAtom(numberOfDaysAtom);
const [formulaResult, setFormulaResult] = useAtom(formulaResultAtom); const [formulaResult, setFormulaResult] = useAtom(formulaResultAtom);
useEffect(() => { useEffect(() => {
const basePrice = initialPrice + (numberOfDevices - 1) * discountPercentage; const basePrice = initialPrice + (numberOfDevices - 1) * discountPercentage;
setFormulaResult( setFormulaResult(
`Price for ${numberOfDevices} device(s) over ${numberOfDays} day(s): MVR ${basePrice.toFixed(2)}`, `Price for ${numberOfDevices} device(s) over ${numberOfDays} day(s): MVR ${basePrice.toFixed(2)}`,
); );
}, [ }, [
initialPrice, initialPrice,
discountPercentage, discountPercentage,
numberOfDevices, numberOfDevices,
numberOfDays, numberOfDays,
setFormulaResult, setFormulaResult,
]); ]);
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 </div>
</h3> <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
</div> {/* Initial Price Input */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-2"> <NumberInput
{/* Initial Price Input */} label="Initial Price"
<NumberInput value={initialPrice}
label="Initial Price" onChange={(value) => setInitialPrice(value)}
value={initialPrice} />
onChange={(value) => setInitialPrice(value)} {/* Number of Devices Input */}
/> <NumberInput
{/* Number of Devices Input */} label="Number of Devices"
<NumberInput value={numberOfDevices}
label="Number of Devices" onChange={(value) => setNumberOfDevices(value)}
value={numberOfDevices} />
onChange={(value) => setNumberOfDevices(value)} {/* Number of Days Input */}
/> <NumberInput
{/* Number of Days Input */} label="Number of Days"
<NumberInput value={numberOfDays}
label="Number of Days" onChange={(value) => setNumberOfDays(value)}
value={numberOfDays} />
onChange={(value) => setNumberOfDays(value)}
/>
{/* Discount Percentage Input */} {/* Discount Percentage Input */}
<NumberInput <NumberInput
label="Discount Percentage" label="Discount Percentage"
value={discountPercentage} value={discountPercentage}
onChange={(value) => setDiscountPercentage(value)} onChange={(value) => setDiscountPercentage(value)}
/> />
</div> </div>
<div className="mt-4"> <div className="mt-4">
<div className="title-bg relative rounded-lg border border-input 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-50 [&:has(input:is(:disabled))_*]:pointer-events-none"> <div className="title-bg relative rounded-lg border border-input 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-50 [&:has(input:is(:disabled))_*]:pointer-events-none">
<label <label
htmlFor="" htmlFor=""
className="block px-3 pt-2 text-md font-medium text-foreground" className="block px-3 pt-2 text-md font-medium text-foreground"
> >
Total Total
</label> </label>
<input <input
className="flex font-mono font-semibold h-10 w-full bg-transparent px-3 pb-2 text-sm text-foreground placeholder:text-muted-foreground/70 focus-visible:outline-none" className="flex font-mono font-semibold h-10 w-full bg-transparent px-3 pb-2 text-sm text-foreground placeholder:text-muted-foreground/70 focus-visible:outline-none"
value={formulaResult} value={formulaResult}
readOnly readOnly
placeholder={"Result"} placeholder={"Result"}
/> />
</div> </div>
</div> </div>
</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 {
@ -124,13 +121,13 @@ export default function TopupToPay({
<TableCell className="text-right text-sarLinkOrange"> <TableCell className="text-right text-sarLinkOrange">
{topup?.paid_at {topup?.paid_at
? new Date(topup.paid_at).toLocaleDateString("en-US", { ? new Date(topup.paid_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>
@ -154,5 +151,3 @@ export default function TopupToPay({
</div> </div>
); );
} }

View File

@ -1,66 +1,66 @@
"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({
className, className,
...props ...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) { }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
return ( return (
<AccordionPrimitive.Item <AccordionPrimitive.Item
data-slot="accordion-item" data-slot="accordion-item"
className={cn("border-b last:border-b-0", className)} className={cn("border-b last:border-b-0", className)}
{...props} {...props}
/> />
) );
} }
function AccordionTrigger({ function AccordionTrigger({
className, className,
children, children,
...props ...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) { }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
return ( return (
<AccordionPrimitive.Header className="flex"> <AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger <AccordionPrimitive.Trigger
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}
> >
{children} {children}
<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({
className, className,
children, children,
...props ...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) { }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
return ( return (
<AccordionPrimitive.Content <AccordionPrimitive.Content
data-slot="accordion-content" data-slot="accordion-content"
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm" className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
{...props} {...props}
> >
<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,156 +1,156 @@
"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({
...props ...props
}: 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({
...props ...props
}: 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({
className, className,
...props ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) { }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
return ( return (
<AlertDialogPrimitive.Overlay <AlertDialogPrimitive.Overlay
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({
className, className,
...props ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) { }: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
return ( return (
<AlertDialogPortal> <AlertDialogPortal>
<AlertDialogOverlay /> <AlertDialogOverlay />
<AlertDialogPrimitive.Content <AlertDialogPrimitive.Content
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({
className, className,
...props ...props
}: React.ComponentProps<"div">) { }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="alert-dialog-header" data-slot="alert-dialog-header"
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({
className, className,
...props ...props
}: React.ComponentProps<"div">) { }: React.ComponentProps<"div">) {
return ( return (
<div <div
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({
className, className,
...props ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) { }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
return ( return (
<AlertDialogPrimitive.Title <AlertDialogPrimitive.Title
data-slot="alert-dialog-title" data-slot="alert-dialog-title"
className={cn("text-lg font-semibold", className)} className={cn("text-lg font-semibold", className)}
{...props} {...props}
/> />
) );
} }
function AlertDialogDescription({ function AlertDialogDescription({
className, className,
...props ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) { }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
return ( return (
<AlertDialogPrimitive.Description <AlertDialogPrimitive.Description
data-slot="alert-dialog-description" data-slot="alert-dialog-description"
className={cn("text-muted-foreground text-sm", className)} className={cn("text-muted-foreground text-sm", className)}
{...props} {...props}
/> />
) );
} }
function AlertDialogAction({ function AlertDialogAction({
className, className,
...props ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) { }: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
return ( return (
<AlertDialogPrimitive.Action <AlertDialogPrimitive.Action
className={cn(buttonVariants(), className)} className={cn(buttonVariants(), className)}
{...props} {...props}
/> />
) );
} }
function AlertDialogCancel({ function AlertDialogCancel({
className, className,
...props ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) { }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
return ( return (
<AlertDialogPrimitive.Cancel <AlertDialogPrimitive.Cancel
className={cn(buttonVariants({ variant: "outline" }), className)} className={cn(buttonVariants({ variant: "outline" }), className)}
{...props} {...props}
/> />
) );
} }
export { export {
AlertDialog, AlertDialog,
AlertDialogPortal, AlertDialogPortal,
AlertDialogOverlay, AlertDialogOverlay,
AlertDialogTrigger, AlertDialogTrigger,
AlertDialogContent, AlertDialogContent,
AlertDialogHeader, AlertDialogHeader,
AlertDialogFooter, AlertDialogFooter,
AlertDialogTitle, AlertDialogTitle,
AlertDialogDescription, AlertDialogDescription,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
} };

View File

@ -1,59 +1,59 @@
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",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-background text-foreground", default: "bg-background text-foreground",
destructive: destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
}, },
} },
) );
const Alert = React.forwardRef< const Alert = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => ( >(({ className, variant, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
role="alert" role="alert"
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,
React.HTMLAttributes<HTMLHeadingElement> React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<h5 <h5
ref={ref} ref={ref}
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,
React.HTMLAttributes<HTMLParagraphElement> React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
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

@ -40,17 +40,17 @@ type Categories = {
id: string; id: string;
children: ( children: (
| { | {
title: string; title: string;
link: string; link: string;
perm_identifier: string; perm_identifier: string;
icon: React.JSX.Element; icon: React.JSX.Element;
} }
| { | {
title: string; title: string;
link: string; link: string;
icon: React.JSX.Element; icon: React.JSX.Element;
perm_identifier?: undefined; perm_identifier?: undefined;
} }
)[]; )[];
}[]; }[];
@ -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(
const permissionParts = permission.name.split(" "); (permission: Permission) => {
const modelNameFromPermission = permissionParts.slice(2).join(" "); const permissionParts = permission.name.split(" ");
return modelNameFromPermission === permIdentifier; const modelNameFromPermission = permissionParts.slice(2).join(" ");
}); 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,50 +1,50 @@
"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>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AvatarPrimitive.Root <AvatarPrimitive.Root
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>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AvatarPrimitive.Image <AvatarPrimitive.Image
ref={ref} ref={ref}
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>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback <AvatarPrimitive.Fallback
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,36 +1,36 @@
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",
{ {
variants: { variants: {
variant: { variant: {
default: default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary: secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground", outline: "text-foreground",
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
}, },
} },
) );
export interface BadgeProps export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>, extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> { } VariantProps<typeof badgeVariants> {}
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,115 +1,115 @@
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,
React.ComponentPropsWithoutRef<"ol"> React.ComponentPropsWithoutRef<"ol">
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<ol <ol
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,
React.ComponentPropsWithoutRef<"li"> React.ComponentPropsWithoutRef<"li">
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<li <li
ref={ref} ref={ref}
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
ref={ref} ref={ref}
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,
React.ComponentPropsWithoutRef<"span"> React.ComponentPropsWithoutRef<"span">
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<span <span
ref={ref} ref={ref}
role="link" role="link"
aria-disabled="true" aria-disabled="true"
aria-current="page" aria-current="page"
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,
className, className,
...props ...props
}: React.ComponentProps<"li">) => ( }: React.ComponentProps<"li">) => (
<li <li
role="presentation" role="presentation"
aria-hidden="true" aria-hidden="true"
className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)} className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
{...props} {...props}
> >
{children ?? <ChevronRight />} {children ?? <ChevronRight />}
</li> </li>
) );
BreadcrumbSeparator.displayName = "BreadcrumbSeparator" BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
const BreadcrumbEllipsis = ({ const BreadcrumbEllipsis = ({
className, className,
...props ...props
}: React.ComponentProps<"span">) => ( }: React.ComponentProps<"span">) => (
<span <span
role="presentation" role="presentation"
aria-hidden="true" aria-hidden="true"
className={cn("flex h-9 w-9 items-center justify-center", className)} className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props} {...props}
> >
<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,
BreadcrumbList, BreadcrumbList,
BreadcrumbItem, BreadcrumbItem,
BreadcrumbLink, BreadcrumbLink,
BreadcrumbPage, BreadcrumbPage,
BreadcrumbSeparator, BreadcrumbSeparator,
BreadcrumbEllipsis, BreadcrumbEllipsis,
} };

View File

@ -1,59 +1,59 @@
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",
{ {
variants: { variants: {
variant: { variant: {
default: default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive: destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline: outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary: secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3", default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4", lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9", icon: "size-9",
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
size: "default", size: "default",
}, },
} },
) );
function Button({ function Button({
className, className,
variant, variant,
size, size,
asChild = false, asChild = false,
...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
data-slot="button" data-slot="button"
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
{...props} {...props}
/> />
) );
} }
export { Button, buttonVariants } export { Button, buttonVariants };

View File

@ -1,210 +1,210 @@
"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,
classNames, classNames,
showOutsideDays = true, showOutsideDays = true,
captionLayout = "label", captionLayout = "label",
buttonVariant = "ghost", buttonVariant = "ghost",
formatters, formatters,
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
showOutsideDays={showOutsideDays} showOutsideDays={showOutsideDays}
className={cn( className={cn(
"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={{
formatMonthDropdown: (date) => formatMonthDropdown: (date) =>
date.toLocaleString("default", { month: "short" }), date.toLocaleString("default", { month: "short" }),
...formatters, ...formatters,
}} }}
classNames={{ classNames={{
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(
"select-none font-medium", "select-none font-medium",
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,
}} }}
components={{ components={{
Root: ({ className, rootRef, ...props }) => { Root: ({ className, rootRef, ...props }) => {
return ( return (
<div <div
data-slot="calendar" data-slot="calendar"
ref={rootRef} ref={rootRef}
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") {
return ( return (
<ChevronRightIcon <ChevronRightIcon
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 }) => {
return ( return (
<td {...props}> <td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center"> <div className="flex size-(--cell-size) items-center justify-center text-center">
{children} {children}
</div> </div>
</td> </td>
) );
}, },
...components, ...components,
}} }}
{...props} {...props}
/> />
) );
} }
function CalendarDayButton({ function CalendarDayButton({
className, className,
day, day,
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
ref={ref} ref={ref}
variant="ghost" variant="ghost"
size="icon" size="icon"
data-day={day.date.toLocaleDateString()} data-day={day.date.toLocaleDateString()}
data-selected-single={ data-selected-single={
modifiers.selected && modifiers.selected &&
!modifiers.range_start && !modifiers.range_start &&
!modifiers.range_end && !modifiers.range_end &&
!modifiers.range_middle !modifiers.range_middle
} }
data-range-start={modifiers.range_start} data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end} data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle} data-range-middle={modifiers.range_middle}
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,76 +1,83 @@
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,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
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,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
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,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
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,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
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,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
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,262 +1,262 @@
"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<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & CarouselProps React.HTMLAttributes<HTMLDivElement> & CarouselProps
>( >(
( (
{ {
orientation = "horizontal", orientation = "horizontal",
opts, opts,
setApi, setApi,
plugins, plugins,
className, className,
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
value={{ value={{
carouselRef, carouselRef,
api: api, api: api,
opts, opts,
orientation: orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"), orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev, scrollPrev,
scrollNext, scrollNext,
canScrollPrev, canScrollPrev,
canScrollNext, canScrollNext,
}} }}
> >
<div <div
ref={ref} ref={ref}
onKeyDownCapture={handleKeyDown} onKeyDownCapture={handleKeyDown}
className={cn("relative", className)} className={cn("relative", className)}
role="region" role="region"
aria-roledescription="carousel" aria-roledescription="carousel"
{...props} {...props}
> >
{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">
<div <div
ref={ref} ref={ref}
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
ref={ref} ref={ref}
role="group" role="group"
aria-roledescription="slide" aria-roledescription="slide"
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
ref={ref} ref={ref}
variant={variant} variant={variant}
size={size} size={size}
className={cn( className={cn(
"absolute h-8 w-8 rounded-full", "absolute h-8 w-8 rounded-full",
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}
{...props} {...props}
> >
<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
ref={ref} ref={ref}
variant={variant} variant={variant}
size={size} size={size}
className={cn( className={cn(
"absolute h-8 w-8 rounded-full", "absolute h-8 w-8 rounded-full",
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}
{...props} {...props}
> >
<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,
Carousel, Carousel,
CarouselContent, CarouselContent,
CarouselItem, CarouselItem,
CarouselPrevious, CarouselPrevious,
CarouselNext, CarouselNext,
} };

View File

@ -1,30 +1,30 @@
"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>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
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}
> >
<CheckboxPrimitive.Indicator <CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")} className={cn("flex items-center justify-center text-current")}
> >
<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,184 +1,184 @@
"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,
...props ...props
}: React.ComponentProps<typeof CommandPrimitive>) { }: React.ComponentProps<typeof CommandPrimitive>) {
return ( return (
<CommandPrimitive <CommandPrimitive
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({
title = "Command Palette", title = "Command Palette",
description = "Search for a command to run...", description = "Search for a command to run...",
children, children,
className, className,
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}>
<DialogHeader className="sr-only"> <DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle> <DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription> <DialogDescription>{description}</DialogDescription>
</DialogHeader> </DialogHeader>
<DialogContent <DialogContent
className={cn("overflow-hidden p-0", className)} className={cn("overflow-hidden p-0", className)}
showCloseButton={showCloseButton} showCloseButton={showCloseButton}
> >
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"> <Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children} {children}
</Command> </Command>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
) );
} }
function CommandInput({ function CommandInput({
className, className,
...props ...props
}: React.ComponentProps<typeof CommandPrimitive.Input>) { }: React.ComponentProps<typeof CommandPrimitive.Input>) {
return ( return (
<div <div
data-slot="command-input-wrapper" data-slot="command-input-wrapper"
className="flex h-9 items-center gap-2 border-b px-3" className="flex h-9 items-center gap-2 border-b px-3"
> >
<SearchIcon className="size-4 shrink-0 opacity-50" /> <SearchIcon className="size-4 shrink-0 opacity-50" />
<CommandPrimitive.Input <CommandPrimitive.Input
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({
className, className,
...props ...props
}: React.ComponentProps<typeof CommandPrimitive.List>) { }: React.ComponentProps<typeof CommandPrimitive.List>) {
return ( return (
<CommandPrimitive.List <CommandPrimitive.List
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({
...props ...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) { }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
return ( return (
<CommandPrimitive.Empty <CommandPrimitive.Empty
data-slot="command-empty" data-slot="command-empty"
className="py-6 text-center text-sm" className="py-6 text-center text-sm"
{...props} {...props}
/> />
) );
} }
function CommandGroup({ function CommandGroup({
className, className,
...props ...props
}: React.ComponentProps<typeof CommandPrimitive.Group>) { }: React.ComponentProps<typeof CommandPrimitive.Group>) {
return ( return (
<CommandPrimitive.Group <CommandPrimitive.Group
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({
className, className,
...props ...props
}: React.ComponentProps<typeof CommandPrimitive.Separator>) { }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
return ( return (
<CommandPrimitive.Separator <CommandPrimitive.Separator
data-slot="command-separator" data-slot="command-separator"
className={cn("bg-border -mx-1 h-px", className)} className={cn("bg-border -mx-1 h-px", className)}
{...props} {...props}
/> />
) );
} }
function CommandItem({ function CommandItem({
className, className,
...props ...props
}: React.ComponentProps<typeof CommandPrimitive.Item>) { }: React.ComponentProps<typeof CommandPrimitive.Item>) {
return ( return (
<CommandPrimitive.Item <CommandPrimitive.Item
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({
className, className,
...props ...props
}: React.ComponentProps<"span">) { }: React.ComponentProps<"span">) {
return ( return (
<span <span
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 {
Command, Command,
CommandDialog, CommandDialog,
CommandInput, CommandInput,
CommandList, CommandList,
CommandEmpty, CommandEmpty,
CommandGroup, CommandGroup,
CommandItem, CommandItem,
CommandShortcut, CommandShortcut,
CommandSeparator, CommandSeparator,
} };

View File

@ -1,200 +1,200 @@
"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
ref={ref} ref={ref}
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>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent> React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<ContextMenuPrimitive.SubContent <ContextMenuPrimitive.SubContent
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>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content> React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Portal> <ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content <ContextMenuPrimitive.Content
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
ref={ref} ref={ref}
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>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem> React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => ( >(({ className, children, checked, ...props }, ref) => (
<ContextMenuPrimitive.CheckboxItem <ContextMenuPrimitive.CheckboxItem
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}
> >
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator> <ContextMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" /> <Check className="h-4 w-4" />
</ContextMenuPrimitive.ItemIndicator> </ContextMenuPrimitive.ItemIndicator>
</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>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem> React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.RadioItem <ContextMenuPrimitive.RadioItem
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}
> >
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator> <ContextMenuPrimitive.ItemIndicator>
<Circle className="h-4 w-4 fill-current" /> <Circle className="h-4 w-4 fill-current" />
</ContextMenuPrimitive.ItemIndicator> </ContextMenuPrimitive.ItemIndicator>
</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
ref={ref} ref={ref}
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>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator> React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator <ContextMenuPrimitive.Separator
ref={ref} ref={ref}
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,
...props ...props
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<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,
ContextMenuTrigger, ContextMenuTrigger,
ContextMenuContent, ContextMenuContent,
ContextMenuItem, ContextMenuItem,
ContextMenuCheckboxItem, ContextMenuCheckboxItem,
ContextMenuRadioItem, ContextMenuRadioItem,
ContextMenuLabel, ContextMenuLabel,
ContextMenuSeparator, ContextMenuSeparator,
ContextMenuShortcut, ContextMenuShortcut,
ContextMenuGroup, ContextMenuGroup,
ContextMenuPortal, ContextMenuPortal,
ContextMenuSub, ContextMenuSub,
ContextMenuSubContent, ContextMenuSubContent,
ContextMenuSubTrigger, ContextMenuSubTrigger,
ContextMenuRadioGroup, ContextMenuRadioGroup,
} };

View File

@ -1,129 +1,136 @@
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>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
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" />
{date ? format(date, "PPP") : <span>Pick a date</span>} {date ? format(date, "PPP") : <span>Pick a date</span>}
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start"> <PopoverContent className="w-auto p-0" align="start">
<div className="flex justify-between p-2 space-x-1"> <div className="flex justify-between p-2 space-x-1">
<Select onValueChange={handleYearChange} value={year.toString()}> <Select onValueChange={handleYearChange} value={year.toString()}>
<SelectTrigger className="w-[120px]"> <SelectTrigger className="w-[120px]">
<SelectValue placeholder="Year" /> <SelectValue placeholder="Year" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{years.map((y) => ( {years.map((y) => (
<SelectItem key={y} value={y.toString()}> <SelectItem key={y} value={y.toString()}>
{y} {y}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
<Select onValueChange={handleMonthChange} value={month.toString()}> <Select onValueChange={handleMonthChange} value={month.toString()}>
<SelectTrigger className="w-[120px]"> <SelectTrigger className="w-[120px]">
<SelectValue placeholder="Month" /> <SelectValue placeholder="Month" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{months.map((m, index) => ( {months.map((m, index) => (
<SelectItem key={`${index + 1}`} value={index.toString()}> <SelectItem key={`${index + 1}`} value={index.toString()}>
{format(m, "MMMM")} {format(m, "MMMM")}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<Calendar <Calendar
mode="single" mode="single"
selected={date} selected={date}
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,143 +1,143 @@
"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({
className, className,
...props ...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) { }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return ( return (
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
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({
className, className,
children, children,
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">
<DialogOverlay /> <DialogOverlay />
<DialogPrimitive.Content <DialogPrimitive.Content
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}
> >
{children} {children}
{showCloseButton && ( {showCloseButton && (
<DialogPrimitive.Close <DialogPrimitive.Close
data-slot="dialog-close" data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
> >
<XIcon /> <XIcon />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</DialogPrimitive.Close> </DialogPrimitive.Close>
)} )}
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPortal> </DialogPortal>
) );
} }
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="dialog-header" data-slot="dialog-header"
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">) {
return ( return (
<div <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({
className, className,
...props ...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) { }: React.ComponentProps<typeof DialogPrimitive.Title>) {
return ( return (
<DialogPrimitive.Title <DialogPrimitive.Title
data-slot="dialog-title" data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)} className={cn("text-lg leading-none font-semibold", className)}
{...props} {...props}
/> />
) );
} }
function DialogDescription({ function DialogDescription({
className, className,
...props ...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) { }: React.ComponentProps<typeof DialogPrimitive.Description>) {
return ( return (
<DialogPrimitive.Description <DialogPrimitive.Description
data-slot="dialog-description" data-slot="dialog-description"
className={cn("text-start text-muted-foreground text-sm", className)} className={cn("text-start text-muted-foreground text-sm", className)}
{...props} {...props}
/> />
) );
} }
export { export {
Dialog, Dialog,
DialogClose, DialogClose,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
DialogOverlay, DialogOverlay,
DialogPortal, DialogPortal,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} };

View File

@ -1,118 +1,118 @@
"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,
...props ...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => ( }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root <DrawerPrimitive.Root
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>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay> React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay <DrawerPrimitive.Overlay
ref={ref} ref={ref}
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>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<DrawerPortal> <DrawerPortal>
<DrawerOverlay /> <DrawerOverlay />
<DrawerPrimitive.Content <DrawerPrimitive.Content
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}
> >
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" /> <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
{children} {children}
</DrawerPrimitive.Content> </DrawerPrimitive.Content>
</DrawerPortal> </DrawerPortal>
)) ));
DrawerContent.displayName = "DrawerContent" DrawerContent.displayName = "DrawerContent";
const DrawerHeader = ({ const DrawerHeader = ({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
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,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
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>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title> React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DrawerPrimitive.Title <DrawerPrimitive.Title
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>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description> React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DrawerPrimitive.Description <DrawerPrimitive.Description
ref={ref} ref={ref}
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,
DrawerPortal, DrawerPortal,
DrawerOverlay, DrawerOverlay,
DrawerTrigger, DrawerTrigger,
DrawerClose, DrawerClose,
DrawerContent, DrawerContent,
DrawerHeader, DrawerHeader,
DrawerFooter, DrawerFooter,
DrawerTitle, DrawerTitle,
DrawerDescription, DrawerDescription,
} };

View File

@ -1,201 +1,201 @@
"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
ref={ref} ref={ref}
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>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
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>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => ( >(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal> <DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content <DropdownMenuPrimitive.Content
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
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
ref={ref} ref={ref}
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>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => ( >(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
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}
> >
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" /> <Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</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>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem <DropdownMenuPrimitive.RadioItem
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}
> >
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" /> <Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</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
ref={ref} ref={ref}
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>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
ref={ref} ref={ref}
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,
...props ...props
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
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,
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuRadioItem, DropdownMenuRadioItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuGroup, DropdownMenuGroup,
DropdownMenuPortal, DropdownMenuPortal,
DropdownMenuSub, DropdownMenuSub,
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuRadioGroup, DropdownMenuRadioGroup,
} };

View File

@ -1,50 +1,56 @@
'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> {
label?: (value: number | undefined) => React.ReactNode; labelPosition?: "top" | "bottom";
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(
{...props} "relative flex w-full touch-none select-none items-center",
> className,
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary"> )}
<SliderPrimitive.Range className="absolute h-full bg-primary" /> {...props}
</SliderPrimitive.Track> >
{initialValue.map((value, index) => ( <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
<React.Fragment key={`${index + 1}`}> <SliderPrimitive.Range className="absolute h-full bg-primary" />
<SliderPrimitive.Thumb className="relative block h-4 w-4 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"> </SliderPrimitive.Track>
{label && ( {initialValue.map((value, index) => (
<span <React.Fragment key={`${index + 1}`}>
className={cn( <SliderPrimitive.Thumb className="relative block h-4 w-4 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50">
'absolute flex w-full justify-center', {label && (
labelPosition === 'top' && '-top-7', <span
labelPosition === 'bottom' && 'top-4', className={cn(
)} "absolute flex w-full justify-center",
> labelPosition === "top" && "-top-7",
{label(value)} labelPosition === "bottom" && "top-4",
</span> )}
)} >
</SliderPrimitive.Thumb> {label(value)}
</React.Fragment> </span>
))} )}
</SliderPrimitive.Root> </SliderPrimitive.Thumb>
); </React.Fragment>
))}
</SliderPrimitive.Root>
);
}); });
DualRangeSlider.displayName = 'DualRangeSlider'; DualRangeSlider.displayName = "DualRangeSlider";
export { DualRangeSlider }; export { DualRangeSlider };

View File

@ -1,45 +1,55 @@
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 (
FloatingInput.displayName = 'FloatingInput'; <Input
placeholder=" "
className={cn("peer", className)}
ref={ref}
{...props}
/>
);
});
FloatingInput.displayName = "FloatingInput";
const FloatingLabel = React.forwardRef< const FloatingLabel = React.forwardRef<
React.ElementRef<typeof Label>, React.ElementRef<typeof Label>,
React.ComponentPropsWithoutRef<typeof Label> React.ComponentPropsWithoutRef<typeof Label>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
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}
{...props} {...props}
/> />
); );
}); });
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>,
React.PropsWithoutRef<FloatingLabelInputProps> React.PropsWithoutRef<FloatingLabelInputProps>
>(({ id, label, ...props }, ref) => { >(({ id, label, ...props }, ref) => {
return ( return (
<div className="relative"> <div className="relative">
<FloatingInput ref={ref} id={id} {...props} /> <FloatingInput ref={ref} id={id} {...props} />
<FloatingLabel htmlFor={id}>{label}</FloatingLabel> <FloatingLabel htmlFor={id}>{label}</FloatingLabel>
</div> </div>
); );
}); });
FloatingLabelInput.displayName = 'FloatingLabelInput'; FloatingLabelInput.displayName = "FloatingLabelInput";
export { FloatingInput, FloatingLabel, FloatingLabelInput }; export { FloatingInput, FloatingLabel, FloatingLabelInput };

View File

@ -1,178 +1,179 @@
"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,
FormProvider, FormProvider,
useFormContext, useFormContext,
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>) => {
return ( return (
<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,
name: fieldContext.name, name: fieldContext.name,
formItemId: `${id}-form-item`, formItemId: `${id}-form-item`,
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
ref={ref} ref={ref}
className={cn(error && "text-destructive", className)} className={cn(error && "text-destructive", className)}
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
ref={ref} ref={ref}
id={formItemId} id={formItemId}
aria-describedby={ aria-describedby={
!error !error
? `${formDescriptionId}` ? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}` : `${formDescriptionId} ${formMessageId}`
} }
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
ref={ref} ref={ref}
id={formDescriptionId} id={formDescriptionId}
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 (
<p <p
ref={ref} ref={ref}
id={formMessageId} id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)} className={cn("text-[0.8rem] font-medium text-destructive", className)}
{...props} {...props}
> >
{body} {body}
</p> </p>
) );
}) });
FormMessage.displayName = "FormMessage" FormMessage.displayName = "FormMessage";
export { export {
useFormField, useFormField,
Form, Form,
FormItem, FormItem,
FormLabel, FormLabel,
FormControl, FormControl,
FormDescription, FormDescription,
FormMessage, FormMessage,
FormField, FormField,
} };

View File

@ -1,29 +1,29 @@
"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>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content> React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content <HoverCardPrimitive.Content
ref={ref} ref={ref}
align={align} align={align}
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,71 +1,71 @@
"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>,
React.ComponentPropsWithoutRef<typeof OTPInput> React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => ( >(({ className, containerClassName, ...props }, ref) => (
<OTPInput <OTPInput
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
ref={ref} ref={ref}
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}
> >
{char} {char}
{hasFakeCaret && ( {hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center"> <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" /> <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
</div> </div>
)} )}
</div> </div>
) );
}) });
InputOTPSlot.displayName = "InputOTPSlot" InputOTPSlot.displayName = "InputOTPSlot";
const InputOTPSeparator = React.forwardRef< const InputOTPSeparator = React.forwardRef<
React.ElementRef<"div">, React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div"> React.ComponentPropsWithoutRef<"div">
>(({ ...props }, ref) => ( >(({ ...props }, ref) => (
<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,21 +1,21 @@
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 (
<input <input
type={type} type={type}
data-slot="input" data-slot="input"
className={cn( className={cn(
"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,26 +1,26 @@
"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>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants> VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<LabelPrimitive.Root <LabelPrimitive.Root
ref={ref} ref={ref}
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,256 +1,256 @@
"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<
React.ElementRef<typeof MenubarPrimitive.Root>, React.ElementRef<typeof MenubarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root> React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<MenubarPrimitive.Root <MenubarPrimitive.Root
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>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger> React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<MenubarPrimitive.Trigger <MenubarPrimitive.Trigger
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
ref={ref} ref={ref}
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>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent> React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<MenubarPrimitive.SubContent <MenubarPrimitive.SubContent
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>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content> React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
>( >(
( (
{ 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
ref={ref} ref={ref}
align={align} align={align}
alignOffset={alignOffset} alignOffset={alignOffset}
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
ref={ref} ref={ref}
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>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem> React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => ( >(({ className, children, checked, ...props }, ref) => (
<MenubarPrimitive.CheckboxItem <MenubarPrimitive.CheckboxItem
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}
> >
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator> <MenubarPrimitive.ItemIndicator>
<Check className="h-4 w-4" /> <Check className="h-4 w-4" />
</MenubarPrimitive.ItemIndicator> </MenubarPrimitive.ItemIndicator>
</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>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem> React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<MenubarPrimitive.RadioItem <MenubarPrimitive.RadioItem
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}
> >
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator> <MenubarPrimitive.ItemIndicator>
<Circle className="h-4 w-4 fill-current" /> <Circle className="h-4 w-4 fill-current" />
</MenubarPrimitive.ItemIndicator> </MenubarPrimitive.ItemIndicator>
</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
ref={ref} ref={ref}
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>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator> React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<MenubarPrimitive.Separator <MenubarPrimitive.Separator
ref={ref} ref={ref}
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,
...props ...props
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<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,
MenubarMenu, MenubarMenu,
MenubarTrigger, MenubarTrigger,
MenubarContent, MenubarContent,
MenubarItem, MenubarItem,
MenubarSeparator, MenubarSeparator,
MenubarLabel, MenubarLabel,
MenubarCheckboxItem, MenubarCheckboxItem,
MenubarRadioGroup, MenubarRadioGroup,
MenubarRadioItem, MenubarRadioItem,
MenubarPortal, MenubarPortal,
MenubarSubContent, MenubarSubContent,
MenubarSubTrigger, MenubarSubTrigger,
MenubarGroup, MenubarGroup,
MenubarSub, MenubarSub,
MenubarShortcut, MenubarShortcut,
} };

View File

@ -1,128 +1,128 @@
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>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root> React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root <NavigationMenuPrimitive.Root
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>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List> React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List <NavigationMenuPrimitive.List
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>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger> React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger <NavigationMenuPrimitive.Trigger
ref={ref} ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)} className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props} {...props}
> >
{children}{" "} {children}{" "}
<ChevronDown <ChevronDown
className="relative top-px ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180" className="relative top-px ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
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>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content> React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content <NavigationMenuPrimitive.Content
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>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport> React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}> <div className={cn("absolute left-0 top-full flex justify-center")}>
<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>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator> React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator <NavigationMenuPrimitive.Indicator
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,
NavigationMenu, NavigationMenu,
NavigationMenuList, NavigationMenuList,
NavigationMenuItem, NavigationMenuItem,
NavigationMenuContent, NavigationMenuContent,
NavigationMenuTrigger, NavigationMenuTrigger,
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,33 +1,33 @@
"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>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal> <PopoverPrimitive.Portal>
<PopoverPrimitive.Content <PopoverPrimitive.Content
ref={ref} ref={ref}
align={align} align={align}
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,28 +1,32 @@
"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>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => ( >(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root <ProgressPrimitive.Root
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(
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} "h-full w-full flex-1 transition-all",
/> className,
</ProgressPrimitive.Root> (value ?? 0) < 20 ? "bg-red-500" : "bg-sarLinkOrange",
)) )}
Progress.displayName = ProgressPrimitive.Root.displayName style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
));
Progress.displayName = ProgressPrimitive.Root.displayName;
export { Progress } export { Progress };

View File

@ -1,44 +1,44 @@
"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>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root> React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
return ( return (
<RadioGroupPrimitive.Root <RadioGroupPrimitive.Root
className={cn("grid gap-2", className)} className={cn("grid gap-2", className)}
{...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>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
return ( return (
<RadioGroupPrimitive.Item <RadioGroupPrimitive.Item
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}
> >
<RadioGroupPrimitive.Indicator className="flex items-center justify-center"> <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<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,45 +1,45 @@
"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,
...props ...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => ( }: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
<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}
> >
{withHandle && ( {withHandle && (
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border"> <div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
<GripVertical className="h-2.5 w-2.5" /> <GripVertical className="h-2.5 w-2.5" />
</div> </div>
)} )}
</ResizablePrimitive.PanelResizeHandle> </ResizablePrimitive.PanelResizeHandle>
) );
export { ResizablePanelGroup, ResizablePanel, ResizableHandle } export { ResizablePanelGroup, ResizablePanel, ResizableHandle };

View File

@ -1,48 +1,48 @@
"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>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root <ScrollAreaPrimitive.Root
ref={ref} ref={ref}
className={cn("relative overflow-hidden", className)} className={cn("relative overflow-hidden", className)}
{...props} {...props}
> >
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]"> <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children} {children}
</ScrollAreaPrimitive.Viewport> </ScrollAreaPrimitive.Viewport>
<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>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar> React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => ( >(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar <ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref} ref={ref}
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"flex touch-none select-none transition-colors", "flex touch-none select-none transition-colors",
orientation === "vertical" && orientation === "vertical" &&
"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,159 +1,159 @@
"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>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger <SelectPrimitive.Trigger
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}
> >
{children} {children}
<SelectPrimitive.Icon asChild> <SelectPrimitive.Icon asChild>
<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>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton> React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton <SelectPrimitive.ScrollUpButton
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>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton> React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton <SelectPrimitive.ScrollDownButton
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>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => ( >(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal> <SelectPrimitive.Portal>
<SelectPrimitive.Content <SelectPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"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}
> >
<SelectScrollUpButton /> <SelectScrollUpButton />
<SelectPrimitive.Viewport <SelectPrimitive.Viewport
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}
</SelectPrimitive.Viewport> </SelectPrimitive.Viewport>
<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>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.Label <SelectPrimitive.Label
ref={ref} ref={ref}
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>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item <SelectPrimitive.Item
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}
> >
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator> <SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" /> <Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator> </SelectPrimitive.ItemIndicator>
</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>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.Separator <SelectPrimitive.Separator
ref={ref} ref={ref}
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,
SelectGroup, SelectGroup,
SelectValue, SelectValue,
SelectTrigger, SelectTrigger,
SelectContent, SelectContent,
SelectLabel, SelectLabel,
SelectItem, SelectItem,
SelectSeparator, SelectSeparator,
SelectScrollUpButton, SelectScrollUpButton,
SelectScrollDownButton, SelectScrollDownButton,
} };

View File

@ -1,31 +1,31 @@
"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>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>( >(
( (
{ className, orientation = "horizontal", decorative = true, ...props }, { className, orientation = "horizontal", decorative = true, ...props },
ref ref,
) => ( ) => (
<SeparatorPrimitive.Root <SeparatorPrimitive.Root
ref={ref} ref={ref}
decorative={decorative} decorative={decorative}
orientation={orientation} orientation={orientation}
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,140 +1,140 @@
"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>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<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",
{ {
variants: { variants: {
side: { side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom: bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right: right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
}, },
}, },
defaultVariants: { defaultVariants: {
side: "right", side: "right",
}, },
} },
) );
interface SheetContentProps interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {} VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef< const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>, React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => ( >(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal> <SheetPortal>
<SheetOverlay /> <SheetOverlay />
<SheetPrimitive.Content <SheetPrimitive.Content
ref={ref} ref={ref}
className={cn(sheetVariants({ side }), className)} className={cn(sheetVariants({ side }), className)}
{...props} {...props}
> >
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"> <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" /> <X className="h-4 w-4" />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</SheetPrimitive.Close> </SheetPrimitive.Close>
{children} {children}
</SheetPrimitive.Content> </SheetPrimitive.Content>
</SheetPortal> </SheetPortal>
)) ));
SheetContent.displayName = SheetPrimitive.Content.displayName SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({ const SheetHeader = ({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement>) => (
<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,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement>) => (
<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>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title> React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SheetPrimitive.Title <SheetPrimitive.Title
ref={ref} ref={ref}
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>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description> React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SheetPrimitive.Description <SheetPrimitive.Description
ref={ref} ref={ref}
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,
SheetPortal, SheetPortal,
SheetOverlay, SheetOverlay,
SheetTrigger, SheetTrigger,
SheetClose, SheetClose,
SheetContent, SheetContent,
SheetHeader, SheetHeader,
SheetFooter, SheetFooter,
SheetTitle, SheetTitle,
SheetDescription, SheetDescription,
} };

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,15 @@
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function Skeleton({ function Skeleton({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) { }: React.HTMLAttributes<HTMLDivElement>) {
return ( return (
<div <div
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,28 +1,28 @@
"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>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SliderPrimitive.Root <SliderPrimitive.Root
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}
> >
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20"> <SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
<SliderPrimitive.Range className="absolute h-full bg-primary" /> <SliderPrimitive.Range className="absolute h-full bg-primary" />
</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,31 +1,31 @@
"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
theme={theme as ToasterProps["theme"]} theme={theme as ToasterProps["theme"]}
className="toaster group" className="toaster group"
toastOptions={{ toastOptions={{
classNames: { classNames: {
toast: toast:
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg", "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground", description: "group-[.toast]:text-muted-foreground",
actionButton: actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground", "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton: cancelButton:
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground", "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
}, },
}} }}
{...props} {...props}
/> />
) );
} };
export { Toaster } export { Toaster };

View File

@ -1,29 +1,29 @@
"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>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<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,120 +1,120 @@
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,
React.HTMLAttributes<HTMLTableElement> React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto"> <div className="relative w-full overflow-auto">
<table <table
ref={ref} ref={ref}
className={cn("w-full caption-bottom text-sm", className)} className={cn("w-full caption-bottom text-sm", className)}
{...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,
React.HTMLAttributes<HTMLTableSectionElement> React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<tbody <tbody
ref={ref} ref={ref}
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,
React.HTMLAttributes<HTMLTableSectionElement> React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<tfoot <tfoot
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,
React.HTMLAttributes<HTMLTableRowElement> React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<tr <tr
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,
React.ThHTMLAttributes<HTMLTableCellElement> React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<th <th
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,
React.TdHTMLAttributes<HTMLTableCellElement> React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<td <td
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,
React.HTMLAttributes<HTMLTableCaptionElement> React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<caption <caption
ref={ref} ref={ref}
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,
TableHeader, TableHeader,
TableBody, TableBody,
TableFooter, TableFooter,
TableHead, TableHead,
TableRow, TableRow,
TableCell, TableCell,
TableCaption, TableCaption,
} };

View File

@ -1,55 +1,55 @@
"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>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<TabsPrimitive.List <TabsPrimitive.List
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>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
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>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content> React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<TabsPrimitive.Content <TabsPrimitive.Content
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,55 +1,55 @@
'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;
as?: React.ElementType; as?: React.ElementType;
className?: string; className?: string;
duration?: number; duration?: number;
spread?: number; spread?: number;
} }
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(() => {
return children.length * spread; return children.length * spread;
}, [children, spread]); }, [children, spread]);
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
} }
> >
{children} {children}
</MotionComponent> </MotionComponent>
); );
} }

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