mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Fixed handling of null URLs in tier add form (#18575)
refs https://github.com/TryGhost/Product/issues/3832 Updates to match the old admin, where nothing in the text field means null and the URL is not formatted until you enter something.
This commit is contained in:
parent
5bcc7f9a7a
commit
704b041202
5 changed files with 26 additions and 16 deletions
|
@ -3,7 +3,11 @@ import TextField, {TextFieldProps} from './TextField';
|
|||
import validator from 'validator';
|
||||
import {useFocusContext} from '../../providers/DesignSystemProvider';
|
||||
|
||||
export const formatUrl = (value: string, baseUrl?: string) => {
|
||||
export const formatUrl = (value: string, baseUrl?: string, nullable?: boolean) => {
|
||||
if (nullable && !value) {
|
||||
return {save: null, display: ''};
|
||||
}
|
||||
|
||||
let url = value.trim();
|
||||
|
||||
if (!url) {
|
||||
|
@ -98,25 +102,27 @@ export const formatUrl = (value: string, baseUrl?: string) => {
|
|||
* - Anchor links are displayed and saved as-is (e.g. `#test`)
|
||||
* - Values that don't look like URLs are displayed and saved as-is (e.g. `test`)
|
||||
*/
|
||||
const URLTextField: React.FC<Omit<TextFieldProps, 'onChange'> & {
|
||||
const URLTextField: React.FC<Omit<TextFieldProps, 'value' | 'onChange'> & {
|
||||
baseUrl?: string;
|
||||
transformPathWithoutSlash?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
}> = ({baseUrl, value, transformPathWithoutSlash, onChange, ...props}) => {
|
||||
nullable?: boolean;
|
||||
value: string | null;
|
||||
onChange: (value: string | null) => void;
|
||||
}> = ({baseUrl, value, transformPathWithoutSlash, nullable, onChange, ...props}) => {
|
||||
const [displayedUrl, setDisplayedUrl] = useState('');
|
||||
const {setFocusState} = useFocusContext();
|
||||
|
||||
useEffect(() => {
|
||||
setDisplayedUrl(formatUrl(value || '', baseUrl).display);
|
||||
}, [value, baseUrl]);
|
||||
setDisplayedUrl(formatUrl(value || '', baseUrl, nullable).display);
|
||||
}, [value, baseUrl, nullable]);
|
||||
|
||||
const updateUrl = () => {
|
||||
let urls = formatUrl(displayedUrl, baseUrl);
|
||||
let urls = formatUrl(displayedUrl, baseUrl, nullable);
|
||||
|
||||
// If the user entered something like "bla", try to parse it as a relative URL
|
||||
// If parsing as "/bla" results in a valid URL, use that instead
|
||||
if (transformPathWithoutSlash && !urls.display.includes('//')) {
|
||||
const candidate = formatUrl('/' + displayedUrl, baseUrl);
|
||||
if (transformPathWithoutSlash && !urls.display.includes('//') && (displayedUrl || !nullable)) {
|
||||
const candidate = formatUrl('/' + displayedUrl, baseUrl, nullable);
|
||||
|
||||
if (candidate.display.includes('//')) {
|
||||
urls = candidate;
|
||||
|
@ -124,7 +130,9 @@ const URLTextField: React.FC<Omit<TextFieldProps, 'onChange'> & {
|
|||
}
|
||||
|
||||
setDisplayedUrl(urls.display);
|
||||
onChange(urls.save);
|
||||
if (urls.save !== value) {
|
||||
onChange(urls.save);
|
||||
}
|
||||
setFocusState(false);
|
||||
};
|
||||
|
||||
|
|
|
@ -53,7 +53,8 @@ const TierDetailModalContent: React.FC<{tier?: Tier}> = ({tier}) => {
|
|||
...(tier || {}),
|
||||
trial_days: tier?.trial_days?.toString() || '',
|
||||
currency: tier?.currency || currencies[0].isoCode,
|
||||
visibility: tier?.visibility || 'none'
|
||||
visibility: tier?.visibility || 'none',
|
||||
welcome_page_url: tier?.welcome_page_url || null
|
||||
},
|
||||
onSave: async () => {
|
||||
const {trial_days: trialDays, currency, ...rest} = formState;
|
||||
|
@ -281,7 +282,8 @@ const TierDetailModalContent: React.FC<{tier?: Tier}> = ({tier}) => {
|
|||
hint='Redirect to this URL after signup for premium membership'
|
||||
placeholder={siteData?.url}
|
||||
title='Welcome page'
|
||||
value={formState.welcome_page_url || ''}
|
||||
value={formState.welcome_page_url || null}
|
||||
nullable
|
||||
transformPathWithoutSlash
|
||||
onChange={value => updateForm(state => ({...state, welcome_page_url: value || null}))}
|
||||
/>
|
||||
|
|
|
@ -44,7 +44,7 @@ const NavigationItemEditor: React.FC<NavigationItemEditorProps> = ({baseUrl, ite
|
|||
unstyled={unstyled}
|
||||
value={item.url}
|
||||
hideTitle
|
||||
onChange={value => updateItem?.({url: value})}
|
||||
onChange={value => updateItem?.({url: value || ''})}
|
||||
onKeyDown={() => clearError?.('url')}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -22,7 +22,7 @@ interface AddRecommendationModalProps {
|
|||
}
|
||||
|
||||
const doFormatUrl = (url: string) => {
|
||||
return formatUrl(url).save;
|
||||
return formatUrl(url).save || '';
|
||||
};
|
||||
|
||||
const validateUrl = function (errors: ErrorMessages, url: string) {
|
||||
|
@ -60,7 +60,7 @@ const AddRecommendationModal: React.FC<RoutingModalProps & AddRecommendationModa
|
|||
const {formState, updateForm, handleSave, errors, saveState, clearError, setErrors} = useForm({
|
||||
initialState: recommendation ?? {
|
||||
title: '',
|
||||
url: initialUrlCleaned,
|
||||
url: initialUrlCleaned || '',
|
||||
description: '',
|
||||
excerpt: null,
|
||||
featured_image: null,
|
||||
|
|
|
@ -96,7 +96,7 @@ const RecommendationDescriptionForm: React.FC<Props<EditOrAddRecommendation | Re
|
|||
onChange={u => updateForm((state) => {
|
||||
return {
|
||||
...state,
|
||||
url: u
|
||||
url: u || ''
|
||||
};
|
||||
})}
|
||||
/>}
|
||||
|
|
Loading…
Add table
Reference in a new issue