/* eslint-disable @typescript-eslint/no-explicit-any */ import React, { useCallback, useEffect, useState } from "react"; import { Formik, Form } from "formik"; import * as Yup from "yup"; import Swal from "sweetalert2"; import { updateCustomerProfile } from "../../../../Api/CustomerApi"; import HourGlassLoader from "../../../Loader/Loader"; import { getMasterList } from "../../../../Api/MastersApi"; import BottomSheetModal from "../../../Modal/BottomSheetModal"; import AddAddressModal from "../../../Modal/AddAddressModal"; import { formatDate, getFormattedAddress } from "../../../../lib/Helper"; import { createExternalApiCredentials, getExternalApiCredentials, regenerateExternalApiCredentials, deleteExternalApiCredentials, getAllOrderFlowTypesWithSelected, updateCustomerOrderFlowType, } from "../../../../Api/CustomerApi"; import { SettlementInterval, UserTypes } from "../../../../src/Constant/enums"; import { toast } from "react-toastify"; import ProfileInfoCard from "./ProfileInfoCard"; import PackageDescription from "./PackageDescription"; import BasicInformation from "./BasicInformation"; import ShortCode from "./ShortCode"; import ExteranalApiService from "./ExteranalApiService"; import SettlementService from "./SettlementService"; import PickupAddress from "./PickupAddress"; import BankDetails from "./BankDetails"; import AlternativeContactInfomation from "./AlternativeContactInfomation"; import BillingAddress from "./BillingAddress"; import BillingEmails from "./BillingEmails"; interface FormValues { first_name?: string; last_name?: string; email: string; phone?: string; company_name?: string; company_registration_number?: string; block_no?: string; road_no?: string; block_id?: string; building_no?: string; flat_no?: string; road_id?: string; building_id?: string; city_id?: string; nationality_id?: string; first_name_ar?: string; last_name_ar?: string; vat_number?: string; address_line_one?: string; address_line_two?: string; profile_picture?: File | null; package_description?: string; flat_or_office_number: string; short_code?: string; designation?: string; contact_email?: string; alt_first_name?: string; alt_first_name_ar?: string; alt_last_name?: string; alt_last_name_ar?: string; alt_designation?: string; alt_phone?: string; billing_address?: string; billing_emails?: string[]; } interface ComponentPropsType { allAccordionOpen?: boolean; setAllAccordionOpen?: any; } const ProfileEdit = ({ allAccordionOpen }: ComponentPropsType) => { const [isLoading, setIsLoading] = useState(false); const [basicInfoDisabled, setBasicInfoDisabled] = useState(true); const [altInfoDisabled, setAltInfoDisabled] = useState(true); const [packageDescriptionDisabled, setPackageDescriptionDisabled] = useState(true); const apiCredStr = localStorage.getItem("EXTERNAL_API_CREDENTIALS"); const apiCred = apiCredStr ? JSON.parse(apiCredStr) : ""; const [billingAddressDisabled, setBillingAddressDisabled] = useState(true); const [billingEmailsDisabled, setBillingEmailsDisabled] = useState(true); const handleBillingAddressDisabled = () => setBillingAddressDisabled(!billingAddressDisabled); const handleBillingEmailsDisabled = () => setBillingEmailsDisabled(!billingEmailsDisabled); const handleBasicInfoDisabled = () => setBasicInfoDisabled(!basicInfoDisabled); const handleAltInfoDisabled = () => setAltInfoDisabled(!altInfoDisabled); const handlePackageDescriptionDisabled = (isTrue?: boolean) => setPackageDescriptionDisabled(isTrue ?? !packageDescriptionDisabled); const [addAddress, setAddAddress] = useState(false); const toggle = () => setAddAddress(!addAddress); const [addBankDetails, setAddBankDetails] = useState(false); const toggleBankDetails = () => setAddBankDetails(!addBankDetails); const storedUser = localStorage.getItem("ALL_DATA"); const allData = storedUser ? JSON.parse(storedUser)?.data : null; const primaryAddressPhone = allData?.addresses?.find((addr: any) => addr.is_primary)?.phone || allData?.phone || ""; const initialValues: FormValues = { first_name: allData?.firstName || "", last_name: allData?.lastName || "", email: allData?.email || "", phone: primaryAddressPhone, company_name: allData?.companyName || "", company_registration_number: allData?.companyRegistrationNumber || "", block_no: allData?.blockDetails?.name || "", road_no: allData?.roadDetails?.name || "", building_no: allData?.buildingDetails?.name || "", flat_no: allData?.addressLineOne || "", profile_picture: null, road_id: allData?.roadId || "", block_id: allData?.blockId || "", building_id: allData?.buildingId || "", city_id: allData?.cityId || "", nationality_id: allData?.nationalityDetails?.id || "", flat_or_office_number: allData?.flatOrOfficeNumber || "", first_name_ar: allData?.firstNameAr || "", last_name_ar: allData?.lastNameAr || "", vat_number: allData?.vatNumber || "", address_line_one: allData?.addressLineOne || "", address_line_two: allData?.addressLineTwo || "", package_description: allData?.packageDescription || "", short_code: allData?.shortCode || "", designation: allData?.designation || "", contact_email: allData?.contactEmail || "", alt_first_name: allData?.altFirstName || "", alt_first_name_ar: allData?.altFirstNameAr || "", alt_last_name: allData?.altLastName || "", alt_last_name_ar: allData?.altLastNameAr || "", alt_designation: allData?.altDesignation || "", alt_phone: allData?.altPhone || "", billing_address: allData?.billingAddress || "", billing_emails: allData?.billingEmails || [], }; const validationSchema = Yup.object({ first_name: Yup.string().required("First name is required"), last_name: Yup.string().required("Last name is required"), email: Yup.string().email("Invalid email").required("Email is required"), phone: Yup.string().required("Mobile number is required"), company_name: Yup.string(), block_no: Yup.string(), road_no: Yup.string(), building_no: Yup.string(), flat_no: Yup.string(), package_description: Yup.string().max( 255, "Package description cannot exceed 255 characters" ), short_code: Yup.string() .matches( /^[A-Z0-9]{3}$/, "Short code must be exactly 3 uppercase letters or numbers" ) .optional(), }); const [nationalities, setNationalities] = useState([]); const [cities, setCities] = useState([]); const [roads, setRoads] = useState([]); const [blocks, setBlocks] = useState([]); const [buildings, setBuildings] = useState([]); const [imagePreview, setImagePreview] = useState(null); const [selectedBlock, setSelectedBlock] = useState(null); const [selectedRoad, setSelectedRoad] = useState(null); const [shortCodeDisabled, setShortCodeDisabled] = useState(true); const [shortCodeError, setShortCodeError] = useState(""); const [shortCodeSuggestions, setShortCodeSuggestions] = useState( [] ); const [externalApiCredentials, setExternalApiCredentials] = useState(null); const [externalApiLoading, setExternalApiLoading] = useState(false); const [externalApiError, setExternalApiError] = useState(""); const fetchExternalApiCredentials = async () => { setExternalApiLoading(true); setExternalApiError(""); try { const res = await getExternalApiCredentials(); if (res?.data) { setExternalApiCredentials(res.data); } else { setExternalApiCredentials(null); } } catch (err: any) { setExternalApiCredentials(null); setExternalApiError(err?.message || "Failed to fetch credentials"); } finally { setExternalApiLoading(false); } }; useEffect(() => { fetchExternalApiCredentials(); }, []); const handleCreateExternalApi = async () => { setExternalApiLoading(true); setExternalApiError(""); try { const res = await createExternalApiCredentials({ expiry_days: 90 }); if (res?.data) { setExternalApiCredentials(res.data); // Only set the credentials object toast.success(res?.message); } else { toast.error(res?.message); setExternalApiError(res?.message || "Failed to create credentials"); setExternalApiCredentials(null); // Defensive: clear credentials on error } } catch (err: any) { toast.error(err?.message); setExternalApiError(err?.message || "Failed to create credentials"); setExternalApiCredentials(null); // Defensive: clear credentials on error } finally { // fetchExternalApiCredentials(); setExternalApiLoading(false); } }; const handleRegenerateExternalApi = async () => { setExternalApiLoading(true); setExternalApiError(""); try { const res = await regenerateExternalApiCredentials({ expiry_days: 90 }); if (res?.data) { setExternalApiCredentials(res.data); // Store credentials in localStorage localStorage.setItem( "EXTERNAL_API_CREDENTIALS", JSON.stringify(res.data) ); toast.success(res?.message); } else { toast.error(res?.message); setExternalApiError(res?.message || "Failed to regenerate credentials"); } } catch (err: any) { toast.error(err?.message); setExternalApiError(err?.message || "Failed to regenerate credentials"); } finally { // fetchExternalApiCredentials(); setExternalApiLoading(false); } }; const handleDeleteExternalApi = async () => { setExternalApiLoading(true); setExternalApiError(""); try { const res = await deleteExternalApiCredentials(); toast.success(res?.message); setExternalApiCredentials(null); } catch (err: any) { toast.error(err?.message); setExternalApiError(err?.message || "Failed to delete credentials"); } finally { fetchExternalApiCredentials(); setExternalApiLoading(false); } }; // Function to generate short code suggestions const generateShortCodeSuggestions = () => { const suggestions: string[] = []; const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const numbers = "0123456789"; // Generate suggestions based on user's name if (allData?.firstName && allData?.lastName) { const firstName = allData.firstName.toUpperCase(); const lastName = allData.lastName.toUpperCase(); // First 3 letters of first name if (firstName.length >= 3) { suggestions.push(firstName.substring(0, 3)); } // First 3 letters of last name if (lastName.length >= 3) { suggestions.push(lastName.substring(0, 3)); } // First letter of first name + first 2 letters of last name if (firstName.length >= 1 && lastName.length >= 2) { suggestions.push(firstName[0] + lastName.substring(0, 2)); } // First 2 letters of first name + first letter of last name if (firstName.length >= 2 && lastName.length >= 1) { suggestions.push(firstName.substring(0, 2) + lastName[0]); } } // Generate random suggestions for (let i = 0; i < 5; i++) { let suggestion = ""; for (let j = 0; j < 3; j++) { if (Math.random() > 0.5) { suggestion += letters[Math.floor(Math.random() * letters.length)]; } else { suggestion += numbers[Math.floor(Math.random() * numbers.length)]; } } if (!suggestions.includes(suggestion)) { suggestions.push(suggestion); } } // Limit to 8 suggestions return suggestions.slice(0, 8); }; // Function to fetch roads and buildings for a block const fetchRoadsAndBuildings = useCallback( async (blockId: string) => { try { setIsLoading(true); const [roadResponse, buildingResponse] = await Promise.all([ getMasterList("road", { block_id: blockId }), getMasterList("building", { block_id: blockId, road_id: selectedRoad || "", }), ]); // Check if the response has data property const roadData = roadResponse.data?.data || roadResponse.data || []; const buildingData = buildingResponse.data?.data || buildingResponse.data || []; setRoads(roadData); setBuildings(buildingData); } catch (err) { console.error("Error loading roads and buildings", err); setRoads([]); setBuildings([]); } finally { setIsLoading(false); } }, [selectedRoad] ); useEffect(() => { const fetchMasterLists = async () => { try { setIsLoading(true); const [nat, city, block] = await Promise.all([ getMasterList("nationality"), getMasterList("city"), getMasterList("block"), ]); setNationalities(nat.data || []); setCities(city.data?.data || []); setBlocks(block.data || []); } catch (err) { console.error("Error loading masterlists", err); } finally { setIsLoading(false); } }; fetchMasterLists(); }, []); // Add effect to fetch roads and buildings when block changes useEffect(() => { if (selectedBlock) { fetchRoadsAndBuildings(selectedBlock); } else { setRoads([]); setBuildings([]); } }, [fetchRoadsAndBuildings, selectedBlock]); // Add effect to fetch buildings when road changes useEffect(() => { if (selectedBlock && selectedRoad) { fetchRoadsAndBuildings(selectedBlock); } }, [fetchRoadsAndBuildings, selectedBlock, selectedRoad]); // Add effect to update selected values from form useEffect(() => { const storedUser = localStorage.getItem("ALL_DATA"); const allData = storedUser ? JSON.parse(storedUser)?.data : null; if (allData?.blockId) { setSelectedBlock(allData.blockId); fetchRoadsAndBuildings(allData.blockId); } if (allData?.roadId) { setSelectedRoad(allData.roadId); } }, [fetchRoadsAndBuildings]); const handleShortCodeDisabled = () => { setShortCodeDisabled(!shortCodeDisabled); if (shortCodeDisabled) { // When enabling edit mode, generate suggestions const suggestions = generateShortCodeSuggestions(); setShortCodeSuggestions(suggestions); } else { // When disabling edit mode, clear suggestions setShortCodeSuggestions([]); } }; const handleSubmit = async (values: FormValues) => { // Check if the click originated from pickup-address section const pickupAddressSection = document.getElementById("pickup-address"); if (pickupAddressSection?.contains(document.activeElement)) { return; // Don't submit if click was in pickup-address section } // Validate short code if it's being updated if (!shortCodeDisabled && values.short_code) { if (values.short_code.length !== 3) { setShortCodeError("Short code must be exactly 3 characters"); return; } if (!/^[A-Z0-9]{3}$/.test(values.short_code)) { setShortCodeError( "Short code must contain only uppercase letters and numbers" ); return; } } setIsLoading(true); const formData = new FormData(); // Transform and append values to FormData for (const key in values) { const value = values[key as keyof FormValues]; if (key === "profile_picture") { // Only append profile picture if it exists and is a File if (value instanceof File && value.size > 0) { formData.append("profile_picture", value); } } else if (value !== undefined && value !== null && value !== "") { // Convert numeric fields to numbers if ( ["road_id", "building_id", "city_id", "nationality_id"].includes(key) ) { formData.append(key, String(value)); } else if (key === "phone") { const allDataLocal = JSON.parse( localStorage.getItem("ALL_DATA") || "{}" ); const addresses = allDataLocal?.data?.addresses || []; // ✅ Find primary address (if any) const primaryAddress = addresses.find((addr: any) => addr.is_primary); // ✅ Use phone from primary address if form phone empty const finalPhone = primaryAddress?.phone ?? values.phone; formData.append(key, String(finalPhone)); } else { // Ensure string values are properly trimmed formData.append(key, String(value).trim()); } } } const existingAllData = JSON.parse( localStorage.getItem("ALL_DATA") || "{}" ); // Add user_type as required by backend formData.append("user_type", existingAllData?.data?.userType?.id); try { const response = await updateCustomerProfile(formData); if (response.status) { const existingAllData = JSON.parse( localStorage.getItem("ALL_DATA") || "{}" ); const updatedAllData = { ...existingAllData, data: { ...existingAllData.data, ...response.data, }, }; if (response.data.firstName) { localStorage.setItem("USER_NAME", response.data.firstName); } localStorage.setItem("ALL_DATA", JSON.stringify(updatedAllData)); // Add short code to unavailable list if it was updated if (!shortCodeDisabled && values.short_code) { setShortCodeError(""); setShortCodeDisabled(true); } // Swal.fire({ // icon: "success", // title: "Profile Updated Successfully!", // text: "Your changes have been saved.", // customClass: { // confirmButton: "delybell-primary px-4", // cancelButton: "delybell-dark", // }, // }); if (!basicInfoDisabled) handleBasicInfoDisabled(); if (!packageDescriptionDisabled) handlePackageDescriptionDisabled(); if (!altInfoDisabled) handleAltInfoDisabled(); } else { Swal.fire({ icon: "error", title: "Error!", text: response.message || "Something went wrong. Please try again.", customClass: { confirmButton: "delybell-primary px-4", cancelButton: "delybell-dark", }, }); } } catch (error: any) { Swal.fire({ icon: "error", title: "Error!", text: error?.message || "An error occurred while updating the profile.", customClass: { confirmButton: "delybell-primary px-4", cancelButton: "delybell-dark", }, }); if (!basicInfoDisabled) handleBasicInfoDisabled(); if (!packageDescriptionDisabled) handlePackageDescriptionDisabled(); if (!altInfoDisabled) handleAltInfoDisabled(); } finally { setIsLoading(false); } }; interface SelectOption { id: string | number; name: string; value: string | number; label: string; } const getOptions = (name: string): SelectOption[] => { switch (name) { case "nationality_id": return nationalities.map((item) => ({ id: item.id, name: item.name, value: item.id, label: item.name, })); case "city_id": return cities.map((item) => ({ id: item.id, name: item.name, value: item.id, label: item.name, })); case "block_id": return blocks.map((item) => ({ id: item.id, name: item.name, value: item.id, label: item.name, })); case "road_id": if (!selectedBlock) return []; return roads.map((item) => ({ id: item.id, name: item.name || item.roadName, // Try both name and roadName value: item.id, label: item.name || item.roadName, })); case "building_id": if (!selectedBlock || !selectedRoad) return []; return buildings.map((item) => ({ id: item.id, name: item.name || item.buildingName, // Try both name and buildingName value: item.id, label: item.name || item.buildingName, })); default: return []; } }; // Handle file change const handleFileChange = async ( e: React.ChangeEvent, setFieldValue: any, values: FormValues ) => { const file = e.target.files ? e.target.files[0] : null; if (file) { // Create an object URL for the selected image const imageUrl = URL.createObjectURL(file); setImagePreview(imageUrl); setFieldValue("profile_picture", file); // Create FormData and append the file const formData = new FormData(); formData.append("profile_picture", file); // Add required fields formData.append("first_name", values.first_name || ""); formData.append("last_name", values.last_name || ""); formData.append("email", values.email || ""); formData.append("phone", values.phone || ""); // Add user_type as required by backend const existingAllData = JSON.parse( localStorage.getItem("ALL_DATA") || "{}" ); formData.append("user_type", existingAllData?.data?.userType?.id); try { setIsLoading(true); const response = await updateCustomerProfile(formData); if (response.status) { const existingAllData = JSON.parse( localStorage.getItem("ALL_DATA") || "{}" ); const updatedAllData = { ...existingAllData, data: { ...existingAllData.data, ...response.data, }, }; localStorage.setItem("ALL_DATA", JSON.stringify(updatedAllData)); Swal.fire({ icon: "success", title: "Profile Picture Updated Successfully!", text: "Your profile picture has been updated.", customClass: { confirmButton: "delybell-primary px-4", cancelButton: "delybell-dark", }, }); } else { Swal.fire({ icon: "error", title: "Error!", text: response.message || "Something went wrong. Please try again.", customClass: { confirmButton: "delybell-primary px-4", cancelButton: "delybell-dark", }, }); } } catch (error: any) { Swal.fire({ icon: "error", title: "Error!", text: error?.message || "An error occurred while updating the profile picture.", customClass: { confirmButton: "delybell-primary px-4", cancelButton: "delybell-dark", }, }); } finally { setIsLoading(false); } } }; const [profilePicError, setProfilePicError] = useState(false); // 2. Add state for order flow types const [orderFlowTypes, setOrderFlowTypes] = useState([]); const [selectedOrderFlowTypes, setSelectedOrderFlowTypes] = useState< string[] >([]); const [orderFlowTypesLoading, setOrderFlowTypesLoading] = useState(false); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [orderFlowTypesError, setOrderFlowTypesError] = useState(""); const [orderFlowTypesSuccess, setOrderFlowTypesSuccess] = useState(""); const [showApiSection, setShowApiSection] = useState(false); const [selectedAddress, setSelectedAddress] = React.useState({}); // Auto-hide order flow types success message after 2 seconds useEffect(() => { if (orderFlowTypesSuccess) { const timer = setTimeout(() => setOrderFlowTypesSuccess(""), 2000); return () => clearTimeout(timer); } }, [orderFlowTypesSuccess]); // 3. Fetch order flow types on mount const fetchOrderFlowTypes = useCallback(async () => { setOrderFlowTypesLoading(true); setOrderFlowTypesError(""); try { const res = await getAllOrderFlowTypesWithSelected(); if (res?.status && Array.isArray(res.data)) { setOrderFlowTypes(res.data); setSelectedOrderFlowTypes( res.data.filter((t: any) => t.selected).map((t: any) => String(t.id)) ); setShowApiSection( res?.data?.filter( (type: any) => type.name === "Externel API" && type?.selected )?.length > 0 ? allData?.userType?.id !== UserTypes?.NormalCustomer ? true : false : false ); } else { setOrderFlowTypes([]); setOrderFlowTypesError("Failed to load order flow types"); } } catch (err: any) { setOrderFlowTypes([]); setOrderFlowTypesError(err?.message || "Failed to load order flow types"); } finally { setOrderFlowTypesLoading(false); } }, [allData?.userType?.id]); useEffect(() => { fetchOrderFlowTypes(); }, [fetchOrderFlowTypes]); // 4. Handler for checkbox change (auto-save) const handleOrderFlowTypeChange = async (id: string) => { setOrderFlowTypesSuccess(""); setOrderFlowTypesError(""); let updated; setOrderFlowTypesLoading(true); setSelectedOrderFlowTypes((prev) => { if (prev.includes(id)) { updated = prev.filter((v) => v !== id); return updated; } else { updated = [...prev, id]; return updated; } }); try { // Wait for state update before calling API const newSelected = selectedOrderFlowTypes.includes(id) ? selectedOrderFlowTypes.filter((v) => v !== id) : [...selectedOrderFlowTypes, id]; const res = await updateCustomerOrderFlowType({ order_flow_type_ids: newSelected, }); if (res?.status) { setOrderFlowTypesSuccess("Order flow types updated successfully."); toast.success(res?.message || "Order flow types updated successfully."); // Optionally update ALL_DATA in localStorage const existingAllData = JSON.parse( localStorage.getItem("ALL_DATA") || "{}" ); const updatedAllData = { ...existingAllData, data: { ...existingAllData.data, orderFlowTypes: orderFlowTypes.filter((t) => newSelected.includes(String(t.id)) ), }, }; localStorage.setItem("ALL_DATA", JSON.stringify(updatedAllData)); } else { setOrderFlowTypesError( res?.message || "Failed to update order flow types" ); toast.error(res?.message || "Failed to update order flow types"); } } catch (err: any) { setOrderFlowTypesError( err?.message || "Failed to update order flow types" ); toast.error(err?.message || "Failed to update order flow types"); } finally { fetchOrderFlowTypes(); setOrderFlowTypesLoading(false); } }; const [openSections, setOpenSections] = useState({ packageInfo: true, basicInfo: true, altContactInfo: true, shortCode: true, pickupAddress: true, bankDetails: true, externalApi: true, orderFlowTypes: true, settlementIntervals: true, }); // Effect to update all accordions when flag changes useEffect(() => { setOpenSections({ packageInfo: allAccordionOpen, basicInfo: allAccordionOpen, altContactInfo: allAccordionOpen, shortCode: allAccordionOpen, pickupAddress: allAccordionOpen, bankDetails: allAccordionOpen, externalApi: allAccordionOpen, orderFlowTypes: allAccordionOpen, settlementIntervals: allAccordionOpen, }); }, [allAccordionOpen]); const toggleSection = (section: keyof typeof openSections) => { setOpenSections((prev: any) => ({ ...prev, [section]: !prev[section] })); }; const [showShortCodeSection, setShowShortCodeSection] = useState(true); // or false by default useEffect(() => { // Example: show only if "Short Code" order flow type is selected const shouldShow = orderFlowTypes.some( (type) => type.name === "Short Code" && type.selected ); setShowShortCodeSection(shouldShow); }, [orderFlowTypes]); return ( <> {isLoading && } initialValues={initialValues} validationSchema={validationSchema} onSubmit={handleSubmit} > {({ setFieldValue, values }) => { return (
{allData?.userType?.id === UserTypes?.InternationalCustomer && ( )}
{allData?.userType?.id !== UserTypes?.NormalCustomer && ( )} {false ? ( <> ) : ( "" )}
{/*
*/}
); }} {addAddress && ( { toggle(); window.location.reload(); }} data={selectedAddress} setData={setSelectedAddress} /> )} {/** Hidden image to detect if the profile picture URL is broken */} {/* eslint-disable-next-line @next/next/no-img-element */} Profile setProfilePicError(true)} onLoad={() => setProfilePicError(false)} /> ); }; export default ProfileEdit;