mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
feat: remember listing delete preference (#314)
* feat: remember listing delete preference Persist soft/hard choice and skip-confirm in user settings.
This commit is contained in:
@@ -151,4 +151,28 @@ export default async function userSettingsPlugin(fastify) {
|
|||||||
return reply.code(500).send({ error: error.message });
|
return reply.code(500).send({ error: error.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fastify.post('/listing-deletion-preference', async (request, reply) => {
|
||||||
|
const userId = request.session.currentUser;
|
||||||
|
const { listing_deletion_preference } = request.body;
|
||||||
|
|
||||||
|
const globalSettings = await getSettings();
|
||||||
|
if (globalSettings.demoMode && !isAdmin(request)) {
|
||||||
|
return reply.code(403).send({ error: 'In demo mode, it is not allowed to change settings.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listing_deletion_preference == null) {
|
||||||
|
return reply.code(400).send({ error: 'listing_deletion_preference is required.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { skipPrompt, hardDelete } = listing_deletion_preference;
|
||||||
|
|
||||||
|
try {
|
||||||
|
upsertSettings({ listing_deletion_preference: { skipPrompt, hardDelete } }, userId);
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error updating listing deletion preference', error);
|
||||||
|
return reply.code(500).send({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ const routes = {
|
|||||||
'GET /api/dashboard': dashboard,
|
'GET /api/dashboard': dashboard,
|
||||||
'GET /api/demo': { demoMode: false },
|
'GET /api/demo': { demoMode: false },
|
||||||
'POST /api/user/settings/news-hash': {},
|
'POST /api/user/settings/news-hash': {},
|
||||||
|
'POST /api/user/settings/listing-deletion-preference': {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
const server = http.createServer((req, res) => {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Modal, Radio, RadioGroup, Typography } from '@douyinfe/semi-ui-19';
|
import { Modal, Radio, RadioGroup, Typography, Checkbox } from '@douyinfe/semi-ui-19';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
@@ -15,11 +15,24 @@ const ListingDeletionModal = ({
|
|||||||
title = 'Delete Listings',
|
title = 'Delete Listings',
|
||||||
showOptions = true,
|
showOptions = true,
|
||||||
message = 'How would you like to delete the selected listing(s)?',
|
message = 'How would you like to delete the selected listing(s)?',
|
||||||
|
defaultDeleteType = 'soft',
|
||||||
}) => {
|
}) => {
|
||||||
const [deleteType, setDeleteType] = useState('soft');
|
const [deleteType, setDeleteType] = useState('soft');
|
||||||
|
const [remember, setRemember] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
setDeleteType(defaultDeleteType);
|
||||||
|
setRemember(false);
|
||||||
|
}
|
||||||
|
}, [visible, defaultDeleteType]);
|
||||||
|
|
||||||
const handleOk = () => {
|
const handleOk = () => {
|
||||||
onConfirm(!showOptions || deleteType === 'hard');
|
if (showOptions) {
|
||||||
|
onConfirm(deleteType === 'hard', remember);
|
||||||
|
} else {
|
||||||
|
onConfirm(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -36,32 +49,37 @@ const ListingDeletionModal = ({
|
|||||||
<Text>{message}</Text>
|
<Text>{message}</Text>
|
||||||
</div>
|
</div>
|
||||||
{showOptions && (
|
{showOptions && (
|
||||||
<RadioGroup value={deleteType} onChange={(e) => setDeleteType(e.target.value)} style={{ width: '100%' }}>
|
<>
|
||||||
<Radio value="soft" style={{ alignItems: 'flex-start', width: '100%' }}>
|
<RadioGroup value={deleteType} onChange={(e) => setDeleteType(e.target.value)} style={{ width: '100%' }}>
|
||||||
<div style={{ marginLeft: 8 }}>
|
<Radio value="soft" style={{ alignItems: 'flex-start', width: '100%' }}>
|
||||||
<Text strong>Mark as deleted (Soft Delete)</Text>
|
<div style={{ marginLeft: 8 }}>
|
||||||
<br />
|
<Text strong>Mark as deleted (Soft Delete)</Text>
|
||||||
<Text type="secondary">
|
|
||||||
Listings are kept in the database but marked as hidden. They will <b>not</b> re-appear during the next
|
|
||||||
scraping session.
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Radio>
|
|
||||||
<Radio value="hard" style={{ marginTop: 16, alignItems: 'flex-start', width: '100%' }}>
|
|
||||||
<div style={{ marginLeft: 8 }}>
|
|
||||||
<Text strong>Remove from database (Hard Delete)</Text>
|
|
||||||
<br />
|
|
||||||
<Text type="secondary">
|
|
||||||
Listings are completely removed from the database.
|
|
||||||
<br />
|
<br />
|
||||||
<Text type="warning">
|
<Text type="secondary">
|
||||||
Consequence: They might re-appear when scraping the next time because Fredy won't know they were
|
Listings are kept in the database but marked as hidden. They will <b>not</b> re-appear during the next
|
||||||
previously found.
|
scraping session.
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</div>
|
||||||
</div>
|
</Radio>
|
||||||
</Radio>
|
<Radio value="hard" style={{ marginTop: 16, alignItems: 'flex-start', width: '100%' }}>
|
||||||
</RadioGroup>
|
<div style={{ marginLeft: 8 }}>
|
||||||
|
<Text strong>Remove from database (Hard Delete)</Text>
|
||||||
|
<br />
|
||||||
|
<Text type="secondary">
|
||||||
|
Listings are completely removed from the database.
|
||||||
|
<br />
|
||||||
|
<Text type="warning">
|
||||||
|
Consequence: They might re-appear when scraping the next time because Fredy won't know they were
|
||||||
|
previously found.
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
<Checkbox checked={remember} onChange={(e) => setRemember(e.target.checked)} style={{ marginTop: 16 }}>
|
||||||
|
Remember my choice and skip this dialog next time
|
||||||
|
</Checkbox>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ const JobGrid = () => {
|
|||||||
|
|
||||||
const userSettings = useSelector((state) => state.userSettings.settings);
|
const userSettings = useSelector((state) => state.userSettings.settings);
|
||||||
const viewMode = userSettings?.jobs_view_mode ?? 'grid';
|
const viewMode = userSettings?.jobs_view_mode ?? 'grid';
|
||||||
|
const listingDeletionPref = userSettings?.listing_deletion_preference;
|
||||||
|
const defaultDeleteType = listingDeletionPref?.hardDelete ? 'hard' : 'soft';
|
||||||
|
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const pageSize = 12;
|
const pageSize = 12;
|
||||||
@@ -142,13 +144,21 @@ const JobGrid = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onListingRemoval = (jobId) => {
|
const onListingRemoval = (jobId) => {
|
||||||
setPendingDeletion({ type: 'listings', jobId });
|
const deletion = { type: 'listings', jobId };
|
||||||
|
if (listingDeletionPref?.skipPrompt) {
|
||||||
|
confirmDeletion(listingDeletionPref.hardDelete, false, deletion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setPendingDeletion(deletion);
|
||||||
setDeleteModalVisible(true);
|
setDeleteModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmDeletion = async (hardDelete) => {
|
const confirmDeletion = async (hardDelete, remember, deletion = pendingDeletion) => {
|
||||||
const { type, jobId } = pendingDeletion;
|
const { type, jobId } = deletion;
|
||||||
try {
|
try {
|
||||||
|
if (remember && type === 'listings') {
|
||||||
|
await actions.userSettings.setListingDeletionPreference({ skipPrompt: true, hardDelete });
|
||||||
|
}
|
||||||
if (type === 'job') {
|
if (type === 'job') {
|
||||||
await xhrDelete('/api/jobs', { jobId });
|
await xhrDelete('/api/jobs', { jobId });
|
||||||
Toast.success('Job and listings successfully removed');
|
Toast.success('Job and listings successfully removed');
|
||||||
@@ -425,6 +435,7 @@ const JobGrid = () => {
|
|||||||
visible={deleteModalVisible}
|
visible={deleteModalVisible}
|
||||||
title={pendingDeletion?.type === 'job' ? 'Delete Job' : 'Delete Listings'}
|
title={pendingDeletion?.type === 'job' ? 'Delete Job' : 'Delete Listings'}
|
||||||
showOptions={pendingDeletion?.type !== 'job'}
|
showOptions={pendingDeletion?.type !== 'job'}
|
||||||
|
defaultDeleteType={defaultDeleteType}
|
||||||
message={
|
message={
|
||||||
pendingDeletion?.type === 'job'
|
pendingDeletion?.type === 'job'
|
||||||
? 'Are you sure you want to delete this job? All associated listings will be removed from the database.'
|
? 'Are you sure you want to delete this job? All associated listings will be removed from the database.'
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ const ListingsOverview = () => {
|
|||||||
const sp = useSearchParams();
|
const sp = useSearchParams();
|
||||||
|
|
||||||
const viewMode = userSettings?.listings_view_mode ?? 'grid';
|
const viewMode = userSettings?.listings_view_mode ?? 'grid';
|
||||||
|
const listingDeletionPref = userSettings?.listing_deletion_preference;
|
||||||
|
const defaultDeleteType = listingDeletionPref?.hardDelete ? 'hard' : 'soft';
|
||||||
|
|
||||||
const [page, setPage] = useSearchParamState(sp, 'page', 1, parseNumber);
|
const [page, setPage] = useSearchParamState(sp, 'page', 1, parseNumber);
|
||||||
const pageSize = 40;
|
const pageSize = 40;
|
||||||
@@ -91,15 +93,22 @@ const ListingsOverview = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (id) => {
|
const handleDelete = (id) => {
|
||||||
|
if (listingDeletionPref?.skipPrompt) {
|
||||||
|
confirmDeletion(listingDeletionPref.hardDelete, false, id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setListingToDelete(id);
|
setListingToDelete(id);
|
||||||
setDeleteModalVisible(true);
|
setDeleteModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNavigate = (id) => navigate(`/listings/listing/${id}`);
|
const handleNavigate = (id) => navigate(`/listings/listing/${id}`);
|
||||||
|
|
||||||
const confirmDeletion = async (hardDelete) => {
|
const confirmDeletion = async (hardDelete, remember, id = listingToDelete) => {
|
||||||
try {
|
try {
|
||||||
await xhrDelete('/api/listings/', { ids: [listingToDelete], hardDelete });
|
if (remember) {
|
||||||
|
await actions.userSettings.setListingDeletionPreference({ skipPrompt: true, hardDelete });
|
||||||
|
}
|
||||||
|
await xhrDelete('/api/listings/', { ids: [id], hardDelete });
|
||||||
Toast.success('Listing successfully removed');
|
Toast.success('Listing successfully removed');
|
||||||
loadData();
|
loadData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -251,6 +260,7 @@ const ListingsOverview = () => {
|
|||||||
|
|
||||||
<ListingDeletionModal
|
<ListingDeletionModal
|
||||||
visible={deleteModalVisible}
|
visible={deleteModalVisible}
|
||||||
|
defaultDeleteType={defaultDeleteType}
|
||||||
onConfirm={confirmDeletion}
|
onConfirm={confirmDeletion}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setDeleteModalVisible(false);
|
setDeleteModalVisible(false);
|
||||||
|
|||||||
@@ -349,6 +349,20 @@ export const useFredyState = create(
|
|||||||
throw Exception;
|
throw Exception;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async setListingDeletionPreference(listing_deletion_preference) {
|
||||||
|
try {
|
||||||
|
await xhrPost('/api/user/settings/listing-deletion-preference', { listing_deletion_preference });
|
||||||
|
set((state) => ({
|
||||||
|
userSettings: {
|
||||||
|
...state.userSettings,
|
||||||
|
settings: { ...state.userSettings.settings, listing_deletion_preference },
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
} catch (Exception) {
|
||||||
|
console.error('Error while trying to update listing deletion preference. Error:', Exception);
|
||||||
|
throw Exception;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ import {
|
|||||||
AutoComplete,
|
AutoComplete,
|
||||||
Select,
|
Select,
|
||||||
Banner,
|
Banner,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
Typography,
|
||||||
} from '@douyinfe/semi-ui-19';
|
} from '@douyinfe/semi-ui-19';
|
||||||
import { InputNumber } from '@douyinfe/semi-ui-19';
|
import { InputNumber } from '@douyinfe/semi-ui-19';
|
||||||
import { xhrPost, xhrGet } from '../../services/xhr';
|
import { xhrPost, xhrGet } from '../../services/xhr';
|
||||||
@@ -33,6 +36,8 @@ import { debounce } from '../../utils';
|
|||||||
import Headline from '../../components/headline/Headline.jsx';
|
import Headline from '../../components/headline/Headline.jsx';
|
||||||
import './GeneralSettings.less';
|
import './GeneralSettings.less';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
function formatFromTimestamp(ts) {
|
function formatFromTimestamp(ts) {
|
||||||
const date = new Date(ts);
|
const date = new Date(ts);
|
||||||
return `${date.getHours()}:${date.getMinutes() > 9 ? date.getMinutes() : '0' + date.getMinutes()}`;
|
return `${date.getHours()}:${date.getMinutes() > 9 ? date.getMinutes() : '0' + date.getMinutes()}`;
|
||||||
@@ -74,9 +79,12 @@ const GeneralSettings = function GeneralSettings() {
|
|||||||
// User settings state
|
// User settings state
|
||||||
const homeAddress = useSelector((state) => state.userSettings.settings.home_address);
|
const homeAddress = useSelector((state) => state.userSettings.settings.home_address);
|
||||||
const providerDetails = useSelector((state) => state.userSettings.settings.provider_details);
|
const providerDetails = useSelector((state) => state.userSettings.settings.provider_details);
|
||||||
|
const listingDeletionPreference = useSelector((state) => state.userSettings.settings.listing_deletion_preference);
|
||||||
const allProviders = useSelector((state) => state.provider);
|
const allProviders = useSelector((state) => state.provider);
|
||||||
const [address, setAddress] = useState(homeAddress?.address || '');
|
const [address, setAddress] = useState(homeAddress?.address || '');
|
||||||
const [coords, setCoords] = useState(homeAddress?.coords || null);
|
const [coords, setCoords] = useState(homeAddress?.coords || null);
|
||||||
|
const [listingDeleteHard, setListingDeleteHard] = useState(false);
|
||||||
|
const [listingDeleteSkipPrompt, setListingDeleteSkipPrompt] = useState(false);
|
||||||
const saving = useIsLoading(actions.userSettings.setHomeAddress);
|
const saving = useIsLoading(actions.userSettings.setHomeAddress);
|
||||||
const [dataSource, setDataSource] = useState([]);
|
const [dataSource, setDataSource] = useState([]);
|
||||||
|
|
||||||
@@ -110,6 +118,11 @@ const GeneralSettings = function GeneralSettings() {
|
|||||||
setCoords(homeAddress?.coords || null);
|
setCoords(homeAddress?.coords || null);
|
||||||
}, [homeAddress]);
|
}, [homeAddress]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setListingDeleteHard(listingDeletionPreference?.hardDelete ?? false);
|
||||||
|
setListingDeleteSkipPrompt(listingDeletionPreference?.skipPrompt ?? false);
|
||||||
|
}, [listingDeletionPreference]);
|
||||||
|
|
||||||
const nullOrEmpty = (val) => val == null || val.length === 0;
|
const nullOrEmpty = (val) => val == null || val.length === 0;
|
||||||
|
|
||||||
const handleStore = async () => {
|
const handleStore = async () => {
|
||||||
@@ -218,6 +231,10 @@ const GeneralSettings = function GeneralSettings() {
|
|||||||
try {
|
try {
|
||||||
const responseJson = await actions.userSettings.setHomeAddress(address);
|
const responseJson = await actions.userSettings.setHomeAddress(address);
|
||||||
setCoords(responseJson.coords);
|
setCoords(responseJson.coords);
|
||||||
|
await actions.userSettings.setListingDeletionPreference({
|
||||||
|
skipPrompt: listingDeleteSkipPrompt,
|
||||||
|
hardDelete: listingDeleteHard,
|
||||||
|
});
|
||||||
await actions.userSettings.getUserSettings();
|
await actions.userSettings.getUserSettings();
|
||||||
Toast.success('Settings saved. Distance calculations are running in the background.');
|
Toast.success('Settings saved. Distance calculations are running in the background.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -459,6 +476,48 @@ const GeneralSettings = function GeneralSettings() {
|
|||||||
/>
|
/>
|
||||||
</SegmentPart>
|
</SegmentPart>
|
||||||
|
|
||||||
|
<SegmentPart
|
||||||
|
name="Listing deletion"
|
||||||
|
helpText="Choose the default deletion mode. Soft delete hides them without re-scraping; hard delete removes them from the database."
|
||||||
|
>
|
||||||
|
<RadioGroup
|
||||||
|
value={listingDeleteHard ? 'hard' : 'soft'}
|
||||||
|
onChange={(e) => setListingDeleteHard(e.target.value === 'hard')}
|
||||||
|
>
|
||||||
|
<Radio value="soft">
|
||||||
|
<div>
|
||||||
|
<Text strong>Mark as deleted (Soft Delete)</Text>
|
||||||
|
<br />
|
||||||
|
<Text type="secondary">
|
||||||
|
Listings are kept in the database but marked as hidden. They will <b>not</b> re-appear during
|
||||||
|
the next scraping session.
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Radio>
|
||||||
|
<Radio value="hard">
|
||||||
|
<div>
|
||||||
|
<Text strong>Remove from database (Hard Delete)</Text>
|
||||||
|
<br />
|
||||||
|
<Text type="secondary">
|
||||||
|
Listings are completely removed from the database.
|
||||||
|
<br />
|
||||||
|
<Text type="warning">
|
||||||
|
Consequence: They might re-appear when scraping the next time because Fredy won't know they
|
||||||
|
were previously found.
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
<Checkbox
|
||||||
|
checked={listingDeleteSkipPrompt}
|
||||||
|
onChange={(e) => setListingDeleteSkipPrompt(e.target.checked)}
|
||||||
|
style={{ marginTop: 12 }}
|
||||||
|
>
|
||||||
|
Skip confirmation dialog
|
||||||
|
</Checkbox>
|
||||||
|
</SegmentPart>
|
||||||
|
|
||||||
<div className="generalSettings__save-row">
|
<div className="generalSettings__save-row">
|
||||||
<Button
|
<Button
|
||||||
icon={<IconSave />}
|
icon={<IconSave />}
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ export default function ListingDetail() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const actions = useActions();
|
const actions = useActions();
|
||||||
const listing = useSelector((state) => state.listingsData.currentListing);
|
const listing = useSelector((state) => state.listingsData.currentListing);
|
||||||
const homeAddress = useSelector((state) => state.userSettings.settings.home_address);
|
const userSettings = useSelector((state) => state.userSettings.settings);
|
||||||
|
const homeAddress = userSettings?.home_address;
|
||||||
|
const listingDeletionPref = userSettings?.listing_deletion_preference;
|
||||||
|
const defaultDeleteType = listingDeletionPref?.hardDelete ? 'hard' : 'soft';
|
||||||
const mapContainer = useRef(null);
|
const mapContainer = useRef(null);
|
||||||
const map = useRef(null);
|
const map = useRef(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -242,8 +245,11 @@ export default function ListingDetail() {
|
|||||||
};
|
};
|
||||||
}, [listing, loading, homeAddress]);
|
}, [listing, loading, homeAddress]);
|
||||||
|
|
||||||
const confirmDeletion = async (hardDelete) => {
|
const confirmDeletion = async (hardDelete, remember) => {
|
||||||
try {
|
try {
|
||||||
|
if (remember) {
|
||||||
|
await actions.userSettings.setListingDeletionPreference({ skipPrompt: true, hardDelete });
|
||||||
|
}
|
||||||
await xhrDelete('/api/listings/', { ids: [listing.id], hardDelete });
|
await xhrDelete('/api/listings/', { ids: [listing.id], hardDelete });
|
||||||
Toast.success('Listing successfully removed');
|
Toast.success('Listing successfully removed');
|
||||||
navigate('/listings');
|
navigate('/listings');
|
||||||
@@ -347,7 +353,13 @@ export default function ListingDetail() {
|
|||||||
</a>
|
</a>
|
||||||
<Button
|
<Button
|
||||||
icon={<IconDelete />}
|
icon={<IconDelete />}
|
||||||
onClick={() => setDeleteModalVisible(true)}
|
onClick={() => {
|
||||||
|
if (listingDeletionPref?.skipPrompt) {
|
||||||
|
confirmDeletion(listingDeletionPref.hardDelete);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setDeleteModalVisible(true);
|
||||||
|
}}
|
||||||
theme="light"
|
theme="light"
|
||||||
type="danger"
|
type="danger"
|
||||||
>
|
>
|
||||||
@@ -423,6 +435,7 @@ export default function ListingDetail() {
|
|||||||
|
|
||||||
<ListingDeletionModal
|
<ListingDeletionModal
|
||||||
visible={deleteModalVisible}
|
visible={deleteModalVisible}
|
||||||
|
defaultDeleteType={defaultDeleteType}
|
||||||
onConfirm={confirmDeletion}
|
onConfirm={confirmDeletion}
|
||||||
onCancel={() => setDeleteModalVisible(false)}
|
onCancel={() => setDeleteModalVisible(false)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ export default function MapView() {
|
|||||||
const sp = useSearchParams();
|
const sp = useSearchParams();
|
||||||
const [searchParams, setSearchParams] = sp;
|
const [searchParams, setSearchParams] = sp;
|
||||||
const listings = useSelector((state) => state.listingsData.mapListings);
|
const listings = useSelector((state) => state.listingsData.mapListings);
|
||||||
const homeAddress = useSelector((state) => state.userSettings.settings.home_address);
|
const userSettings = useSelector((state) => state.userSettings.settings);
|
||||||
|
const homeAddress = userSettings?.home_address;
|
||||||
|
const listingDeletionPref = userSettings?.listing_deletion_preference;
|
||||||
|
const defaultDeleteType = listingDeletionPref?.hardDelete ? 'hard' : 'soft';
|
||||||
|
|
||||||
const jobs = useSelector((state) => state.jobsData.jobs);
|
const jobs = useSelector((state) => state.jobsData.jobs);
|
||||||
const [jobId, setJobId] = useSearchParamState(sp, 'job', null, parseString);
|
const [jobId, setJobId] = useSearchParamState(sp, 'job', null, parseString);
|
||||||
@@ -52,10 +55,14 @@ export default function MapView() {
|
|||||||
|
|
||||||
const [deleteModalVisible, setDeleteModalVisible] = useState(false);
|
const [deleteModalVisible, setDeleteModalVisible] = useState(false);
|
||||||
const [listingToDelete, setListingToDelete] = useState(null);
|
const [listingToDelete, setListingToDelete] = useState(null);
|
||||||
|
const deleteListingRef = useRef(null);
|
||||||
|
|
||||||
const confirmListingDeletion = async (hardDelete) => {
|
const confirmListingDeletion = async (hardDelete, remember, id = listingToDelete) => {
|
||||||
try {
|
try {
|
||||||
await xhrDelete('/api/listings/', { ids: [listingToDelete], hardDelete });
|
if (remember) {
|
||||||
|
await actions.userSettings.setListingDeletionPreference({ skipPrompt: true, hardDelete });
|
||||||
|
}
|
||||||
|
await xhrDelete('/api/listings/', { ids: [id], hardDelete });
|
||||||
Toast.success('Listing successfully removed');
|
Toast.success('Listing successfully removed');
|
||||||
fetchListings();
|
fetchListings();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -66,6 +73,15 @@ export default function MapView() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
deleteListingRef.current = (id) => {
|
||||||
|
if (listingDeletionPref?.skipPrompt) {
|
||||||
|
confirmListingDeletion(listingDeletionPref.hardDelete, false, id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setListingToDelete(id);
|
||||||
|
setDeleteModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only reset to full range when no URL override is set
|
// Only reset to full range when no URL override is set
|
||||||
if (urlPriceMax === null) {
|
if (urlPriceMax === null) {
|
||||||
@@ -88,10 +104,7 @@ export default function MapView() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.deleteListing = (id) => {
|
window.deleteListing = (id) => deleteListingRef.current(id);
|
||||||
setListingToDelete(id);
|
|
||||||
setDeleteModalVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.viewDetails = (id) => {
|
window.viewDetails = (id) => {
|
||||||
navigate(`/listings/listing/${id}`);
|
navigate(`/listings/listing/${id}`);
|
||||||
@@ -472,6 +485,7 @@ export default function MapView() {
|
|||||||
|
|
||||||
<ListingDeletionModal
|
<ListingDeletionModal
|
||||||
visible={deleteModalVisible}
|
visible={deleteModalVisible}
|
||||||
|
defaultDeleteType={defaultDeleteType}
|
||||||
onConfirm={confirmListingDeletion}
|
onConfirm={confirmListingDeletion}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setDeleteModalVisible(false);
|
setDeleteModalVisible(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user