mirror of
https://github.com/i701/sarlink-portal.git
synced 2025-02-22 09:22:01 +00:00
Refactor dashboard components and update global styles
- Updated the title and description in layout.tsx to reflect the new application name. - Replaced the background color in globals.css with a background image for the title section. - Enhanced the Devices and UserDevices pages by adding search and filter components for improved user interaction. - Introduced a new DevicesTable component for displaying device data with pagination. - Updated the Users page to improve layout and added a filter for user status. - Made various UI adjustments across components for better consistency and usability.
This commit is contained in:
parent
490150f9b7
commit
b91f34b6b1
@ -1,15 +1,37 @@
|
|||||||
import { auth } from "@/lib/auth";
|
import { DevicesTable } from "@/components/devices-table";
|
||||||
import { headers } from "next/headers";
|
import Search from "@/components/search";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
export default async function Devices() {
|
import React, { Suspense } from "react";
|
||||||
const session = await auth.api.getSession({
|
export default async function Devices({
|
||||||
headers: await headers(),
|
searchParams,
|
||||||
});
|
}: {
|
||||||
|
searchParams: Promise<{
|
||||||
|
query: string;
|
||||||
|
page: number;
|
||||||
|
sortBy: string;
|
||||||
|
status: string;
|
||||||
|
}>;
|
||||||
|
}) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>Server session</h2>
|
<div className="flex justify-between items-center border-b-2 text-gray-500 text-2xl font-bold title-bg py-4 px-2 mb-4">
|
||||||
<pre>{JSON.stringify(session?.user, null, 2)}</pre>
|
<h3>
|
||||||
|
My Devices
|
||||||
|
</h3>
|
||||||
|
<Button>Add new device</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="user-filters"
|
||||||
|
className=" border-b-2 pb-4 gap-4 flex items-center justify-start"
|
||||||
|
>
|
||||||
|
<Search />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<Suspense fallback={"loading...."}>
|
||||||
|
<DevicesTable searchParams={searchParams} />
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,54 @@
|
|||||||
import React from "react";
|
import Filter from "@/components/filter";
|
||||||
|
import Search from "@/components/search";
|
||||||
|
import { UsersTable } from "@/components/user-table";
|
||||||
|
import { CheckCheck, Hourglass, Minus } from "lucide-react";
|
||||||
|
import React, { Suspense } from "react";
|
||||||
|
export default async function UserDevcies({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: Promise<{
|
||||||
|
query: string;
|
||||||
|
page: number;
|
||||||
|
sortBy: string;
|
||||||
|
status: string;
|
||||||
|
}>;
|
||||||
|
}) {
|
||||||
|
|
||||||
export default function UserDevices() {
|
return (
|
||||||
return <div>UserDevices</div>;
|
<div>
|
||||||
|
<h3 className="border-b-2 text-2xl font-bold title-bg py-4 px-2 mb-4">
|
||||||
|
My Devices
|
||||||
|
</h3>
|
||||||
|
<div
|
||||||
|
id="user-filters"
|
||||||
|
className=" border-b-2 pb-4 gap-4 flex items-center justify-start"
|
||||||
|
>
|
||||||
|
<Search />
|
||||||
|
<Filter
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
value: "all",
|
||||||
|
label: "ALL",
|
||||||
|
icon: <Minus size={14} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "unverified",
|
||||||
|
label: "Unverfieid",
|
||||||
|
icon: <CheckCheck size={14} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "verified",
|
||||||
|
label: "Verified",
|
||||||
|
icon: <Hourglass size={14} />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
defaultOption="all"
|
||||||
|
queryParamKey="status"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Suspense fallback={"loading...."}>
|
||||||
|
<UsersTable searchParams={searchParams} />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -18,35 +18,38 @@ export default async function AdminUsers({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="border-b-2 text-2xl font-bold title-bg py-4 px-2 mb-4">
|
<h3 className="border-b-2 text-gray-500 text-2xl font-bold title-bg py-4 px-2 mb-4">
|
||||||
Users
|
Users
|
||||||
</h3>
|
</h3>
|
||||||
<div
|
<div
|
||||||
id="user-filters"
|
id="user-filters"
|
||||||
className=" border-b-2 pb-4 gap-4 flex items-center justify-start"
|
className=" border-b-2 pb-4 gap-4 flex items-center justify-start"
|
||||||
>
|
>
|
||||||
<Search />
|
<div className="flex flex-col sm:flex-row flex-wrap items-start justify-start gap-2">
|
||||||
<Filter
|
<Search />
|
||||||
options={[
|
<Filter
|
||||||
{
|
options={[
|
||||||
value: "all",
|
{
|
||||||
label: "ALL",
|
value: "all",
|
||||||
icon: <Minus size={14} />,
|
label: "ALL",
|
||||||
},
|
icon: <Minus size={14} />,
|
||||||
{
|
},
|
||||||
value: "unverified",
|
{
|
||||||
label: "Unverfieid",
|
value: "unverified",
|
||||||
icon: <CheckCheck size={14} />,
|
label: "Unverfieid",
|
||||||
},
|
icon: <CheckCheck size={14} />,
|
||||||
{
|
},
|
||||||
value: "verified",
|
{
|
||||||
label: "Verified",
|
value: "verified",
|
||||||
icon: <Hourglass size={14} />,
|
label: "Verified",
|
||||||
},
|
icon: <Hourglass size={14} />,
|
||||||
]}
|
},
|
||||||
defaultOption="all"
|
]}
|
||||||
queryParamKey="status"
|
defaultOption="all"
|
||||||
/>
|
queryParamKey="status"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<Suspense fallback={"loading...."}>
|
<Suspense fallback={"loading...."}>
|
||||||
<UsersTable searchParams={searchParams} />
|
<UsersTable searchParams={searchParams} />
|
||||||
|
@ -89,6 +89,5 @@ body {
|
|||||||
|
|
||||||
|
|
||||||
.title-bg {
|
.title-bg {
|
||||||
background-color: #fefefe;
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg width='6' height='6' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23c1d3c8' fill-opacity='0.21' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml,%3Csvg width='6' height='6' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23c1d3c8' fill-opacity='0.21' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E");
|
||||||
}
|
}
|
@ -13,8 +13,8 @@ const barlow = Barlow({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "SAR Link Portal",
|
||||||
description: "Generated by create next app",
|
description: "Sarlink Portal",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
@ -50,7 +50,7 @@ export async function ApplicationLayout({
|
|||||||
<AccountPopover user={session?.user} />
|
<AccountPopover user={session?.user} />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className="px-8 py-6">{children}</div>
|
<div className="p-4">{children}</div>
|
||||||
</SidebarInset>
|
</SidebarInset>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
);
|
);
|
||||||
|
@ -33,7 +33,7 @@ export default function LoginForm() {
|
|||||||
<p className="text-red-500 text-sm">{state.message}</p>
|
<p className="text-red-500 text-sm">{state.message}</p>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
className="dark:bg-gray-800 w-full dark:text-white"
|
className=""
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
|
@ -5,10 +5,10 @@ import Link from "next/link";
|
|||||||
|
|
||||||
import { signup } from "@/actions/auth-actions";
|
import { signup } from "@/actions/auth-actions";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Loader } from "lucide-react";
|
|
||||||
import { useActionState } from "react";
|
|
||||||
import { useSearchParams } from "next/navigation";
|
|
||||||
import type { Island, Prisma } from "@prisma/client";
|
import type { Island, Prisma } from "@prisma/client";
|
||||||
|
import { Loader } from "lucide-react";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import { useActionState } from "react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -20,6 +20,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { Checkbox } from "../ui/checkbox";
|
||||||
|
|
||||||
type AtollWithIslands = Prisma.AtollGetPayload<{
|
type AtollWithIslands = Prisma.AtollGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
@ -31,13 +32,16 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
|
|||||||
const [atoll, setAtoll] = React.useState<AtollWithIslands>();
|
const [atoll, setAtoll] = React.useState<AtollWithIslands>();
|
||||||
const [islands, setIslands] = React.useState<Island[]>();
|
const [islands, setIslands] = React.useState<Island[]>();
|
||||||
|
|
||||||
|
const [actionState, action, isPending] = useActionState(signup, {
|
||||||
|
message: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setIslands(atoll?.islands);
|
setIslands(atoll?.islands);
|
||||||
}, [atoll]);
|
}, [atoll]);
|
||||||
|
|
||||||
const [actionState, action, isPending] = useActionState(signup, {
|
|
||||||
message: "",
|
|
||||||
});
|
|
||||||
const params = useSearchParams();
|
const params = useSearchParams();
|
||||||
const phoneNumberFromUrl = params.get("phone_number");
|
const phoneNumberFromUrl = params.get("phone_number");
|
||||||
const NUMBER_WITHOUT_DASH = phoneNumberFromUrl?.split("-").join("");
|
const NUMBER_WITHOUT_DASH = phoneNumberFromUrl?.split("-").join("");
|
||||||
@ -83,7 +87,7 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
|
|||||||
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"
|
||||||
/>
|
/>
|
||||||
@ -160,26 +164,26 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="house_name" className="text-sm">
|
<label htmlFor="address" className="text-sm">
|
||||||
House Name
|
Address
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-base",
|
"text-base",
|
||||||
actionState.errors?.fieldErrors?.house_name &&
|
actionState.errors?.fieldErrors?.address &&
|
||||||
"border-2 border-red-500",
|
"border-2 border-red-500",
|
||||||
)}
|
)}
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
name="house_name"
|
name="address"
|
||||||
defaultValue={
|
defaultValue={
|
||||||
(actionState.payload?.get("house_name") || "") as string
|
(actionState.payload?.get("address") || "") as string
|
||||||
}
|
}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="House Name"
|
placeholder="Address"
|
||||||
/>
|
/>
|
||||||
{actionState.errors?.fieldErrors?.house_name && (
|
{actionState.errors?.fieldErrors?.address && (
|
||||||
<span className="text-sm inline-block text-red-500">
|
<span className="text-sm inline-block text-red-500">
|
||||||
{actionState.errors?.fieldErrors?.house_name}
|
{actionState.errors?.fieldErrors?.address}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -217,8 +221,8 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
|
|||||||
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)}
|
||||||
@ -235,6 +239,51 @@ export default function SignUpForm({ atolls }: { atolls: AtollWithIslands[] }) {
|
|||||||
{actionState.message}
|
{actionState.message}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
<div className="flex flex-col gap-2 items-start justify-start py-2">
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<Checkbox
|
||||||
|
defaultChecked={(actionState.payload?.get("terms") || "") as string === 'on'}
|
||||||
|
name="terms" id="terms" />
|
||||||
|
<label
|
||||||
|
htmlFor="terms"
|
||||||
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
i accept
|
||||||
|
</span>
|
||||||
|
<Link className="ml-1 underline" href="">
|
||||||
|
terms and conditions
|
||||||
|
</Link>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{actionState.errors?.fieldErrors?.terms && (
|
||||||
|
<span className="text-sm inline-block text-red-500">
|
||||||
|
{actionState.errors?.fieldErrors?.terms}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
name="policy" id="terms" />
|
||||||
|
<label
|
||||||
|
htmlFor="terms"
|
||||||
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
i undertand
|
||||||
|
</span>
|
||||||
|
<Link className="ml-1 underline" href="">
|
||||||
|
the privacy policy
|
||||||
|
</Link>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{actionState.errors?.fieldErrors?.policy && (
|
||||||
|
<span className="text-sm inline-block text-red-500">
|
||||||
|
{actionState.errors?.fieldErrors?.policy}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
<Button disabled={isPending} className="mt-4 w-full" type="submit">
|
<Button disabled={isPending} className="mt-4 w-full" type="submit">
|
||||||
{isPending ? <Loader className="animate-spin" /> : "Submit"}
|
{isPending ? <Loader className="animate-spin" /> : "Submit"}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -53,7 +53,7 @@ export default function VerifyOTPForm({
|
|||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
className="bg-white dark:bg-gray-900 w-full max-w-xs rounded-lg shadow my-4"
|
className="w-full max-w-xs rounded-lg shadow my-4"
|
||||||
>
|
>
|
||||||
<div className="grid pb-4 pt-4 gap-4 px-4">
|
<div className="grid pb-4 pt-4 gap-4 px-4">
|
||||||
<div className="">
|
<div className="">
|
||||||
@ -71,7 +71,7 @@ export default function VerifyOTPForm({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
className="dark:bg-gray-800 w-full dark:text-white"
|
className="w-full"
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
|
133
components/devices-table.tsx
Normal file
133
components/devices-table.tsx
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCaption,
|
||||||
|
TableCell,
|
||||||
|
TableFooter,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import prisma from "@/lib/db";
|
||||||
|
import Pagination from "./pagination";
|
||||||
|
|
||||||
|
export async function DevicesTable({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: Promise<{
|
||||||
|
query: string;
|
||||||
|
page: number;
|
||||||
|
sortBy: string;
|
||||||
|
}>;
|
||||||
|
}) {
|
||||||
|
const query = (await searchParams)?.query || "";
|
||||||
|
const page = (await searchParams)?.page;
|
||||||
|
const sortBy = (await searchParams)?.sortBy || "asc";
|
||||||
|
const totalDevices = await prisma.device.count({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
contains: query || "",
|
||||||
|
mode: "insensitive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mac: {
|
||||||
|
contains: query || "",
|
||||||
|
mode: "insensitive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(totalDevices / 10);
|
||||||
|
const limit = 10;
|
||||||
|
const offset = (Number(page) - 1) * limit || 0;
|
||||||
|
|
||||||
|
const devices = await prisma.device.findMany({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
contains: query || "",
|
||||||
|
mode: "insensitive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mac: {
|
||||||
|
contains: query || "",
|
||||||
|
mode: "insensitive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
skip: offset,
|
||||||
|
take: limit,
|
||||||
|
orderBy: {
|
||||||
|
name: `${sortBy}` as "asc" | "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{devices.length === 0 ? (
|
||||||
|
<div className="h-[calc(100svh-400px)] flex flex-col items-center justify-center my-4">
|
||||||
|
<h3>No devices yet.</h3>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Table className="overflow-scroll">
|
||||||
|
<TableCaption>Table of all devices.</TableCaption>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Device Name</TableHead>
|
||||||
|
<TableHead>MAC Address</TableHead>
|
||||||
|
<TableHead>Status</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody className="overflow-scroll">
|
||||||
|
{devices.map((device) => (
|
||||||
|
<TableRow
|
||||||
|
|
||||||
|
key={device.id}
|
||||||
|
>
|
||||||
|
<TableCell className="font-medium">{device.name}</TableCell>
|
||||||
|
<TableCell className="font-medium">{device.mac}</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
Hi
|
||||||
|
{/* <UserVerifyDialog user={user} /> */}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={2}>
|
||||||
|
{query.length > 0 && (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Showing {devices.length} locations for "{query}
|
||||||
|
"
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-muted-foreground">
|
||||||
|
{totalDevices} devices
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
</Table>
|
||||||
|
<Pagination totalPages={totalPages} currentPage={page} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -52,7 +52,7 @@ export default function Filter({
|
|||||||
value={selectedOption}
|
value={selectedOption}
|
||||||
onValueChange={(val) => handleFilterChange(val)}
|
onValueChange={(val) => handleFilterChange(val)}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-auto bg-white">
|
<SelectTrigger className="w-auto">
|
||||||
<SelectValue>
|
<SelectValue>
|
||||||
{options.find((option) => option.value === selectedOption)?.label}
|
{options.find((option) => option.value === selectedOption)?.label}
|
||||||
</SelectValue>
|
</SelectValue>
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { cn } from "@/lib/utils";
|
import { Loader } from "lucide-react";
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useRef, useTransition } from "react";
|
import { useRef, useTransition } from "react";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { Loader } from "lucide-react";
|
|
||||||
|
|
||||||
export default function Search({ disabled }: { disabled?: boolean }) {
|
export default function Search({ disabled }: { disabled?: boolean }) {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
@ -32,12 +31,12 @@ export default function Search({ disabled }: { disabled?: boolean }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2 items-center justify-end">
|
<div className="flex w-full gap-2 items-center justify-between">
|
||||||
<Input
|
<Input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
className={cn("bg-white")}
|
|
||||||
type="text"
|
type="text"
|
||||||
|
className="w-full"
|
||||||
name="search"
|
name="search"
|
||||||
id="search"
|
id="search"
|
||||||
defaultValue={searchQuery ? searchQuery : ""}
|
defaultValue={searchQuery ? searchQuery : ""}
|
||||||
|
@ -30,10 +30,18 @@ const data = {
|
|||||||
title: "Devices",
|
title: "Devices",
|
||||||
url: "/devices",
|
url: "/devices",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Parental Controls",
|
||||||
|
url: "/parental-controls",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Payments",
|
title: "Payments",
|
||||||
url: "/payments",
|
url: "/payments",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Agreements",
|
||||||
|
url: "/agreements",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -65,7 +73,7 @@ export function AppSidebar({
|
|||||||
return (
|
return (
|
||||||
<Sidebar {...props} className="z-50">
|
<Sidebar {...props} className="z-50">
|
||||||
<SidebarHeader>
|
<SidebarHeader>
|
||||||
<h4 className="bg-gray-200 p-2 rounded shadow text-center uppercase dark:bg-gray-800">
|
<h4 className="p-2 rounded shadow text-center uppercase ">
|
||||||
Sar Link Portal
|
Sar Link Portal
|
||||||
</h4>
|
</h4>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
|
@ -9,8 +9,8 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import prisma from "@/lib/db";
|
import prisma from "@/lib/db";
|
||||||
import { Badge } from "./ui/badge";
|
|
||||||
import Pagination from "./pagination";
|
import Pagination from "./pagination";
|
||||||
|
import { Badge } from "./ui/badge";
|
||||||
import { UserVerifyDialog } from "./user/user-verify-dialog";
|
import { UserVerifyDialog } from "./user/user-verify-dialog";
|
||||||
|
|
||||||
export async function UsersTable({
|
export async function UsersTable({
|
||||||
@ -139,7 +139,7 @@ export async function UsersTable({
|
|||||||
<TableBody className="overflow-scroll">
|
<TableBody className="overflow-scroll">
|
||||||
{users.map((user) => (
|
{users.map((user) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
className={`${user.verified && "title-bg"}`}
|
className={`${user.verified && "title-bg dark:bg-black"}`}
|
||||||
key={user.id}
|
key={user.id}
|
||||||
>
|
>
|
||||||
<TableCell className="font-medium">{user.name}</TableCell>
|
<TableCell className="font-medium">{user.name}</TableCell>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user