mirror of
https://github.com/umami-software/umami.git
synced 2025-12-08 05:12:36 +01:00
Pixel editing.
This commit is contained in:
parent
eabdd18604
commit
d130242a0a
23 changed files with 72 additions and 49 deletions
|
|
@ -7,19 +7,17 @@ import { useApi, useMessages, useModified } from '@/components/hooks';
|
|||
|
||||
export function PixelDeleteButton({
|
||||
pixelId,
|
||||
websiteId,
|
||||
name,
|
||||
onSave,
|
||||
}: {
|
||||
pixelId: string;
|
||||
websiteId: string;
|
||||
name: string;
|
||||
onSave?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { del, useMutation } = useApi();
|
||||
const { mutate, isPending, error } = useMutation({
|
||||
mutationFn: () => del(`/websites/${websiteId}/pixels/${pixelId}`),
|
||||
mutationFn: () => del(`/pixels/${pixelId}`),
|
||||
});
|
||||
const { touch } = useModified();
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export function PixelEditButton({ pixelId }: { pixelId: string }) {
|
|||
|
||||
return (
|
||||
<ActionButton title={formatMessage(labels.edit)} icon={<Edit />}>
|
||||
<Dialog title={formatMessage(labels.pixel)} style={{ width: 800 }}>
|
||||
<Dialog title={formatMessage(labels.pixel)} style={{ width: 800, minHeight: 300 }}>
|
||||
{({ close }) => {
|
||||
return <PixelEditForm pixelId={pixelId} onClose={close} />;
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {
|
|||
Row,
|
||||
TextField,
|
||||
Button,
|
||||
Text,
|
||||
Label,
|
||||
Column,
|
||||
Icon,
|
||||
|
|
@ -16,6 +15,8 @@ import { useMessages } from '@/components/hooks';
|
|||
import { Refresh } from '@/components/icons';
|
||||
import { getRandomChars } from '@/lib/crypto';
|
||||
import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { PIXELS_URL } from '@/lib/constants';
|
||||
|
||||
const generateId = () => getRandomChars(9);
|
||||
|
||||
|
|
@ -31,29 +32,48 @@ export function PixelEditForm({
|
|||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { mutate, error, isPending } = useUpdateQuery('/pixels', { id: pixelId, teamId });
|
||||
const { pixelDomain } = useConfig();
|
||||
const { mutate, error, isPending, touch } = useUpdateQuery(
|
||||
pixelId ? `/pixels/${pixelId}` : '/pixels',
|
||||
{
|
||||
id: pixelId,
|
||||
teamId,
|
||||
},
|
||||
);
|
||||
const { pixelsUrl } = useConfig();
|
||||
const hostUrl = pixelsUrl || PIXELS_URL;
|
||||
const { data, isLoading } = usePixelQuery(pixelId);
|
||||
const [slug, setSlug] = useState(generateId());
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
mutate(data, {
|
||||
onSuccess: async () => {
|
||||
touch('pixels');
|
||||
onSave?.();
|
||||
onClose?.();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (pixelId && !isLoading) {
|
||||
const handleSlug = () => {
|
||||
const slug = generateId();
|
||||
|
||||
setSlug(slug);
|
||||
|
||||
return slug;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setSlug(data.slug);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
if (pixelId && 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 (
|
||||
<>
|
||||
|
|
@ -65,22 +85,29 @@ export function PixelEditForm({
|
|||
<TextField autoComplete="off" />
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
name="slug"
|
||||
rules={{
|
||||
required: formatMessage(labels.required),
|
||||
}}
|
||||
style={{ display: 'none' }}
|
||||
>
|
||||
<input type="hidden" />
|
||||
</FormField>
|
||||
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.pixel)}</Label>
|
||||
<Label>{formatMessage(labels.link)}</Label>
|
||||
<Row alignItems="center" gap>
|
||||
<Text>{pixelDomain || 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 />
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import Link from 'next/link';
|
||||
import { DataTable, DataColumn, Row } from '@umami/react-zen';
|
||||
import { useConfig, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { useConfig, useMessages } from '@/components/hooks';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { DateDistance } from '@/components/common/DateDistance';
|
||||
import { PixelEditButton } from './PixelEditButton';
|
||||
import { PixelDeleteButton } from './PixelDeleteButton';
|
||||
import { PIXELS_URL } from '@/lib/constants';
|
||||
import { ExternalLink } from '@/components/common/ExternalLink';
|
||||
|
||||
export function PixelsTable({ data = [] }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { websiteId } = useNavigation();
|
||||
const { pixelsUrl } = useConfig();
|
||||
const hostUrl = pixelsUrl || PIXELS_URL;
|
||||
|
||||
|
|
@ -23,7 +22,7 @@ export function PixelsTable({ data = [] }) {
|
|||
<DataColumn id="url" label="URL">
|
||||
{({ slug }: any) => {
|
||||
const url = `${hostUrl}/${slug}`;
|
||||
return <Link href={url}>{url}</Link>;
|
||||
return <ExternalLink href={url}>{url}</ExternalLink>;
|
||||
}}
|
||||
</DataColumn>
|
||||
<DataColumn id="created" label={formatMessage(labels.created)}>
|
||||
|
|
@ -36,7 +35,7 @@ export function PixelsTable({ data = [] }) {
|
|||
return (
|
||||
<Row>
|
||||
<PixelEditButton pixelId={id} />
|
||||
<PixelDeleteButton pixelId={id} websiteId={websiteId} name={name} />
|
||||
<PixelDeleteButton pixelId={id} name={name} />
|
||||
</Row>
|
||||
);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { createContext, ReactNode } from 'react';
|
||||
import { useWebsiteQuery } from '@/components/hooks';
|
||||
import { Loading } from '@umami/react-zen';
|
||||
import { Website } from '@prisma/client';
|
||||
import { Website } from '@/generated/prisma/client';
|
||||
|
||||
export const WebsiteContext = createContext<Website>(null);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ export async function GET(request: Request, { params }: { params: Promise<{ pixe
|
|||
export async function POST(request: Request, { params }: { params: Promise<{ pixelId: string }> }) {
|
||||
const schema = z.object({
|
||||
name: z.string(),
|
||||
url: z.string().url(),
|
||||
slug: z.string().min(8),
|
||||
});
|
||||
|
||||
|
|
@ -36,14 +35,14 @@ export async function POST(request: Request, { params }: { params: Promise<{ pix
|
|||
}
|
||||
|
||||
const { pixelId } = await params;
|
||||
const { name, domain, shareId } = body;
|
||||
const { name, slug } = body;
|
||||
|
||||
if (!(await canUpdatePixel(auth, pixelId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
try {
|
||||
const pixel = await updatePixel(pixelId, { name, domain, shareId });
|
||||
const pixel = await updatePixel(pixelId, { name, slug });
|
||||
|
||||
return Response.json(pixel);
|
||||
} catch (e: any) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { json, unauthorized } from '@/lib/response';
|
|||
import { uuid } from '@/lib/crypto';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { pagingParams, searchParams } from '@/lib/schema';
|
||||
import { createPixel, getUserLinks } from '@/queries';
|
||||
import { createPixel, getUserPixels } from '@/queries';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const schema = z.object({
|
||||
|
|
@ -20,7 +20,7 @@ export async function GET(request: Request) {
|
|||
|
||||
const filters = await getQueryFilters(query);
|
||||
|
||||
const links = await getUserLinks(auth.user.id, filters);
|
||||
const links = await getUserPixels(auth.user.id, filters);
|
||||
|
||||
return json(links);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import Link from 'next/link';
|
|||
import { Icon, Row, Text } from '@umami/react-zen';
|
||||
import { ExternalLink as LinkIcon } from '@/components/icons';
|
||||
|
||||
export function ExternalLink({ href, children, ...props }: Icon) {
|
||||
export function ExternalLink({ href, children, ...props }) {
|
||||
return (
|
||||
<Row alignItems="center" overflow="hidden" gap>
|
||||
<Text title={href} truncate>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export function usePixelQuery(pixelId: string) {
|
|||
return useQuery({
|
||||
queryKey: ['pixel', { pixelId, modified }],
|
||||
queryFn: () => {
|
||||
return get(`/pixel/${pixelId}`);
|
||||
return get(`/pixels/${pixelId}`);
|
||||
},
|
||||
enabled: !!pixelId,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Website, Session } from '@prisma/client';
|
||||
import { Website, Session } from '@/generated/prisma/client';
|
||||
import redis from '@/lib/redis';
|
||||
import { getWebsiteSession, getWebsite } from '@/queries';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Prisma, Link } from '@prisma/client';
|
||||
import { Prisma, Link } from '@/generated/prisma/client';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { PageResult, QueryFilters } from '@/lib/types';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Prisma, Pixel } from '@prisma/client';
|
||||
import { Prisma, Pixel } from '@/generated/prisma/client';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { PageResult, QueryFilters } from '@/lib/types';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Prisma, Report } from '@prisma/client';
|
||||
import { Prisma, Report } from '@/generated/prisma/client';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { PageResult, QueryFilters } from '@/lib/types';
|
||||
import ReportFindManyArgs = Prisma.ReportFindManyArgs;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import prisma from '@/lib/prisma';
|
||||
import { Prisma, Segment } from '@prisma/client';
|
||||
import { Prisma, Segment } from '@/generated/prisma/client';
|
||||
|
||||
async function findSegment(criteria: Prisma.SegmentFindUniqueArgs): Promise<Segment> {
|
||||
return prisma.client.Segment.findUnique(criteria);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Prisma, Team } from '@prisma/client';
|
||||
import { Prisma, Team } from '@/generated/prisma/client';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { uuid } from '@/lib/crypto';
|
||||
import prisma from '@/lib/prisma';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Prisma, TeamUser } from '@prisma/client';
|
||||
import { Prisma, TeamUser } from '@/generated/prisma/client';
|
||||
import { uuid } from '@/lib/crypto';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { PageResult, QueryFilters } from '@/lib/types';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Prisma, User } from '@prisma/client';
|
||||
import { Prisma, User } from '@/generated/prisma/client';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { PageResult, Role, QueryFilters } from '@/lib/types';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Prisma, Website } from '@prisma/client';
|
||||
import { Prisma, Website } from '@/generated/prisma/client';
|
||||
import redis from '@/lib/redis';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { PageResult, QueryFilters } from '@/lib/types';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { EventData } from '@prisma/client';
|
||||
import { EventData } from '@/generated/prisma/client';
|
||||
import prisma from '@/lib/prisma';
|
||||
import clickhouse from '@/lib/clickhouse';
|
||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Prisma } from '@prisma/client';
|
||||
import { Prisma } from '@/generated/prisma/client';
|
||||
import prisma from '@/lib/prisma';
|
||||
|
||||
export async function createSession(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Auth } from '@/lib/types';
|
||||
import { Report } from '@prisma/client';
|
||||
import { Report } from '@/generated/prisma/client';
|
||||
import { canViewWebsite } from './website';
|
||||
|
||||
export async function canViewReport(auth: Auth, report: Report) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue