0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-06 22:40:14 -05:00

Wired up Offer update Page

This commit is contained in:
Princi Vershwal 2023-11-09 11:24:29 +05:30 committed by GitHub
parent 1382e34e42
commit 1e8176f596
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 152 additions and 55 deletions

View file

@ -1,4 +1,5 @@
import {Meta, createQuery, createQueryWithId} from '../utils/api/hooks';
import {Meta, createMutation, createQuery, createQueryWithId} from '../utils/api/hooks';
import {updateQueryCache} from '../utils/api/updateQueries';
export type Offer = {
id: string;
@ -26,6 +27,10 @@ export interface OffersResponseType {
offers: Offer[]
}
export interface OfferEditResponseType extends OffersResponseType {
meta?: Meta
}
const dataType = 'OffersResponseType';
export const useBrowseOffers = createQuery<OffersResponseType>({
@ -37,3 +42,14 @@ export const useBrowseOffersById = createQueryWithId<OffersResponseType>({
dataType,
path: `/offers/`
});
export const useEditOffer = createMutation<OfferEditResponseType, Offer>({
method: 'PUT',
path: offer => `/offers/${offer.id}/`,
body: offer => ({offers: [offer]}),
updateQueries: {
dataType,
emberUpdateType: 'createOrUpdate',
update: updateQueryCache('offers')
}
});

View file

@ -1,60 +1,97 @@
import NiceModal, {useModal} from '@ebay/nice-modal-react';
import useFeatureFlag from '../../../../hooks/useFeatureFlag';
import useForm, {ErrorMessages} from '../../../../hooks/useForm';
import useHandleError from '../../../../utils/api/handleError';
import useRouting from '../../../../hooks/useRouting';
import {Button, Form, PreviewModalContent, TextArea, TextField} from '@tryghost/admin-x-design-system';
import {Offer, useBrowseOffersById} from '../../../../api/offers';
import {Button, Form, PreviewModalContent, TextArea, TextField, showToast} from '@tryghost/admin-x-design-system';
import {Offer, useBrowseOffersById, useEditOffer} from '../../../../api/offers';
import {RoutingModalProps} from '../../../providers/RoutingProvider';
import {useEffect} from 'react';
import {getHomepageUrl} from '../../../../api/site';
import {useEffect, useState} from 'react';
import {useGlobalData} from '../../../providers/GlobalDataProvider';
const Sidebar: React.FC<{offer: Offer}> = ({offer}) => {
return (
<div className='pt-7'>
<Form>
<TextField
hint='Visible to members on Stripe Checkout page.'
placeholder='Black Friday'
title='Name'
value={offer?.name}
/>
<section className='mt-4'>
<h2 className='mb-4 text-lg'>Portal Settings</h2>
<div className='flex flex-col gap-6'>
const Sidebar: React.FC<{
clearError: (field: string) => void,
errors: ErrorMessages,
offer: Offer,
updateOffer: (fields: Partial<Offer>) => void,
validate: () => void}> = ({clearError, errors, offer, updateOffer, validate}) => {
const {siteData} = useGlobalData();
const [isCopied, setIsCopied] = useState(false);
const offerUrl = `${getHomepageUrl(siteData!)}/#/portal/offers/${offer?.code}`;
const handleCopyClick = async () => {
try {
await navigator.clipboard.writeText(offerUrl);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 1000); // reset after 1 seconds
} catch (err) {
// eslint-disable-next-line no-console
console.error('Failed to copy text: ', err);
}
};
return (
<div className='pt-7'>
<Form>
<TextField
placeholder='Black Friday Special'
title='Display title'
value={offer?.display_title}
error={Boolean(errors.name)}
hint={errors.name || 'Visible to members on Stripe Checkout page'}
placeholder='Black Friday'
title='Name'
value={offer?.name}
onBlur={validate}
onChange={e => updateOffer({name: e.target.value})}
onKeyDown={() => clearError('name')}
/>
<TextField
placeholder='black-friday'
title='Offer code'
value={offer?.code}
/>
<TextArea
placeholder='Take advantage of this limited-time offer.'
title='Display description'
value={offer.display_description}
/>
<div className='flex flex-col gap-1.5'>
<TextField
placeholder='https://www.example.com'
title='URL'
type='url'
value={`http://localhost:2368//#/portal/offers/${offer?.code}`}
/>
<Button color='green' fullWidth={true} label='Copy link' />
</div>
</div>
</section>
</Form>
</div>
);
};
<section className='mt-4'>
<h2 className='mb-4 text-lg'>Portal Settings</h2>
<div className='flex flex-col gap-6'>
<TextField
placeholder='Black Friday Special'
title='Display title'
value={offer?.display_title}
onChange={e => updateOffer({display_title: e.target.value})}
/>
<TextField
error={Boolean(errors.code)}
hint={errors.code}
placeholder='black-friday'
title='Offer code'
value={offer?.code}
onBlur={validate}
onChange={e => updateOffer({code: e.target.value})}
onKeyDown={() => clearError('name')}
/>
<TextArea
placeholder='Take advantage of this limited-time offer.'
title='Display description'
value={offer?.display_description}
onChange={e => updateOffer({display_description: e.target.value})}
/>
<div className='flex flex-col gap-1.5'>
<TextField
disabled={Boolean(true)}
placeholder='https://www.example.com'
title='URL'
type='url'
value={offerUrl}
/>
<Button color={isCopied ? 'green' : 'black'} label={isCopied ? 'Copied!' : 'Copy code'} onClick={handleCopyClick} />
</div>
</div>
</section>
</Form>
</div>
);
};
const EditOfferModal: React.FC<RoutingModalProps> = ({params}) => {
const modal = useModal();
const {updateRoute} = useRouting();
const handleError = useHandleError();
const hasOffers = useFeatureFlag('adminXOffers');
let offer : Offer;
const {mutateAsync: editOffer} = useEditOffer();
useEffect(() => {
if (!hasOffers) {
@ -64,19 +101,62 @@ const EditOfferModal: React.FC<RoutingModalProps> = ({params}) => {
}, [hasOffers, modal, updateRoute]);
const {data: {offers: offerById = []} = {}} = useBrowseOffersById(params?.id ? params?.id : '');
if (offerById.length === 0) {
return null;
}
offer = offerById[0];
const {formState, saveState, updateForm, setFormState, handleSave, validate, errors, clearError, okProps} = useForm({
initialState: offerById[0],
savingDelay: 500,
onSave: async () => {
await editOffer(formState);
},
onSaveError: handleError,
onValidate: () => {
const newErrors: Record<string, string> = {};
if (!formState?.name) {
newErrors.name = 'Please enter a name';
}
if (!formState?.code) {
newErrors.code = 'Please enter a code';
}
return newErrors;
}
});
useEffect(() => {
setFormState(() => offerById[0]);
}, [setFormState, offerById[0]]);
const updateOffer = (fields: Partial<Offer>) => {
updateForm(state => ({...state, ...fields}));
};
const sidebar = <Sidebar
offer={offer}
clearError={clearError}
errors={errors}
offer={formState}
updateOffer={updateOffer}
validate={validate}
/>;
return <PreviewModalContent deviceSelector={false} okLabel='Update' sidebar={sidebar} size='full' title={offer?.name} onCancel={() => {
modal.remove();
updateRoute('offers/edit');
}} />;
return offerById ? <PreviewModalContent deviceSelector={false}
dirty={saveState === 'unsaved'}
okColor={okProps.color}
okLabel={okProps.label || 'Save'}
sidebar={sidebar} size='full' title='Offer'
onCancel={() => {
modal.remove();
updateRoute('offers/edit');
}}
onOk={async () => {
if (!(await handleSave({fakeWhenUnchanged: true}))) {
showToast({
type: 'pageError',
message: 'Can\'t save offer, please double check that you\'ve filled all mandatory fields.'
});
}
}} /> : null;
};
export default NiceModal.create(EditOfferModal);

View file

@ -279,6 +279,7 @@ const fetchSettings = function () {
const emberDataTypeMapping = {
IntegrationsResponseType: {type: 'integration'},
InvitesResponseType: {type: 'invite'},
OffersResponseType: {type: 'offer'},
NewslettersResponseType: {type: 'newsletter'},
RecommendationResponseType: {type: 'recommendation'},
SettingsResponseType: {type: 'setting', singleton: true},