feat(reset-password): implement reset password functionality for admins and customers
- Added new ResetPasswordModalContent component for displaying generated passwords. - Integrated reset password functionality in the AdminsTable and CustomersTable components, allowing users to reset passwords for admins and customers. - Created useResetPasswordModalPresenter hook to manage password visibility and clipboard copying. - Updated API services and hooks to support reset password requests for both admins and customers. - Enhanced tooltip functionality for reset password actions in the tables.
This commit is contained in:
@@ -600,6 +600,57 @@ export function PencilSquareIcon(props: IconProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export function KeyIcon(props: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M15.5 11.5C18.5376 11.5 21 9.03757 21 6C21 2.96243 18.5376 0.5 15.5 0.5C12.4624 0.5 10 2.96243 10 6C10 6.79363 10.168 7.5481 10.4705 8.22988L0.5 18.2003V23.5H5.79972L7.40195 21.8978L7.40123 19.7032L9.59639 19.7039L9.59707 17.5093L11.792 17.5093L15.2701 14.0295C15.9519 14.332 16.7064 14.5 17.5 14.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.6"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<circle cx="16.75" cy="5" r="1.25" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function CopyIcon(props: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<rect
|
||||
x="8"
|
||||
y="8"
|
||||
width="13"
|
||||
height="13"
|
||||
rx="2.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.6"
|
||||
/>
|
||||
<path
|
||||
d="M16 8V5.5C16 4.39543 15.1046 3.5 14 3.5H5.5C4.39543 3.5 3.5 4.39543 3.5 5.5V14C3.5 15.1046 4.39543 16 5.5 16H8"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.6"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function UploadIcon(props: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { PencilSquareIcon, TrashIcon, UserSettingIcon } from "@/assets/icons";
|
||||
import {
|
||||
KeyIcon,
|
||||
PencilSquareIcon,
|
||||
TrashIcon,
|
||||
UserSettingIcon,
|
||||
} from "@/assets/icons";
|
||||
import EmptyListWrapper from "@/components/HOC/EmptyListWrapper";
|
||||
import ConfirmationModal from "@/components/ui/ConfirmationModal";
|
||||
import IsVisible from "@/components/ui/IsVisible";
|
||||
@@ -8,6 +13,7 @@ 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 ResetPasswordModalContent from "@/components/ui/ResetPasswordModalContent";
|
||||
import Status from "@/components/ui/Status";
|
||||
import {
|
||||
Table,
|
||||
@@ -56,6 +62,11 @@ const AdminsTable = () => {
|
||||
navigateToEditAdmin,
|
||||
openDeleteModalForUser,
|
||||
openChangeStatusModalForUser,
|
||||
isResetPasswordModalOpen,
|
||||
closeResetPasswordModal,
|
||||
openResetPasswordModalForUser,
|
||||
generatedPassword,
|
||||
isResettingPassword,
|
||||
} = useAdminsPresenter();
|
||||
|
||||
return (
|
||||
@@ -153,6 +164,23 @@ const AdminsTable = () => {
|
||||
<span className="sr-only">Change Admin Status</span>
|
||||
<UserSettingIcon />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-tooltip-id="reset-password"
|
||||
data-tooltip-content="Reset password"
|
||||
data-tooltip-place="top"
|
||||
className="hover:text-primary"
|
||||
data-cy={`admins-row-reset-password-${item?.id ?? index}`}
|
||||
onClick={() => openResetPasswordModalForUser(item)}
|
||||
>
|
||||
<Tooltip
|
||||
{..._TooltipDefaultParams({
|
||||
id: "reset-password",
|
||||
})}
|
||||
/>
|
||||
<span className="sr-only">Reset Admin Password</span>
|
||||
<KeyIcon />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-tooltip-id="edit"
|
||||
@@ -190,6 +218,20 @@ const AdminsTable = () => {
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
isOpen={isResetPasswordModalOpen}
|
||||
onClose={closeResetPasswordModal}
|
||||
showButtons={false}
|
||||
>
|
||||
<ResetPasswordModalContent
|
||||
subjectName={currentUser?.full_name}
|
||||
subjectLabel="admin"
|
||||
password={generatedPassword}
|
||||
isLoading={isResettingPassword}
|
||||
isOpen={isResetPasswordModalOpen}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
{isModalOpen && (
|
||||
<ConfirmationModal
|
||||
isOpen={isModalOpen}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
useChangeAdminStatus,
|
||||
useDeleteAdmin,
|
||||
useGetAdminListQuery,
|
||||
useResetAdminPassword,
|
||||
} from "@/hooks/queries";
|
||||
import { IUser, TChangeAdminStatusCredentials } from "@/lib/api";
|
||||
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||
@@ -27,6 +28,11 @@ export const useAdminsPresenter = () => {
|
||||
const [isChangeStatusModalOpen, setIsChangeStatusModalOpen] = useState(false);
|
||||
const [currentUser, setCurrentUser] = useState<IUser | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isResetPasswordModalOpen, setIsResetPasswordModalOpen] =
|
||||
useState(false);
|
||||
const [generatedPassword, setGeneratedPassword] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [status, setStatus] = useState<string>(
|
||||
currentUser?.status ?? AdminStatus.ACTIVE,
|
||||
);
|
||||
@@ -115,6 +121,26 @@ export const useAdminsPresenter = () => {
|
||||
setIsChangeStatusModalOpen(true);
|
||||
}, []);
|
||||
|
||||
const { mutate: resetAdminPassword, isPending: isResettingPassword } =
|
||||
useResetAdminPassword({
|
||||
successCallback: (password) => setGeneratedPassword(password),
|
||||
});
|
||||
|
||||
const openResetPasswordModalForUser = useCallback(
|
||||
(user: IUser) => {
|
||||
setCurrentUser(user);
|
||||
setGeneratedPassword(null);
|
||||
setIsResetPasswordModalOpen(true);
|
||||
resetAdminPassword(user.id);
|
||||
},
|
||||
[resetAdminPassword],
|
||||
);
|
||||
|
||||
const closeResetPasswordModal = useCallback(() => {
|
||||
setIsResetPasswordModalOpen(false);
|
||||
setGeneratedPassword(null);
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const reduced =
|
||||
typeof window !== "undefined" &&
|
||||
@@ -237,5 +263,10 @@ export const useAdminsPresenter = () => {
|
||||
navigateToEditAdmin,
|
||||
openDeleteModalForUser,
|
||||
openChangeStatusModalForUser,
|
||||
isResetPasswordModalOpen,
|
||||
closeResetPasswordModal,
|
||||
openResetPasswordModalForUser,
|
||||
generatedPassword,
|
||||
isResettingPassword,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
"use client";
|
||||
import { ExclamationIcon, PencilSquareIcon, TrashIcon } from "@/assets/icons";
|
||||
import {
|
||||
ExclamationIcon,
|
||||
KeyIcon,
|
||||
PencilSquareIcon,
|
||||
TrashIcon,
|
||||
} from "@/assets/icons";
|
||||
import { Building } from "@/components/Layouts/sidebar/icons";
|
||||
import EmptyListWrapper from "@/components/HOC/EmptyListWrapper";
|
||||
import ConfirmationModal from "@/components/ui/ConfirmationModal";
|
||||
@@ -8,6 +13,7 @@ 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 ResetPasswordModalContent from "@/components/ui/ResetPasswordModalContent";
|
||||
import Status from "@/components/ui/Status";
|
||||
import {
|
||||
Table,
|
||||
@@ -58,6 +64,11 @@ const CustomersTable = () => {
|
||||
dataTbodyRef,
|
||||
pagination,
|
||||
handlePaginationChange,
|
||||
isResetPasswordModalOpen,
|
||||
closeResetPasswordModal,
|
||||
openResetPasswordModalForCustomer,
|
||||
generatedPassword,
|
||||
isResettingPassword,
|
||||
} = useCustomerListPresenter();
|
||||
|
||||
return (
|
||||
@@ -215,6 +226,26 @@ const CustomersTable = () => {
|
||||
<span className="sr-only">Assign To Company </span>
|
||||
<Building />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-tooltip-id="reset-password"
|
||||
data-tooltip-content="Reset password"
|
||||
data-tooltip-place="top"
|
||||
className="hover:text-primary"
|
||||
onClick={() =>
|
||||
openResetPasswordModalForCustomer(customer)
|
||||
}
|
||||
>
|
||||
<Tooltip
|
||||
{..._TooltipDefaultParams({
|
||||
id: "reset-password",
|
||||
})}
|
||||
/>
|
||||
<span className="sr-only">
|
||||
Reset Customer Password
|
||||
</span>
|
||||
<KeyIcon />
|
||||
</button>
|
||||
<button
|
||||
data-tooltip-id="feedback"
|
||||
data-tooltip-content="Feedback"
|
||||
@@ -269,6 +300,19 @@ const CustomersTable = () => {
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={isResetPasswordModalOpen}
|
||||
onClose={closeResetPasswordModal}
|
||||
showButtons={false}
|
||||
>
|
||||
<ResetPasswordModalContent
|
||||
subjectName={currentCustomer?.full_name}
|
||||
subjectLabel="customer"
|
||||
password={generatedPassword}
|
||||
isLoading={isResettingPassword}
|
||||
isOpen={isResetPasswordModalOpen}
|
||||
/>
|
||||
</Modal>
|
||||
{isModalOpen && (
|
||||
<ConfirmationModal
|
||||
isOpen={isModalOpen}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
useCompanyFullList,
|
||||
useCustomersQuery,
|
||||
useDeleteCustomerQuery,
|
||||
useResetCustomerPassword,
|
||||
} from "@/hooks/queries";
|
||||
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
||||
@@ -27,6 +28,11 @@ const useCustomerListPresenter = () => {
|
||||
);
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isResetPasswordModalOpen, setIsResetPasswordModalOpen] =
|
||||
useState(false);
|
||||
const [generatedPassword, setGeneratedPassword] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const pathName = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const [params, setParams] = useState<Record<string, any>>({
|
||||
@@ -90,6 +96,26 @@ const useCustomerListPresenter = () => {
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: resetCustomerPassword, isPending: isResettingPassword } =
|
||||
useResetCustomerPassword({
|
||||
successCallback: (password) => setGeneratedPassword(password),
|
||||
});
|
||||
|
||||
const openResetPasswordModalForCustomer = useCallback(
|
||||
(customer: TCustomer) => {
|
||||
setCurrentCustomer(customer);
|
||||
setGeneratedPassword(null);
|
||||
setIsResetPasswordModalOpen(true);
|
||||
resetCustomerPassword(customer.id);
|
||||
},
|
||||
[resetCustomerPassword],
|
||||
);
|
||||
|
||||
const closeResetPasswordModal = useCallback(() => {
|
||||
setIsResetPasswordModalOpen(false);
|
||||
setGeneratedPassword(null);
|
||||
}, []);
|
||||
|
||||
const search = (formData: any) => {
|
||||
const newParams = buildFirstPageParams({ ...params, ...formData });
|
||||
const cleanedParams = deleteEmptyKeys(newParams);
|
||||
@@ -154,6 +180,11 @@ const useCustomerListPresenter = () => {
|
||||
dataTbodyRef,
|
||||
pagination,
|
||||
handlePaginationChange,
|
||||
isResetPasswordModalOpen,
|
||||
closeResetPasswordModal,
|
||||
openResetPasswordModalForCustomer,
|
||||
generatedPassword,
|
||||
isResettingPassword,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -119,6 +119,8 @@ const parseCpvCodesFromSearchParams = (
|
||||
|
||||
const buildTenderFilterFormValues = (params: Record<string, any>) => ({
|
||||
...params,
|
||||
documents_scraped:
|
||||
params.documents_scraped === true || params.documents_scraped === "true",
|
||||
cpv_codes: formatCpvCodesForForm(params.cpv_codes),
|
||||
created_at_range: buildRangeFormValue(
|
||||
params,
|
||||
@@ -157,6 +159,7 @@ const TENDER_FILTER_FORM_DEFAULTS: Record<string, any> = {
|
||||
main_classification: "",
|
||||
classifications: "",
|
||||
cpv_codes: "",
|
||||
documents_scraped: false,
|
||||
created_at_range: [],
|
||||
tender_deadline_range: [],
|
||||
publication_date_range: [],
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
"use client";
|
||||
|
||||
import { CheckIcon, CopyIcon, EyeIcon, KeyIcon } from "@/assets/icons";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { FC } from "react";
|
||||
import IsVisible from "./IsVisible";
|
||||
import { useResetPasswordModalPresenter } from "./useResetPasswordModalPresenter";
|
||||
|
||||
interface IProps {
|
||||
subjectName?: string | null;
|
||||
subjectLabel?: string;
|
||||
password: string | null;
|
||||
isLoading: boolean;
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
const ResetPasswordModalContent: FC<IProps> = ({
|
||||
subjectName,
|
||||
subjectLabel = "user",
|
||||
password,
|
||||
isLoading,
|
||||
isOpen,
|
||||
}) => {
|
||||
const {
|
||||
isCopied,
|
||||
isPasswordVisible,
|
||||
togglePasswordVisibility,
|
||||
copyPasswordToClipboard,
|
||||
} = useResetPasswordModalPresenter({ isOpen, password });
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full max-w-md sm:min-w-[24rem]"
|
||||
data-cy="reset-password-modal-content"
|
||||
>
|
||||
<div className="relative mb-5 flex flex-col items-center text-center">
|
||||
<div className="pointer-events-none absolute -top-6 h-24 w-24 rounded-full bg-primary/25 blur-3xl" />
|
||||
<div className="relative flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-br from-primary/15 via-primary/10 to-primary/5 ring-1 ring-inset ring-primary/20 shadow-inner">
|
||||
<KeyIcon className="h-7 w-7 text-primary" />
|
||||
</div>
|
||||
<h3 className="mt-4 text-lg font-semibold tracking-tight text-dark dark:text-white">
|
||||
Reset Password
|
||||
</h3>
|
||||
<p className="mt-1 max-w-xs text-sm leading-relaxed text-dark-5 dark:text-dark-6">
|
||||
<IsVisible condition={!!subjectName}>
|
||||
A new password has been generated for{" "}
|
||||
<span className="font-medium text-dark dark:text-white">
|
||||
{subjectName}
|
||||
</span>
|
||||
. Copy it now — it will not be shown again.
|
||||
</IsVisible>
|
||||
<IsVisible condition={!subjectName}>
|
||||
Generating a new password for this {subjectLabel}. Copy it now — it
|
||||
will not be shown again.
|
||||
</IsVisible>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<IsVisible condition={isLoading}>
|
||||
<div
|
||||
className="flex h-24 items-center justify-center rounded-xl bg-gray-1/60 ring-1 ring-inset ring-stroke dark:bg-dark/40 dark:ring-stroke-dark"
|
||||
data-cy="reset-password-loading"
|
||||
>
|
||||
<span className="inline-flex items-center gap-2 text-sm font-medium text-dark-5 dark:text-dark-6">
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-primary/30 border-t-primary" />
|
||||
Generating a secure password…
|
||||
</span>
|
||||
</div>
|
||||
</IsVisible>
|
||||
|
||||
<IsVisible condition={!isLoading && !!password}>
|
||||
<div
|
||||
className="group relative overflow-hidden rounded-xl bg-gradient-to-br from-gray-1 via-white to-gray-1 p-4 ring-1 ring-inset ring-stroke shadow-sm dark:from-dark/40 dark:via-dark-2 dark:to-dark/40 dark:ring-stroke-dark"
|
||||
data-cy="reset-password-result"
|
||||
>
|
||||
<p className="mb-2 text-xs font-semibold uppercase tracking-wider text-dark-5 dark:text-dark-6">
|
||||
New password
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<code
|
||||
className="flex-1 select-all rounded-lg bg-white px-3 py-2.5 font-mono text-sm font-semibold tracking-wide text-dark shadow-inner ring-1 ring-inset ring-stroke break-all dark:bg-dark-3 dark:text-white dark:ring-stroke-dark"
|
||||
data-cy="reset-password-value"
|
||||
>
|
||||
{isPasswordVisible
|
||||
? password
|
||||
: "•".repeat(Math.max(password?.length ?? 0, 8))}
|
||||
</code>
|
||||
<button
|
||||
type="button"
|
||||
onClick={togglePasswordVisibility}
|
||||
className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-white text-dark-5 ring-1 ring-inset ring-stroke transition hover:-translate-y-0.5 hover:text-primary dark:bg-dark-3 dark:text-dark-6 dark:ring-stroke-dark dark:hover:text-primary"
|
||||
data-cy="reset-password-toggle-visibility"
|
||||
aria-label={
|
||||
isPasswordVisible ? "Hide password" : "Show password"
|
||||
}
|
||||
>
|
||||
<EyeIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={copyPasswordToClipboard}
|
||||
disabled={isCopied}
|
||||
className={cn(
|
||||
"mt-4 inline-flex w-full items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-semibold transition-all duration-200 hover:-translate-y-0.5 hover:brightness-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-dark-2",
|
||||
isCopied
|
||||
? "bg-gradient-to-br from-green to-green-dark text-white shadow-[0_6px_18px_-4px_rgba(34,173,92,0.55)] focus-visible:ring-green/40"
|
||||
: "bg-gradient-to-br from-primary to-[#4C46C9] text-white shadow-[0_6px_18px_-4px_rgba(87,80,241,0.55)] focus-visible:ring-primary/40",
|
||||
)}
|
||||
data-cy="reset-password-copy-button"
|
||||
>
|
||||
<IsVisible condition={isCopied}>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
<span>Copied</span>
|
||||
</IsVisible>
|
||||
<IsVisible condition={!isCopied}>
|
||||
<CopyIcon className="h-4 w-4" />
|
||||
<span>Copy to clipboard</span>
|
||||
</IsVisible>
|
||||
</button>
|
||||
</div>
|
||||
</IsVisible>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPasswordModalContent;
|
||||
@@ -0,0 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
const COPY_FEEDBACK_TIMEOUT_MS = 2000;
|
||||
|
||||
export const useResetPasswordModalPresenter = ({
|
||||
isOpen,
|
||||
password,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
password: string | null;
|
||||
}) => {
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setIsCopied(false);
|
||||
setIsPasswordVisible(false);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const togglePasswordVisibility = useCallback(() => {
|
||||
setIsPasswordVisible((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const copyPasswordToClipboard = useCallback(async () => {
|
||||
if (!password) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(password);
|
||||
setIsCopied(true);
|
||||
toast.success("Password copied to clipboard");
|
||||
setTimeout(() => setIsCopied(false), COPY_FEEDBACK_TIMEOUT_MS);
|
||||
} catch {
|
||||
toast.error("Failed to copy password");
|
||||
}
|
||||
}, [password]);
|
||||
|
||||
return {
|
||||
isCopied,
|
||||
isPasswordVisible,
|
||||
togglePasswordVisibility,
|
||||
copyPasswordToClipboard,
|
||||
};
|
||||
};
|
||||
@@ -13,42 +13,42 @@ const variantPalettes: Record<string, Palette> = {
|
||||
from: "#F23030",
|
||||
to: "#B00B0B",
|
||||
shadow:
|
||||
"0 10px 30px -10px rgba(242,48,48,0.55), inset 0 1px 0 rgba(255,255,255,0.18)",
|
||||
"0 0 24px -4px rgba(242,48,48,0.55), inset 0 1px 0 rgba(255,255,255,0.18)",
|
||||
border: "rgba(255,255,255,0.14)",
|
||||
},
|
||||
info: {
|
||||
from: "#5750F1",
|
||||
to: "#3B36B3",
|
||||
shadow:
|
||||
"0 10px 30px -10px rgba(87,80,241,0.55), inset 0 1px 0 rgba(255,255,255,0.2)",
|
||||
"0 0 24px -4px rgba(87,80,241,0.55), inset 0 1px 0 rgba(255,255,255,0.2)",
|
||||
border: "rgba(255,255,255,0.16)",
|
||||
},
|
||||
success: {
|
||||
from: "#22AD5C",
|
||||
to: "#147F3D",
|
||||
shadow:
|
||||
"0 10px 30px -10px rgba(34,173,92,0.55), inset 0 1px 0 rgba(255,255,255,0.2)",
|
||||
"0 0 24px -4px rgba(34,173,92,0.55), inset 0 1px 0 rgba(255,255,255,0.2)",
|
||||
border: "rgba(255,255,255,0.16)",
|
||||
},
|
||||
warning: {
|
||||
from: "#FBB040",
|
||||
to: "#D97706",
|
||||
shadow:
|
||||
"0 10px 30px -10px rgba(217,119,6,0.55), inset 0 1px 0 rgba(255,255,255,0.2)",
|
||||
"0 0 24px -4px rgba(217,119,6,0.55), inset 0 1px 0 rgba(255,255,255,0.2)",
|
||||
border: "rgba(255,255,255,0.16)",
|
||||
},
|
||||
dark: {
|
||||
from: "#1F2937",
|
||||
to: "#0B1220",
|
||||
shadow:
|
||||
"0 10px 30px -10px rgba(0,0,0,0.65), inset 0 1px 0 rgba(255,255,255,0.08)",
|
||||
"0 0 24px -4px rgba(0,0,0,0.65), inset 0 1px 0 rgba(255,255,255,0.08)",
|
||||
border: "rgba(255,255,255,0.08)",
|
||||
},
|
||||
light: {
|
||||
from: "#FFFFFF",
|
||||
to: "#F1F5F9",
|
||||
shadow:
|
||||
"0 10px 30px -10px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.6)",
|
||||
"0 0 24px -4px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.6)",
|
||||
border: "rgba(15,23,42,0.06)",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -129,6 +129,25 @@ export const useUpdateCustomer = (id: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useResetCustomerPassword = ({
|
||||
successCallback,
|
||||
}: {
|
||||
successCallback?: (password: string) => void;
|
||||
} = {}) => {
|
||||
const mutationKey = useMemo(
|
||||
() => [API_ENDPOINTS.CUSTOMERS.READ_ALL, "reset-password"],
|
||||
[],
|
||||
);
|
||||
return useMutation({
|
||||
mutationKey,
|
||||
mutationFn: customersService.resetCustomerPassword,
|
||||
onSuccess: (response) => {
|
||||
toast.success(response.message);
|
||||
successCallback?.(response.data.password);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteCustomerQuery = (successCallback?: () => void) => {
|
||||
const queryClient = useQueryClient();
|
||||
const mutationKey = useMemo(
|
||||
|
||||
@@ -135,6 +135,25 @@ export const useGetUsersQuery = (params?: Record<string, any>) => {
|
||||
queryFn: () => userService.adminsList(params),
|
||||
});
|
||||
};
|
||||
export const useResetAdminPassword = ({
|
||||
successCallback,
|
||||
}: {
|
||||
successCallback?: (password: string) => void;
|
||||
} = {}) => {
|
||||
const mutationKey = useMemo(
|
||||
() => [API_ENDPOINTS.USER.ADMINS, "reset-password"],
|
||||
[],
|
||||
);
|
||||
return useMutation({
|
||||
mutationKey,
|
||||
mutationFn: userService.resetAdminPassword,
|
||||
onSuccess: (response) => {
|
||||
toast.success(response.message);
|
||||
successCallback?.(response.data.password);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useChangeAdminStatus = ({
|
||||
id,
|
||||
successCallback,
|
||||
|
||||
@@ -13,6 +13,7 @@ export const API_ENDPOINTS = {
|
||||
ADMINS: "users",
|
||||
ADMIN_DETAILS: (id: string) => `users/${id}`,
|
||||
CHANGE_ADMIN_STATUS: (id: string) => `users/${id}/status`,
|
||||
RESET_ADMIN_PASSWORD: (id: string) => `users/${id}/reset-password`,
|
||||
},
|
||||
FILES: {
|
||||
UPLOAD: "files/upload",
|
||||
@@ -55,6 +56,7 @@ export const API_ENDPOINTS = {
|
||||
DELETE: (id: string) => `customers/${id}`,
|
||||
DETAILS: (id: string) => `customers/${id}`,
|
||||
ASSIGN_COMPANY_TO_CUSTOMER: (id: string) => `customers/${id}/companies`,
|
||||
RESET_PASSWORD: (id: string) => `customers/${id}/reset-password`,
|
||||
},
|
||||
FEEDBACK: {
|
||||
READ_ALL: "feedback",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import api from "../axios";
|
||||
import { API_ENDPOINTS } from "../endpoints";
|
||||
import { ApiResponse } from "../types";
|
||||
import { ApiResponse, TResetPasswordResponse } from "../types";
|
||||
import {
|
||||
CreateCustomerCredentials,
|
||||
CustomerListResponseSchema,
|
||||
@@ -57,6 +57,19 @@ export const customersService = {
|
||||
const response = await api.get(API_ENDPOINTS.CUSTOMERS.DETAILS(id));
|
||||
return response.data;
|
||||
},
|
||||
resetCustomerPassword: async (
|
||||
id: string,
|
||||
): Promise<ApiResponse<TResetPasswordResponse>> => {
|
||||
try {
|
||||
return (await api.post(API_ENDPOINTS.CUSTOMERS.RESET_PASSWORD(id))).data;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"ERROR caught in Customers Service Reset Password:",
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
assignCompanyToCustomer: async ({
|
||||
credentials,
|
||||
id,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
LoginResponseSchema,
|
||||
LogoutResponseSchema,
|
||||
TChangeAdminStatusCredentials,
|
||||
TResetPasswordResponse,
|
||||
} from "../types";
|
||||
|
||||
export const userService = {
|
||||
@@ -92,6 +93,16 @@ export const userService = {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
resetAdminPassword: async (
|
||||
id: string,
|
||||
): Promise<ApiResponse<TResetPasswordResponse>> => {
|
||||
try {
|
||||
return (await api.post(API_ENDPOINTS.USER.RESET_ADMIN_PASSWORD(id))).data;
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in User Service Reset Admin Password", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
changeAdminStatus: async ({
|
||||
id,
|
||||
credentials,
|
||||
|
||||
@@ -69,6 +69,9 @@ export const LogoutResponse = z.object({
|
||||
export const changeAdminStatusCredentialsSchema = z.object({
|
||||
status: z.string(),
|
||||
});
|
||||
export const ResetPasswordResponseSchema = z.object({
|
||||
password: z.string(),
|
||||
});
|
||||
export type IUser = z.infer<typeof UserSchema>;
|
||||
export type ILogoutResponse = z.infer<typeof LogoutResponse>;
|
||||
export type ILoginCredentials = z.infer<typeof LoginCredentials>;
|
||||
@@ -77,6 +80,9 @@ export type IAdminListResponse = z.infer<typeof AdminListResponse>;
|
||||
export type TChangeAdminStatusCredentials = z.infer<
|
||||
typeof changeAdminStatusCredentialsSchema
|
||||
>;
|
||||
export type TResetPasswordResponse = z.infer<
|
||||
typeof ResetPasswordResponseSchema
|
||||
>;
|
||||
export const LogoutResponseSchema = createApiResponseSchema(LogoutResponse);
|
||||
export const LoginResponseSchema = createApiResponseSchema(LoginResponse);
|
||||
export const AdminListResponseSchema =
|
||||
|
||||
Reference in New Issue
Block a user