refactor(auth, layouts): enhance UI components for improved aesthetics and usability

- Updated SignIn and Profile components with new background styles and layout adjustments for a more modern look.
- Refined button styles in GoogleSigninButton and SigninWithPassword for better user interaction feedback.
- Enhanced InputGroup and Select components with improved styling and error handling for a more consistent user experience.
- Adjusted Sidebar and Header components for better responsiveness and visual appeal.
- Implemented spotlight effect in UserInfo dropdown for a more engaging user interface.
This commit is contained in:
AmirReza Jamali
2026-04-23 20:53:23 +03:30
parent bb1af8b31c
commit f195bba03d
50 changed files with 1245 additions and 319 deletions
+3 -2
View File
@@ -4,6 +4,7 @@ import { PencilSquareIcon, TrashIcon, UserSettingIcon } from "@/assets/icons";
import EmptyListWrapper from "@/components/HOC/EmptyListWrapper";
import ConfirmationModal from "@/components/ui/ConfirmationModal";
import ListHeader from "@/components/ui/ListHeader";
import ListWrapper from "@/components/ui/ListWrapper";
import Modal from "@/components/ui/modal";
import Status from "@/components/ui/Status";
import {
@@ -50,7 +51,7 @@ const AdminsTable = () => {
} = useAdminsPresenter();
return (
<div className="flex flex-col rounded-[10px] bg-white px-7.5 pb-4 pt-7.5 shadow-1 dark:bg-gray-dark dark:shadow-card">
<ListWrapper>
<ListHeader
onFilter={() => setIsFilterModalOpen(true)}
createButtonText="Create Admin"
@@ -188,7 +189,7 @@ const AdminsTable = () => {
onCancel={() => setIsModalOpen(false)}
/>
)}
</div>
</ListWrapper>
);
};
@@ -88,7 +88,6 @@ export const useAdminsPresenter = () => {
"row",
"full name",
"email",
"username",
"status",
"actions",
+3 -2
View File
@@ -18,6 +18,7 @@ import { getPaginatedRowNumber, unixToDate } from "@/utils/shared";
import { Tooltip } from "react-tooltip";
import CmsListFilters from "./CmsListFilters";
import useCmsTablePresenter from "./useCmsTablePresenter";
import ListWrapper from "@/components/ui/ListWrapper";
const CmsTable = () => {
const {
@@ -41,7 +42,7 @@ const CmsTable = () => {
isMutating,
} = useCmsTablePresenter();
return (
<div className="flex flex-col rounded-[10px] bg-white px-7.5 pb-4 pt-7.5 shadow-1 dark:bg-gray-dark dark:shadow-card">
<ListWrapper>
<ListHeader onFilter={() => setIsFilterModalOpen(true)} />
<Table>
<TableHeader>
@@ -143,7 +144,7 @@ const CmsTable = () => {
onCancel={() => setIsModalOpen(false)}
/>
)}
</div>
</ListWrapper>
);
};
@@ -4,6 +4,7 @@ import { Switch } from "@/components/FormElements/switch";
import EmptyListWrapper from "@/components/HOC/EmptyListWrapper";
import ConfirmationModal from "@/components/ui/ConfirmationModal";
import ListHeader from "@/components/ui/ListHeader";
import ListWrapper from "@/components/ui/ListWrapper";
import Modal from "@/components/ui/modal";
import {
Table,
@@ -46,7 +47,7 @@ const CompanyCategoriesTable = () => {
} = useCompanyCategoriesPresenter();
return (
<div className="flex flex-col rounded-[10px] bg-white px-7.5 pb-4 pt-7.5 shadow-1 dark:bg-gray-dark dark:shadow-card">
<ListWrapper>
<ListHeader
onFilter={() => setIsFilterModalOpen(true)}
createButtonText="Create Category"
@@ -166,7 +167,7 @@ const CompanyCategoriesTable = () => {
onCancel={() => setIsModalOpen(false)}
/>
)}
</div>
</ListWrapper>
);
};
+6 -5
View File
@@ -7,6 +7,9 @@ import {
} from "@/assets/icons";
import EmptyListWrapper from "@/components/HOC/EmptyListWrapper";
import ConfirmationModal from "@/components/ui/ConfirmationModal";
import ListHeader from "@/components/ui/ListHeader";
import ListWrapper from "@/components/ui/ListWrapper";
import Modal from "@/components/ui/modal";
import {
Table,
TableBody,
@@ -19,10 +22,8 @@ import TableSkeleton from "@/components/ui/TableSkeleton";
import { _TooltipDefaultParams } from "@/constants/tooltip";
import { formatPhoneNumber, getPaginatedRowNumber } from "@/utils/shared";
import { Tooltip } from "react-tooltip";
import { useCompanyListPresenter } from "./useCompanyListPresenter";
import ListHeader from "@/components/ui/ListHeader";
import Modal from "@/components/ui/modal";
import CompanyListFilters from "./CompanyListFilters";
import { useCompanyListPresenter } from "./useCompanyListPresenter";
const CompaniesTable = () => {
const {
@@ -49,7 +50,7 @@ const CompaniesTable = () => {
(c): c is NonNullable<typeof c> => c != null,
);
return (
<div className="flex flex-col rounded-[10px] bg-white px-7.5 pb-4 pt-7.5 shadow-1 dark:bg-gray-dark dark:shadow-card">
<ListWrapper>
<ListHeader
onFilter={() => setIsFilterModalOpen(true)}
createButtonText="Create Company"
@@ -197,7 +198,7 @@ const CompaniesTable = () => {
onCancel={() => setIsModalOpen(false)}
/>
)}
</div>
</ListWrapper>
);
};
+3 -4
View File
@@ -2,6 +2,7 @@
import { TrashIcon } from "@/assets/icons";
import EmptyListWrapper from "@/components/HOC/EmptyListWrapper";
import ConfirmationModal from "@/components/ui/ConfirmationModal";
import ListWrapper from "@/components/ui/ListWrapper";
import {
Table,
TableBody,
@@ -19,8 +20,6 @@ import { useContactUsListPresenter } from "./useContactUsListPresenter";
const ContactUsTable = () => {
const {
columns,
pathname,
router,
isPending,
contacts,
onConfirmDelete,
@@ -30,7 +29,7 @@ const ContactUsTable = () => {
currentContact,
} = useContactUsListPresenter();
return (
<div className="flex flex-col rounded-[10px] bg-white px-7.5 pb-4 pt-7.5 shadow-1 dark:bg-gray-dark dark:shadow-card">
<ListWrapper>
<Table>
<TableHeader>
<TableRow>
@@ -113,7 +112,7 @@ const ContactUsTable = () => {
onCancel={() => setIsModalOpen(false)}
/>
)}
</div>
</ListWrapper>
);
};
+3 -2
View File
@@ -24,6 +24,7 @@ import { Tooltip } from "react-tooltip";
import AssignToCompanyModalContent from "./AssignToCompanyModalContent";
import CustomerListFilters from "./CustomerListFilters";
import useCustomerListPresenter from "./useCustomerListPresenter";
import ListWrapper from "@/components/ui/ListWrapper";
const CustomersTable = () => {
const {
@@ -57,7 +58,7 @@ const CustomersTable = () => {
} = useCustomerListPresenter();
return (
<div className="flex flex-col rounded-[10px] bg-white px-7.5 pb-4 pt-7.5 shadow-1 dark:bg-gray-dark dark:shadow-card">
<ListWrapper>
<ListHeader
onFilter={() => setIsFilterModalOpen(true)}
createButtonText="Create Customer"
@@ -251,7 +252,7 @@ const CustomersTable = () => {
onCancel={() => setIsModalOpen(false)}
/>
)}
</div>
</ListWrapper>
);
};
+11 -6
View File
@@ -4,6 +4,7 @@ import EmptyListWrapper from "@/components/HOC/EmptyListWrapper";
import ConfirmationModal from "@/components/ui/ConfirmationModal";
import IsVisible from "@/components/ui/IsVisible";
import ListHeader from "@/components/ui/ListHeader";
import ListWrapper from "@/components/ui/ListWrapper";
import Modal from "@/components/ui/modal";
import Pagination from "@/components/ui/pagination";
import Status from "@/components/ui/Status";
@@ -17,7 +18,12 @@ import {
} from "@/components/ui/table";
import TableSkeleton from "@/components/ui/TableSkeleton";
import { _TooltipDefaultParams } from "@/constants/tooltip";
import { formatPhoneNumber, getPaginatedRowNumber, unixToDate } from "@/utils/shared";
import { TInquiries } from "@/lib/api";
import {
formatPhoneNumber,
getPaginatedRowNumber,
unixToDate,
} from "@/utils/shared";
import { Tooltip } from "react-tooltip";
import ChangeStatusForm from "./ChangeStatusForm";
import InquiriesListFilters from "./InquiriesListFilters";
@@ -50,7 +56,7 @@ const InquiriesTable = () => {
} = useInquiriesListPresenter();
return (
<div className="flex flex-col rounded-[10px] bg-white px-7.5 pb-4 pt-7.5 shadow-1 dark:bg-gray-dark dark:shadow-card">
<ListWrapper>
<ListHeader
onFilter={() => setIsFilterModalOpen(true)}
hasCreate={false}
@@ -58,7 +64,7 @@ const InquiriesTable = () => {
<Table>
<TableHeader>
<TableRow className="border-none uppercase [&>th]:text-start">
{columns.map((column) => (
{columns.map((column: string) => (
<TableHead key={column} colSpan={100}>
{column}
</TableHead>
@@ -73,7 +79,7 @@ const InquiriesTable = () => {
emptyMessage="No Inquiries were found"
isLoading={isPending}
>
{inquiries?.map((inquiry, index) => (
{inquiries?.map((inquiry: TInquiries, index: number) => (
<TableRow key={inquiry?.id ?? index}>
<TableCell className="text-start" colSpan={100}>
{getPaginatedRowNumber({
@@ -184,7 +190,6 @@ const InquiriesTable = () => {
onSubmit={handleChangeStatus}
onCancel={() => setIsChangeStatusModalOpen(false)}
currentStatus={currentInquiry?.status ?? "pending"}
/>
</Modal>
{isDeleteModalOpen && (
@@ -202,7 +207,7 @@ const InquiriesTable = () => {
onCancel={() => setIsDeleteModalOpen(false)}
/>
)}
</div>
</ListWrapper>
);
};
+3 -2
View File
@@ -7,6 +7,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import ListWrapper from "@/components/ui/ListWrapper";
import { cn } from "@/lib/utils";
import dayjs from "dayjs";
import { getInvoiceTableData } from "./fetch";
@@ -16,7 +17,7 @@ export async function InvoiceTable() {
const data = await getInvoiceTableData();
return (
<div className="rounded-[10px] border border-stroke bg-white p-4 shadow-1 dark:border-dark-3 dark:bg-gray-dark dark:shadow-card sm:p-7.5">
<ListWrapper>
<Table>
<TableHeader>
<TableRow className="border-none bg-[#F7F9FC] dark:bg-dark-2 [&>th]:py-4 [&>th]:text-base [&>th]:text-dark [&>th]:dark:text-white">
@@ -83,6 +84,6 @@ export async function InvoiceTable() {
))}
</TableBody>
</Table>
</div>
</ListWrapper>
);
}
@@ -1,7 +1,6 @@
"use client";
import { EyeIcon } from "@/assets/icons";
import EmptyListWrapper from "@/components/HOC/EmptyListWrapper";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import IsVisible from "@/components/ui/IsVisible";
import Pagination from "@/components/ui/pagination";
import Status from "@/components/ui/Status";
@@ -15,11 +14,16 @@ import {
} from "@/components/ui/table";
import TableSkeleton from "@/components/ui/TableSkeleton";
import { _TooltipDefaultParams } from "@/constants/tooltip";
import { getPaginatedRowNumber, truncateString, unixToDate } from "@/utils/shared";
import {
getPaginatedRowNumber,
truncateString,
unixToDate,
} from "@/utils/shared";
import { Tooltip } from "react-tooltip";
import Boolean from "@/components/ui/Boolean";
import ListHeader from "@/components/ui/ListHeader";
import ListWrapper from "@/components/ui/ListWrapper";
import Modal from "@/components/ui/modal";
import NotificationHistoryListFilters from "./NotificationHistoryListFilters";
import useNotificationHistoryTablePresenter from "./useNotificationHistoryTablePresenter";
@@ -46,7 +50,7 @@ const NotificationHistoryTable = () => {
return (
<>
<ShowcaseSection className="flex flex-col">
<ListWrapper>
<ListHeader
createButtonText="Create notification"
onFilter={() => setIsFilterModalOpen(true)}
@@ -55,56 +59,57 @@ const NotificationHistoryTable = () => {
<TableHeader>
<TableRow>
{columns.map((column) => (
<TableHead colSpan={100} key={column}>
<TableHead key={column}>
{column}
</TableHead>
))}
</TableRow>
</TableHeader>
{isLoading && <TableSkeleton column={columns.length} />}
{isLoading && (
<TableSkeleton column={columns.length} cellColSpan={1} />
)}
<TableBody>
<EmptyListWrapper
list={notificationHistory ?? []}
columns={columns}
emptyMessage="No notifications were found"
isLoading={isLoading}
>
{(notificationHistory ?? []).map((item, index) => (
<TableRow key={item.id}>
<TableCell colSpan={100}>
<TableCell>
{getPaginatedRowNumber({
index,
page: metadata?.page,
limit: metadata?.limit,
})}
</TableCell>
<TableCell colSpan={100}>{item.title}</TableCell>
<TableCell colSpan={100}>{item.event_type}</TableCell>
<TableCell colSpan={100}>
<TableCell>{item.title}</TableCell>
<TableCell>{item.event_type}</TableCell>
<TableCell>
{unixToDate({
hasTime: true,
unix: item.created_at,
})}
</TableCell>
<TableCell colSpan={100}>
<TableCell>
{item.recipient?.full_name}
</TableCell>
<TableCell colSpan={100} className="truncate">
<TableCell className="truncate">
{truncateString(item.message, 20, 20)}
</TableCell>
<TableCell className="capitalize" colSpan={100}>
<TableCell className="capitalize">
{item.priority}
</TableCell>
<TableCell className="capitalize" colSpan={100}>
<TableCell className="capitalize">
{item.type}
</TableCell>
<TableCell className="capitalize" colSpan={100}>
<TableCell className="capitalize">
<Boolean value={item.seen} />
</TableCell>
<TableCell colSpan={100}>
<TableCell>
<Status status={item.status}>{item.status}</Status>
</TableCell>
<TableCell colSpan={100}>
<TableCell>
<button
data-tooltip-id="details"
data-tooltip-content="Details"
@@ -131,7 +136,7 @@ const NotificationHistoryTable = () => {
currentPage={metadata?.page ? metadata?.page - 1 : 0}
totalPages={metadata?.pages ?? 1}
onPageChange={(e) => {
setParams((currentParams) => ({
setParams((currentParams: Record<string, any>) => ({
...currentParams,
offset: e.selected * (metadata?.limit ?? 10),
limit: metadata?.limit ?? 10,
@@ -139,7 +144,7 @@ const NotificationHistoryTable = () => {
}}
/>
</IsVisible>
</ShowcaseSection>
</ListWrapper>
<Modal
isOpen={isFilterModalOpen}
onClose={() => setIsFilterModalOpen(false)}
@@ -1,5 +1,6 @@
import InputGroup from "@/components/FormElements/InputGroup";
import { Select } from "@/components/FormElements/select";
import { Button } from "@/components/ui-elements/button";
import { Countries } from "@/constants/countries";
import { TenderStatus } from "@/constants/enums";
import { getEnumAsArray } from "@/utils/shared";
@@ -63,24 +64,29 @@ const TenderListFilters = ({
onClear={() => setValue("country_code", undefined)}
/>
<div className="flex gap-4 col-span-2">
<button
<div className="col-span-2 flex gap-4">
<Button
type="submit"
className="mt-6 flex w-fit justify-center rounded-lg bg-primary p-[13px] font-medium capitalize text-white hover:bg-opacity-90"
>
{isMutating ? (
size="small"
shape="rounded"
className="mt-6 w-fit capitalize"
label={
isMutating ? (
<div className="h-5 w-5 animate-spin rounded-full border-b-2 border-gray-1"></div>
) : (
<span>Search</span>
)}
</button>
<button
)
}
/>
<Button
type="button"
className="mt-6 flex w-fit justify-center rounded-lg bg-error p-[13px] font-medium capitalize text-white hover:bg-opacity-90"
variant="error"
size="small"
shape="rounded"
className="mt-6 w-fit capitalize"
onClick={() => setIsFilterModalOpen(false)}
>
Close
</button>
label="Close"
/>
</div>
</form>
);
+3 -2
View File
@@ -17,6 +17,7 @@ import { ExclamationIcon, EyeIcon } from "@/assets/icons";
import IsVisible from "@/components/ui/IsVisible";
import ListHeader from "@/components/ui/ListHeader";
import ListWrapper from "@/components/ui/ListWrapper";
import Modal from "@/components/ui/modal";
import Pagination from "@/components/ui/pagination";
import TableSkeleton from "@/components/ui/TableSkeleton";
@@ -45,7 +46,7 @@ const TendersTable = () => {
} = useTenderListPresenter();
return (
<div className="flex flex-col rounded-[10px] bg-white px-7.5 pb-4 pt-7.5 shadow-1 dark:bg-gray-dark dark:shadow-card">
<ListWrapper>
<ListHeader
onFilter={() => setIsFilterModalOpen(true)}
createButtonText="Create Tender"
@@ -166,7 +167,7 @@ const TendersTable = () => {
}}
/>
</IsVisible>
</div>
</ListWrapper>
);
};
+48 -50
View File
@@ -6,6 +6,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import ListWrapper from "@/components/ui/ListWrapper";
import { compactFormat, standardFormat } from "@/lib/format-number";
import { cn } from "@/lib/utils";
import Image from "next/image";
@@ -15,58 +16,55 @@ export async function TopChannels({ className }: { className?: string }) {
const data = await getTopChannels();
return (
<div
className={cn(
"grid rounded-[10px] bg-white px-7.5 pb-4 pt-7.5 shadow-1 dark:bg-gray-dark dark:shadow-card",
className,
)}
>
<h2 className="mb-4 text-body-2xlg font-bold text-dark dark:text-white">
Top Channels
</h2>
<div className={cn("grid", className)}>
<ListWrapper>
<h2 className="mb-4 text-body-2xlg font-bold text-dark dark:text-white">
Top Channels
</h2>
<Table>
<TableHeader>
<TableRow className="border-none uppercase [&>th]:text-center">
<TableHead className="min-w-[120px] !text-left">Source</TableHead>
<TableHead>Visitors</TableHead>
<TableHead className="!text-right">Revenues</TableHead>
<TableHead>Sales</TableHead>
<TableHead>Conversion</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((channel, i) => (
<TableRow
className="text-center text-base font-medium text-dark dark:text-white"
key={channel.name + i}
>
<TableCell className="flex min-w-fit items-center gap-3">
<Image
src={channel.logo}
className="size-8 rounded-full object-cover"
width={40}
height={40}
alt={channel.name + " Logo"}
role="presentation"
/>
<div className="">{channel.name}</div>
</TableCell>
<TableCell>{compactFormat(channel.visitors)}</TableCell>
<TableCell className="!text-right text-green-light-1">
${standardFormat(channel.revenues)}
</TableCell>
<TableCell>{channel.sales}</TableCell>
<TableCell>{channel.conversion}%</TableCell>
<Table>
<TableHeader>
<TableRow className="border-none uppercase [&>th]:text-center">
<TableHead className="min-w-[120px] !text-left">Source</TableHead>
<TableHead>Visitors</TableHead>
<TableHead className="!text-right">Revenues</TableHead>
<TableHead>Sales</TableHead>
<TableHead>Conversion</TableHead>
</TableRow>
))}
</TableBody>
</Table>
</TableHeader>
<TableBody>
{data.map((channel, i) => (
<TableRow
className="text-center text-base font-medium text-dark dark:text-white"
key={channel.name + i}
>
<TableCell className="flex min-w-fit items-center gap-3">
<Image
src={channel.logo}
className="size-8 rounded-full object-cover"
width={40}
height={40}
alt={channel.name + " Logo"}
role="presentation"
/>
<div className="">{channel.name}</div>
</TableCell>
<TableCell>{compactFormat(channel.visitors)}</TableCell>
<TableCell className="!text-right text-green-light-1">
${standardFormat(channel.revenues)}
</TableCell>
<TableCell>{channel.sales}</TableCell>
<TableCell>{channel.conversion}%</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ListWrapper>
</div>
);
}
@@ -7,10 +7,11 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import ListWrapper from "@/components/ui/ListWrapper";
export function TopChannelsSkeleton() {
return (
<div className="rounded-[10px] bg-white px-7.5 pb-4 pt-7.5 shadow-1 dark:bg-gray-dark dark:shadow-card">
<ListWrapper>
<h2 className="mb-5.5 text-body-2xlg font-bold text-dark dark:text-white">
Top Channels
</h2>
@@ -36,6 +37,6 @@ export function TopChannelsSkeleton() {
))}
</TableBody>
</Table>
</div>
</ListWrapper>
);
}
+6 -7
View File
@@ -6,6 +6,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import ListWrapper from "@/components/ui/ListWrapper";
import Image from "next/image";
import { getTopProducts } from "../fetch";
@@ -13,12 +14,10 @@ export async function TopProducts() {
const data = await getTopProducts();
return (
<div className="rounded-[10px] bg-white shadow-1 dark:bg-gray-dark dark:shadow-card">
<div className="px-6 py-4 sm:px-7 sm:py-5 xl:px-8.5">
<h2 className="text-2xl font-bold text-dark dark:text-white">
Top Products
</h2>
</div>
<ListWrapper>
<h2 className="mb-4 text-2xl font-bold text-dark dark:text-white">
Top Products
</h2>
<Table>
<TableHeader>
@@ -66,6 +65,6 @@ export async function TopProducts() {
))}
</TableBody>
</Table>
</div>
</ListWrapper>
);
}
@@ -7,11 +7,12 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import ListWrapper from "@/components/ui/ListWrapper";
export function TopProductsSkeleton() {
return (
<div className="rounded-[10px] bg-white shadow-1 dark:bg-gray-dark dark:shadow-card">
<h2 className="px-4 py-6 text-2xl font-bold text-dark dark:text-white md:px-6 xl:px-9">
<ListWrapper>
<h2 className="mb-4 text-2xl font-bold text-dark dark:text-white">
Top Products
</h2>
@@ -36,6 +37,6 @@ export function TopProductsSkeleton() {
))}
</TableBody>
</Table>
</div>
</ListWrapper>
);
}