🧹 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.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }],
"eslint.workingDirectories": [ "eslint.workingDirectories": [
{ "pattern": "apps/*/" }, { "pattern": "apps/*/" },
{ "pattern": "packages/*/" }, { "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). 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_TIMEOUT = 1000 * 60 * 30;
const SESSION_END_TIMEOUT = SESSION_TIMEOUT + 1000; 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 { try {
const start = Date.now(); const start = Date.now();
const res = await promise; const res = await promise;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
'use client'; 'use client';
import { api, handleError } from '@/app/_trpc/client';
import { InputWithLabel } from '@/components/forms/input-with-label'; import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Widget, WidgetBody, WidgetHead } from '@/components/widget'; import { Widget, WidgetBody, WidgetHead } from '@/components/widget';
import { api, handleError } from '@/trpc/client';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { toast } from 'sonner'; 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 { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced'; import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
@@ -15,6 +14,7 @@ import {
SheetTrigger, SheetTrigger,
} from '@/components/ui/sheet'; } from '@/components/ui/sheet';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { api } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { PlusIcon, SendIcon } from 'lucide-react'; import { PlusIcon, SendIcon } from 'lucide-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';

View File

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

View File

@@ -1,9 +1,9 @@
'use client'; 'use client';
import { api, handleError } from '@/app/_trpc/client';
import { InputWithLabel } from '@/components/forms/input-with-label'; import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Widget, WidgetBody, WidgetHead } from '@/components/widget'; import { Widget, WidgetBody, WidgetHead } from '@/components/widget';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form'; 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 { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { SaveIcon } from 'lucide-react'; import { SaveIcon } from 'lucide-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -13,8 +14,6 @@ import { useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { z } from 'zod'; import { z } from 'zod';
import { api, handleError } from '../../_trpc/client';
const validation = z.object({ const validation = z.object({
name: z.string().min(1), name: z.string().min(1),
}); });

View File

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

View File

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

View File

@@ -32,6 +32,6 @@ export async function POST(request: Request) {
return Response.json({ message: 'Webhook received!' }); return Response.json({ message: 'Webhook received!' });
} }
export async function GET() { export function GET() {
return Response.json({ message: 'Hello World!' }); 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 { getAuth } from '@clerk/nextjs/server';
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
@@ -7,7 +7,7 @@ const handler = (req: Request) =>
endpoint: '/api/trpc', endpoint: '/api/trpc',
req, req,
router: appRouter, router: appRouter,
async createContext({ req }) { createContext({ req }) {
const session = getAuth(req as any); const session = getAuth(req as any);
return { return {
session, 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'; 'use client';
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import { api } from '@/app/_trpc/client';
import { TooltipProvider } from '@/components/ui/tooltip'; import { TooltipProvider } from '@/components/ui/tooltip';
import { ModalProvider } from '@/modals'; import { ModalProvider } from '@/modals';
import type { AppStore } from '@/redux'; import type { AppStore } from '@/redux';
import makeStore from '@/redux'; import makeStore from '@/redux';
import { api } from '@/trpc/client';
import { ClerkProvider, useAuth } from '@clerk/nextjs'; import { ClerkProvider, useAuth } from '@clerk/nextjs';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpLink } from '@trpc/client'; import { httpLink } from '@trpc/client';

View File

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

View File

@@ -11,13 +11,17 @@ import {
import { MoonIcon, SunIcon } from 'lucide-react'; import { MoonIcon, SunIcon } from 'lucide-react';
import { useTheme } from 'next-themes'; import { useTheme } from 'next-themes';
export default function DarkModeToggle() { interface Props {
className?: string;
}
export default function DarkModeToggle({ className }: Props) {
const { setTheme } = useTheme(); const { setTheme } = useTheme();
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <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" /> <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" /> <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> <span className="sr-only">Toggle theme</span>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { api } from '@/app/_trpc/client'; import { api } from '@/trpc/client';
import type { IChartInput } from '@openpanel/validation'; 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, useMemo,
useState, useState,
} from 'react'; } from 'react';
import type { IChartSerie } from '@/server/api/routers/chart'; import type { IChartSerie } from '@/trpc/api/routers/chart';
import type { IChartInput } from '@openpanel/validation'; import type { IChartInput } from '@openpanel/validation';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import type { IChartData } from '@/app/_trpc/client';
import { useVisibleSeries } from '@/hooks/useVisibleSeries'; import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import type { IChartData } from '@/trpc/client';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import { useChartContext } from './ChartProvider'; 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 { AutoSizer } from '@/components/react-virtualized-auto-sizer';
import { useVisibleSeries } from '@/hooks/useVisibleSeries'; import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import type { IChartData } from '@/trpc/client';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import { round } from '@/utils/math'; import { round } from '@/utils/math';
import { getChartColor } from '@/utils/theme'; import { getChartColor } from '@/utils/theme';

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import type { RouterOutputs } from '@/app/_trpc/client'; import type { RouterOutputs } from '@/trpc/client';
import { api } from '@/app/_trpc/client'; import { api } from '@/trpc/client';
import type { IChartInput } from '@openpanel/validation'; 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 { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch } from '@/redux'; import { useDispatch } from '@/redux';
import { api } from '@/trpc/client';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import { DatabaseIcon } from 'lucide-react'; import { DatabaseIcon } from 'lucide-react';

View File

@@ -1,10 +1,10 @@
'use client'; 'use client';
import { api } from '@/app/_trpc/client';
import { ColorSquare } from '@/components/color-square'; import { ColorSquare } from '@/components/color-square';
import { Combobox } from '@/components/ui/combobox'; import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch, useSelector } from '@/redux'; import { useDispatch, useSelector } from '@/redux';
import { api } from '@/trpc/client';
import { SplitIcon } from 'lucide-react'; import { SplitIcon } from 'lucide-react';
import type { IChartBreakdown } from '@openpanel/validation'; 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 { ColorSquare } from '@/components/color-square';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced'; import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
@@ -7,6 +6,7 @@ import { RenderDots } from '@/components/ui/RenderDots';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { useMappings } from '@/hooks/useMappings'; import { useMappings } from '@/hooks/useMappings';
import { useDispatch } from '@/redux'; import { useDispatch } from '@/redux';
import { api } from '@/trpc/client';
import { SlidersHorizontal, Trash } from 'lucide-react'; import { SlidersHorizontal, Trash } from 'lucide-react';
import { operators } from '@openpanel/constants'; import { operators } from '@openpanel/constants';

View File

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

View File

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

View File

@@ -1,7 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import * as TogglePrimitive from '@radix-ui/react-toggle'; 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( 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', '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) { export function useEventNames(projectId: string) {
const query = api.chart.events.useQuery({ 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) { export function useEventProperties(projectId: string, event?: string) {
const query = api.chart.properties.useQuery({ 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( export function useEventValues(
projectId: string, projectId: string,

View File

@@ -1,4 +1,4 @@
import { api } from '@/app/_trpc/client'; import { api } from '@/trpc/client';
export function useProfileProperties(projectId: string) { export function useProfileProperties(projectId: string) {
const query = api.profile.properties.useQuery({ 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) { export function useProfileValues(projectId: string, property: string) {
const query = api.profile.values.useQuery({ const query = api.profile.values.useQuery({

View File

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

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import { useEffect, useMemo, useState } from 'react'; 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 type IVisibleSeries = ReturnType<typeof useVisibleSeries>['series'];
export function useVisibleSeries(data: IChartData, limit?: number | undefined) { 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'; 'use client';
import { api, handleError } from '@/app/_trpc/client';
import { CreateClientSuccess } from '@/components/clients/create-client-success'; import { CreateClientSuccess } from '@/components/clients/create-client-success';
import { Button, buttonVariants } from '@/components/ui/button'; import { Button, buttonVariants } from '@/components/ui/button';
import { Combobox } from '@/components/ui/combobox'; import { Combobox } from '@/components/ui/combobox';
@@ -9,6 +8,7 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { api, handleError } from '@/trpc/client';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { SaveIcon, WallpaperIcon } from 'lucide-react'; import { SaveIcon, WallpaperIcon } from 'lucide-react';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
'use client'; 'use client';
import { api, handleError } from '@/app/_trpc/client';
import { ButtonContainer } from '@/components/button-container'; import { ButtonContainer } from '@/components/button-container';
import { InputWithLabel } from '@/components/forms/input-with-label'; import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { Controller, useForm } from 'react-hook-form'; 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 { chartRouter } from './routers/chart';
import { clientRouter } from './routers/client'; import { clientRouter } from './routers/client';

View File

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

View File

@@ -1,5 +1,5 @@
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc'; import { createTRPCRouter, protectedProcedure } from '@/trpc/api/trpc';
import { z } from 'zod'; import { z } from 'zod';
import { hashPassword, stripTrailingSlash } from '@openpanel/common'; 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 { getId } from '@/utils/getDbId';
import { PrismaError } from 'prisma-error-enum'; import { PrismaError } from 'prisma-error-enum';
import { z } from 'zod'; 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 { z } from 'zod';
import { db } from '@openpanel/db'; import { db } from '@openpanel/db';

View File

@@ -1,5 +1,5 @@
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc'; import { createTRPCRouter, protectedProcedure } from '@/trpc/api/trpc';
import { clerkClient } from '@clerk/nextjs'; import { clerkClient } from '@clerk/nextjs';
import { z } from 'zod'; 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 { clerkClient } from '@clerk/nextjs';
import { z } from 'zod'; import { z } from 'zod';

View File

@@ -2,7 +2,7 @@ import {
createTRPCRouter, createTRPCRouter,
protectedProcedure, protectedProcedure,
publicProcedure, publicProcedure,
} from '@/server/api/trpc'; } from '@/trpc/api/trpc';
import { flatten, map, pipe, prop, sort, uniq } from 'ramda'; import { flatten, map, pipe, prop, sort, uniq } from 'ramda';
import { z } from 'zod'; 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 { getId } from '@/utils/getDbId';
import { z } from 'zod'; import { z } from 'zod';

View File

@@ -2,7 +2,7 @@ import {
createTRPCRouter, createTRPCRouter,
protectedProcedure, protectedProcedure,
publicProcedure, publicProcedure,
} from '@/server/api/trpc'; } from '@/trpc/api/trpc';
import { z } from 'zod'; import { z } from 'zod';
import { db, getReferences } from '@openpanel/db'; 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 { z } from 'zod';
import { db } from '@openpanel/db'; 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 ShortUniqueId from 'short-unique-id';
import { db } from '@openpanel/db'; 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 { clerkClient } from '@clerk/nextjs';
import { z } from 'zod'; 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 type { TRPCClientErrorBase } from '@trpc/react-query';
import { createTRPCReact } from '@trpc/react-query'; import { createTRPCReact } from '@trpc/react-query';
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server'; import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';

View File

@@ -6,7 +6,7 @@ export function DeviceIdWarning() {
<Callout> <Callout>
Read more about{' '} Read more about{' '}
<Link href="/docs/device-id">device id and why you might want it</Link>. <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> </Callout>
); );
} }

View File

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

View File

@@ -41,9 +41,9 @@ export default function Page() {
<p> <p>
<strong>Affiliate</strong> means an entity that controls, is <strong>Affiliate</strong> means an entity that controls, is
controlled by or is under common control with a party, where controlled by or is under common control with a party, where
"control" means ownership of 50% or more of the shares, equity &quot;control&quot; means ownership of 50% or more of the shares,
interest or other securities entitled to vote for election of equity interest or other securities entitled to vote for election
directors or other managing authority. of directors or other managing authority.
</p> </p>
</li> </li>
<li> <li>
@@ -54,9 +54,10 @@ export default function Page() {
</li> </li>
<li> <li>
<p> <p>
<strong>Company</strong> (referred to as either "the Company", <strong>Company</strong> (referred to as either &quot;the
"We", "Us" or "Our" in this Agreement) refers to Coderax AB, Sankt Company&quot;, &quot;We&quot;, &quot;Us&quot; or &quot;Our&quot;
Eriksgatan 100, 113 31, Stockholm. in this Agreement) refers to Coderax AB, Sankt Eriksgatan 100, 113
31, Stockholm.
</p> </p>
</li> </li>
<li> <li>
@@ -151,7 +152,7 @@ export default function Page() {
<h4>Usage Data</h4> <h4>Usage Data</h4>
<p>Usage Data is collected automatically when using the Service.</p> <p>Usage Data is collected automatically when using the Service.</p>
<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 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, 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 the time spent on those pages, unique device identifiers and other
@@ -198,10 +199,10 @@ export default function Page() {
</li> </li>
</ul> </ul>
<p> <p>
Cookies can be "Persistent" or "Session" Cookies. Persistent Cookies Cookies can be &quot;Persistent&quot; or &quot;Session&quot; Cookies.
remain on Your personal computer or mobile device when You go offline, Persistent Cookies remain on Your personal computer or mobile device
while Session Cookies are deleted as soon as You close Your web when You go offline, while Session Cookies are deleted as soon as You
browser. You can learn more about cookies{' '} close Your web browser. You can learn more about cookies{' '}
<a <a
href="https://www.termsfeed.com/blog/cookies/#What_Are_Cookies" href="https://www.termsfeed.com/blog/cookies/#What_Are_Cookies"
target="_blank" target="_blank"
@@ -291,11 +292,11 @@ export default function Page() {
<p> <p>
<strong>To contact You:</strong> To contact You by email, <strong>To contact You:</strong> To contact You by email,
telephone calls, SMS, or other equivalent forms of electronic telephone calls, SMS, or other equivalent forms of electronic
communication, such as a mobile application's push notifications communication, such as a mobile application&apos;s push
regarding updates or informative communications related to the notifications regarding updates or informative communications
functionalities, products or contracted services, including the related to the functionalities, products or contracted services,
security updates, when necessary or reasonable for their including the security updates, when necessary or reasonable for
implementation. their implementation.
</p> </p>
</li> </li>
<li> <li>
@@ -391,12 +392,12 @@ export default function Page() {
<h3>Transfer of Your Personal Data</h3> <h3>Transfer of Your Personal Data</h3>
<p> <p>
Your information, including Personal Data, is processed at the Your information, including Personal Data, is processed at the
Company's operating offices and in any other places where the parties Company&apos;s operating offices and in any other places where the
involved in the processing are located. It means that this information parties involved in the processing are located. It means that this
may be transferred to — and maintained on — computers located outside information may be transferred to and maintained on computers
of Your state, province, country or other governmental jurisdiction located outside of Your state, province, country or other governmental
where the data protection laws may differ than those from Your jurisdiction where the data protection laws may differ than those from
jurisdiction. Your jurisdiction.
</p> </p>
<p> <p>
Your consent to this Privacy Policy followed by Your submission of 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 acceptable means to protect Your Personal Data, We cannot guarantee
its absolute security. its absolute security.
</p> </p>
<h2>Children's Privacy</h2> <h2>Children&apos;s Privacy</h2>
<p> <p>
Our Service does not address anyone under the age of 13. We do not Our Service does not address anyone under the age of 13. We do not
knowingly collect personally identifiable information from anyone knowingly collect personally identifiable information from anyone
@@ -481,15 +482,15 @@ export default function Page() {
<p> <p>
If We need to rely on consent as a legal basis for processing Your 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 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. information.
</p> </p>
<h2>Links to Other Websites</h2> <h2>Links to Other Websites</h2>
<p> <p>
Our Service may contain links to other websites that are not operated 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 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 that third party&apos;s site. We strongly advise You to review the
Policy of every site You visit. Privacy Policy of every site You visit.
</p> </p>
<p> <p>
We have no control over and assume no responsibility for the content, We have no control over and assume no responsibility for the content,
@@ -502,8 +503,8 @@ export default function Page() {
</p> </p>
<p> <p>
We will let You know via email and/or a prominent notice on Our 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 Service, prior to the change becoming effective and update the
updated" date at the top of this Privacy Policy. &quot;Last updated&quot; date at the top of this Privacy Policy.
</p> </p>
<p> <p>
You are advised to review this Privacy Policy periodically for any 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 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 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 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>
<p> <p>
To the maximum extent permitted by applicable law, in no event shall 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 Some states do not allow the exclusion of implied warranties or
limitation of liability for incidental or consequential damages, which limitation of liability for incidental or consequential damages, which
means that some of the above limitations may not apply. In these means that some of the above limitations may not apply. In these
states, each party's liability will be limited to the greatest extent states, each party&apos;s liability will be limited to the greatest
permitted by law. extent permitted by law.
</p> </p>
<h2>&quot;AS IS&quot; and &quot;AS AVAILABLE&quot; Disclaimer</h2> <h2>&quot;AS IS&quot; and &quot;AS AVAILABLE&quot; Disclaimer</h2>
<p> <p>
@@ -357,9 +357,9 @@ export default function Page() {
</p> </p>
<p> <p>
Without limiting the foregoing, neither the Company nor any of the Without limiting the foregoing, neither the Company nor any of the
company's provider makes any representation or warranty of any kind, company&apos;s provider makes any representation or warranty of any
express or implied: (i) as to the operation or availability of the kind, express or implied: (i) as to the operation or availability of
Service, or the information, content, and materials or products the Service, or the information, content, and materials or products
included thereon; (ii) that the Service will be uninterrupted or included thereon; (ii) that the Service will be uninterrupted or
error-free; (iii) as to the accuracy, reliability, or currency of any error-free; (iii) as to the accuracy, reliability, or currency of any
information or content provided through the Service; or (iv) that the information or content provided through the Service; or (iv) that the
@@ -420,7 +420,7 @@ export default function Page() {
<p> <p>
Except as provided herein, the failure to exercise a right or to Except as provided herein, the failure to exercise a right or to
require performance of an obligation under these Terms shall not 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 performance at any time thereafter nor shall the waiver of a breach
constitute a waiver of any subsequent breach. constitute a waiver of any subsequent breach.
</p> </p>
@@ -434,9 +434,9 @@ export default function Page() {
<p> <p>
We reserve the right, at Our sole discretion, to modify or replace 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 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 reasonable efforts to provide at least 30 days&apos; notice prior to
new terms taking effect. What constitutes a material change will be any new terms taking effect. What constitutes a material change will
determined at Our sole discretion. be determined at Our sole discretion.
</p> </p>
<p> <p>
By continuing to access or use Our Service after those revisions 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({ await db.waitlist.create({
data: { data: {
email: body.email.toLowerCase(), email: String(body.email).toLowerCase(),
}, },
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,5 +14,5 @@
"strictNullChecks": true "strictNullChecks": true
}, },
"include": [".", ".next/types/**/*.ts"], "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