Files
shuffle-cracked/frontend/src/components/PartnerSettings.jsx
T
2025-08-26 16:07:44 +02:00

409 lines
17 KiB
React

import React, { useEffect, useState, useContext } from 'react';
import { toast } from "react-toastify";
import { Context } from '../context/ContextApi.jsx';
import { getTheme } from '../theme.jsx';
import {
Button,
Typography,
Box,
IconButton,
Tooltip,
} from "@mui/material";
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import { useNavigate } from 'react-router';
import PartnerHeader from '../components/PartnerHeader.jsx';
import PartnerDetails from '../components/PartnerDetails.jsx';
const PartnerSettings = (props) => {
const {
isCloud,
userdata,
globalUrl,
serverside,
loadingPartnerData,
selectedOrganization,
setSelectedOrganization,
handleGetOrg,
partnerData,
setPartnerData,
} = props;
const [isPartner, setIsPartner] = React.useState(isCloud ? userdata?.active_org?.is_partner ? true : false : false);
const [partnerTypes, setPartnerTypes] = React.useState({});
const [isPublishing, setIsPublishing] = React.useState(false);
const [isToggling, setIsToggling] = React.useState(false);
useEffect(() => {
setIsPartner(isCloud ? userdata?.active_org?.is_partner ? true : false : false);
if(userdata?.support){
setIsPartner(true);
}
}, [userdata, isCloud]);
// Partner Types handling : Getting from org status
useEffect(() => {
const partnerTypes = {};
userdata?.org_status?.forEach(status => {
if (status.includes("_partner")) {
partnerTypes[status] = true;
}
});
setPartnerTypes(partnerTypes);
}, [selectedOrganization]);
// Partner Type Colors
const partnerTypeColors = {
"tech_partner": "#ff8544",
"distribution_partner": "#2BC07E",
"service_partner": "#a99cf9",
"integration_partner": "#fb47a0",
"channel_partner": "#4caf50",
}
const handleSendUpdateRequest = () => {
toast("Your request has been sent to the support team. They will review your request and get back to you as soon as possible.")
}
// Open partner page in new tab
const handleOpenPartnerPage = () => {
if (partnerData?.id) {
if(partnerData?.public){
window.open(`${window.location.origin}/partners/${partnerData.name.toLowerCase().replaceAll(" ", "_")}`)
}else{
window.open(`${window.location.origin}/partners/${partnerData?.id}`, "_blank");
}
} else {
toast.error("Partner ID not found");
}
}
// Update Partner Details
const handleUpdatePartnerDetails = () => {
// Validate required fields before publishing
const requiredStringFields = [
{ field: partnerData?.name, name: "Partner Name" },
{ field: partnerData?.description, name: "Description" },
{ field: selectedOrganization?.id, name: "Organization Id" },
{ field: partnerData?.image_url, name: "Logo Image" },
{ field: partnerData?.landscape_image_url, name: "Landscape Image" },
{ field: partnerData?.website_url, name: "Website URL" },
{ field: partnerData?.article_url, name: "Article URL" },
{ field: partnerData?.contact_email, name: "Contact Email" },
{ field: partnerData?.country, name: "Country" },
{ field: partnerData?.region, name: "Region" },
];
// Required array fields (multi-select dropdowns)
const requiredArrayFields = [
{ field: partnerData?.solutions, name: "Solutions" },
];
// Check if any required string fields are empty
const emptyStringFields = requiredStringFields.filter(item =>
!item.field || item.field.trim() === ""
);
// Check if any required array fields are empty
const emptyArrayFields = requiredArrayFields.filter(item =>
!item.field || !Array.isArray(item.field) || item.field.length === 0
);
// Combine all empty fields
const allEmptyFields = [...emptyStringFields, ...emptyArrayFields];
// If there are empty required fields, show error and return
if (allEmptyFields.length > 0) {
const missingFields = allEmptyFields.map(item => item.name).join(", ");
toast.error(`Please fill in all required fields: ${missingFields}`);
return;
}
// Check if at least one partner type is selected
if (Object.keys(partnerTypes).length === 0) {
toast.error("There should be at least one partner type");
return;
}
// Validate description length
if (partnerData?.description && partnerData.description.length > 1000) {
toast.error("Description should be less than or equal to 1000 characters");
return;
}
setIsPublishing(true);
const url = globalUrl + "/api/v1/partners/" + userdata?.active_org?.id;
const data = {
id: partnerData.id?.trim() || null,
name: partnerData.name?.trim(),
org_id: selectedOrganization?.id?.trim(),
description: partnerData.description?.trim(),
website_url: partnerData.website_url?.trim(),
article_url: partnerData.article_url?.trim(),
contact_email: partnerData.contact_email?.trim(),
partner_type: partnerTypes,
expertise: partnerData?.expertise || [],
services: partnerData?.services || [],
solutions: partnerData?.solutions || [],
country: partnerData?.country || "",
region: partnerData?.region || "",
image_url: partnerData?.image_url?.trim(),
landscape_image_url: partnerData?.landscape_image_url?.trim(),
public: partnerData?.public
}
fetch(url, {
mode: "cors",
method: "POST",
body: JSON.stringify(data),
credentials: "include",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then((response) => {
setIsPublishing(false);
if (response.status === 200) {
toast.success("Partner details successfully updated");
} else {
toast.error("Failed to publish partner");
}
})
.catch((error) => {
setIsPublishing(false);
toast.error("Failed to update partner details: " + error?.reason);
})
}
// Toggle Partner Publish Status
const handleTogglePublishStatus = () => {
setIsToggling(true);
const newPublishStatus = !partnerData?.public;
const url = globalUrl + "/api/v1/partners/" + userdata?.active_org?.id;
const data = {
id: partnerData.id?.trim() || null,
name: partnerData.name?.trim(),
org_id: selectedOrganization?.id?.trim(),
description: partnerData.description?.trim(),
website_url: partnerData.website_url?.trim(),
article_url: partnerData.article_url?.trim(),
contact_email: partnerData.contact_email?.trim(),
partner_type: partnerTypes,
usecases: partnerData?.usecases || [],
expertise: partnerData?.expertise || [],
services: partnerData?.services || [],
solutions: partnerData?.solutions || [],
country: partnerData?.country || "",
region: partnerData?.region || "",
image_url: partnerData?.image_url?.trim(),
landscape_image_url: partnerData?.landscape_image_url?.trim(),
public: newPublishStatus
}
fetch(url, {
mode: "cors",
method: "POST",
body: JSON.stringify(data),
credentials: "include",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then((response) => {
setIsToggling(false);
if (response.status !== 200) {
toast.error("Failed to update publish status");
}
return response.json();
})
.then((responseJson) => {
// Update local state
setPartnerData(prev => ({
...prev,
public: newPublishStatus
}));
toast.success(`Partner ${newPublishStatus ? 'published' : 'unpublished'} successfully`);
})
.catch((error) => {
setIsToggling(false);
toast.error("Failed to update publish status: " + error?.message);
})
}
const { themeMode } = useContext(Context);
const theme = getTheme(themeMode);
const navigate = useNavigate();
return (
<div style={{ width: "100%", boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: theme.palette.platformColor, borderRadius: '16px', }}>
<div style={{ height: "100%", width: "100%", overflowX: 'hidden', scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin'}} >
<div style={{ marginBottom: 40 }}>
<div style={{display:"flex", alignItems:"center", justifyContent:"space-between"}}>
<div style={{width:'100%'}}>
<Box
sx={{display:"flex", alignItems:"flex-start", gap:2, justifyContent:"flex-start"
}}>
<Typography variant='h3' sx={{ marginBottom: "15px", marginTop: 0, }}>Configuration</Typography>
{Object?.entries(partnerTypes)
?.filter(([key, value]) => key !== "distribution_partner")
?.map(([key, value]) => {
let displayText = key?.replace("_", " ")?.replace(/\b\w/g, char => char.toUpperCase());
if (displayText === "Tech Partner") {
displayText = "Technology Partner";
}
return (
<Box
key={key}
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "999px",
py: 1.2,
px: 2.5,
fontSize: "13px",
fontWeight: 500,
fontFamily: theme.typography.fontFamily,
color: "#fff",
backgroundColor: "transparent",
border: `1.5px solid ${
partnerTypeColors[key]
}`,
transition: "all 0.2s ease",
textAlign: "center",
whiteSpace: "nowrap",
color: partnerTypeColors[key],
}}
>
{displayText}
</Box>
);
})}
</Box>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
marginTop: 0.5,
}}>
<Typography variant="body2" style={{ color: theme.palette.text.secondary, fontSize: 16, maxWidth: "60%" }}>
Set up and manage partner details and information to be displayed on the Partners page.
</Typography>
<Box sx={{ display: "flex", gap: 2, alignItems: "center" }}>
{isPartner && (
<>
{/* View Partner Page Button */}
<Tooltip title="View Partner Page">
<IconButton
sx={{
color: theme.palette.primary.main,
backgroundColor: theme.palette.action.hover,
"&:hover": {
backgroundColor: theme.palette.action.selected,
}
}}
onClick={handleOpenPartnerPage}
disabled={!partnerData?.id}
>
<OpenInNewIcon />
</IconButton>
</Tooltip>
{/* Update Details Button */}
<Button
sx={{
fontSize: 16,
textTransform: 'capitalize',
boxShadow: "none",
px: 4,
}}
variant="contained"
color="primary"
disabled={isPublishing || isToggling}
onClick={handleUpdatePartnerDetails}
>
{isPublishing ? "Updating..." : "Update Details"}
</Button>
{/* Toggle Publish/Unpublish Button */}
<Button
sx={{
fontSize: 16,
textTransform: 'capitalize',
boxShadow: "none",
marginRight: 4,
px: 3,
backgroundColor: partnerData?.public ? "#FD4C62" : "#2BC07E",
"&:hover": {
backgroundColor: partnerData?.public ? "#FD4C62" : "#2BC07E"
}
}}
variant="contained"
disabled={isToggling || isPublishing}
onClick={handleTogglePublishStatus}
>
{isToggling ? (partnerData?.public ? "Unpublishing..." : "Publishing...") : (partnerData?.public ? "Unpublish" : "Publish")}
</Button>
</>
)}
{!isPartner && (
<Button
sx={{
fontSize: 16,
textTransform: 'capitalize',
boxShadow: "none",
px: 5,
textDecoration: "none",
marginRight: 4
}}
variant="contained"
color="primary"
onClick={() => {
if(!isCloud){
navigate("/become-partner")
}else{
window.open("https://shuffler.io/become-partner")
}
}}
>
Become a partner
</Button>
)}
</Box>
</Box>
</div>
</div>
</div>
<PartnerHeader
isCloud={isCloud}
userdata={userdata}
isPublishing={isPublishing}
setSelectedOrganization={setSelectedOrganization}
globalUrl={globalUrl}
selectedOrganization={selectedOrganization}
isEditOrgTab={true}
loadingPartnerData={loadingPartnerData}
partnerData={partnerData}
setPartnerData={setPartnerData}
handleGetOrg={handleGetOrg}
/>
<PartnerDetails
isCloud={isCloud}
userdata={userdata}
partnerData={partnerData}
isPublishing={isPublishing}
loadingPartnerData={loadingPartnerData}
setPartnerData={setPartnerData}
setSelectedOrganization={setSelectedOrganization}
globalUrl={globalUrl}
selectedOrganization={selectedOrganization}
isEditOrgTab={true}
handleGetOrg={handleGetOrg}
serverside={serverside}
/>
</div>
</div >
)
}
export default PartnerSettings;