This commit is contained in:
rockyeverlast
2025-04-09 16:24:53 +05:30
24 changed files with 1040 additions and 463 deletions

4
.env
View File

@@ -7,7 +7,7 @@
# VITE_API_URL='https://ssa.betadelivery.com/testing/apia/v1'
VITE_API_URL='https://ssa.betadelivery.com/apia/v1'
VITE_USER_NAME="Admin"
# VITE_PASSWORD="71%@L%es^bUX94`J9XT*@bh,._WWM{$%^^&&"
VITE_PASSWORD="71%@L%es^bUX94`J9XT*%4&^%tUU^%Q^ffgt"
VITE_PASSWORD="71%@L%es^bUX94`J9XT*@bh,._WWM{$%^^&&"
# VITE_PASSWORD="71%@L%es^bUX94`J9XT*%4&^%tUU^%Q^ffgt"
VITE_APP_NAME=MyViteApp
VITE_IMG_TEMPLATES='https://ssa.betadelivery.com/storage/app/public/uploads/post_templates/'

View File

@@ -1,5 +1,5 @@
VITE_API_URL='https://ssa.betadelivery.com/testing/apia/'
VITE_USER_NAME="Admin"
VITE_PASSWORD="71%@L%es^bUX94`J9XT*@bh,._WWM{$%^^&&"
VITE_APP_NAME=MyViteApp
VITE_IMG_TEMPLATES='https://ssa.betadelivery.com/storage/app/public/uploads/post_templates/'
# VITE_API_URL='https://ssa.betadelivery.com/testing/apia/'
# VITE_USER_NAME="Admin"
# VITE_PASSWORD="71%@L%es^bUX94`J9XT*@bh,._WWM{$%^^&&"
# VITE_APP_NAME=MyViteApp
# VITE_IMG_TEMPLATES='https://ssa.betadelivery.com/storage/app/public/uploads/post_templates/'

View File

@@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"url": "index.html",
"revision": "0.7aakaijcn38"
"revision": "0.4sre1e6vpfo"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

View File

@@ -31,6 +31,10 @@ const ForgotPassword = () => {
} = useForm<FormValues>();
const onSubmit = handleSubmit(async (data) => {
const username = import.meta.env.VITE_USER_NAME || ""; // Replace with actual username
const password = import.meta.env.VITE_PASSWORD || ""; // Replace with actual password
const basicAuth = `${username}:${password}`; // Encode to Base64
setIsLoading(true);
try {
const res = await axios.post(
@@ -38,12 +42,24 @@ const ForgotPassword = () => {
{
mobile_number: Number(data.mobileNumber),
},
{
headers: {
Authorization: `Basic ${basicAuth}`,
"Content-Type": "application/json",
},
}
);
if (res.status === 200) {
navigate(`/forgot-password/verify?phone=${data.mobileNumber}`)
} else {
alert(res.data.message || "Something went wrong");
// alert(res.data.message || "Something went wrong");
toaster.create({
// title: error?.response?.data?.message,
title: res.data.message || "Something went wrong",
type: "error",
})
setIsLoading(false);
}
console.log("============", res);

View File

@@ -46,8 +46,7 @@ const Login = () => {
// Encode Basic Auth Credentials
const username = import.meta.env.VITE_USER_NAME || ""; // Replace with actual username
const password = import.meta.env.VITE_PASSWORD || ""; // Replace with actual password
const credentials = `${username}:${password}`; // Encode to Base64
const basicAuth = btoa(credentials); // Use btoa for Base64 encoding
const basicAuth = `${username}:${password}`; // Encode to Base64
try {
const res = await axios.post(

View File

@@ -1,85 +1,3 @@
// import { Button } from "../../../components/ui/button";
// import {
// DialogBody,
// DialogCloseTrigger,
// DialogContent,
// DialogFooter,
// DialogHeader,
// DialogRoot,
// DialogTitle,
// DialogTrigger,
// } from "../../../components/ui/dialog";
// import { Field, Input, Stack, Textarea } from "@chakra-ui/react";
// import Edit from "../../../components/ActionIcons/Edit";
// function EditDetails() {
// return (
// <DialogRoot placement="center">
// <DialogTrigger asChild>
// {/* */}
// <Button bg={"#fff"} color={"black"}><Edit /></Button>
// </DialogTrigger>
// <DialogContent
// bg={"#fff"}
// // w={{ lg: "60%", md: "230px" }}
// w={{ base: "90%", md: "400px" }}
// height={"auto"}
// p={3} // Reduced padding
// bgSize={"md"}
// >
// <DialogHeader bg="white">
// <DialogTitle alignSelf="center" color="black" fontSize="14px">
// Edit Details
// </DialogTitle>
// </DialogHeader>
// <DialogBody bg="white">
// <Stack py={3}>
// <Field.Root>
// <Field.Label color="black" pt={1} fontSize="12px">
// Questions
// </Field.Label>
// <Input
// placeholder="Questions"
// bgColor="#EEEEEE"
// color="black"
// border="none"
// pl={1}
// fontSize="12px"
// height="30px"
// />
// <Field.Label color="black" pt={1} fontSize="12px">
// Answer
// </Field.Label>
// <Textarea
// placeholder="Answer"
// bgColor="#EEEEEE"
// color="black"
// border="none"
// pl={1}
// fontSize="12px"
// height="30px"
// pt={1.5}
// />
// </Field.Root>
// </Stack>
// </DialogBody>
// <DialogFooter display="flex" justifyContent="center" pt={"2"}>
// <Button w="100%" bg="#02A0A0" color={"#fff"}>
// Save
// </Button>
// </DialogFooter>
// <DialogCloseTrigger color="black" />
// </DialogContent>
// </DialogRoot>
// );
// }
// export default EditDetails;
import { Button } from "../../../components/ui/button";
import {
DialogBody,
@@ -99,6 +17,7 @@ import { Toaster, toaster } from "../../../components/ui/toaster";
interface RowData {
id: number;
principal_type_xid: number;
question: string;
answer: string;
}
@@ -138,7 +57,8 @@ function EditDetails({ rowData, refetch }: {rowData: RowData, refetch: VoidFunct
const payload = {
id: rowData?.id,
question: faqQuestion,
answer: faqAnswer
answer: faqAnswer,
principal_type_xid: rowData?.principal_type_xid,
};
try {
@@ -161,6 +81,11 @@ function EditDetails({ rowData, refetch }: {rowData: RowData, refetch: VoidFunct
} catch (error) {
console.error("Error updating template:", error);
// alert("Failed to update template");
toaster.create({
title: "Error",
description: "Something went wrong",
type: "error",
});
}
};

View File

@@ -1,4 +1,4 @@
import { Box, HStack, Image, Text } from "@chakra-ui/react";
import { Box, HStack, Image, Spinner, Text } from "@chakra-ui/react";
import MainFrame from "../../../components/MainFrame"
import EditDetails from "./EditDetails";
// import { InputGroup } from "../../../components/ui/input-group";
@@ -19,6 +19,7 @@ import { Toaster, toaster } from "../../../components/ui/toaster";
const tableHeadRow = [
"Sr. No",
'Category',
"Question",
"Answer",
"Action",
@@ -52,7 +53,7 @@ const tableHeadRow = [
// ];
const FAQ = () => {
const { data, refetch, isLoading } = useGetFaqQuery()
const { data, refetch, isLoading, isFetching } = useGetFaqQuery()
const [localData, setLocalData] = useState<any[]>([]);
const [faqToggle] = useFaqToggleMutation()
const [deleteFaqPost] = useDeleteFaqPostMutation()
@@ -60,22 +61,23 @@ const FAQ = () => {
const [deleteModal, setDeleteModal] = useState(false)
const [selectedFaqId, setSelectedFaqId] = useState<number | null>(null);
// console.log('DATA', data?.data);
console.log('DATA', data?.data.data);
useEffect(() => {
if (data?.data) {
setLocalData(data?.data);
if (data) {
setLocalData(data?.data.data);
}
}, [data]);
const handleToggle = async (agencyId: string, currentStatus: number) => {
const newStatus = currentStatus ? 0 : 1;
const handleToggle = async (agencyId: number, currentStatus: string) => {
const newStatus = currentStatus === '1' ? '0' : '1';
setLocalData((prevData) =>
prevData.map((agency) =>
agency.id === agencyId ? { ...agency, is_active: newStatus } : agency
)
);
try {
await faqToggle({ id: agencyId, is_active: newStatus }).unwrap();
refetch()
@@ -91,8 +93,8 @@ const FAQ = () => {
const handleDeleteFaq = async (faqId: number) => {
try {
const response = await deleteFaqPost(faqId).unwrap();
if (response.success) {
const response = await deleteFaqPost({ id: faqId }).unwrap();
if (response?.status === "success") {
toaster.create({
title: "Success",
description: "FAQ deleted successfully",
@@ -112,59 +114,76 @@ const FAQ = () => {
}
};
const filteredData = localData?.filter((agency) =>
agency.question.toLowerCase().includes(searchTerm.toLowerCase())
agency.translations.some((translation: any) => {
const searchLower = searchTerm.toLowerCase();
const questionMatch = translation.question?.toLowerCase().includes(searchLower);
const answerMatch = translation.answer?.toLowerCase().includes(searchLower);
return questionMatch || answerMatch;
})
);
const managepost = filteredData?.map((agency: FaqData, index: number) => ({
'id': agency.id,
"Sr. No": index + 1,
"Question": agency.question,
"Answer": agency.answer,
console.log("filteredData", filteredData);
"Action": (
<HStack justifyContent="center">
<EditDetails rowData={{ id: agency.id, question: agency.question, answer: agency.answer }} refetch={refetch} />
<AlertDailog
isOpen={deleteModal}
AltertTiggerIcon={() => <Delete onClick={() => {
setSelectedFaqId(agency.id);
setDeleteModal(true)
}} />}
alertText="Delete FAQ"
alertIcon={<Image src={"DeleteIcon"} h={"39px"} />}
alertCaption="are you sure you want to delete ?"
onClose={() => setDeleteModal(false)}
onConfirm={() => {
// console.log("Deleting FAQ with ID:", selectedFaqId); // Correct ID
if (selectedFaqId) {
setDeleteModal(false);
handleDeleteFaq(selectedFaqId);
}
}}
/>
<Box>
<Switch
colorPalette={'teal'}
size={"xs"}
onChange={() => handleToggle(agency.id.toString(), Number(agency.is_active))}
checked={Boolean(Number(agency.is_active))}
const managepost = filteredData?.flatMap((agency: FaqData, index: number) =>
agency.translations.map((translation: any) => ({
'id': translation.id,
"Sr. No": index + 1,
"Question": translation.question,
"Answer": translation.answer,
'Category': agency.principal_type_xid === 2 ? "Job Seeker" : "Recruiter",
"Action": (
<HStack justifyContent="center">
<EditDetails rowData={{ id: agency.id, question: translation.question, answer: translation.answer, principal_type_xid: agency.principal_type_xid }} refetch={refetch} />
<AlertDailog
isOpen={deleteModal}
AltertTiggerIcon={() => <Delete onClick={() => {
setSelectedFaqId(agency.id);
setDeleteModal(true)
}} />}
alertText="Delete FAQ"
alertIcon={<Image src={"DeleteIcon"} h={"39px"} />}
alertCaption="are you sure you want to delete ?"
onClose={() => setDeleteModal(false)}
onConfirm={() => {
// console.log("Deleting FAQ with ID:", selectedFaqId); // Correct ID
if (selectedFaqId) {
setDeleteModal(false);
handleDeleteFaq(selectedFaqId);
}
}}
/>
</Box>
</HStack>
),
}));
<Box>
<Switch
colorPalette={'teal'}
size={"xs"}
onChange={() => handleToggle(Number(agency.id), agency.is_active.toString())}
checked={Boolean(Number(agency.is_active))}
key={`switch-${agency.id}`}
/>
</Box>
</HStack>
),
})))
// if (!isLoading && !data?.data) {
// return (
// <NoData />
// )
// }
if (isFetching) {
return (
<HStack
w={"100%"}
justifyContent={"center"}
mb={4}
py={0}
px={3}
>
<Spinner color={'teal'}/>
</HStack>
)
}
return (
<MainFrame>
<Box>
<HStack
@@ -187,15 +206,16 @@ const FAQ = () => {
<FaqAddModel refetch={refetch} />
</HStack>
</HStack>
{isFetching && <Spinner />}
{!isLoading && !data?.data ? (
<NoData />
) : <DataTable
<NoData message={'No data found'} text={'Please add new data'}/>
) : (<DataTable
sortableColumns={["Name"]}
tableHeadRow={tableHeadRow}
data={managepost}
paginationData={data?.data}
refetch={refetch}
/>}
/>)}
</Box>
<Toaster />
</MainFrame>

View File

@@ -21,6 +21,8 @@ const PrivacyPolicy = () => {
setIsBarLoading(isFetching)
}, [data])
console.log('Privacy Policy Data:', data?.data);
return (
<MainFrame transperant={true}>
<VStack gap={4} pb={4} pt={0}>
@@ -38,7 +40,7 @@ const PrivacyPolicy = () => {
Privacy Policy <Badge variant={'surface'} colorPalette="cyan" ms={2} size={'sm'} fontSize={'xs'} px={2}>🎓 {privacy_language?.language_name}</Badge>
</Text>
<PrivacyPolicyAddModel policyData={data?.data} refetch={refetch} />
<PrivacyPolicyAddModel policyData={{ id, content, privacy_language }} refetch={refetch} />
</HStack>
<Text
as="p"

View File

@@ -4,12 +4,10 @@ import {
DialogCloseTrigger,
DialogContent,
DialogFooter,
DialogHeader,
DialogRoot,
DialogTitle,
DialogTrigger,
} from "../../../components/ui/dialog";
import { Field, Stack, Text } from "@chakra-ui/react";
import { Box, Field, Stack, Text } from "@chakra-ui/react";
import { Button } from "../../../components/ui/button";
import { useUpdatePrivacyPolicyMutation } from "../../../Redux/Service/privacy.policy.service";
import { Controller, useForm } from "react-hook-form";
@@ -18,7 +16,6 @@ import ReactQuill from "react-quill";
function PrivacyPolicyAddModel({ policyData, refetch }: { policyData: any, refetch: VoidFunction }) {
const [isOpen, setIsOpen] = useState(false);
const [selectedId, setSelectedId] = useState(null);
const [updatePrivacyPolicy] = useUpdatePrivacyPolicyMutation()
const {
control,
@@ -35,7 +32,6 @@ function PrivacyPolicyAddModel({ policyData, refetch }: { policyData: any, refet
console.log('POLICY', policyData);
const handleEditClick = (data: any) => {
setSelectedId(data?.id)
setValue("content", data.content); // Pre-fill the content field
setValue("languageCode", data.privacy_language.language_code); // Pre-fill the language code
setIsOpen(true); // Open dialog
@@ -46,7 +42,7 @@ function PrivacyPolicyAddModel({ policyData, refetch }: { policyData: any, refet
try {
await updatePrivacyPolicy({
id: selectedId,
id: policyData.id,
content: formData.content,
language_code: formData.languageCode,
}).unwrap();
@@ -60,14 +56,14 @@ function PrivacyPolicyAddModel({ policyData, refetch }: { policyData: any, refet
return (
<DialogRoot placement="center" open={isOpen}>
{policyData?.map((item: any) => (
<Box key={policyData.id}>
<DialogTrigger asChild>
<Button
bgColor={"#EEEEEE"}
pl={3} pr={3}
size={"xs"}
color={"#000"}
onClick={() => handleEditClick(item)}
onClick={() => handleEditClick(policyData)}
>
{" "}
<FaRegEdit
@@ -79,21 +75,21 @@ function PrivacyPolicyAddModel({ policyData, refetch }: { policyData: any, refet
</Text>
</Button>
</DialogTrigger>
))}
</Box>
<DialogContent
bg={"#fff"}
// w={{ lg: "60%", md: "230px" }}
w={{ base: "90%", md: "400px" }}
minW={'600px'}
// w={{ base: "90%", md: "400px" }}
height={"auto"}
p={3} // Reduced padding
bgSize={"md"}
>
<DialogHeader bg="white">
{/* <DialogHeader bg="white">
<DialogTitle alignSelf="center" color="black" fontSize="14px">
Edit
</DialogTitle>
</DialogHeader>
</DialogHeader> */}
<DialogBody bg="white">
<Stack py={3}>

View File

@@ -1,48 +1,58 @@
import { Box, HStack, Text } from "@chakra-ui/react";
import { Badge, HStack, Spinner, Stack, Text, VStack } from "@chakra-ui/react";
import MainFrame from "../../../components/MainFrame"
import { p } from "framer-motion/client";
import TermsAndConditionsAddModel from "./TermsAndConditionsAddModel";
import { useGetTermsQuery } from "../../../Redux/Service/terms.and.condition.service";
const TermsAndConditions = () => {
const { data, refetch } = useGetTermsQuery()
const termsData = data?.data
const { data, refetch, isLoading, isFetching } = useGetTermsQuery()
return (
<MainFrame>
<Box>
<HStack
w={"100%"}
justifyContent={"space-between"}
mb={4}
py={0}
px={3}
>
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
Terms And Conditions
</Text>
<MainFrame transperant={true}>
<VStack gap={4} pb={4} pt={0}>
<Stack bg={"#fff"} w={"100%"} mt={2} p={4} borderRadius={4}><Text color={"black"} textAlign={"left"} fontWeight={"600"} >Terms and Conditions
</Text></Stack>
<HStack >
{isLoading || isFetching ? (
<Spinner />
) : (
data?.data?.map(({ id, content, terms_cond_language }) => (
<VStack
bg={"#fff"}
boxShadow={"rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"}
rounded={"lg"}
p={3}
key={id}
>
<TermsAndConditionsAddModel termsData={termsData} refetch={refetch}/>
</HStack>
</HStack>
{data?.data.map((item) => (
<Text
as={p}
fontSize={"sm"}
fontWeight={400}
color={"#1D1D1D"}
px={3} w={"85%"} mb={"15px"}
dangerouslySetInnerHTML={{ __html: item.content }}
/>
))}
<HStack w={"100%"} justifyContent={"space-between"} py={0} px={0}>
<Text as={"span"} fontSize={"sm"} fontWeight={500} color={"#000"}>
<Badge
variant={"surface"}
colorPalette="cyan"
ms={2}
size={"sm"}
fontSize={"xs"}
px={2}
>
🎓 {terms_cond_language?.language_name}
</Badge>
</Text>
<Text as={p} fontSize={"sm"} fontWeight={400} color={"#1D1D1D"} px={3} w={"85%"} >
</Text>
</Box>
{/* Pass Data to AboutUsAddModel */}
<TermsAndConditionsAddModel termsData={{ id, content, terms_cond_language }} refetch={refetch} />
</HStack>
<Text as="p" fontSize="sm" fontWeight={400} color="#1D1D1D">
{/* {content} */}
<div dangerouslySetInnerHTML={{ __html: content }} />
</Text>
</VStack>
))
)}
</VStack>
</MainFrame>
</MainFrame >
)
}

View File

@@ -16,9 +16,8 @@ import ReactQuill from "react-quill";
import { useState } from "react";
import { useUpdateTermsMutation } from "../../../Redux/Service/terms.and.condition.service";
function TermsAndConditionsAddModel({ termsData, refetch }: { termsData: any, refetch: VoidFunction}) {
function TermsAndConditionsAddModel({ termsData, refetch }: { termsData: any, refetch: VoidFunction }) {
const [isOpen, setIsOpen] = useState(false);
const [selectedId, setSelectedId] = useState(null);
const [updateTerms] = useUpdateTermsMutation()
const {
control,
@@ -33,7 +32,6 @@ function TermsAndConditionsAddModel({ termsData, refetch }: { termsData: any, re
});
const handleEditClick = (data: any) => {
setSelectedId(data?.id)
setValue("content", data.content); // Pre-fill the content field
setValue("languageCode", data.terms_cond_language.language_code); // Pre-fill the language code
setIsOpen(true); // Open dialog
@@ -44,7 +42,7 @@ function TermsAndConditionsAddModel({ termsData, refetch }: { termsData: any, re
try {
await updateTerms({
id: selectedId,
id: termsData.id,
content: formData.content,
language_code: formData.languageCode,
}).unwrap();
@@ -58,31 +56,28 @@ function TermsAndConditionsAddModel({ termsData, refetch }: { termsData: any, re
return (
<DialogRoot placement="center" open={isOpen}>
{termsData?.map((item: any) => (
<DialogTrigger asChild>
<Button
bgColor={"#EEEEEE"}
pl={3} pr={3}
size={"xs"}
color={"#000"}
onClick={() => handleEditClick(item)}
>
{" "}
<FaRegEdit
color="#000"
style={{ height: "14px", width: "14px" }}
/>{" "}
<Text color={"#000"} mt={1}>
Edit
</Text>
</Button>
</DialogTrigger>
))}
<DialogTrigger asChild>
<Button
bgColor={"#EEEEEE"}
pl={3} pr={3}
size={"xs"}
color={"#000"}
onClick={() => handleEditClick(termsData)}
>
{" "}
<FaRegEdit
color="#000"
style={{ height: "14px", width: "14px" }}
/>{" "}
<Text color={"#000"} mt={1}>
Edit
</Text>
</Button>
</DialogTrigger>
<DialogContent
bg={"#fff"}
// w={{ lg: "60%", md: "230px" }}
w={{ base: "90%", md: "400px" }}
maxW={'600px'}
// w={{ base: "90%", md: "600px" }}
height={"auto"}
p={3} // Reduced padding
bgSize={"md"}

View File

@@ -1,46 +1,246 @@
import { Field, Input, Stack } from "@chakra-ui/react";
import { DialogCloseTrigger, Field, IconButton, Input, Stack, Text } from "@chakra-ui/react";
import { Button } from "../../components/ui/button";
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../components/ui/dialog";
import EnterPassword from "./EnterPassword";
function Changepassword() {
// import EnterPassword from "./EnterPassword";
import { useForm } from "react-hook-form";
import { useEffect, useState } from "react";
import { toaster } from "../../components/ui/toaster";
import { useNewPasswordSetMutation } from "../../Redux/Service/profile.password";
import { LuEye, LuEyeOff } from "react-icons/lu";
import { InputGroup } from "../../components/ui/input-group";
type EnterPasswordProps = {
onClose: () => void;
isOpen: boolean;
};
type FormData = {
new_password: string;
confirm_password: string;
};
function Changepassword({ onClose, isOpen }: EnterPasswordProps) {
const [showOldPassword, setShowOldPassword] = useState(false);
const [showNewPassword, setShowNewPassword] = useState(false);
const [newPasswordSet] = useNewPasswordSetMutation()
const [isLoading, setIsLoading] = useState(false);
const {
register,
handleSubmit,
formState: { errors },
setError,
clearErrors,
watch,
reset,
} = useForm<FormData>({
defaultValues: {
new_password: '',
confirm_password: ''
}
});
const newPassword = watch("new_password");
const confirmPassword = watch("confirm_password");
useEffect(() => {
if (newPassword && confirmPassword && newPassword !== confirmPassword) {
setError("confirm_password", {
type: "manual",
message: "Passwords do not match"
});
} else if (confirmPassword) {
clearErrors("confirm_password");
}
}, [newPassword, confirmPassword, setError, clearErrors]);
const onSubmit = async (data: FormData) => {
if (data.new_password === '' || data.confirm_password === '') {
return
}
if (data.new_password !== data.confirm_password) {
setError("confirm_password", {
type: "manual",
message: "Passwords do not match"
});
return;
}
console.log('ERROR', errors)
console.log('Change submitted:', data);
clearErrors("confirm_password");
setIsLoading(true);
const payload = {
new_password: data.new_password,
confirm_password: data.confirm_password
}
try {
const res = await newPasswordSet(payload).unwrap()
if (res.status === 'success') {
toaster.create({
title: "Password changed Successfully",
type: "success",
});
onClose()
} else {
toaster.create({
title: res.data.message || "Invalid password",
type: "error",
});
}
reset()
} catch (error: any) {
toaster.create({
title: error.data.message || "Something went wrong",
type: "error",
});
} finally {
setIsLoading(false);
}
};
return (
<DialogRoot placement="center">
<DialogTrigger asChild>
<Button bg="#02A0A0" size={'2xs'} color={"#fff"} px={2} >
Change Password
</Button>
</DialogTrigger>
<DialogContent
bg={"#fff"}
w={{ base: '90%', md: '400px' }}
p={2}
bgSize={'md'}
<>
<DialogRoot
placement="center"
open={isOpen}
onOpenChange={(open) => !open && onClose()}
>
<DialogHeader bg="white" pt={3} pb={2} >
<DialogTitle alignSelf="center" color="black" fontSize="18px" textAlign={"center"}>CHANGE PASSWORD</DialogTitle>
</DialogHeader>
<DialogBody bg="white" pt={5}>
<Stack p={2} >
<Field.Root>
<Field.Label color="black" pt={1} fontSize="12px">New password</Field.Label>
<Input color="black" pl={1} fontSize="12px" type="password" border="1px solid grey" /></Field.Root>
<Field.Root>
<Field.Label color="black" pt={1} fontSize="12px">Confirm password</Field.Label>
<Input color="black" pl={1} fontSize="12px" type="password" border="1px solid grey" /></Field.Root>
<DialogTrigger asChild>
<Button bg="#02A0A0" size={'2xs'} color={"#fff"} px={2} >
Change Password
</Button>
</DialogTrigger>
</Stack>
</DialogBody>
<DialogFooter display="flex" justifyContent="center" mt={2} p={2}
<DialogContent
bg={"#fff"}
w={{ base: '90%', md: '400px' }}
p={2}
bgSize={'md'}
>
<EnterPassword />
</DialogFooter>
</DialogContent>
</DialogRoot >
<DialogHeader bg="white" pt={3} pb={2} >
<DialogTitle alignSelf="center" color="black" fontSize="18px" textAlign={"center"}>CHANGE PASSWORD</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit(onSubmit)}>
<DialogBody bg="white" pt={5}>
<Stack p={2} >
<Field.Root>
<Field.Label color="black" pt={1} fontSize="12px">New password</Field.Label>
<InputGroup
width={'100%'}
endElement={
<IconButton
aria-label={showOldPassword ? "Hide password" : "Show password"}
size="sm"
// variant="outline"
onClick={() => setShowOldPassword(!showOldPassword)}
_hover={{ bg: "transparent" }}
height={'fit-content'}
mr={2}
>
{showOldPassword ? <LuEye /> : <LuEyeOff />}
</IconButton>
}>
<Input
color="black"
pl={1}
fontSize="12px"
height="30px"
type={showOldPassword ? "text" : "password"}
border={errors.new_password ? "1px solid red" : "1px solid grey"}
{...register("new_password", {
required: "Password is required",
minLength: {
value: 8,
message: "Password must be at least 8 characters",
},
})}
/>
</InputGroup>
{/* <IconButton
aria-label={showPassword ? "Hide password" : "Show password"}
size="sm"
variant="ghost"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <LuEye /> : <LuEyeOff />}
</IconButton> */}
{errors.new_password && (
<Text color="red.500" fontSize="xs" mt={1}>
{errors.new_password.message}
</Text>
)}
</Field.Root>
<Field.Root>
<Field.Label color="black" pt={1} fontSize="12px">Confirm password</Field.Label>
<InputGroup
width={'100%'}
endElement={
<IconButton
aria-label={showNewPassword ? "Hide password" : "Show password"}
size="sm"
// variant="outline"
onClick={() => setShowNewPassword(!showNewPassword)}
_hover={{ bg: "transparent" }}
height={'fit-content'}
mr={2}
>
{showNewPassword ? <LuEye /> : <LuEyeOff />}
</IconButton>
}>
<Input
color="black"
pl={1}
fontSize="12px"
height="30px"
type= {showNewPassword ? "password" : "Text"}
border={errors.confirm_password ? "1px solid red" : "1px solid grey"}
{...register("confirm_password", {
required: "Please confirm your password",
validate: (value) =>
value === newPassword || "Passwords do not match",
})}
/>
</InputGroup>
{errors.confirm_password && (
<Text color="red.500" fontSize="xs" mt={1}>
{errors.confirm_password.message}
</Text>
)}
</Field.Root>
</Stack>
</DialogBody>
<DialogFooter display="flex" justifyContent="center" mt={2} p={2}
>
{/* <EnterPassword /> */}
<Button
loading={isLoading}
mt={6}
w="100%"
bg="#02A0A0"
color="white"
type="submit"
>
Submit
</Button>
</DialogFooter>
</form>
<DialogCloseTrigger color="black" />
</DialogContent>
</DialogRoot >
</>
)
}

View File

@@ -1,11 +1,87 @@
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../components/ui/dialog"
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle } from "../../components/ui/dialog"
import { Box, Input, Stack, Text } from "@chakra-ui/react"
import { Button } from "../../components/ui/button";
import { useState } from "react";
import { BiSolidEdit } from "react-icons/bi";
import { useEffect, useState } from "react";
// import { BiSolidEdit } from "react-icons/bi";
import { Toaster, toaster } from "../../components/ui/toaster";
import { useGetProfileQuery, useResendOtpMutation, useVerifyOTPMutation } from "../../Redux/Service/profile.password";
import { useForm } from "react-hook-form";
import Changepassword from "./ChangePassword";
function EnterOTP() {
const [otp, setOtp] = useState<string[]>(["", "", "", ""]);
type EnterOTPProps = {
onClose: () => void;
isOpen: boolean;
};
type FormData = {
otp: string[];
};
function EnterOTP({ onClose, isOpen }: EnterOTPProps) {
const [isLoading, setIsLoading] = useState(false);
const [timer, setTimer] = useState(60);
const [isResendDisabled, setIsResendDisabled] = useState(true);
const [verifyOTP] = useVerifyOTPMutation()
const [otpSuccess, setOtpSuccess] = useState(false);
const [resendOtp] = useResendOtpMutation()
const { data } = useGetProfileQuery()
const id = data?.data.id
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm<FormData>({
defaultValues: {
otp: ["", "", "", ""] // Initialize with empty strings for each digit
}
});
useEffect(() => {
let interval: NodeJS.Timeout;
if (timer > 0 && isResendDisabled) {
interval = setInterval(() => {
setTimer((prevTimer) => prevTimer - 1);
}, 1000);
} else if (timer === 0) {
setIsResendDisabled(false);
}
return () => clearInterval(interval);
}, [timer, isResendDisabled]);
const handleResendOTP = async () => {
setIsResendDisabled(true);
setTimer(60); // Reset timer to 1 minute
try {
const res = await resendOtp({ mobile_number: Number(id) }).unwrap()
if (res.status === 200) {
toaster.create({
title: "OTP resent successfully",
type: "success",
});
} else {
toaster.create({
title: res.data.message || "Failed to resend OTP",
type: "error",
});
setIsResendDisabled(false); // Enable button if failed
}
} catch (error: any) {
toaster.create({
title: error.response?.data?.message || "Failed to resend OTP",
type: "error",
});
setIsResendDisabled(false); // Enable button if error
}
};
// Handle change for OTP inputs
const handleChange = (e: React.ChangeEvent<HTMLInputElement>, index: number): void => {
@@ -15,94 +91,174 @@ function EnterOTP() {
if (/[^0-9]/.test(value)) return;
// Update the OTP state with the new value
const newOtp = [...otp];
newOtp[index] = value;
setOtp(newOtp);
// const newOtp = [...otp];
// newOtp[index] = value;
// setOtp(newOtp);
// Move focus to the next input automatically
if (value && index < otp.length - 1) {
if (value && index < 3) {
const nextInput = document.getElementById(`otp-input-${index + 1}`) as HTMLInputElement;
if (nextInput) nextInput.focus();
}
// Move focus to the next input automatically
// if (value && index < otp.length - 1) {
// const nextInput = document.getElementById(`otp-input-${index + 1}`) as HTMLInputElement;
// if (nextInput) nextInput.focus();
// }
if (value === "" && index > 0) {
const prevInput = document.getElementById(`otp-input-${index - 1}`) as HTMLInputElement;
if (prevInput) prevInput.focus();
}
};
const onSubmit = async (data: FormData) => {
console.log('ERROR', errors)
if (data.otp.length !== 4) {
toaster.create({
title: "OTP must be 4 digits",
type: "error",
});
return;
}
const fullOtp = data.otp.join('');
console.log('OTP submitted:', fullOtp);
setIsLoading(true);
try {
const res = await verifyOTP({ otp: fullOtp }).unwrap()
if (res.status === 'success') {
toaster.create({
title: "OTP Verified Successfully",
type: "success",
});
setOtpSuccess(true);
reset();
} else {
toaster.create({
title: res.data.message || "Invalid OTP",
type: "error",
});
}
reset()
} catch (error: any) {
toaster.create({
title: error.data?.message || "Something went wrong",
type: "error",
});
setOtpSuccess(false);
} finally {
setIsLoading(false);
}
};
return (
<DialogRoot placement="center">
<DialogTrigger asChild>
<Button w="100%" bg="#02A0A0" color={"#fff"}>
Save
</Button>
</DialogTrigger>
<DialogContent
bg={"#fff"}
// w={{ lg: "60%", md: "230px" }}
w={{ base: '90%', md: '400px' }}
// height={'80vh'}
// overflow={'scroll'}
p={2} // Reduced padding
bgSize={'md'}
>
<DialogHeader bg="white" pt={3}>
<DialogTitle alignSelf="center" color="black" fontSize="18px" textAlign={"center"}>ENTER OTP</DialogTitle>
</DialogHeader>
<DialogBody bg="white" pt={2}>
<Text color={"black"} textAlign={"center"}>OTP has been send to your E-mail Address</Text>
<Box display="flex" flexDirection="row" alignItems="center" justifyContent="center" p={3}>
<BiSolidEdit color="black" />
<Text color="black" textAlign="center" ml={2}>9619565889</Text>
</Box>
<Stack direction="row" justify="center" pt={2}>
{/* 4 OTP Inputs */}
{otp.map((digit, index) => (
<Input
key={index}
id={`otp-input-${index}`}
value={digit}
maxW="50px"
color={"black"}
textAlign="center"
fontSize="20px"
placeholder="0"
border="1px solid grey"
onChange={(e) => handleChange(e, index)}
maxLength={1} // Only allows 1 character per input
/>
))}
</Stack>
<Box textAlign="center">
<Text
color="#4746F4"
textDecoration="underline"
fontWeight="bold"
mt={3}
cursor="pointer"
display="inline-block"
px={2} // Adds padding for better appearance
>
Resend OTP
</Text>
</Box>
</DialogBody>
<DialogFooter display="flex" justifyContent="center" mt={2} p={2}
>
<>
<DialogRoot placement="center"
open={isOpen}
onOpenChange={(open) => !open && onClose()}>
{/* <DialogTrigger asChild>
<Button w="100%" bg="#02A0A0" color={"#fff"}>
Save
Submit
</Button>
</DialogFooter>
{/* <DialogCloseTrigger color="black" /> */}
</DialogContent>
</DialogRoot >
</DialogTrigger> */}
<DialogContent
bg={"#fff"}
// w={{ lg: "60%", md: "230px" }}
w={{ base: '90%', md: '400px' }}
// height={'80vh'}
// overflow={'scroll'}
p={2} // Reduced padding
bgSize={'md'}
>
<DialogHeader bg="white" pt={3} alignSelf="center">
<DialogTitle alignSelf="center" color="black" fontSize="18px" textAlign={"center"}>ENTER OTP</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit(onSubmit)}>
<DialogBody bg="white" pt={2}>
<Text color={"black"} textAlign={"center"}>OTP has been sent successfully</Text>
{/* <Box display="flex" flexDirection="row" alignItems="center" justifyContent="center" p={3}>
<BiSolidEdit color="black" />
<Text color="black" textAlign="center" ml={2}>9619565889</Text>
</Box> */}
<Stack direction="row" justify="center" pt={2}>
{/* 4 OTP Inputs */}
{Array.from({ length: 4 }).map((_, index) => (
<Input
key={index}
id={`otp-input-${index}`}
maxW="50px"
color="black"
textAlign="center"
fontSize="20px"
placeholder="0"
border={errors.otp?.[index] ? "1px solid red" : "1px solid grey"}
maxLength={1}
{...register(`otp.${index}`, {
required: "Digit required",
pattern: {
value: /^[0-9]$/,
message: "Must be a single digit"
},
onChange: (e) => handleChange(e, index)
})}
/>
))}
</Stack>
<Box textAlign="center" mt={4}>
{isResendDisabled ? (
<Text color="gray.500">
Resend OTP in {timer} seconds
</Text>
) : (
<Text
color="#4746F4"
textDecoration="underline"
fontWeight="bold"
cursor="pointer"
onClick={handleResendOTP}
>
Resend OTP
</Text>
)}
</Box>
</DialogBody>
<DialogFooter display="flex" justifyContent="center" mt={2} p={2}
>
<Button
loading={isLoading}
mt={6}
w="100%"
bg="#02A0A0"
color="white"
type="submit"
>
Verify OTP
</Button>
</DialogFooter>
</form>
<DialogCloseTrigger color="black" />
</DialogContent>
</DialogRoot >
{otpSuccess && (<Changepassword
onClose={() => {
setOtpSuccess(false);
onClose()
}}
isOpen={otpSuccess}
/>)}
<Toaster />
</>
)
}

View File

@@ -1,53 +1,127 @@
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../components/ui/dialog"
import { Field, Input, Stack } from "@chakra-ui/react"
import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../components/ui/dialog"
import { Field, Input, Stack, Text } from "@chakra-ui/react"
import { useForm } from "react-hook-form";
import { Button } from "../../components/ui/button";
import EnterOTP from "./EnterOTP";
import { useProfilePasswordMutation } from "../../Redux/Service/profile.password";
import { useState } from "react";
import { Toaster, toaster } from "../../components/ui/toaster";
type FormData = {
password: string;
};
function EnterPassword() {
const [profilePassword] = useProfilePasswordMutation();
const [isSuccess, setIsSuccess] = useState(false);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm<FormData>();
const onSubmit = async (data: FormData) => {
if (!data.password) {
return
}
try {
await profilePassword({ password: data.password }).unwrap();
setIsSuccess(true);
reset();
setIsDialogOpen(false);
// Handle success (e.g., show a success message or redirect)
} catch (error: any) {
// Handle error (e.g., show an error message)
toaster.create({
title: error?.data.message || "Invalid password",
type: "error",
});
console.error("Password change failed:", error.data.message);
setIsSuccess(false);
}
};
return (
<>
<DialogRoot placement="center">
<DialogTrigger asChild>
<Button w="100%" bg="#02A0A0" color={"#fff"}>
Save
</Button>
</DialogTrigger>
<DialogContent
bg={"#fff"}
// w={{ lg: "60%", md: "230px" }}
w={{ base: '90%', md: '400px' }}
// height={'80vh'}
// overflow={'scroll'}
p={2} // Reduced padding
bgSize={'md'}
<DialogRoot placement="center"
open={isDialogOpen}
onOpenChange={(details) => setIsDialogOpen(details.open)}
>
<DialogHeader bg="white" pt={3} pb={2} >
<DialogTitle alignSelf="center" color="black" fontSize="18px" textAlign={"center"}>ENTER PASSWORD</DialogTitle>
</DialogHeader>
<DialogTrigger asChild>
<Button bg="#02A0A0" size={'2xs'} color={"#fff"} px={2} >
Change Password
</Button>
<DialogBody bg="white" pt={5}>
<Stack p={2} >
<Field.Root>
<Field.Label color="black" pt={1} fontSize="12px">Password</Field.Label>
<Input color="black" pl={1} fontSize="12px" height="30px" type="password" border="1px solid grey" /></Field.Root>
</DialogTrigger>
<DialogContent
bg={"#fff"}
// w={{ lg: "60%", md: "230px" }}
w={{ base: '90%', md: '400px' }}
</Stack>
</DialogBody>
<DialogFooter display="flex" justifyContent="center" mt={2} p={2}
// height={'80vh'}
// overflow={'scroll'}
p={2} // Reduced padding
bgSize={'md'}
>
{/* <Button w="100%" bg="#02A0A0" color={"#fff"}>
<DialogHeader bg="white" pt={3} pb={2} >
<DialogTitle alignSelf="center" color="black" fontSize="18px" textAlign={"center"}>ENTER PASSWORD</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit(onSubmit)}>
<DialogBody bg="white" pt={5}>
<Stack p={2} >
<Field.Root>
<Field.Label color="black" pt={1} fontSize="12px">Password</Field.Label>
<Input
color="black"
pl={1}
fontSize="12px"
height="30px"
type="password"
border={errors.password ? "1px solid red" : "1px solid grey"}
{...register("password", {
required: "Password is required",
minLength: {
value: 8,
message: "Password must be at least 8 characters",
},
})}
/>
{errors.password && (
<Text color="red.500" fontSize="xs" mt={1}>
{errors.password.message}
</Text>
)}
</Field.Root>
</Stack>
</DialogBody>
<DialogFooter display="flex" justifyContent="center" mt={2} p={2}
>
{/* <Button w="100%" bg="#02A0A0" color={"#fff"}>
Save
</Button> */}
<EnterOTP />
</DialogFooter>
<Button type="submit" w="100%" bg="#02A0A0" color={"#fff"}>
Submit
</Button>
</DialogFooter>
{/* <DialogCloseTrigger color="black" /> */}
</DialogContent>
</DialogRoot >
<DialogCloseTrigger color="black" />
</form>
</DialogContent>
</DialogRoot >
{isSuccess && (<EnterOTP
onClose={() => {
setIsSuccess(false);
setIsDialogOpen(false);
}}
isOpen={isSuccess}
/>)
}
<Toaster />
</>
)
}

View File

@@ -3,62 +3,63 @@ import { FaCamera } from "react-icons/fa";
import EditableInput from "../../components/EditableInput";
import MainFrame from "../../components/MainFrame";
import { Field } from "../../components/ui/field";
import Changepassword from "./ChangePassword";
// import Changepassword from "./ChangePassword";
import EnterPassword from "./EnterPassword";
import { useGetProfileQuery } from "../../Redux/Service/profile.password";
const Profile = () => {
const { data } = useGetProfileQuery()
console.log('PROFILE DATA:', data?.data);
return (
<MainFrame >
<HStack alignItems={'flex-start'} justifyContent={'center'} pt={0} h={'89vh'} w={'100%'} >
<HStack alignItems={'flex-start'} justifyContent={'center'} pt={0} h={'89vh'} w={'100%'} >
<VStack w={'50%'} p={3} rounded={'lg'} mb={3}>
<VStack w={'50%'} p={3} rounded={'lg'} mb={3}>
<HStack shadow={'md'} rounded={'lg'} justifyContent={'space-between'} alignItems={'flex-end'} w={'100%'} px={3} py={3} >
<VStack w={'100%'} alignItems={'flex-start'} gap={0}>
<Box mb={2} position="relative" width="fit-content"
cursor="pointer" onClick={() => alert("Avatar clicked!")}>
<Avatar.Root size={"2xl"} style={{ display: "inline-block", width: "auto" }}>
<Avatar.Fallback />
<Avatar.Image src="https://i.pinimg.com/736x/d6/cd/0f/d6cd0ffd4634b0763d3958a7325ce26e.jpg" />
</Avatar.Root>
<Box
position="absolute"
bottom="-2px"
left={"39px"}
p="2px"
>
<FaCamera color="#ccc" size={16} />
</Box>
</Box>
<Text color={"black"} as={'span'} fontSize={'sm'} fontWeight={"bold"}>Ritesh Pandey</Text>
<Text color="black" as={'span'} fontSize={'xs'}>
Employee ID: <span>#1245679</span>
</Text>
</VStack>
<HStack shadow={'md'} rounded={'lg'} justifyContent={'space-between'} alignItems={'flex-end'} w={'100%'} px={3} py={3} >
<VStack w={'100%'} alignItems={'flex-start'} gap={0}>
<Box mb={2} position="relative" width="fit-content"
cursor="pointer" onClick={() => alert("Avatar clicked!")}>
<Avatar.Root size={"2xl"} style={{ display: "inline-block", width: "auto" }}>
<Avatar.Fallback />
<Avatar.Image src="https://i.pinimg.com/736x/d6/cd/0f/d6cd0ffd4634b0763d3958a7325ce26e.jpg" />
</Avatar.Root>
<Box
position="absolute"
bottom="-2px"
left={"39px"}
p="2px"
>
<FaCamera color="#ccc" size={16} />
</Box>
</Box>
<Text color={"black"} as={'span'} fontSize={'sm'} fontWeight={"bold"}>{`${data?.data?.first_name.charAt(0).toUpperCase()}${data?.data.first_name.slice(1)}`}
</Text>
{/* <Text color="black" as={'span'} fontSize={'xs'}>
Employee ID: <span>#1245679</span>
</Text> */}
</VStack>
<Changepassword />
</HStack>
<EnterPassword />
{/* <Changepassword /> */}
</HStack>
<VStack w={"100%"} >
<Field mt={4} label="First Name" fontSize="xs" required>
<EditableInput defaultValue="Ritesh" placeholder="Enter first name" />
<VStack w={"100%"} >
<Field mt={4} label="First Name" fontSize="xs" required>
<EditableInput value={`${data?.data.first_name}`} placeholder="Enter first name" isDisabled />
</Field>
<Field mt={4} label="Last Name" fontSize="xs" required>
<EditableInput defaultValue="Pandey" placeholder="Enter last name" />
<EditableInput value={`${data?.data.last_name}`} placeholder="Enter last name" isDisabled />
</Field>
<Field mt={4} label="Mobile Number" fontSize="xs" required>
<EditableInput defaultValue={"98234567892"} placeholder="Mobile Number" type='number' />
<EditableInput value={`${data?.data.phone_number}`} placeholder="Mobile Number" type='number' isDisabled />
</Field>
</VStack>
</VStack>
</VStack>
</HStack>

View File

@@ -2,6 +2,7 @@ import {
Box,
Center,
HStack,
IconButton,
Image,
Input,
Stack,
@@ -14,12 +15,18 @@ import { useNavigate } from "react-router-dom";
import logo from "../assets/logo.svg";
import { Button } from "../components/ui/button";
import { toaster, Toaster } from "../components/ui/toaster";
import { InputGroup } from "../components/ui/input-group";
import { LuEye, LuEyeOff } from "react-icons/lu";
const SetNewPassword = () => {
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const queryParams = new URLSearchParams(window.location.search);
const id = queryParams.get("id");
const [showOldPassword, setShowOldPassword] = useState(false);
const [showNewPassword, setShowNewPassword] = useState(false);
const handlePasswordSubmit = async () => {
// Validation
@@ -42,13 +49,13 @@ const SetNewPassword = () => {
setIsLoading(true);
try {
const res = await axios.post(`${import.meta.env.VITE_API_URL}/reset-password`, {
const res = await axios.post(`${import.meta.env.VITE_API_URL}/update-password`, {
password: password,
confirm_password: confirmPassword,
// id: id
id: Number(id)
});
if (res.status === 200) {
if (res.data.status === 'success') {
toaster.create({
title: "Password updated successfully",
type: "success",
@@ -90,26 +97,58 @@ const SetNewPassword = () => {
<Stack p={2}>
<Text color="black" pt={1} fontSize="12px">New password</Text>
<Input
color="black"
pl={1}
fontSize="12px"
type="password"
border="1px solid grey"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<InputGroup
width={'100%'}
endElement={
<IconButton
aria-label={showOldPassword ? "Hide password" : "Show password"}
size="sm"
// variant="outline"
onClick={() => setShowOldPassword(!showOldPassword)}
_hover={{ bg: "transparent" }}
height={'fit-content'}
mr={2}
>
{showOldPassword ? <LuEye /> : <LuEyeOff />}
</IconButton>
}>
<Input
color="black"
pl={1}
fontSize="12px"
type={showOldPassword ? "password" : "text"}
border="1px solid grey"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</InputGroup>
<Text color="black" pt={1} fontSize="12px">Confirm password</Text>
<Input
color="black"
pl={1}
fontSize="12px"
type="password"
border="1px solid grey"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
<InputGroup
width={'100%'}
endElement={
<IconButton
aria-label={showNewPassword ? "Hide password" : "Show password"}
size="sm"
// variant="outline"
onClick={() => setShowNewPassword(!showNewPassword)}
_hover={{ bg: "transparent" }}
height={'fit-content'}
mr={2}
>
{showNewPassword ? <LuEye /> : <LuEyeOff />}
</IconButton>
}>
<Input
color="black"
pl={1}
fontSize="12px"
type={showNewPassword ? "password" : "text"}
border="1px solid grey"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
</InputGroup>
</Stack>
<Button

View File

@@ -8,7 +8,7 @@ import {
VStack,
} from "@chakra-ui/react";
import axios from "axios";
import { useState } from "react";
import { useEffect, useState } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import logo from "../assets/logo.svg";
import { Button } from "../components/ui/button";
@@ -16,13 +16,58 @@ import { toaster, Toaster } from "../components/ui/toaster";
const VerifyOTP = () => {
const [otp, setOtp] = useState<string[]>(["", "", "", ""]);
const [isLoading, setIsLoading] = useState(false);
const [timer, setTimer] = useState(60);
const [isResendDisabled, setIsResendDisabled] = useState(true);
const navigate = useNavigate();
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const phoneNumber = queryParams.get("phone");
useEffect(() => {
let interval: NodeJS.Timeout;
const [isLoading, setIsLoading] = useState(false);
if (timer > 0 && isResendDisabled) {
interval = setInterval(() => {
setTimer((prevTimer) => prevTimer - 1);
}, 1000);
} else if (timer === 0) {
setIsResendDisabled(false);
}
return () => clearInterval(interval);
}, [timer, isResendDisabled]);
const handleResendOTP = async () => {
setIsResendDisabled(true);
setTimer(60); // Reset timer to 1 minute
try {
const res = await axios.post(`${import.meta.env.VITE_API_URL}/send-otp`, {
mobile_number: phoneNumber,
});
if (res.status === 200) {
toaster.create({
title: "OTP resent successfully",
type: "success",
});
} else {
toaster.create({
title: res.data.message || "Failed to resend OTP",
type: "error",
});
setIsResendDisabled(false); // Enable button if failed
}
} catch (error: any) {
toaster.create({
title: error.response?.data?.message || "Failed to resend OTP",
type: "error",
});
setIsResendDisabled(false); // Enable button if error
}
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>, index: number): void => {
const value = e.target.value;
@@ -51,11 +96,13 @@ const VerifyOTP = () => {
return;
}
const fullOtp = otp.join('');
setIsLoading(true);
try {
const res = await axios.post(`${import.meta.env.VITE_API_URL}/verify-otp`, {
mobile_number: phoneNumber,
otp: otp,
otp: fullOtp,
});
if (res.status === 200) {
@@ -63,8 +110,8 @@ const VerifyOTP = () => {
title: "OTP Verified Successfully",
type: "success",
});
navigate("/forgot-password/reset-password"); // Navigate to reset password page
const userid = res.data.data.id;
navigate(`/forgot-password/reset-password?id=${userid}`);
} else {
toaster.create({
title: res.data.message || "Invalid OTP",
@@ -133,6 +180,23 @@ const VerifyOTP = () => {
Resend OTP
</Text>
</Box> */}
<Box textAlign="center" mt={4}>
{isResendDisabled ? (
<Text color="gray.500">
Resend OTP in {timer} seconds
</Text>
) : (
<Text
color="#4746F4"
textDecoration="underline"
fontWeight="bold"
cursor="pointer"
onClick={handleResendOTP}
>
Resend OTP
</Text>
)}
</Box>
<Button
loading={isLoading}

View File

@@ -1,27 +1,30 @@
import { createApi } from "@reduxjs/toolkit/query/react";
import { baseQueryWithReauth } from "./apiSlice";
interface Faq {
id: number;
principal_type_xid: number;
is_active: string;
}
export interface FaqData {
id: number;
faqs_xid: number;
language_master_xid: number;
question: string;
answer: string;
is_active: string;
faq: Faq;
principal_type_xid: number;
is_active: number;
translations:{
id: string,
faqs_xid: number,
language_master_xid: number,
question: string,
answer: string,
}[]
}
interface ApiResponse {
status: string;
status_code: number;
message: string;
data: FaqData[];
data: {
current_page: number,
last_page: number,
total: number,
from: number,
data: FaqData[];
};
}
@@ -65,10 +68,11 @@ export const faqs = createApi({
}),
}),
deleteFaqPost: builder.mutation<{ success: boolean }, number>({
query: (id) => ({
url: `/faq-delete/${id}`,
method: "DELETE",
deleteFaqPost: builder.mutation<{ status: string; message: string }, { id: number }>({
query: ({ id }) => ({
url: `/faq-delete`,
method: "POST",
body: { id },
}),
}),

View File

@@ -39,7 +39,7 @@ export const aboutUs = createApi({
updateAboutUs: builder.mutation({
query: (updatedData) => ({
url: "/about-us/update", // ✅ Updated URL
method: "PUT",
method: "POST",
body: updatedData,
}),
}),

View File

@@ -0,0 +1,69 @@
import { createApi } from "@reduxjs/toolkit/query/react";
import { baseQueryWithReauth } from "./apiSlice";
interface profileGet {
data: {
id: number,
first_name: string,
last_name: string,
phone_number: string,
profile_photo: string,
}
}
export type ProfilePass = {
password: string;
};
export const profile = createApi({
reducerPath: "profile",
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
endpoints: (builder) => ({
profilePassword: builder.mutation<ProfilePass, Partial<ProfilePass>>({
query: (data) => ({
url: "/profile-password",
method: "POST",
body: data,
}),
}),
// 🔹 GET: Fetch all posts
getProfile: builder.query<profileGet, void>({
query: () => "/profile-view",
}),
verifyOTP: builder.mutation({
query: (updatedData) => ({
url: "/profile-otp",
method: "POST",
body: updatedData,
}),
}),
newPasswordSet: builder.mutation({
query: (updatedData) => ({
url: "/profile-change-password",
method: "POST",
body: updatedData,
}),
}),
resendOtp: builder.mutation({
query: ({ data }) => ({
url: `/profile-resend-otp`,
method: "POST",
body: data,
}),
}),
}),
});
export const {
useGetProfileQuery,
useProfilePasswordMutation,
useVerifyOTPMutation,
useNewPasswordSetMutation,
useResendOtpMutation
} = profile;

View File

@@ -18,6 +18,7 @@ import { termsAndCondition } from "./Service/terms.and.condition.service";
import { templateMaster } from "./Service/template.master.service";
import { jobType } from "./Service/job.type.service";
import { industryMaster } from "./Service/industry.master.service";
import { profile } from "./Service/profile.password";
export const store = configureStore({
reducer: {
@@ -39,6 +40,7 @@ export const store = configureStore({
[templateMaster.reducerPath]: templateMaster.reducer,
[jobType.reducerPath]: jobType.reducer,
[industryMaster.reducerPath]: industryMaster.reducer,
[profile.reducerPath]: profile.reducer,
auth: authReducer,
},
middleware: (getDefaultMiddleware) =>
@@ -60,6 +62,7 @@ export const store = configureStore({
templateMaster.middleware,
jobType.middleware,
industryMaster.middleware,
profile.middleware,
),
});

View File

@@ -296,6 +296,7 @@ function EditSubAdmin({ id, refetch }: { id: number, refetch: VoidFunction }) {
color="black"
checked={permission.is_active}
onChange={() => handleCheckboxToggle(permission.id)}
cursor={'pointer'}
>
<Text fontSize={12}>{label}</Text>
</Checkbox>

View File

@@ -8,6 +8,7 @@ interface InputProps {
placeholder?: string;
props?: any;
type?:string
isDisabled?: boolean;
}
const EditableInput: FC<InputProps> = ({
value,
@@ -15,6 +16,7 @@ const EditableInput: FC<InputProps> = ({
defaultValue,
placeholder,
type,
isDisabled,
...props
}) => {
return (
@@ -45,6 +47,7 @@ const EditableInput: FC<InputProps> = ({
outline={"#007F33"}
bgSize={"xs"}
ps={3}
disabled={isDisabled}
/>
</Editable.Root>
);

View File

@@ -1,12 +1,12 @@
import { Center, Heading, Stack, Text } from '@chakra-ui/react';
const NoData = () => {
const NoData = ({ message, text }: { message:string, text:string }) => {
return (
<Center h="80vh" textAlign="center">
<Stack gap={6}>
<Heading fontSize="xl">No data found</Heading>
<Text fontSize="sm">Please add new data</Text>
<Heading fontSize="xl">{message}</Heading>
<Text fontSize="sm">{text}</Text>
</Stack>
</Center>
)