mirror of
https://github.com/umami-software/umami.git
synced 2026-02-08 14:47:14 +01:00
# Conflicts: # pnpm-lock.yaml
This commit is contained in:
commit
aefc36b476
64 changed files with 699 additions and 319 deletions
|
|
@ -4,13 +4,14 @@ import {
|
|||
Form,
|
||||
FormField,
|
||||
FormSubmitButton,
|
||||
Grid,
|
||||
Icon,
|
||||
Label,
|
||||
Loading,
|
||||
Row,
|
||||
TextField,
|
||||
} from '@umami/react-zen';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useConfig, useLinkQuery, useMessages } from '@/components/hooks';
|
||||
import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery';
|
||||
import { RefreshCw } from '@/components/icons';
|
||||
|
|
@ -42,7 +43,7 @@ export function LinkEditForm({
|
|||
const { linksUrl } = useConfig();
|
||||
const hostUrl = linksUrl || LINKS_URL;
|
||||
const { data, isLoading } = useLinkQuery(linkId);
|
||||
const [slug, setSlug] = useState(generateId());
|
||||
const [defaultSlug] = useState(generateId());
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
await mutateAsync(data, {
|
||||
|
|
@ -55,14 +56,6 @@ export function LinkEditForm({
|
|||
});
|
||||
};
|
||||
|
||||
const handleSlug = () => {
|
||||
const slug = generateId();
|
||||
|
||||
setSlug(slug);
|
||||
|
||||
return slug;
|
||||
};
|
||||
|
||||
const checkUrl = (url: string) => {
|
||||
if (!isValidUrl(url)) {
|
||||
return formatMessage(labels.invalidUrl);
|
||||
|
|
@ -70,19 +63,19 @@ export function LinkEditForm({
|
|||
return true;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setSlug(data.slug);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
if (linkId && isLoading) {
|
||||
return <Loading placement="absolute" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={getErrorMessage(error)} defaultValues={{ slug, ...data }}>
|
||||
{({ setValue }) => {
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
error={getErrorMessage(error)}
|
||||
defaultValues={{ slug: defaultSlug, ...data }}
|
||||
>
|
||||
{({ setValue, watch }) => {
|
||||
const slug = watch('slug');
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
|
|
@ -101,15 +94,25 @@ export function LinkEditForm({
|
|||
<TextField placeholder="https://example.com" autoComplete="off" />
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
name="slug"
|
||||
rules={{
|
||||
required: formatMessage(labels.required),
|
||||
}}
|
||||
style={{ display: 'none' }}
|
||||
>
|
||||
<input type="hidden" />
|
||||
</FormField>
|
||||
<Grid columns="1fr auto" alignItems="end" gap>
|
||||
<FormField
|
||||
name="slug"
|
||||
label={formatMessage({ id: 'label.slug', defaultMessage: 'Slug' })}
|
||||
rules={{
|
||||
required: formatMessage(labels.required),
|
||||
}}
|
||||
>
|
||||
<TextField autoComplete="off" />
|
||||
</FormField>
|
||||
<Button
|
||||
variant="quiet"
|
||||
onPress={() => setValue('slug', generateId(), { shouldDirty: true })}
|
||||
>
|
||||
<Icon>
|
||||
<RefreshCw />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.link)}</Label>
|
||||
|
|
@ -121,14 +124,6 @@ export function LinkEditForm({
|
|||
allowCopy
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<Button
|
||||
variant="quiet"
|
||||
onPress={() => setValue('slug', handleSlug(), { shouldDirty: true })}
|
||||
>
|
||||
<Icon>
|
||||
<RefreshCw />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Row>
|
||||
</Column>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import type { Metadata } from 'next';
|
||||
import { getLink } from '@/queries/prisma';
|
||||
import { LinkPage } from './LinkPage';
|
||||
|
||||
export default async function ({ params }: { params: Promise<{ linkId: string }> }) {
|
||||
const { linkId } = await params;
|
||||
const link = await getLink(linkId);
|
||||
|
||||
if (!link || link?.deletedAt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <LinkPage linkId={linkId} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import type { Metadata } from 'next';
|
||||
import { getPixel } from '@/queries/prisma';
|
||||
import { PixelPage } from './PixelPage';
|
||||
|
||||
export default async function ({ params }: { params: { pixelId: string } }) {
|
||||
const { pixelId } = await params;
|
||||
const pixel = await getPixel(pixelId);
|
||||
|
||||
if (!pixel || pixel?.deletedAt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <PixelPage pixelId={pixelId} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { DateRangeSetting } from './DateRangeSetting';
|
|||
import { LanguageSetting } from './LanguageSetting';
|
||||
import { ThemeSetting } from './ThemeSetting';
|
||||
import { TimezoneSetting } from './TimezoneSetting';
|
||||
import { VersionSetting } from './VersionSetting';
|
||||
|
||||
export function PreferenceSettings() {
|
||||
const { user } = useLoginQuery();
|
||||
|
|
@ -31,6 +32,10 @@ export function PreferenceSettings() {
|
|||
<Label>{formatMessage(labels.theme)}</Label>
|
||||
<ThemeSetting />
|
||||
</Column>
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.version)}</Label>
|
||||
<VersionSetting />
|
||||
</Column>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
8
src/app/(main)/settings/preferences/VersionSetting.tsx
Normal file
8
src/app/(main)/settings/preferences/VersionSetting.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
'use client';
|
||||
|
||||
import { Text } from '@umami/react-zen';
|
||||
import { CURRENT_VERSION } from '@/lib/constants';
|
||||
|
||||
export function VersionSetting() {
|
||||
return <Text>{CURRENT_VERSION}</Text>;
|
||||
}
|
||||
|
|
@ -12,9 +12,10 @@ import { ListTable } from '@/components/metrics/ListTable';
|
|||
import { MetricCard } from '@/components/metrics/MetricCard';
|
||||
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
||||
import { renderDateLabels } from '@/lib/charts';
|
||||
import { CHART_COLORS } from '@/lib/constants';
|
||||
import { CHART_COLORS, CURRENCY_CONFIG, DEFAULT_CURRENCY } from '@/lib/constants';
|
||||
import { generateTimeSeries } from '@/lib/date';
|
||||
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
|
||||
import { getItem, setItem } from '@/lib/storage';
|
||||
|
||||
export interface RevenueProps {
|
||||
websiteId: string;
|
||||
|
|
@ -24,7 +25,15 @@ export interface RevenueProps {
|
|||
}
|
||||
|
||||
export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
||||
const [currency, setCurrency] = useState('USD');
|
||||
const [currency, setCurrency] = useState(
|
||||
getItem(CURRENCY_CONFIG) || process.env.defaultCurrency || DEFAULT_CURRENCY,
|
||||
);
|
||||
|
||||
const handleCurrencyChange = (value: string) => {
|
||||
setCurrency(value);
|
||||
setItem(CURRENCY_CONFIG, value);
|
||||
};
|
||||
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { locale, dateLocale } = useLocale();
|
||||
const { countryNames } = useCountryNames(locale);
|
||||
|
|
@ -107,7 +116,7 @@ export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
|||
return (
|
||||
<Column gap>
|
||||
<Grid columns="280px" gap>
|
||||
<CurrencySelect value={currency} onChange={setCurrency} />
|
||||
<CurrencySelect value={currency} onChange={handleCurrencyChange} />
|
||||
</Grid>
|
||||
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
||||
{data && (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { Metadata } from 'next';
|
||||
import { WebsiteLayout } from '@/app/(main)/websites/[websiteId]/WebsiteLayout';
|
||||
import { getWebsite } from '@/queries/prisma';
|
||||
|
||||
export default async function ({
|
||||
children,
|
||||
|
|
@ -9,6 +10,11 @@ export default async function ({
|
|||
params: Promise<{ websiteId: string }>;
|
||||
}) {
|
||||
const { websiteId } = await params;
|
||||
const website = await getWebsite(websiteId);
|
||||
|
||||
if (!website || website?.deletedAt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <WebsiteLayout websiteId={websiteId}>{children}</WebsiteLayout>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,5 +17,6 @@ export async function GET(request: Request) {
|
|||
telemetryDisabled: !!process.env.DISABLE_TELEMETRY,
|
||||
trackerScriptName: process.env.TRACKER_SCRIPT_NAME,
|
||||
updatesDisabled: !!process.env.DISABLE_UPDATES,
|
||||
currentVersion: !!process.env.currentVersion,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ export async function POST(request: Request) {
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||
const filters = await getQueryFilters(body.filters, websiteId);
|
||||
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
|
||||
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getAttribution(websiteId, parameters as AttributionParameters, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ export async function POST(request: Request) {
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||
const filters = await getQueryFilters(body.filters, websiteId);
|
||||
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
|
||||
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getBreakdown(websiteId, parameters as BreakdownParameters, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ export async function POST(request: Request) {
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||
const filters = await getQueryFilters(body.filters, websiteId);
|
||||
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
|
||||
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getFunnel(websiteId, parameters as FunnelParameters, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ export async function POST(request: Request) {
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||
const filters = await getQueryFilters(body.filters, websiteId);
|
||||
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
|
||||
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getGoal(websiteId, parameters as GoalParameters, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ export async function POST(request: Request) {
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(body.filters, websiteId);
|
||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
|
||||
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
|
||||
|
||||
const data = await getRetention(websiteId, parameters as RetentionParameters, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ export async function POST(request: Request) {
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||
const filters = await getQueryFilters(body.filters, websiteId);
|
||||
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
|
||||
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getRevenue(websiteId, parameters as RevenuParameters, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ export async function POST(request: Request) {
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(body.filters, websiteId);
|
||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id);
|
||||
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters);
|
||||
|
||||
const data = {
|
||||
utm_source: [],
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getEventDataEvents(websiteId, {
|
||||
...filters,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getEventDataFields(websiteId, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getEventDataProperties(websiteId, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getEventDataStats(websiteId, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { propertyName } = query;
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getEventDataValues(websiteId, {
|
||||
...filters,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getWebsiteEvents(websiteId, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getEventStats(websiteId, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const [events, pages, referrers, browsers, os, devices, countries] = await Promise.all([
|
||||
getEventMetrics(websiteId, { type: 'event' }, filters),
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { type, limit, offset, search } = query;
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
if (search) {
|
||||
filters[type] = `c.${search}`;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { type, limit, offset, search } = query;
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
if (search) {
|
||||
filters[type] = `c.${search}`;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const [pageviews, sessions] = await Promise.all([
|
||||
getPageviewStats(websiteId, filters),
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getSessionDataProperties(websiteId, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export async function GET(
|
|||
}
|
||||
|
||||
const { propertyName } = query;
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getSessionDataValues(websiteId, {
|
||||
...filters,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getSessionActivity(websiteId, sessionId, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getWebsiteSessions(websiteId, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const metrics = await getWebsiteSessionStats(websiteId, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getWeeklyTraffic(websiteId, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
|
||||
const data = await getWebsiteStats(websiteId, filters);
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export async function GET(
|
|||
value: segment.name,
|
||||
}));
|
||||
} else {
|
||||
const filters = await getQueryFilters(query, websiteId);
|
||||
const filters = await getQueryFilters(query, websiteId, auth.user?.id);
|
||||
values = await getValues(websiteId, FILTER_COLUMNS[type], filters);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from 'zod';
|
||||
import { uuid } from '@/lib/crypto';
|
||||
import redis from '@/lib/redis';
|
||||
import { fetchAccount } from '@/lib/load';
|
||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||
import { json, unauthorized } from '@/lib/response';
|
||||
import { pagingParams, searchParams } from '@/lib/schema';
|
||||
|
|
@ -52,7 +52,7 @@ export async function POST(request: Request) {
|
|||
const { id, name, domain, shareId, teamId } = body;
|
||||
|
||||
if (process.env.CLOUD_MODE && !teamId) {
|
||||
const account = await redis.client.get(`account:${auth.user.id}`);
|
||||
const account = await fetchAccount(auth.user.id);
|
||||
|
||||
if (!account?.hasSubscription) {
|
||||
const count = await getWebsiteCount(auth.user.id);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue