🧹 clean up duty 🧹

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-03-28 10:40:49 +01:00
parent 677ae32c84
commit 444e553b74
127 changed files with 218 additions and 5284 deletions

View File

@@ -4,7 +4,6 @@
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }],
"eslint.workingDirectories": [
{ "pattern": "apps/*/" },
{ "pattern": "packages/*/" },

View File

@@ -1,4 +1,4 @@
# openpanel
# Openpanel
Openpanel is a simple analytics tool for logging events on web and react-native. My goal is to make a minimal mixpanel copy with the most basic features (for now).

View File

@@ -1,224 +0,0 @@
import { omit, prop, uniqBy } from 'ramda';
import { generateProfileId, getTime, toISOString } from '@openpanel/common';
import type { Event, IServiceCreateEventPayload } from '@openpanel/db';
import {
createEvent as createClickhouseEvent,
db,
formatClickhouseDate,
getSalts,
} from '@openpanel/db';
import { parseIp } from '../src/utils/parseIp';
import { parseUserAgent } from '../src/utils/parseUserAgent';
const clean = omit([
'ip',
'os',
'ua',
'url',
'hash',
'host',
'path',
'device',
'screen',
'hostname',
'language',
'referrer',
'timezone',
]);
async function main() {
const events = await db.event.findMany({
where: {
project_id: '4e2798cb-e255-4e9d-960d-c9ad095aabd7',
name: 'screen_view',
createdAt: {
gte: new Date('2024-01-01'),
lt: new Date('2024-02-04'),
},
},
orderBy: {
createdAt: 'asc',
},
});
const grouped: Record<string, Event[]> = {};
let index = 0;
for (const event of events.slice()) {
// console.log(index, event.name, event.createdAt.toISOString());
index++;
const properties = event.properties as Record<string, any>;
if (properties.ua?.includes('bot')) {
// console.log('IGNORE', event.id);
continue;
}
if (!event.profile_id) {
// console.log('IGNORE', event.id);
continue;
}
const hej = grouped[event.profile_id];
if (hej) {
hej.push(event);
} else {
grouped[event.profile_id] = [event];
}
}
console.log('Total users', Object.keys(grouped).length);
let uidx = -1;
for (const profile_id of Object.keys(grouped)) {
uidx++;
console.log(`User index ${uidx}`);
const events = uniqBy(prop('createdAt'), grouped[profile_id] || []);
if (events) {
let lastSessionStart = null;
let screenViews = 0;
let totalDuration = 0;
console.log('new user...');
let eidx = -1;
for (const event of events) {
eidx++;
const prevEvent = events[eidx - 1];
const prevEventAt = prevEvent?.createdAt;
const nextEvent = events[eidx + 1];
const properties = event.properties as Record<string, any>;
const projectId = event.project_id;
const path = properties.path!;
const ip = properties.ip!;
const origin = 'https://openpanel.kiddo.se';
const ua = properties.ua!;
const uaInfo = parseUserAgent(ua);
const salts = await getSalts();
const profileId = generateProfileId({
salt: salts.current,
origin,
ip,
ua,
});
const geo = parseIp(ip);
const isNextEventNewSession =
nextEvent &&
nextEvent.createdAt.getTime() - event.createdAt.getTime() >
1000 * 60 * 30;
const payload: IServiceCreateEventPayload = {
name: event.name,
profileId,
projectId,
properties: clean(properties),
createdAt: event.createdAt.toISOString(),
country: geo.country,
city: geo.city,
region: geo.region,
continent: geo.continent,
os: uaInfo.os,
osVersion: uaInfo.osVersion,
browser: uaInfo.browser,
browserVersion: uaInfo.browserVersion,
device: uaInfo.device,
brand: uaInfo.brand,
model: uaInfo.model,
duration:
nextEvent && !isNextEventNewSession
? nextEvent.createdAt.getTime() - event.createdAt.getTime()
: 0,
path,
referrer: properties?.referrer?.host ?? '', // TODO
referrerName: properties?.referrer?.host ?? '', // TODO
};
if (!prevEventAt) {
lastSessionStart = await createSessionStart(payload);
} else if (
event.createdAt.getTime() - prevEventAt.getTime() >
1000 * 60 * 30
) {
if (eidx > 0 && prevEventAt && lastSessionStart) {
await createSessionEnd(prevEventAt, lastSessionStart, {
screenViews,
totalDuration,
});
totalDuration = 0;
screenViews = 0;
lastSessionStart = await createSessionStart(payload);
}
}
screenViews++;
totalDuration += payload.duration;
await createEvent(payload);
} // for each user event
const prevEventAt = events[events.length - 1]?.createdAt;
if (prevEventAt && lastSessionStart) {
await createSessionEnd(prevEventAt, lastSessionStart, {
screenViews,
totalDuration,
});
}
}
}
}
async function createEvent(event: IServiceCreateEventPayload) {
console.log(
`Create ${event.name} - ${event.path} - ${formatClickhouseDate(
event.createdAt
)} - ${event.duration / 1000} sec`
);
await createClickhouseEvent(event);
}
async function createSessionStart(event: IServiceCreateEventPayload) {
const session: IServiceCreateEventPayload = {
...event,
duration: 0,
name: 'session_start',
createdAt: toISOString(getTime(event.createdAt) - 100),
};
await createEvent(session);
return session;
}
async function createSessionEnd(
prevEventAt: Date,
sessionStart: IServiceCreateEventPayload,
options: {
screenViews: number;
totalDuration: number;
}
) {
const properties: Record<string, unknown> = {};
if (options.screenViews === 1) {
properties.__bounce = true;
} else {
properties.__bounce = false;
}
const session: IServiceCreateEventPayload = {
...sessionStart,
properties: {
...properties,
...sessionStart.properties,
},
duration: options.totalDuration,
name: 'session_end',
createdAt: toISOString(prevEventAt.getTime() + 10),
};
await createEvent(session);
return session;
}
main();

View File

@@ -1,18 +0,0 @@
//@ts-nocheck
async function main() {
const crypto = require('crypto');
function createHash(data, len) {
return crypto
.createHash('shake256', { outputLength: len })
.update(data)
.digest('hex');
}
console.log(createHash('foo', 2));
// 1af9
console.log(createHash('foo', 32));
// 1af97f7818a28edf}
}
main();

View File

@@ -18,7 +18,7 @@ import type { PostEventPayload } from '@openpanel/sdk';
const SESSION_TIMEOUT = 1000 * 60 * 30;
const SESSION_END_TIMEOUT = SESSION_TIMEOUT + 1000;
async function withTiming<T>(name: string, promise: T) {
async function withTiming<T>(name: string, promise: Promise<T>) {
try {
const start = Date.now();
const res = await promise;

View File

@@ -8,5 +8,5 @@
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["."],
"exclude": ["node_modules"]
"exclude": ["node_modules", "dist"]
}

View File

@@ -1,11 +1,11 @@
'use client';
import { api, handleErrorToastOptions } from '@/app/_trpc/client';
import { Card, CardActions, CardActionsItem } from '@/components/card';
import { FullPageEmptyState } from '@/components/full-page-empty-state';
import { Button } from '@/components/ui/button';
import { useAppParams } from '@/hooks/useAppParams';
import { pushModal } from '@/modals';
import { api, handleErrorToastOptions } from '@/trpc/client';
import { LayoutPanelTopIcon, Pencil, PlusIcon, Trash } from 'lucide-react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';

View File

@@ -184,7 +184,7 @@ export function EventDetails({ event, open, setOpen }: Props) {
key={item.name}
name={item.name}
value={item.value}
onClick={item.onClick}
onClick={() => item.onClick?.()}
/>
))}
</div>
@@ -225,7 +225,7 @@ export function EventDetails({ event, open, setOpen }: Props) {
className="w-full"
onClick={() => setIsEditOpen(true)}
>
Customize "{name}"
Customize &quot;{name}&quot;
</Button>
</SheetFooter>
</SheetContent>

View File

@@ -1,6 +1,5 @@
import type { Dispatch, SetStateAction } from 'react';
import { useEffect, useState } from 'react';
import { api } from '@/app/_trpc/client';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Label } from '@/components/ui/label';
@@ -11,6 +10,7 @@ import {
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet';
import { api } from '@/trpc/client';
import { cn } from '@/utils/cn';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
@@ -78,7 +78,7 @@ export function EventEdit({ event, open, setOpen }: Props) {
<Sheet open={open} onOpenChange={setOpen}>
<SheetContent>
<SheetHeader>
<SheetTitle>Edit "{name}"</SheetTitle>
<SheetTitle>Edit &quot;{name}&quot;</SheetTitle>
</SheetHeader>
<div className="my-8 flex flex-col gap-8">
<div>

View File

@@ -1,5 +1,4 @@
import { useEffect, useState } from 'react';
import { api } from '@/app/_trpc/client';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Label } from '@/components/ui/label';
@@ -12,6 +11,7 @@ import {
SheetTrigger,
} from '@/components/ui/sheet';
import { Tooltip, TooltipContent } from '@/components/ui/tooltip';
import { api } from '@/trpc/client';
import { cn } from '@/utils/cn';
import { TooltipTrigger } from '@radix-ui/react-tooltip';
import type { VariantProps } from 'class-variance-authority';

View File

@@ -36,7 +36,7 @@ export default function LayoutOrganizationSelector({
.filter((item) => item.slug)
.map((item) => ({
label: item.name,
value: item.slug!,
value: item.slug,
})) ?? []
}
onChange={(value) => {

View File

@@ -1,9 +1,9 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { Widget, WidgetBody, WidgetHead } from '@/components/widget';
import { api, handleError } from '@/trpc/client';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';

View File

@@ -1,4 +1,3 @@
import { api } from '@/app/_trpc/client';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
@@ -15,6 +14,7 @@ import {
SheetTrigger,
} from '@/components/ui/sheet';
import { useAppParams } from '@/hooks/useAppParams';
import { api } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { PlusIcon, SendIcon } from 'lucide-react';
import { useRouter } from 'next/navigation';

View File

@@ -1,7 +1,6 @@
'use client';
import { useState } from 'react';
import { api } from '@/app/_trpc/client';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
import {
Table,
@@ -12,6 +11,7 @@ import {
TableRow,
} from '@/components/ui/table';
import { Widget, WidgetHead } from '@/components/widget';
import { api } from '@/trpc/client';
import type { IServiceMember, IServiceProject } from '@openpanel/db';

View File

@@ -1,9 +1,9 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { Widget, WidgetBody, WidgetHead } from '@/components/widget';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';

View File

@@ -5,6 +5,7 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { useAppParams } from '@/hooks/useAppParams';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { SaveIcon } from 'lucide-react';
import { useRouter } from 'next/navigation';
@@ -13,8 +14,6 @@ import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { z } from 'zod';
import { api, handleError } from '../../_trpc/client';
const validation = z.object({
name: z.string().min(1),
});

View File

@@ -5,7 +5,6 @@ import { notFound, redirect } from 'next/navigation';
import {
getCurrentProjects,
getOrganizationBySlug,
getProjectsByOrganizationSlug,
isWaitlistUserAccepted,
} from '@openpanel/db';
@@ -36,8 +35,8 @@ export default async function Page({ params: { organizationId } }: PageProps) {
<LogoSquare className="mb-8 w-20 md:w-28" />
<h1 className="text-3xl font-medium">Not quite there yet</h1>
<div className="text-lg">
We're still working on Openpanel, but we're not quite there yet.
We'll let you know when we're ready to go!
We&apos;re still working on Openpanel, but we&apos;re not quite
there yet. We&apos;ll let you know when we&apos;re ready to go!
</div>
</div>
</div>

View File

@@ -6,6 +6,7 @@ import { Button, buttonVariants } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { api, handleError } from '@/trpc/client';
import { cn } from '@/utils/cn';
import { zodResolver } from '@hookform/resolvers/zod';
import { SaveIcon, WallpaperIcon } from 'lucide-react';
@@ -14,8 +15,6 @@ import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { api, handleError } from '../_trpc/client';
const validation = z
.object({
organization: z.string().min(3),
@@ -61,8 +60,8 @@ export function CreateOrganization() {
<LogoSquare className="mb-4 w-20" />
<h1 className="text-3xl font-medium">Nice job!</h1>
<div className="mb-4">
You're ready to start using our SDK. Save the client ID and secret (if
you have any)
You&apos;re ready to start using our SDK. Save the client ID and
secret (if you have any)
</div>
<CreateClientSuccess {...mutation.data.client} />
<div className="mt-4 flex gap-4">

View File

@@ -18,8 +18,8 @@ export default async function Page() {
<LogoSquare className="mb-8 w-20 md:w-28" />
<h1 className="text-3xl font-medium">Not quite there yet</h1>
<div className="text-lg">
We're still working on Openpanel, but we're not quite there yet.
We'll let you know when we're ready to go!
We&apos;re still working on Openpanel, but we&apos;re not quite
there yet. We&apos;ll let you know when we&apos;re ready to go!
</div>
</div>
</div>

View File

@@ -32,6 +32,6 @@ export async function POST(request: Request) {
return Response.json({ message: 'Webhook received!' });
}
export async function GET() {
export function GET() {
return Response.json({ message: 'Hello World!' });
}

View File

@@ -1,4 +1,4 @@
import { appRouter } from '@/server/api/root';
import { appRouter } from '@/trpc/api/root';
import { getAuth } from '@clerk/nextjs/server';
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
@@ -7,7 +7,7 @@ const handler = (req: Request) =>
endpoint: '/api/trpc',
req,
router: appRouter,
async createContext({ req }) {
createContext({ req }) {
const session = getAuth(req as any);
return {
session,

View File

@@ -1,19 +0,0 @@
"use client";
import * as Sentry from "@sentry/nextjs";
import Error from "next/error";
import { useEffect } from "react";
export default function GlobalError({ error }) {
useEffect(() => {
Sentry.captureException(error);
}, [error]);
return (
<html>
<body>
<Error />
</body>
</html>
);
}

View File

@@ -0,0 +1,23 @@
'use client';
import { useEffect } from 'react';
import * as Sentry from '@sentry/nextjs';
export default function GlobalError({
error,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
Sentry.captureException(error);
}, [error]);
return (
<html lang="en">
<body>
<h1>Something went wrong</h1>
</body>
</html>
);
}

View File

@@ -1,11 +1,11 @@
'use client';
import React, { useRef, useState } from 'react';
import { api } from '@/app/_trpc/client';
import { TooltipProvider } from '@/components/ui/tooltip';
import { ModalProvider } from '@/modals';
import type { AppStore } from '@/redux';
import makeStore from '@/redux';
import { api } from '@/trpc/client';
import { ClerkProvider, useAuth } from '@clerk/nextjs';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpLink } from '@trpc/client';

View File

@@ -1,7 +1,7 @@
'use client';
import { api } from '@/app/_trpc/client';
import { pushModal, showConfirm } from '@/modals';
import { api } from '@/trpc/client';
import { clipboard } from '@/utils/clipboard';
import { MoreHorizontal } from 'lucide-react';
import { useRouter } from 'next/navigation';

View File

@@ -11,13 +11,17 @@ import {
import { MoonIcon, SunIcon } from 'lucide-react';
import { useTheme } from 'next-themes';
export default function DarkModeToggle() {
interface Props {
className?: string;
}
export default function DarkModeToggle({ className }: Props) {
const { setTheme } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" className={className}>
<SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>

View File

@@ -1,7 +1,7 @@
'use client';
import { Fragment } from 'react';
import { api } from '@/app/_trpc/client';
import { api } from '@/trpc/client';
import { cn } from '@/utils/cn';
import AnimateHeight from 'react-animate-height';
@@ -118,7 +118,7 @@ export function OverviewLiveHistogram({
</TooltipTrigger>
<TooltipContent side="top">
<div>{minute.count} active users</div>
<div>@ {new Date(minute.date).toLocaleTimeString()}</div>
<div>@ {new Date(minute.date).toLocaleTimeString()}</div>
</TooltipContent>
</Tooltip>
);

View File

@@ -1,7 +1,7 @@
'use client';
import { api } from '@/app/_trpc/client';
import { pushModal } from '@/modals';
import { api } from '@/trpc/client';
import { EyeIcon, Globe2Icon, LockIcon } from 'lucide-react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';

View File

@@ -1,7 +1,7 @@
'use client';
import { api } from '@/app/_trpc/client';
import { pushModal, showConfirm } from '@/modals';
import { api } from '@/trpc/client';
import { clipboard } from '@/utils/clipboard';
import { MoreHorizontal } from 'lucide-react';
import { useRouter } from 'next/navigation';

View File

@@ -1,10 +1,10 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
import { Button } from '@/components/ui/button';
import { useAppParams } from '@/hooks/useAppParams';
import { pushModal } from '@/modals';
import { useDispatch, useSelector } from '@/redux';
import { api, handleError } from '@/trpc/client';
import { SaveIcon } from 'lucide-react';
import { toast } from 'sonner';

View File

@@ -1,6 +1,6 @@
'use client';
import { api } from '@/app/_trpc/client';
import { api } from '@/trpc/client';
import type { IChartInput } from '@openpanel/validation';

View File

@@ -1,32 +0,0 @@
import airplane from '@/lottie/airplane.json';
import ballon from '@/lottie/ballon.json';
import noData from '@/lottie/no-data.json';
import { cn } from '@/utils/cn';
import type { LottieComponentProps } from 'lottie-react';
import Lottie from 'lottie-react';
const animations = {
airplane,
ballon,
noData,
};
type Animations = keyof typeof animations;
export const ChartAnimation = ({
name,
...props
}: Omit<LottieComponentProps, 'animationData'> & {
name: Animations;
}) => <Lottie animationData={animations[name]} loop={true} {...props} />;
export const ChartAnimationContainer = (
props: React.ButtonHTMLAttributes<HTMLDivElement>
) => (
<div
{...props}
className={cn(
'rounded-md border border-border bg-background p-8',
props.className
)}
/>
);

View File

@@ -9,7 +9,7 @@ import {
useMemo,
useState,
} from 'react';
import type { IChartSerie } from '@/server/api/routers/chart';
import type { IChartSerie } from '@/trpc/api/routers/chart';
import type { IChartInput } from '@openpanel/validation';

View File

@@ -1,8 +1,8 @@
'use client';
import type { IChartData } from '@/app/_trpc/client';
import { ColorSquare } from '@/components/color-square';
import { fancyMinutes, useNumber } from '@/hooks/useNumerFormatter';
import type { IChartData } from '@/trpc/client';
import { theme } from '@/utils/theme';
import AutoSizer from 'react-virtualized-auto-sizer';
import { Area, AreaChart } from 'recharts';

View File

@@ -1,9 +1,9 @@
import React from 'react';
import type { IChartData } from '@/app/_trpc/client';
import { useFormatDateInterval } from '@/hooks/useFormatDateInterval';
import { useNumber } from '@/hooks/useNumerFormatter';
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import type { IChartData } from '@/trpc/client';
import { getChartColor } from '@/utils/theme';
import {
Area,

View File

@@ -1,9 +1,9 @@
'use client';
import { useMemo } from 'react';
import type { IChartData } from '@/app/_trpc/client';
import { Progress } from '@/components/ui/progress';
import { useNumber } from '@/hooks/useNumerFormatter';
import type { IChartData } from '@/trpc/client';
import { cn } from '@/utils/cn';
import { getChartColor } from '@/utils/theme';

View File

@@ -1,9 +1,9 @@
import React from 'react';
import type { IChartData } from '@/app/_trpc/client';
import { useFormatDateInterval } from '@/hooks/useFormatDateInterval';
import { useNumber } from '@/hooks/useNumerFormatter';
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import type { IChartData } from '@/trpc/client';
import { getChartColor, theme } from '@/utils/theme';
import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts';

View File

@@ -1,11 +1,11 @@
'use client';
import React from 'react';
import type { IChartData } from '@/app/_trpc/client';
import { useFormatDateInterval } from '@/hooks/useFormatDateInterval';
import { useNumber } from '@/hooks/useNumerFormatter';
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import type { IChartData } from '@/trpc/client';
import { getChartColor } from '@/utils/theme';
import {
CartesianGrid,

View File

@@ -1,6 +1,6 @@
import { useMemo } from 'react';
import type { IChartData } from '@/app/_trpc/client';
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import type { IChartData } from '@/trpc/client';
import { theme } from '@/utils/theme';
import WorldMap from 'react-svg-worldmap';
import AutoSizer from 'react-virtualized-auto-sizer';

View File

@@ -1,7 +1,7 @@
'use client';
import type { IChartData } from '@/app/_trpc/client';
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import type { IChartData } from '@/trpc/client';
import { cn } from '@/utils/cn';
import { useChartContext } from './ChartProvider';

View File

@@ -1,6 +1,6 @@
import type { IChartData } from '@/app/_trpc/client';
import { AutoSizer } from '@/components/react-virtualized-auto-sizer';
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import type { IChartData } from '@/trpc/client';
import { cn } from '@/utils/cn';
import { round } from '@/utils/math';
import { getChartColor } from '@/utils/theme';

View File

@@ -1,5 +1,4 @@
import * as React from 'react';
import type { IChartData } from '@/app/_trpc/client';
import { Pagination, usePagination } from '@/components/pagination';
import { Badge } from '@/components/ui/badge';
import { Checkbox } from '@/components/ui/checkbox';
@@ -20,6 +19,7 @@ import { useFormatDateInterval } from '@/hooks/useFormatDateInterval';
import { useMappings } from '@/hooks/useMappings';
import { useNumber } from '@/hooks/useNumerFormatter';
import { useSelector } from '@/redux';
import type { IChartData } from '@/trpc/client';
import { getChartColor } from '@/utils/theme';
import { PreviousDiffIndicator } from '../PreviousDiffIndicator';

View File

@@ -1,6 +1,5 @@
'use client';
import type { RouterOutputs } from '@/app/_trpc/client';
import {
Carousel,
CarouselContent,
@@ -8,6 +7,7 @@ import {
CarouselNext,
CarouselPrevious,
} from '@/components/ui/carousel';
import type { RouterOutputs } from '@/trpc/client';
import { cn } from '@/utils/cn';
import { round } from '@/utils/math';
import { ArrowRight, ArrowRightIcon } from 'lucide-react';

View File

@@ -1,7 +1,7 @@
'use client';
import type { RouterOutputs } from '@/app/_trpc/client';
import { api } from '@/app/_trpc/client';
import type { RouterOutputs } from '@/trpc/client';
import { api } from '@/trpc/client';
import type { IChartInput } from '@openpanel/validation';

View File

@@ -1,7 +1,7 @@
import { api } from '@/app/_trpc/client';
import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch } from '@/redux';
import { api } from '@/trpc/client';
import { cn } from '@/utils/cn';
import { DatabaseIcon } from 'lucide-react';

View File

@@ -1,10 +1,10 @@
'use client';
import { api } from '@/app/_trpc/client';
import { ColorSquare } from '@/components/color-square';
import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch, useSelector } from '@/redux';
import { api } from '@/trpc/client';
import { SplitIcon } from 'lucide-react';
import type { IChartBreakdown } from '@openpanel/validation';

View File

@@ -1,4 +1,3 @@
import { api } from '@/app/_trpc/client';
import { ColorSquare } from '@/components/color-square';
import { Button } from '@/components/ui/button';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
@@ -7,6 +6,7 @@ import { RenderDots } from '@/components/ui/RenderDots';
import { useAppParams } from '@/hooks/useAppParams';
import { useMappings } from '@/hooks/useMappings';
import { useDispatch } from '@/redux';
import { api } from '@/trpc/client';
import { SlidersHorizontal, Trash } from 'lucide-react';
import { operators } from '@openpanel/constants';

View File

@@ -1,7 +1,7 @@
import { api } from '@/app/_trpc/client';
import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch } from '@/redux';
import { api } from '@/trpc/client';
import { FilterIcon } from 'lucide-react';
import type { IChartEvent } from '@openpanel/validation';

View File

@@ -35,12 +35,14 @@ Alert.displayName = 'Alert';
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
>(({ className, children, ...props }, ref) => (
<h5
ref={ref}
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
{...props}
/>
>
{children}
</h5>
));
AlertTitle.displayName = 'AlertTitle';

View File

@@ -1,7 +1,8 @@
import * as React from 'react';
import { cn } from '@/utils/cn';
import * as TogglePrimitive from '@radix-ui/react-toggle';
import { cva, type VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import type { VariantProps } from 'class-variance-authority';
const toggleVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',

View File

@@ -1,4 +1,4 @@
import { api } from '@/app/_trpc/client';
import { api } from '@/trpc/client';
export function useEventNames(projectId: string) {
const query = api.chart.events.useQuery({

View File

@@ -1,4 +1,4 @@
import { api } from '@/app/_trpc/client';
import { api } from '@/trpc/client';
export function useEventProperties(projectId: string, event?: string) {
const query = api.chart.properties.useQuery({

View File

@@ -1,4 +1,4 @@
import { api } from '@/app/_trpc/client';
import { api } from '@/trpc/client';
export function useEventValues(
projectId: string,

View File

@@ -1,4 +1,4 @@
import { api } from '@/app/_trpc/client';
import { api } from '@/trpc/client';
export function useProfileProperties(projectId: string) {
const query = api.profile.properties.useQuery({

View File

@@ -1,4 +1,4 @@
import { api } from '@/app/_trpc/client';
import { api } from '@/trpc/client';
export function useProfileValues(projectId: string, property: string) {
const query = api.profile.values.useQuery({

View File

@@ -1,7 +1,7 @@
'use client';
import { useMemo } from 'react';
import type { IChartData, IChartSerieDataItem } from '@/app/_trpc/client';
import type { IChartData, IChartSerieDataItem } from '@/trpc/client';
import { getChartColor } from '@/utils/theme';
export type IRechartPayloadItem = IChartSerieDataItem & { color: string };

View File

@@ -1,7 +1,7 @@
'use client';
import { useEffect, useMemo, useState } from 'react';
import type { IChartData } from '@/app/_trpc/client';
import type { IChartData } from '@/trpc/client';
export type IVisibleSeries = ReturnType<typeof useVisibleSeries>['series'];
export function useVisibleSeries(data: IChartData, limit?: number | undefined) {

View File

@@ -1,714 +0,0 @@
{
"v": "5.5.8",
"fr": 50,
"ip": 0,
"op": 147,
"w": 800,
"h": 600,
"nm": "Paperplane",
"ddd": 0,
"assets": [
{
"id": "comp_0",
"layers": [
{
"ddd": 0,
"ind": 1,
"ty": 4,
"nm": "planete Outlines - Group 4",
"sr": 1,
"ks": {
"o": {
"a": 1,
"k": [
{
"i": { "x": [0.833], "y": [0.833] },
"o": { "x": [0.167], "y": [0.167] },
"t": 0,
"s": [0]
},
{
"i": { "x": [0.833], "y": [0.833] },
"o": { "x": [0.167], "y": [0.167] },
"t": 38,
"s": [50]
},
{
"i": { "x": [0.833], "y": [0.833] },
"o": { "x": [0.167], "y": [0.167] },
"t": 88,
"s": [50]
},
{ "t": 120, "s": [0] }
],
"ix": 11
},
"r": { "a": 0, "k": 0, "ix": 10 },
"p": {
"a": 1,
"k": [
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 0,
"s": [468.336, 323.378, 0],
"to": [-29, 0, 0],
"ti": [29, 0, 0]
},
{ "t": 102, "s": [294.336, 323.378, 0] }
],
"ix": 2
},
"a": { "a": 0, "k": [453.672, 304.756, 0], "ix": 1 },
"s": { "a": 0, "k": [50, 50, 100], "ix": 6 }
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[6.742, 0],
[0.741, -0.14],
[0, 0.074],
[13.484, 0],
[1.669, -0.361],
[19.79, 0],
[3.317, -19.082],
[2.691, 0],
[0, -13.484],
[-0.048, -0.629],
[2.405, 0],
[0, -6.742],
[-6.742, 0],
[0, 0],
[0, 6.743]
],
"o": [
[-0.781, 0],
[0.001, -0.074],
[0, -13.484],
[-1.778, 0],
[-3.594, -18.742],
[-20.03, 0],
[-2.421, -0.804],
[-13.485, 0],
[0, 0.642],
[-1.89, -1.199],
[-6.742, 0],
[0, 6.743],
[0, 0],
[6.742, 0],
[0, -6.742]
],
"v": [
[75.134, 16.175],
[72.85, 16.396],
[72.856, 16.175],
[48.44, -8.241],
[43.262, -7.685],
[3.406, -40.591],
[-36.571, -6.995],
[-44.269, -8.241],
[-68.685, 16.175],
[-68.604, 18.079],
[-75.133, 16.175],
[-87.341, 28.383],
[-75.133, 40.592],
[75.134, 40.592],
[87.342, 28.383]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.815686334348, 0.823529471603, 0.827451040231, 1],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": { "a": 0, "k": [453.672, 304.756], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 4",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
}
],
"ip": 0,
"op": 151,
"st": 0,
"bm": 0
},
{
"ddd": 0,
"ind": 2,
"ty": 4,
"nm": "Merged Shape Layer",
"sr": 1,
"ks": {
"o": { "a": 0, "k": 100, "ix": 11 },
"r": {
"a": 1,
"k": [
{
"i": { "x": [0.667], "y": [1] },
"o": { "x": [0.547], "y": [0] },
"t": 0,
"s": [0]
},
{
"i": { "x": [0.845], "y": [1] },
"o": { "x": [0.333], "y": [0] },
"t": 77,
"s": [35]
},
{ "t": 150, "s": [0] }
],
"ix": 10
},
"p": {
"a": 1,
"k": [
{
"i": { "x": 0.667, "y": 1 },
"o": { "x": 0.333, "y": 0 },
"t": 0,
"s": [390.319, 298.2, 0],
"to": [0, -2.583, 0],
"ti": [0, 0, 0]
},
{
"i": { "x": 0.667, "y": 1 },
"o": { "x": 0.333, "y": 0 },
"t": 44,
"s": [390.319, 282.7, 0],
"to": [0, 0, 0],
"ti": [0, 0, 0]
},
{
"i": { "x": 0.667, "y": 1 },
"o": { "x": 0.333, "y": 0 },
"t": 110,
"s": [390.319, 319.25, 0],
"to": [0, 0, 0],
"ti": [0, 0, 0]
},
{ "t": 150, "s": [390.319, 298.2, 0] }
],
"ix": 2
},
"a": { "a": 0, "k": [664.319, 256.2, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100, 100], "ix": 6 }
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[0, 0],
[0, 0],
[0, 0]
],
"o": [
[0, 0],
[0, 0],
[0, 0]
],
"v": [
[18.967, -3.189],
[-18.967, 19.935],
[-0.949, -19.935]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [
0.223528981209, 0.192156970501, 0.674510002136, 1
],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": { "a": 0, "k": [236.879, 292.737], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 1",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "tr",
"p": { "a": 0, "k": [633.939, 275.369], "ix": 2 },
"a": { "a": 0, "k": [236.879, 292.737], "ix": 1 },
"s": { "a": 0, "k": [50, 50], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "planete Outlines - Group 1",
"np": 1,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "gr",
"it": [
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[0, 0],
[0, 0],
[0, 0],
[0, 0]
],
"o": [
[0, 0],
[0, 0],
[0, 0],
[0, 0]
],
"v": [
[-98.335, 64.79],
[-105.619, 4.984],
[105.619, -64.79],
[-80.316, 24.919]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [
0.278430998325, 0.294117987156, 0.847059011459, 1
],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": { "a": 0, "k": [316.247, 247.882], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 2",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "tr",
"p": { "a": 0, "k": [673.623, 252.941], "ix": 2 },
"a": { "a": 0, "k": [316.247, 247.882], "ix": 1 },
"s": { "a": 0, "k": [50, 50], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "planete Outlines - Group 2",
"np": 1,
"cix": 2,
"bm": 0,
"ix": 2,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "gr",
"it": [
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0]
],
"o": [
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0]
],
"v": [
[-133.812, -42.171],
[133.812, -75.141],
[5.765, 75.141],
[-61.708, 18.402],
[124.227, -71.307],
[-87.011, -1.534]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [
0.365000009537, 0.407999992371, 0.976000010967, 1
],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": { "a": 0, "k": [297.638, 254.4], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 3",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "tr",
"p": { "a": 0, "k": [664.319, 256.2], "ix": 2 },
"a": { "a": 0, "k": [297.638, 254.4], "ix": 1 },
"s": { "a": 0, "k": [50, 50], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "planete Outlines - Group 3",
"np": 1,
"cix": 2,
"bm": 0,
"ix": 3,
"mn": "ADBE Vector Group",
"hd": false
}
],
"ip": 0,
"op": 151,
"st": 0,
"bm": 0
},
{
"ddd": 0,
"ind": 3,
"ty": 4,
"nm": "planete Outlines - Group 5",
"sr": 1,
"ks": {
"o": {
"a": 1,
"k": [
{
"i": { "x": [0.667], "y": [1] },
"o": { "x": [0.333], "y": [0] },
"t": 0,
"s": [0]
},
{
"i": { "x": [0.667], "y": [1] },
"o": { "x": [0.333], "y": [0] },
"t": 45,
"s": [100]
},
{
"i": { "x": [0.667], "y": [1] },
"o": { "x": [0.333], "y": [0] },
"t": 102,
"s": [100]
},
{ "t": 150, "s": [0] }
],
"ix": 11
},
"r": { "a": 0, "k": 0, "ix": 10 },
"p": {
"a": 1,
"k": [
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 0,
"s": [327.38, 267.583, 0],
"to": [25.833, 0, 0],
"ti": [-25.833, 0, 0]
},
{ "t": 150, "s": [482.38, 267.583, 0] }
],
"ix": 2
},
"a": { "a": 0, "k": [171.76, 193.166, 0], "ix": 1 },
"s": { "a": 0, "k": [50, 50, 100], "ix": 6 }
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[13.485, 0],
[4.38, -4.171],
[21.913, 0],
[3.575, -18.765],
[1.851, 0],
[0, -13.484],
[-0.011, -0.291],
[1.599, 0],
[0, -6.743],
[-6.742, 0],
[0, 0],
[0, 13.485]
],
"o": [
[-6.526, 0],
[-0.793, -21.719],
[-19.806, 0],
[-1.734, -0.391],
[-13.485, 0],
[0, 0.293],
[-1.4, -0.559],
[-6.742, 0],
[0, 6.742],
[0, 0],
[13.485, 0],
[0, -13.484]
],
"v": [
[59.669, -8.242],
[42.84, -1.506],
[2.287, -40.592],
[-37.576, -7.638],
[-42.962, -8.242],
[-67.378, 16.174],
[-67.356, 17.049],
[-71.878, 16.174],
[-84.086, 28.383],
[-71.878, 40.591],
[59.669, 40.591],
[84.086, 16.174]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.816000007181, 0.823999980852, 0.827000038297, 1],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": { "a": 0, "k": [171.76, 193.166], "ix": 2 },
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 5",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
}
],
"ip": 0,
"op": 151,
"st": 0,
"bm": 0
}
]
}
],
"layers": [
{
"ddd": 0,
"ind": 1,
"ty": 0,
"nm": "Pre-comp 1",
"refId": "comp_0",
"sr": 1,
"ks": {
"o": { "a": 0, "k": 100, "ix": 11 },
"r": { "a": 0, "k": 0, "ix": 10 },
"p": { "a": 0, "k": [406, 306, 0], "ix": 2 },
"a": { "a": 0, "k": [400, 300, 0], "ix": 1 },
"s": { "a": 0, "k": [179, 179, 100], "ix": 6 }
},
"ao": 0,
"w": 800,
"h": 600,
"ip": 0,
"op": 147,
"st": 0,
"bm": 0
}
],
"markers": []
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
import { CreateClientSuccess } from '@/components/clients/create-client-success';
import { Button, buttonVariants } from '@/components/ui/button';
import { Combobox } from '@/components/ui/combobox';
@@ -9,6 +8,7 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { useAppParams } from '@/hooks/useAppParams';
import { api, handleError } from '@/trpc/client';
import { cn } from '@/utils/cn';
import { zodResolver } from '@hookform/resolvers/zod';
import { SaveIcon, WallpaperIcon } from 'lucide-react';

View File

@@ -1,10 +1,10 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
import { ButtonContainer } from '@/components/button-container';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { useAppParams } from '@/hooks/useAppParams';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';

View File

@@ -1,9 +1,9 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
import { ButtonContainer } from '@/components/button-container';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';

View File

@@ -1,11 +1,11 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
import { ButtonContainer } from '@/components/button-container';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import { useAppParams } from '@/hooks/useAppParams';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
import { Controller, useForm } from 'react-hook-form';

View File

@@ -1,9 +1,9 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
import { ButtonContainer } from '@/components/button-container';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';

View File

@@ -1,9 +1,9 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
import { ButtonContainer } from '@/components/button-container';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';

View File

@@ -1,9 +1,9 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
import { ButtonContainer } from '@/components/button-container';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';

View File

@@ -1,12 +1,12 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
import { ButtonContainer } from '@/components/button-container';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { Combobox } from '@/components/ui/combobox';
import { Label } from '@/components/ui/label';
import { useAppParams } from '@/hooks/useAppParams';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter, useSearchParams } from 'next/navigation';
import { Controller, useForm } from 'react-hook-form';

View File

@@ -1,11 +1,11 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
import { ButtonContainer } from '@/components/button-container';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { useAppParams } from '@/hooks/useAppParams';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
import { Controller, useForm } from 'react-hook-form';

View File

@@ -1,4 +1,4 @@
import { createTRPCRouter } from '@/server/api/trpc';
import { createTRPCRouter } from '@/trpc/api/trpc';
import { chartRouter } from './routers/chart';
import { clientRouter } from './routers/client';

View File

@@ -2,7 +2,7 @@ import {
createTRPCRouter,
protectedProcedure,
publicProcedure,
} from '@/server/api/trpc';
} from '@/trpc/api/trpc';
import { average, max, min, round, sum } from '@/utils/math';
import { flatten, map, pipe, prop, sort, uniq } from 'ramda';
import { z } from 'zod';

View File

@@ -1,5 +1,5 @@
import { randomUUID } from 'crypto';
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
import { createTRPCRouter, protectedProcedure } from '@/trpc/api/trpc';
import { z } from 'zod';
import { hashPassword, stripTrailingSlash } from '@openpanel/common';

View File

@@ -1,4 +1,4 @@
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
import { createTRPCRouter, protectedProcedure } from '@/trpc/api/trpc';
import { getId } from '@/utils/getDbId';
import { PrismaError } from 'prisma-error-enum';
import { z } from 'zod';

View File

@@ -1,4 +1,4 @@
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
import { createTRPCRouter, protectedProcedure } from '@/trpc/api/trpc';
import { z } from 'zod';
import { db } from '@openpanel/db';

View File

@@ -1,5 +1,5 @@
import { randomUUID } from 'crypto';
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
import { createTRPCRouter, protectedProcedure } from '@/trpc/api/trpc';
import { clerkClient } from '@clerk/nextjs';
import { z } from 'zod';

View File

@@ -1,4 +1,4 @@
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
import { createTRPCRouter, protectedProcedure } from '@/trpc/api/trpc';
import { clerkClient } from '@clerk/nextjs';
import { z } from 'zod';

View File

@@ -2,7 +2,7 @@ import {
createTRPCRouter,
protectedProcedure,
publicProcedure,
} from '@/server/api/trpc';
} from '@/trpc/api/trpc';
import { flatten, map, pipe, prop, sort, uniq } from 'ramda';
import { z } from 'zod';

View File

@@ -1,4 +1,4 @@
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
import { createTRPCRouter, protectedProcedure } from '@/trpc/api/trpc';
import { getId } from '@/utils/getDbId';
import { z } from 'zod';

View File

@@ -2,7 +2,7 @@ import {
createTRPCRouter,
protectedProcedure,
publicProcedure,
} from '@/server/api/trpc';
} from '@/trpc/api/trpc';
import { z } from 'zod';
import { db, getReferences } from '@openpanel/db';

View File

@@ -1,4 +1,4 @@
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
import { createTRPCRouter, protectedProcedure } from '@/trpc/api/trpc';
import { z } from 'zod';
import { db } from '@openpanel/db';

View File

@@ -1,4 +1,4 @@
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
import { createTRPCRouter, protectedProcedure } from '@/trpc/api/trpc';
import ShortUniqueId from 'short-unique-id';
import { db } from '@openpanel/db';

View File

@@ -1,4 +1,4 @@
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
import { createTRPCRouter, protectedProcedure } from '@/trpc/api/trpc';
import { clerkClient } from '@clerk/nextjs';
import { z } from 'zod';

View File

@@ -1,4 +1,4 @@
import type { AppRouter } from '@/server/api/root';
import type { AppRouter } from '@/trpc/api/root';
import type { TRPCClientErrorBase } from '@trpc/react-query';
import { createTRPCReact } from '@trpc/react-query';
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';

View File

@@ -6,7 +6,7 @@ export function DeviceIdWarning() {
<Callout>
Read more about{' '}
<Link href="/docs/device-id">device id and why you might want it</Link>.
**We recommend not to but it's up to you.**
**We recommend not to but it&apos;s up to you.**
</Callout>
);
}

View File

@@ -17,5 +17,6 @@
"title": "Others"
},
"javascript": "Javascript SDK",
"web": "Web SDK"
"web": "Web SDK",
"api": "API"
}

View File

@@ -41,9 +41,9 @@ export default function Page() {
<p>
<strong>Affiliate</strong> means an entity that controls, is
controlled by or is under common control with a party, where
"control" means ownership of 50% or more of the shares, equity
interest or other securities entitled to vote for election of
directors or other managing authority.
&quot;control&quot; means ownership of 50% or more of the shares,
equity interest or other securities entitled to vote for election
of directors or other managing authority.
</p>
</li>
<li>
@@ -54,9 +54,10 @@ export default function Page() {
</li>
<li>
<p>
<strong>Company</strong> (referred to as either "the Company",
"We", "Us" or "Our" in this Agreement) refers to Coderax AB, Sankt
Eriksgatan 100, 113 31, Stockholm.
<strong>Company</strong> (referred to as either &quot;the
Company&quot;, &quot;We&quot;, &quot;Us&quot; or &quot;Our&quot;
in this Agreement) refers to Coderax AB, Sankt Eriksgatan 100, 113
31, Stockholm.
</p>
</li>
<li>
@@ -151,7 +152,7 @@ export default function Page() {
<h4>Usage Data</h4>
<p>Usage Data is collected automatically when using the Service.</p>
<p>
Usage Data may include information such as Your Device's Internet
Usage Data may include information such as Your Device&apos;s Internet
Protocol address (e.g. IP address), browser type, browser version, the
pages of our Service that You visit, the time and date of Your visit,
the time spent on those pages, unique device identifiers and other
@@ -198,10 +199,10 @@ export default function Page() {
</li>
</ul>
<p>
Cookies can be "Persistent" or "Session" Cookies. Persistent Cookies
remain on Your personal computer or mobile device when You go offline,
while Session Cookies are deleted as soon as You close Your web
browser. You can learn more about cookies{' '}
Cookies can be &quot;Persistent&quot; or &quot;Session&quot; Cookies.
Persistent Cookies remain on Your personal computer or mobile device
when You go offline, while Session Cookies are deleted as soon as You
close Your web browser. You can learn more about cookies{' '}
<a
href="https://www.termsfeed.com/blog/cookies/#What_Are_Cookies"
target="_blank"
@@ -291,11 +292,11 @@ export default function Page() {
<p>
<strong>To contact You:</strong> To contact You by email,
telephone calls, SMS, or other equivalent forms of electronic
communication, such as a mobile application's push notifications
regarding updates or informative communications related to the
functionalities, products or contracted services, including the
security updates, when necessary or reasonable for their
implementation.
communication, such as a mobile application&apos;s push
notifications regarding updates or informative communications
related to the functionalities, products or contracted services,
including the security updates, when necessary or reasonable for
their implementation.
</p>
</li>
<li>
@@ -391,12 +392,12 @@ export default function Page() {
<h3>Transfer of Your Personal Data</h3>
<p>
Your information, including Personal Data, is processed at the
Company's operating offices and in any other places where the parties
involved in the processing are located. It means that this information
may be transferred to — and maintained on — computers located outside
of Your state, province, country or other governmental jurisdiction
where the data protection laws may differ than those from Your
jurisdiction.
Company&apos;s operating offices and in any other places where the
parties involved in the processing are located. It means that this
information may be transferred to and maintained on computers
located outside of Your state, province, country or other governmental
jurisdiction where the data protection laws may differ than those from
Your jurisdiction.
</p>
<p>
Your consent to this Privacy Policy followed by Your submission of
@@ -468,7 +469,7 @@ export default function Page() {
acceptable means to protect Your Personal Data, We cannot guarantee
its absolute security.
</p>
<h2>Children's Privacy</h2>
<h2>Children&apos;s Privacy</h2>
<p>
Our Service does not address anyone under the age of 13. We do not
knowingly collect personally identifiable information from anyone
@@ -481,15 +482,15 @@ export default function Page() {
<p>
If We need to rely on consent as a legal basis for processing Your
information and Your country requires consent from a parent, We may
require Your parent's consent before We collect and use that
require Your parent&apos;s consent before We collect and use that
information.
</p>
<h2>Links to Other Websites</h2>
<p>
Our Service may contain links to other websites that are not operated
by Us. If You click on a third party link, You will be directed to
that third party's site. We strongly advise You to review the Privacy
Policy of every site You visit.
that third party&apos;s site. We strongly advise You to review the
Privacy Policy of every site You visit.
</p>
<p>
We have no control over and assume no responsibility for the content,
@@ -502,8 +503,8 @@ export default function Page() {
</p>
<p>
We will let You know via email and/or a prominent notice on Our
Service, prior to the change becoming effective and update the "Last
updated" date at the top of this Privacy Policy.
Service, prior to the change becoming effective and update the
&quot;Last updated&quot; date at the top of this Privacy Policy.
</p>
<p>
You are advised to review this Privacy Policy periodically for any

View File

@@ -313,7 +313,7 @@ export default function Page() {
of the Company and any of its suppliers under any provision of this
Terms and Your exclusive remedy for all of the foregoing shall be
limited to the amount actually paid by You through the Service or 100
USD if You haven't purchased anything through the Service.
USD if You haven&apos;t purchased anything through the Service.
</p>
<p>
To the maximum extent permitted by applicable law, in no event shall
@@ -332,8 +332,8 @@ export default function Page() {
Some states do not allow the exclusion of implied warranties or
limitation of liability for incidental or consequential damages, which
means that some of the above limitations may not apply. In these
states, each party's liability will be limited to the greatest extent
permitted by law.
states, each party&apos;s liability will be limited to the greatest
extent permitted by law.
</p>
<h2>&quot;AS IS&quot; and &quot;AS AVAILABLE&quot; Disclaimer</h2>
<p>
@@ -357,9 +357,9 @@ export default function Page() {
</p>
<p>
Without limiting the foregoing, neither the Company nor any of the
company's provider makes any representation or warranty of any kind,
express or implied: (i) as to the operation or availability of the
Service, or the information, content, and materials or products
company&apos;s provider makes any representation or warranty of any
kind, express or implied: (i) as to the operation or availability of
the Service, or the information, content, and materials or products
included thereon; (ii) that the Service will be uninterrupted or
error-free; (iii) as to the accuracy, reliability, or currency of any
information or content provided through the Service; or (iv) that the
@@ -420,7 +420,7 @@ export default function Page() {
<p>
Except as provided herein, the failure to exercise a right or to
require performance of an obligation under these Terms shall not
affect a party's ability to exercise such right or require such
affect a party&apos;s ability to exercise such right or require such
performance at any time thereafter nor shall the waiver of a breach
constitute a waiver of any subsequent breach.
</p>
@@ -434,9 +434,9 @@ export default function Page() {
<p>
We reserve the right, at Our sole discretion, to modify or replace
these Terms at any time. If a revision is material We will make
reasonable efforts to provide at least 30 days' notice prior to any
new terms taking effect. What constitutes a material change will be
determined at Our sole discretion.
reasonable efforts to provide at least 30 days&apos; notice prior to
any new terms taking effect. What constitutes a material change will
be determined at Our sole discretion.
</p>
<p>
By continuing to access or use Our Service after those revisions

View File

@@ -18,7 +18,7 @@ export async function POST(req: Request) {
await db.waitlist.create({
data: {
email: body.email.toLowerCase(),
email: String(body.email).toLowerCase(),
},
});

View File

@@ -87,8 +87,8 @@ const features: FeatureItem[] = [
description: (
<>
<p>
Deep dive into your user's behavior and understand how they interact
with your app/website.
Deep dive into your user&apos;s behavior and understand how they
interact with your app/website.
</p>
</>
),

View File

@@ -34,8 +34,8 @@ export function JoinWaitlistHero({ className }: JoinWaitlistProps) {
<DialogHeader>
<DialogTitle>Thanks so much!</DialogTitle>
<DialogDescription>
You're now on the waiting list. We'll let you know when we're
ready. Should be within a month or two 🚀
You&apos;re now on the waiting list. We&apos;ll let you know when
we&apos;re ready. Should be within a month or two 🚀
</DialogDescription>
</DialogHeader>

View File

@@ -34,8 +34,8 @@ export function JoinWaitlist({ className }: JoinWaitlistProps) {
<DialogHeader>
<DialogTitle>Thanks so much!</DialogTitle>
<DialogDescription>
You're now on the waiting list. We'll let you know when we're
ready. Should be within a month or two 🚀
You&apos;re now on the waiting list. We&apos;ll let you know when
we&apos;re ready. Should be within a month or two 🚀
</DialogDescription>
</DialogHeader>

View File

@@ -57,14 +57,15 @@ export default function Page() {
<h3 className="text-blue-dark text-lg font-bold">TL;DR</h3>
<Paragraph>
Our open-source analytic library fills a crucial gap by combining
the strengths of Mixpanel's powerful features with Plausible's
clear overview page. Motivated by the lack of an open-source
alternative to Mixpanel and inspired by Plausible's simplicity, we
aim to create an intuitive platform with predictable pricing. With
a single-tier pricing model and limits only on monthly event
counts, our goal is to democratize analytics, offering
unrestricted access to all features while ensuring affordability
and transparency for users of all project sizes.
the strengths of Mixpanel&apos;s powerful features with
Plausible&apos;s clear overview page. Motivated by the lack of an
open-source alternative to Mixpanel and inspired by
Plausible&apos;s simplicity, we aim to create an intuitive
platform with predictable pricing. With a single-tier pricing
model and limits only on monthly event counts, our goal is to
democratize analytics, offering unrestricted access to all
features while ensuring affordability and transparency for users
of all project sizes.
</Paragraph>
<h3 className="text-blue-dark mt-12 text-lg font-bold">The why</h3>
@@ -72,10 +73,10 @@ export default function Page() {
Our open-source analytic library emerged from a clear need within
the analytics community. While platforms like Mixpanel offer
powerful and user-friendly features, they lack a comprehensive
overview page akin to Plausible's, which succinctly summarizes
essential metrics. Recognizing this gap, we saw an opportunity to
combine the strengths of both platforms while addressing their
respective shortcomings.
overview page akin to Plausible&apos;s, which succinctly
summarizes essential metrics. Recognizing this gap, we saw an
opportunity to combine the strengths of both platforms while
addressing their respective shortcomings.
</Paragraph>
<Paragraph>
@@ -87,7 +88,7 @@ export default function Page() {
</Paragraph>
<Paragraph>
Inspired by Plausible's exemplary approach to simplicity and
Inspired by Plausible&apos;s exemplary approach to simplicity and
clarity, we aim to build upon their foundation and further refine
the user experience. By harnessing the best practices demonstrated
by Plausible, we aspire to create an intuitive and streamlined
@@ -108,12 +109,13 @@ export default function Page() {
<Paragraph>
In line with our commitment to fairness and accessibility, our
pricing model will only impose limits on the number of events
users can send each month. This approach, akin to Plausible's,
ensures that users have the freedom to explore and utilize our
platform to its fullest potential without arbitrary restrictions
on reports or user counts. Ultimately, our goal is to democratize
analytics by offering a reliable, transparent, and cost-effective
solution for projects of all sizes.
users can send each month. This approach, akin to
Plausible&apos;s, ensures that users have the freedom to explore
and utilize our platform to its fullest potential without
arbitrary restrictions on reports or user counts. Ultimately, our
goal is to democratize analytics by offering a reliable,
transparent, and cost-effective solution for projects of all
sizes.
</Paragraph>
</div>
</div>

View File

@@ -19,8 +19,9 @@ const items = [
title: 'Own Your Own Data',
description: (
<p>
We believe that you should own your own data. That's why we don't sell
your data to third parties. <strong>Ever. Period.</strong>
We believe that you should own your own data. That&apos;s why we
don&apos;t sell your data to third parties.{' '}
<strong>Ever. Period.</strong>
</p>
),
icon: KeyIcon,

View File

@@ -89,11 +89,16 @@ export interface ALinkProps
extends React.AnchorHTMLAttributes<HTMLAnchorElement>,
VariantProps<typeof buttonVariants> {}
export const ALink = ({ variant, size, className, ...props }: ALinkProps) => {
export const ALink = ({
variant,
size,
className,
children,
...props
}: ALinkProps) => {
return (
<a
{...props}
className={cn(buttonVariants({ variant, size, className }))}
/>
<a {...props} className={cn(buttonVariants({ variant, size, className }))}>
{children}
</a>
);
};

View File

@@ -14,5 +14,5 @@
"strictNullChecks": true
},
"include": [".", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"exclude": ["node_modules", "public"]
}

View File

@@ -1,28 +0,0 @@
# Create T3 App
This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`.
## What's next? How do I make an app with this?
We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary.
If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help.
- [Next.js](https://nextjs.org)
- [NextAuth.js](https://next-auth.js.org)
- [Prisma](https://prisma.io)
- [Tailwind CSS](https://tailwindcss.com)
- [tRPC](https://trpc.io)
## Learn More
To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources:
- [Documentation](https://create.t3.gg/)
- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials
You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome!
## How do I deploy this?
Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information.

View File

@@ -1,6 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,23 +0,0 @@
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
* for Docker builds.
*/
/** @type {import("next").NextConfig} */
const config = {
reactStrictMode: false,
transpilePackages: ['@openpanel/sdk', '@openpanel/web', '@openpanel/nextjs'],
eslint: { ignoreDuringBuilds: true },
typescript: { ignoreBuildErrors: true },
/**
* If you are using `appDir` then you must comment the below `i18n` config out.
*
* @see https://github.com/vercel/next.js/issues/41980
*/
i18n: {
locales: ['en'],
defaultLocale: 'en',
},
};
export default config;

Some files were not shown because too many files have changed in this diff Show More