Link editing.

This commit is contained in:
Mike Cao 2025-08-15 11:11:24 -07:00
parent 0558563d35
commit 5f4b83b09c
13 changed files with 123 additions and 89 deletions

View file

@ -7,7 +7,6 @@ import { useApi, useMessages, useModified } from '@/components/hooks';
export function LinkDeleteButton({
linkId,
websiteId,
name,
onSave,
}: {
@ -19,7 +18,7 @@ export function LinkDeleteButton({
const { formatMessage, labels } = useMessages();
const { del, useMutation } = useApi();
const { mutate, isPending, error } = useMutation({
mutationFn: () => del(`/websites/${websiteId}/links/${linkId}`),
mutationFn: () => del(`/links/${linkId}`),
});
const { touch } = useModified();

View file

@ -9,7 +9,7 @@ export function LinkEditButton({ linkId }: { linkId: string }) {
return (
<ActionButton title={formatMessage(labels.edit)} icon={<Edit />}>
<Dialog title={formatMessage(labels.link)} style={{ width: 800 }}>
<Dialog title={formatMessage(labels.link)} style={{ width: 800, minHeight: 300 }}>
{({ close }) => {
return <LinkEditForm linkId={linkId} onClose={close} />;
}}

View file

@ -1,3 +1,4 @@
import { useState, useEffect } from 'react';
import {
Form,
FormField,
@ -5,7 +6,6 @@ import {
Row,
TextField,
Button,
Text,
Label,
Column,
Icon,
@ -16,6 +16,8 @@ import { useMessages } from '@/components/hooks';
import { Refresh } from '@/components/icons';
import { getRandomChars } from '@/lib/crypto';
import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery';
import { LINKS_URL } from '@/lib/constants';
import { isValidUrl } from '@/lib/url';
const generateId = () => getRandomChars(9);
@ -31,29 +33,55 @@ export function LinkEditForm({
onClose?: () => void;
}) {
const { formatMessage, labels } = useMessages();
const { mutate, error, isPending } = useUpdateQuery('/links', { id: linkId, teamId });
const { linkDomain } = useConfig();
const { mutate, error, isPending, touch } = useUpdateQuery(
linkId ? `/links/${linkId}` : '/links',
{
id: linkId,
teamId,
},
);
const { linksUrl } = useConfig();
const hostUrl = linksUrl || LINKS_URL;
const { data, isLoading } = useLinkQuery(linkId);
const [slug, setSlug] = useState(generateId());
const handleSubmit = async (data: any) => {
mutate(data, {
onSuccess: async () => {
touch('links');
onSave?.();
onClose?.();
},
});
};
if (linkId && !isLoading) {
const handleSlug = () => {
const slug = generateId();
setSlug(slug);
return slug;
};
const checkUrl = (url: string) => {
if (!isValidUrl(url)) {
return formatMessage(labels.invalidUrl);
}
return true;
};
useEffect(() => {
if (data) {
setSlug(data.slug);
}
}, [data]);
if (linkId && isLoading) {
return <Loading position="page" />;
}
return (
<Form
onSubmit={handleSubmit}
error={error?.message}
defaultValues={{ slug: generateId(), ...data }}
>
<Form onSubmit={handleSubmit} error={error?.message} defaultValues={{ slug, ...data }}>
{({ setValue }) => {
return (
<>
@ -62,33 +90,40 @@ export function LinkEditForm({
name="name"
rules={{ required: formatMessage(labels.required) }}
>
<TextField autoComplete="off" />
<TextField autoComplete="off" autoFocus />
</FormField>
<FormField
label={formatMessage(labels.destinationUrl)}
name="url"
rules={{ required: formatMessage(labels.required) }}
rules={{ required: formatMessage(labels.required), validate: checkUrl }}
>
<TextField placeholder="https://example.com" autoComplete="off" />
</FormField>
<FormField
name="slug"
rules={{
required: formatMessage(labels.required),
}}
style={{ display: 'none' }}
>
<input type="hidden" />
</FormField>
<Column>
<Label>{formatMessage(labels.link)}</Label>
<Row alignItems="center" gap>
<Text>{linkDomain || window.location.origin}/</Text>
<FormField
name="slug"
rules={{
required: formatMessage(labels.required),
}}
<TextField
value={`${hostUrl}/${slug}`}
autoComplete="off"
isReadOnly
allowCopy
style={{ width: '100%' }}
>
<TextField autoComplete="off" isReadOnly />
</FormField>
/>
<Button
variant="quiet"
onPress={() => setValue('slug', generateId(), { shouldDirty: true })}
onPress={() => setValue('slug', handleSlug(), { shouldDirty: true })}
>
<Icon>
<Refresh />

View file

@ -5,12 +5,13 @@ import { DateDistance } from '@/components/common/DateDistance';
import { ExternalLink } from '@/components/common/ExternalLink';
import { LinkEditButton } from './LinkEditButton';
import { LinkDeleteButton } from './LinkDeleteButton';
import { LINKS_URL } from '@/lib/constants';
export function LinksTable({ data = [] }) {
const { formatMessage, labels } = useMessages();
const { websiteId } = useNavigation();
const { linksUrl } = useConfig();
const hostUrl = linksUrl || `${window.location.origin}/x`;
const hostUrl = linksUrl || LINKS_URL;
if (data.length === 0) {
return <Empty />;

View file

@ -1,16 +1,17 @@
import Link from 'next/link';
import { DataTable, DataColumn, Row } from '@umami/react-zen';
import { useConfig, useMessages, useNavigation } from '@/components/hooks';
import { Empty } from '@/components/common/Empty';
import { DateDistance } from '@/components/common/DateDistance';
import { PixelEditButton } from './PixelEditButton';
import { PixelDeleteButton } from './PixelDeleteButton';
import Link from 'next/link';
import { PIXELS_URL } from '@/lib/constants';
export function PixelsTable({ data = [] }) {
const { formatMessage, labels } = useMessages();
const { websiteId } = useNavigation();
const { pixelsUrl } = useConfig();
const defaultUrl = `${window.location.origin}/p`;
const hostUrl = pixelsUrl || PIXELS_URL;
if (data.length === 0) {
return <Empty />;
@ -21,7 +22,7 @@ export function PixelsTable({ data = [] }) {
<DataColumn id="name" label={formatMessage(labels.name)} />
<DataColumn id="url" label="URL">
{({ slug }: any) => {
const url = `${pixelsUrl || defaultUrl}/${slug}`;
const url = `${hostUrl}/${slug}`;
return <Link href={url}>{url}</Link>;
}}
</DataColumn>