Ensured all data can be set when editing a short URL

This commit is contained in:
Alejandro Celaya
2021-03-27 09:49:47 +01:00
parent a019bd30df
commit eea76d88c3
15 changed files with 73 additions and 64 deletions

View File

@@ -12,7 +12,7 @@ import {
ShlinkTagsResponse,
ShlinkVisits,
ShlinkVisitsParams,
ShlinkShortUrlMeta,
ShlinkShortUrlData,
ShlinkDomain,
ShlinkDomainsResponse,
ShlinkVisitsOverview,
@@ -67,7 +67,7 @@ export default class ShlinkApiClient {
this.performRequest(`/short-urls/${shortCode}`, 'DELETE', { domain })
.then(() => {});
/* @deprecated. If using Shlink 2.6.0 or greater, use updateShortUrlMeta instead */
/* @deprecated. If using Shlink 2.6.0 or greater, use updateShortUrl instead */
public readonly updateShortUrlTags = async (
shortCode: string,
domain: OptionalString,
@@ -76,12 +76,12 @@ export default class ShlinkApiClient {
this.performRequest<{ tags: string[] }>(`/short-urls/${shortCode}/tags`, 'PUT', { domain }, { tags })
.then(({ data }) => data.tags);
public readonly updateShortUrlMeta = async (
public readonly updateShortUrl = async (
shortCode: string,
domain: OptionalString,
meta: ShlinkShortUrlMeta,
data: ShlinkShortUrlData,
): Promise<ShortUrl> =>
this.performRequest<ShortUrl>(`/short-urls/${shortCode}`, 'PATCH', { domain }, meta)
this.performRequest<ShortUrl>(`/short-urls/${shortCode}`, 'PATCH', { domain }, data)
.then(({ data }) => data);
public readonly listTags = async (): Promise<ShlinkTags> =>

View File

@@ -57,8 +57,10 @@ export interface ShlinkVisitsParams {
endDate?: string;
}
export interface ShlinkShortUrlMeta extends ShortUrlMeta {
export interface ShlinkShortUrlData extends ShortUrlMeta {
longUrl?: string;
title?: string;
validateUrl?: boolean;
tags?: string[];
}

View File

@@ -9,13 +9,14 @@ import { Result } from '../utils/Result';
import { ShlinkApiError } from '../api/ShlinkApiError';
import { ShortUrlFormProps } from './ShortUrlForm';
import { ShortUrlDetail } from './reducers/shortUrlDetail';
import { ShortUrl, ShortUrlData } from './data';
import { EditShortUrlData, ShortUrl, ShortUrlData } from './data';
interface EditShortUrlConnectProps extends RouteComponentProps<{ shortCode: string }> {
settings: Settings;
selectedServer: SelectedServer;
shortUrlDetail: ShortUrlDetail;
getShortUrlDetail: (shortCode: string, domain: OptionalString) => void;
editShortUrl: (shortUrl: string, domain: OptionalString, data: EditShortUrlData) => Promise<void>;
}
const getInitialState = (shortUrl?: ShortUrl, settings?: ShortUrlCreationSettings): ShortUrlData => {
@@ -44,6 +45,7 @@ export const EditShortUrl = (ShortUrlForm: FC<ShortUrlFormProps>) => ({
selectedServer,
shortUrlDetail,
getShortUrlDetail,
editShortUrl,
}: EditShortUrlConnectProps) => {
const { loading, error, errorData, shortUrl } = shortUrlDetail;
const { domain } = parseQuery<{ domain?: string }>(search);
@@ -70,7 +72,7 @@ export const EditShortUrl = (ShortUrlForm: FC<ShortUrlFormProps>) => ({
saving={false}
selectedServer={selectedServer}
mode="edit"
onSave={async (shortUrlData) => Promise.resolve(console.log(shortUrlData))}
onSave={async (shortUrlData) => shortUrl && editShortUrl(shortUrl.shortCode, shortUrl.domain, shortUrlData)}
/>
);
};

View File

@@ -2,7 +2,7 @@ import { FC, useEffect, useState } from 'react';
import { InputType } from 'reactstrap/lib/Input';
import { Button, FormGroup, Input, Row } from 'reactstrap';
import { isEmpty, pipe, replace, trim } from 'ramda';
import * as m from 'moment';
import m from 'moment';
import DateInput, { DateInputProps } from '../utils/DateInput';
import {
supportsListingDomains,
@@ -46,8 +46,9 @@ export const ShortUrlForm = (
const reset = () => setShortUrlData(initialState);
const submit = handleEventPreventingDefault(async () => onSave({
...shortUrlData,
validSince: formatIsoDate(shortUrlData.validSince) ?? undefined,
validUntil: formatIsoDate(shortUrlData.validUntil) ?? undefined,
validSince: formatIsoDate(shortUrlData.validSince) ?? null,
validUntil: formatIsoDate(shortUrlData.validUntil) ?? null,
maxVisits: !hasValue(shortUrlData.maxVisits) ? null : Number(shortUrlData.maxVisits),
}).then(() => !isEdit && reset()).catch(() => {}));
useEffect(() => {
@@ -69,7 +70,7 @@ export const ShortUrlForm = (
const renderDateInput = (id: DateFields, placeholder: string, props: Partial<DateInputProps> = {}) => (
<div className="form-group">
<DateInput
selected={shortUrlData[id] as m.Moment | null}
selected={shortUrlData[id] ? m(shortUrlData[id]) : null}
placeholderText={placeholder}
isClearable
onChange={(date) => setShortUrlData({ ...shortUrlData, [id]: date })}
@@ -148,8 +149,8 @@ export const ShortUrlForm = (
<div className="col-sm-6 mb-3">
<SimpleCard title="Limit access to the short URL">
{renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })}
{renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlData.validUntil as m.Moment | undefined })}
{renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlData.validSince as m.Moment | undefined })}
{renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlData.validUntil ? m(shortUrlData.validUntil) : undefined })}
{renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlData.validSince ? m(shortUrlData.validSince) : undefined })}
</SimpleCard>
</div>
</Row>

View File

@@ -1,18 +1,22 @@
import * as m from 'moment';
import { Nullable, OptionalString } from '../../utils/utils';
export interface ShortUrlData {
longUrl: string;
export interface EditShortUrlData {
longUrl?: string;
tags?: string[];
customSlug?: string;
title?: string;
validSince?: m.Moment | string | null;
validUntil?: m.Moment | string | null;
maxVisits?: number | null;
validateUrl?: boolean;
}
export interface ShortUrlData extends EditShortUrlData {
longUrl: string;
customSlug?: string;
shortCodeLength?: number;
domain?: string;
validSince?: m.Moment | string;
validUntil?: m.Moment | string;
maxVisits?: number;
findIfExists?: boolean;
validateUrl?: boolean;
}
export interface ShortUrl {

View File

@@ -3,13 +3,13 @@ import { Modal, ModalBody, ModalFooter, ModalHeader, FormGroup, Input, Button }
import { ExternalLink } from 'react-external-link';
import { ShortUrlEdition } from '../reducers/shortUrlEdition';
import { handleEventPreventingDefault, hasValue, OptionalString } from '../../utils/utils';
import { ShortUrlModalProps } from '../data';
import { EditShortUrlData, ShortUrlModalProps } from '../data';
import { Result } from '../../utils/Result';
import { ShlinkApiError } from '../../api/ShlinkApiError';
interface EditShortUrlModalProps extends ShortUrlModalProps {
shortUrlEdition: ShortUrlEdition;
editShortUrl: (shortUrl: string, domain: OptionalString, longUrl: string) => Promise<void>;
editShortUrl: (shortUrl: string, domain: OptionalString, data: EditShortUrlData) => Promise<void>;
}
const EditShortUrlModal = ({ isOpen, toggle, shortUrl, shortUrlEdition, editShortUrl }: EditShortUrlModalProps) => {
@@ -17,7 +17,7 @@ const EditShortUrlModal = ({ isOpen, toggle, shortUrl, shortUrlEdition, editShor
const url = shortUrl?.shortUrl ?? '';
const [ longUrl, setLongUrl ] = useState(shortUrl.longUrl);
const doEdit = async () => editShortUrl(shortUrl.shortCode, shortUrl.domain, longUrl).then(toggle);
const doEdit = async () => editShortUrl(shortUrl.shortCode, shortUrl.domain, { longUrl }).then(toggle);
return (
<Modal isOpen={isOpen} toggle={toggle} centered size="lg">

View File

@@ -2,7 +2,7 @@ import { Action, Dispatch } from 'redux';
import { buildReducer } from '../../utils/helpers/redux';
import { GetState } from '../../container/types';
import { OptionalString } from '../../utils/utils';
import { ShortUrlIdentifier } from '../data';
import { EditShortUrlData, ShortUrlIdentifier } from '../data';
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
import { ProblemDetailsError } from '../../api/types';
import { parseApiError } from '../../api/utils';
@@ -45,13 +45,16 @@ export default buildReducer<ShortUrlEdition, ShortUrlEditedAction & ShortUrlEdit
export const editShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => (
shortCode: string,
domain: OptionalString,
longUrl: string,
data: EditShortUrlData,
) => async (dispatch: Dispatch, getState: GetState) => {
dispatch({ type: EDIT_SHORT_URL_START });
const { updateShortUrlMeta } = buildShlinkApiClient(getState);
// TODO Pass tags to the updateTags function if server version is lower than 2.6
const { updateShortUrl } = buildShlinkApiClient(getState);
try {
await updateShortUrlMeta(shortCode, domain, { longUrl });
const { longUrl } = await updateShortUrl(shortCode, domain, data as any); // FIXME Parse dates
dispatch<ShortUrlEditedAction>({ shortCode, longUrl, domain, type: SHORT_URL_EDITED });
} catch (e) {
dispatch<ShortUrlEditionFailedAction>({ type: EDIT_SHORT_URL_ERROR, errorData: parseApiError(e) });

View File

@@ -50,10 +50,10 @@ export const editShortUrlMeta = (buildShlinkApiClient: ShlinkApiClientBuilder) =
meta: ShortUrlMeta,
) => async (dispatch: Dispatch, getState: GetState) => {
dispatch({ type: EDIT_SHORT_URL_META_START });
const { updateShortUrlMeta } = buildShlinkApiClient(getState);
const { updateShortUrl } = buildShlinkApiClient(getState);
try {
await updateShortUrlMeta(shortCode, domain, meta);
await updateShortUrl(shortCode, domain, meta);
dispatch<ShortUrlMetaEditedAction>({ shortCode, meta, domain, type: SHORT_URL_META_EDITED });
} catch (e) {
dispatch<ShortUrlMetaEditionFailedAction>({ type: EDIT_SHORT_URL_META_ERROR, errorData: parseApiError(e) });

View File

@@ -54,12 +54,12 @@ export const editShortUrlTags = (buildShlinkApiClient: ShlinkApiClientBuilder) =
dispatch({ type: EDIT_SHORT_URL_TAGS_START });
const { selectedServer } = getState();
const tagsInPatch = supportsTagsInPatch(selectedServer);
const { updateShortUrlTags, updateShortUrlMeta } = buildShlinkApiClient(getState);
const { updateShortUrlTags, updateShortUrl } = buildShlinkApiClient(getState);
try {
const normalizedTags = await (
tagsInPatch
? updateShortUrlMeta(shortCode, domain, { tags }).then(prop('tags'))
? updateShortUrl(shortCode, domain, { tags }).then(prop('tags'))
: updateShortUrlTags(shortCode, domain, tags)
);

View File

@@ -59,7 +59,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('EditShortUrl', EditShortUrl, 'ShortUrlForm');
bottle.decorator(
'EditShortUrl',
connect([ 'shortUrlDetail', 'selectedServer', 'settings' ], [ 'getShortUrlDetail' ]),
connect([ 'shortUrlDetail', 'selectedServer', 'settings' ], [ 'getShortUrlDetail', 'editShortUrl' ]),
);
bottle.serviceFactory('DeleteShortUrlModal', () => DeleteShortUrlModal);