Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
"recurrency",
"regenerant",
"Regenerants",
"Restor",
"seagrass",
"Segoe",
"senatDerWirtschaft",
Expand Down
14 changes: 13 additions & 1 deletion public/static/locales/en/manageProjects.json
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,18 @@
"longitudeRange": "Longitude must be between -180 and 180."
}
},
"editSite": "Edit site"
"editSite": "Edit site",
"syncSites": {
"modal": {
"title": "Send Site Data to Restor.eco",
"subtitle": "By clicking send, data will be submitted to <restorLink>Restor.eco</restorLink>. Their <privacyPolicyLink>Privacy Policy</privacyPolicyLink> applies. Plant-for-the-Planet may not be able to delete the data submitted to Restor.eco."
},
"send": "Send",
"cancel": "Cancel",
"syncing": "Syncing...",
"syncSitesToRestorEco": "Sync Sites to Restor.eco",
"success": "Your sites have been synced successfully with Restor.eco!",
"error": "Failed to sync site data. Please try again later."
}
}
}
12 changes: 9 additions & 3 deletions src/features/common/Layout/CustomModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Modal from '@mui/material/Modal';
import type { ReactNode } from 'react';

import Modal from '@mui/material/Modal';
import styles from './CustomModal.module.scss';

interface Props {
Expand All @@ -9,11 +10,15 @@ interface Props {
continueButtonText: string;
cancelButtonText: string;
modalTitle: string;
modalSubtitle: string;
modalSubtitle: ReactNode;
isLoading?: boolean;
loadingText?: string;
}

export default function CustomModal({
isOpen,
isLoading = false,
loadingText = 'Loading...',
handleContinue,
handleCancel,
continueButtonText,
Expand All @@ -39,8 +44,9 @@ export default function CustomModal({
id={'Continue'}
className={styles.continueButton}
onClick={handleContinue}
disabled={isLoading}
>
{continueButtonText}
{isLoading ? loadingText : continueButtonText}
</button>
<button
id={'Cancel'}
Expand Down
21 changes: 18 additions & 3 deletions src/features/user/ManageProjects/StepForm.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@

.inlineLinkButton {
font-family: $primaryFontFamily;
width: 100%;
padding: 5px 10px;
border: 1px solid transparent;
border-radius: 30px;
font-size: $fontSmall;
font-weight: normal;
font-stretch: normal;
Expand All @@ -227,12 +229,15 @@
color: $dsPrimaryColor;
display: flex;
justify-content: center;
margin-top: 10px;
align-items: center;
}

.inlineLinkButton:hover {
cursor: pointer;
font-weight: bold;
border-color: $dsPrimaryColor;
background-color: $dsPrimaryColorTransparent7;
}

.projSiteSaveAndAdd {
display: flex;
justify-content: center;
Expand Down Expand Up @@ -398,6 +403,15 @@
}
}
}
.syncAndAddSitesButtons {
display: flex;
width: 100%;
gap: 20px;
}

.restorLink {
color: $dsPrimaryColor;
}

.drawingControls {
display: flex;
Expand Down Expand Up @@ -467,6 +481,7 @@
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
border: dashed 1px $dsCoreText;
margin-bottom: 10px;
}

.mapNavigation {
Expand Down
79 changes: 64 additions & 15 deletions src/features/user/ManageProjects/components/ProjectSites.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
} from '../../../common/types/project';
import type { ProjectSiteFeatureCollection } from '../../../common/types/map';

import { useEffect, useState, useContext } from 'react';
import { useEffect, useState, useContext, useCallback } from 'react';
import styles from './../StepForm.module.scss';
import { Controller, useForm } from 'react-hook-form';
import { useTranslations } from 'next-intl';
Expand All @@ -29,6 +29,7 @@ import SitePreviewMap from './microComponent/SitePreviewMap';
import themeProperties from '../../../../theme/themeProperties';
import CustomModal from '../../../common/Layout/CustomModal';
import EditSite from './microComponent/EditSite';
import SitesSyncActions from './microComponent/SitesSyncActions';

const defaultSiteDetails = {
name: '',
Expand Down Expand Up @@ -76,6 +77,10 @@ export default function ProjectSites({
const [isUploadingData, setIsUploadingData] = useState<boolean>(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [openModal, setOpenModal] = useState<boolean>(false);
const [isSyncingSites, setIsSyncingSites] = useState(false);
const [isSiteSyncModalOpen, setIsSiteSyncModalOpen] = useState(false);
const [isSiteSyncSuccessful, setIsSiteSyncSuccessful] = useState(false);
const [snackbarOpen, setSnackbarOpen] = useState(false);
const [showForm, setShowForm] = useState<boolean>(true);
const [editMode, setEditMode] = useState<boolean>(false);
const [geoLocation, setGeoLocation] = useState<GeoLocation | undefined>(
Expand Down Expand Up @@ -259,6 +264,39 @@ export default function ProjectSites({
setOpenModal(true);
};

const handleSyncSites = useCallback(async () => {
// prevent multiple parallel syncs
if (isSyncingSites) return;

const webhookBase = process.env.WEBHOOK_URL;
if (!webhookBase) {
console.warn('WEBHOOK_URL is not defined');
return;
}
setIsSyncingSites(true);

try {
const webhookUrl = `${webhookBase}/33878023-ee47-44e1-8a62-34eb2d2b3246/?project=${projectGUID}`;
const response = await fetch(webhookUrl, {
method: 'GET',
});

if (!response.ok) {
throw new Error(`Webhook call failed with status ${response.status}`);
}

setIsSiteSyncSuccessful(true);
setSnackbarOpen(true);
setErrorMessage(null);
} catch (err) {
console.error('Sync error:', err);
setErrorMessage(t('syncSites.error'));
} finally {
setIsSyncingSites(false);
setIsSiteSyncModalOpen(false);
}
}, [isSyncingSites, projectGUID, t]);

const EditProps = {
openModal,
handleModalClose,
Expand Down Expand Up @@ -401,20 +439,31 @@ export default function ProjectSites({
</Button>
</div>
) : (
<Button
id={'manageProjAddSite'}
onClick={() => {
setShowForm(true);
setGeoJson(null);
setSiteDetails(defaultSiteDetails);
setSiteGUID(null);
setEditMode(false);
setOpenModal(false);
}}
className={styles.formFieldLarge}
>
<p className={styles.inlineLinkButton}>{t('addSite')}</p>
</Button>
<div className={styles.syncAndAddSitesButtons}>
<button
className={styles.inlineLinkButton}
type="button"
onClick={() => {
setShowForm(true);
setGeoJson(null);
setSiteDetails(defaultSiteDetails);
setSiteGUID(null);
setEditMode(false);
setOpenModal(false);
}}
>
{t('addSite')}
</button>
<SitesSyncActions
isSyncingSites={isSyncingSites}
isSiteSyncModalOpen={isSiteSyncModalOpen}
setIsSiteSyncModalOpen={setIsSiteSyncModalOpen}
isSiteSyncSuccessful={isSiteSyncSuccessful}
snackbarOpen={snackbarOpen}
setSnackbarOpen={setSnackbarOpen}
handleSyncSites={handleSyncSites}
/>
</div>
)}

{errorMessage !== null && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { SetState } from '../../../../common/types/common';

import { Alert, Snackbar } from '@mui/material';
import { useTranslations } from 'next-intl';
import CustomModal from '../../../../common/Layout/CustomModal';
import styles from '../../StepForm.module.scss';
import AutorenewIcon from '@mui/icons-material/Autorenew';

interface SitesSyncActionsProps {
isSyncingSites: boolean;
isSiteSyncSuccessful: boolean;
snackbarOpen: boolean;
setSnackbarOpen: SetState<boolean>;
isSiteSyncModalOpen: boolean;
setIsSiteSyncModalOpen: SetState<boolean>;
handleSyncSites: () => Promise<void>;
}

const SitesSyncActions = ({
isSyncingSites,
isSiteSyncSuccessful,
isSiteSyncModalOpen,
setIsSiteSyncModalOpen,
snackbarOpen,
setSnackbarOpen,
handleSyncSites,
}: SitesSyncActionsProps) => {
const tSyncSites = useTranslations('ManageProjects.syncSites');
return (
<>
{!isSiteSyncSuccessful && (
<button
className={styles.inlineLinkButton}
type="button"
onClick={() => setIsSiteSyncModalOpen(true)}
>
<AutorenewIcon />
{tSyncSites('syncSitesToRestorEco')}
</button>
)}
<Snackbar
open={snackbarOpen}
autoHideDuration={4000}
onClose={() => setSnackbarOpen(false)}
>
<Alert
elevation={6}
variant="filled"
severity="success"
onClose={() => setSnackbarOpen(false)}
>
{tSyncSites('success')}
</Alert>
</Snackbar>
<CustomModal
modalTitle={tSyncSites('modal.title')}
modalSubtitle={tSyncSites.rich('modal.subtitle', {
restorLink: (chunks) => (
<a
href="https://restor.eco"
target="_blank"
rel="noopener noreferrer"
className={styles.restorLink}
>
{chunks}
</a>
),
privacyPolicyLink: (chunks) => (
<a
href="https://restor.eco/privacy-policy"
target="_blank"
rel="noopener noreferrer"
className={styles.restorLink}
>
{chunks}
</a>
),
})}
continueButtonText={tSyncSites('send')}
cancelButtonText={tSyncSites('cancel')}
isOpen={isSiteSyncModalOpen}
isLoading={isSyncingSites}
loadingText={tSyncSites('syncing')}
handleCancel={() => setIsSiteSyncModalOpen(false)}
handleContinue={handleSyncSites}
/>
</>
);
};

export default SitesSyncActions;