389 lines
12 KiB
TypeScript
389 lines
12 KiB
TypeScript
import { useEffect, useState, useRef } from "react";
|
|
import SettingNav from "./SettingNav";
|
|
import { useParams, useLocation, useNavigate } from "react-router-dom";
|
|
import api from "../../config/api";
|
|
import UserProfile from './UserProfile';
|
|
import Language from "./Language";
|
|
import CreateAndInvite, { ActionType, FormDataType } from './CreateAndInvite'
|
|
import AsideSection from "./AsideSection";
|
|
import Signature from "./Signature/Signature";
|
|
import { Bounce, toast } from "react-toastify";
|
|
|
|
// Define the shape of state object
|
|
interface Account {
|
|
id: number;
|
|
isDefault: boolean;
|
|
name: string;
|
|
}
|
|
|
|
interface UserData {
|
|
id: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
email: string;
|
|
language: string;
|
|
account: Account;
|
|
}
|
|
|
|
interface SubSection {
|
|
id: string;
|
|
label: string;
|
|
component?: React.ReactNode;
|
|
}
|
|
|
|
interface AccountOption {
|
|
userId: number;
|
|
userEmail: string;
|
|
accountId: number;
|
|
accountName: string;
|
|
role: string;
|
|
status: string;
|
|
}
|
|
|
|
const Setting = () => {
|
|
const { page1 } = useParams<{ page1: string }>();
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
|
|
// Refs for subsections within each main tab
|
|
const subSectionRefs = useRef<{ [key: string]: HTMLDivElement | null }>({});
|
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
|
|
const [activeTab, setActiveTab] = useState("userProfile");
|
|
const [activeSubSection, setActiveSubSection] = useState<string>("");
|
|
const [user, setUser] = useState<UserData>({
|
|
id: "",
|
|
firstName: "",
|
|
lastName: "",
|
|
email: "",
|
|
language: "",
|
|
account: {
|
|
id: 0,
|
|
isDefault: false,
|
|
name: "",
|
|
},
|
|
});
|
|
|
|
const [createAndInviteLoader, setCreateAndInviteLoader] = useState(false);
|
|
|
|
// Dropdown data states
|
|
const [accountOptions, setAccountOptions] = useState<AccountOption[]>([]);
|
|
const [accountOptionsLoading, setAccountOptionsLoading] = useState(false);
|
|
|
|
// Define subsections for each main tab
|
|
const getSubSections = (tabId: string): SubSection[] => {
|
|
switch (tabId) {
|
|
case "userProfile":
|
|
return [
|
|
{ id: "profile-info", label: "Profile Information" },
|
|
{ id: "signature", label: "Signature" },
|
|
];
|
|
default:
|
|
return [];
|
|
}
|
|
};
|
|
|
|
// Function to scroll to specific subsection
|
|
const scrollToSubSection = (subSectionId: string) => {
|
|
const targetRef = subSectionRefs.current[subSectionId];
|
|
|
|
if (targetRef && scrollContainerRef?.current) {
|
|
const container = scrollContainerRef.current;
|
|
const target = targetRef;
|
|
|
|
// Calculate the position relative to the scroll container
|
|
const containerRect = container.getBoundingClientRect();
|
|
const targetRect = target.getBoundingClientRect();
|
|
|
|
// Calculate scroll position (accounting for some padding)
|
|
const scrollTop = container.scrollTop + targetRect.top - containerRect.top - 20;
|
|
|
|
// Smooth scroll to the target
|
|
container.scrollTo({
|
|
top: scrollTop,
|
|
behavior: 'smooth'
|
|
});
|
|
}
|
|
};
|
|
|
|
// Handle subsection click with hash update
|
|
const handleSubSectionClick = (subSectionId: string) => {
|
|
setActiveSubSection(subSectionId);
|
|
// Update URL hash without causing page reload
|
|
navigate(`${location.pathname}#${subSectionId}`, { replace: true });
|
|
scrollToSubSection(subSectionId);
|
|
};
|
|
|
|
// Handle main tab click (independent of aside navigation)
|
|
const handleMainTabClick = (tabId: string) => {
|
|
setActiveTab(tabId);
|
|
// Reset subsection when switching main tabs
|
|
const subSections = getSubSections(tabId);
|
|
if (subSections.length > 0) {
|
|
const firstSubSectionId = subSections[0].id;
|
|
setActiveSubSection(firstSubSectionId);
|
|
// Update URL hash
|
|
navigate(`${location.pathname}#${firstSubSectionId}`, { replace: true });
|
|
} else {
|
|
setActiveSubSection("");
|
|
// Clear hash
|
|
navigate(location.pathname, { replace: true });
|
|
}
|
|
};
|
|
|
|
// Handle browser back/forward and direct URL navigation
|
|
useEffect(() => {
|
|
const hash = location.hash.replace('#', '');
|
|
if (hash) {
|
|
// Check if the hash corresponds to a valid subsection
|
|
const allSubSections = getSubSections(activeTab);
|
|
const validSubSection = allSubSections.find(section => section.id === hash);
|
|
|
|
if (validSubSection) {
|
|
setActiveSubSection(hash);
|
|
// Delay scroll to ensure component is rendered
|
|
setTimeout(() => {
|
|
scrollToSubSection(hash);
|
|
}, 100);
|
|
}
|
|
}
|
|
}, [location.hash, activeTab]);
|
|
|
|
// Fetch account list for dropdown
|
|
const getAccountList = async () => {
|
|
setAccountOptionsLoading(true);
|
|
try {
|
|
const res = await api.get('/api/user/accounts');
|
|
if (res?.data) {
|
|
setAccountOptions(res.data);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching account options:', error);
|
|
} finally {
|
|
setAccountOptionsLoading(false);
|
|
}
|
|
};
|
|
|
|
// Render subsection content based on active tab and subsection
|
|
const renderSubSectionContent = (subSectionId: string) => {
|
|
switch (activeTab) {
|
|
case "userProfile":
|
|
switch (subSectionId) {
|
|
case "profile-info":
|
|
return <UserProfile userData={user} />;
|
|
case "signature":
|
|
return (
|
|
<Signature />
|
|
);
|
|
default:
|
|
return;
|
|
}
|
|
default:
|
|
return (
|
|
<div className="bg-white p-6 rounded-lg shadow-sm">
|
|
<h3 className="text-lg font-semibold mb-4">Coming Soon</h3>
|
|
<p>This section is under development.</p>
|
|
</div>
|
|
);
|
|
}
|
|
};
|
|
|
|
const renderContent = () => {
|
|
const currentSubSections = getSubSections(activeTab);
|
|
|
|
if (currentSubSections.length === 0) {
|
|
// If no subsections, render the main content directly
|
|
switch (activeTab) {
|
|
case "userProfile":
|
|
return <UserProfile userData={user} />;
|
|
case "language":
|
|
return <Language userData={user} />;
|
|
case "create and invite":
|
|
return (
|
|
<CreateAndInvite
|
|
onSubmit={handleSubmit}
|
|
createAndInviteLoader={createAndInviteLoader}
|
|
accountOptions={accountOptions}
|
|
accountOptionsLoading={accountOptionsLoading}
|
|
/>
|
|
);
|
|
default:
|
|
return <div className="bg-white p-6 rounded-lg shadow-sm">Coming Soon...</div>;
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
{currentSubSections.map((subSection) => (
|
|
<div
|
|
key={subSection.id}
|
|
ref={(el) => (subSectionRefs.current[subSection.id] = el)}
|
|
id={subSection.id} // This creates the anchor for hash navigation
|
|
className="scroll-mt-6"
|
|
>
|
|
{renderSubSectionContent(subSection.id)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const tabs = [
|
|
{ id: "userProfile", label: "User Profile", type: "settings" },
|
|
{ id: "create and invite", label: "Create and invite", type: "settings" },
|
|
{ id: "language", label: "Language", type: "settings" },
|
|
{ id: "contactAdd", label: "Add Contact", type: "contact" },
|
|
{ id: "contactRemove", label: "Remove Contact", type: "contact" },
|
|
{ id: "contactShare", label: "Share Contact", type: "contact" },
|
|
];
|
|
|
|
const getUserDetails = async () => {
|
|
try {
|
|
let res = await api.get(`/api/user/details`)
|
|
console.log("getuser", res)
|
|
setUser(res?.data)
|
|
} catch (error) {
|
|
console.error("Error fetching user details:", error);
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
getUserDetails()
|
|
// Fetch account list when component mounts
|
|
getAccountList()
|
|
}, [])
|
|
|
|
// Set initial subsection when component mounts or activeTab changes
|
|
useEffect(() => {
|
|
const subSections = getSubSections(activeTab);
|
|
const hash = location.hash.replace('#', '');
|
|
|
|
if (subSections.length > 0) {
|
|
// If there's a hash and it's valid for current tab, use it
|
|
if (hash && subSections.find(s => s.id === hash)) {
|
|
setActiveSubSection(hash);
|
|
}
|
|
// If no valid hash or no current activeSubSection, use first subsection
|
|
else if (!activeSubSection || !subSections.find(s => s.id === activeSubSection)) {
|
|
const firstSubSectionId = subSections[0].id;
|
|
setActiveSubSection(firstSubSectionId);
|
|
// Update URL hash if no hash exists
|
|
if (!hash) {
|
|
navigate(`${location.pathname}#${firstSubSectionId}`, { replace: true });
|
|
}
|
|
}
|
|
}
|
|
}, [activeTab]);
|
|
|
|
// Auto-scroll to subsection when activeSubSection changes
|
|
useEffect(() => {
|
|
if (activeSubSection) {
|
|
setTimeout(() => {
|
|
scrollToSubSection(activeSubSection);
|
|
}, 100);
|
|
}
|
|
}, [activeSubSection]);
|
|
|
|
const handleSubmit = async (data: FormDataType, action: ActionType) => {
|
|
setCreateAndInviteLoader(true)
|
|
try {
|
|
let res;
|
|
let toastMesaage ;
|
|
if (action === 'create') {
|
|
console.log('Creating account:', data);
|
|
const payloadAccount = {
|
|
name: data.accountName
|
|
}
|
|
res = await api.post(`/api/user/accounts`, payloadAccount)
|
|
console.log("resAccountName", res)
|
|
if (res.status === 201) {
|
|
toastMesaage = `${action.toUpperCase()} Created Successfull...!`
|
|
}
|
|
} else if (action === 'invite') {
|
|
console.log('Inviting user:', data);
|
|
const payloadInvite = {
|
|
email: data.email,
|
|
role: data.role
|
|
}
|
|
res = await api.post(`/api/accounts/${data.i_accountName}/invite`, payloadInvite)
|
|
console.log("InviteNAme",res)
|
|
toastMesaage = "Invite Successfull...!"
|
|
}
|
|
|
|
if (res?.data) {
|
|
toast.success(toastMesaage, {
|
|
position: "top-center",
|
|
autoClose: 1500,
|
|
hideProgressBar: false,
|
|
closeOnClick: false,
|
|
pauseOnHover: true,
|
|
draggable: true,
|
|
progress: undefined,
|
|
theme: "light",
|
|
transition: Bounce,
|
|
});
|
|
|
|
// Refetch account list after successful submission
|
|
await getAccountList();
|
|
}
|
|
} catch (err) {
|
|
console.error('Submission error:', err);
|
|
} finally {
|
|
setCreateAndInviteLoader(false)
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<SettingNav />
|
|
<div className="bg-[#f9fafa] h-[calc(90vh-20px)] flex flex-col mt-2">
|
|
{/* Main Navigation Tabs */}
|
|
<div className="sticky top-0 z-10 bg-white border-b border-gray-200 px-8 pt-5">
|
|
<div className="flex gap-6">
|
|
{tabs.map((tab) =>
|
|
tab.type === page1 && (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => handleMainTabClick(tab.id)}
|
|
className={`cursor-pointer relative pb-3 text-sm font-medium transition-all duration-200
|
|
${activeTab === tab.id
|
|
? "text-blue-600 after:absolute after:bottom-0 after:left-0 after:right-0 after:h-0.5 after:bg-blue-600"
|
|
: "text-gray-600 hover:text-blue-500"
|
|
}`}
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
)
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content with Aside Navigation */}
|
|
<div className="flex mt-3 px-8 flex-1 min-h-0">
|
|
{/* Fixed Aside Navigation - Shows subsections of active tab */}
|
|
{activeTab === 'userProfile' &&
|
|
<section className="flex-shrink-0 mr-6 w-50 relative">
|
|
<div className="sticky top-6 z-20">
|
|
<AsideSection
|
|
activeSubSection={activeSubSection}
|
|
onSubSectionClick={handleSubSectionClick}
|
|
subSections={getSubSections(activeTab)}
|
|
mainTabTitle={tabs.find(t => t.id === activeTab)?.label || ""}
|
|
/>
|
|
</div>
|
|
</section>
|
|
}
|
|
{/* Scrollable Main Content */}
|
|
<div
|
|
ref={scrollContainerRef}
|
|
className="flex-1 min-w-0 overflow-y-auto no-scrollbar"
|
|
>
|
|
{renderContent()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default Setting; |