Merge pull request #2 from Openpanel-dev/feature/rename-db-columns

Feature/rename db columns
This commit is contained in:
Carl-Gerhard Lindesvärd
2024-04-09 13:23:04 +02:00
committed by GitHub
90 changed files with 481 additions and 434 deletions

View File

@@ -33,10 +33,10 @@ const startServer = async () => {
fastify.register(profileRouter, { prefix: '/profile' }); fastify.register(profileRouter, { prefix: '/profile' });
fastify.register(liveRouter, { prefix: '/live' }); fastify.register(liveRouter, { prefix: '/live' });
fastify.register(miscRouter, { prefix: '/misc' }); fastify.register(miscRouter, { prefix: '/misc' });
fastify.setErrorHandler((error, request, reply) => { fastify.setErrorHandler((error) => {
fastify.log.error(error); fastify.log.error(error);
}); });
fastify.get('/', (request, reply) => { fastify.get('/', (_request, reply) => {
reply.send({ name: 'openpanel sdk api' }); reply.send({ name: 'openpanel sdk api' });
}); });
// fastify.get('/health-check', async (request, reply) => { // fastify.get('/health-check', async (request, reply) => {

View File

@@ -89,5 +89,5 @@ export async function validateSdkRequest(
} }
} }
return client.project_id; return client.projectId;
} }

View File

@@ -71,7 +71,7 @@
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"nuqs": "^1.16.1", "nuqs": "^1.16.1",
"prisma-error-enum": "^0.1.3", "prisma-error-enum": "^0.1.3",
"pushmodal": "^0.0.8", "pushmodal": "^1.0.0",
"ramda": "^0.29.1", "ramda": "^0.29.1",
"random-animal-name": "^0.1.1", "random-animal-name": "^0.1.1",
"react": "18.2.0", "react": "18.2.0",

View File

@@ -1,27 +0,0 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import {
getClientsByOrganizationId,
getProjectsByOrganizationSlug,
} from '@openpanel/db';
import ListProjects from './list-projects';
interface PageProps {
params: {
organizationId: string;
};
}
export default async function Page({ params: { organizationId } }: PageProps) {
const [projects, clients] = await Promise.all([
getProjectsByOrganizationSlug(organizationId),
getClientsByOrganizationId(organizationId),
]);
return (
<PageLayout title="Projects" organizationSlug={organizationId}>
<ListProjects projects={projects} clients={clients} />
</PageLayout>
);
}

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { StickyBelowHeader } from '@/app/(app)/[organizationId]/[projectId]/layout-sticky-below-header'; import { StickyBelowHeader } from '@/app/(app)/[organizationSlug]/[projectId]/layout-sticky-below-header';
import { useOverviewOptions } from '@/components/overview/useOverviewOptions'; import { useOverviewOptions } from '@/components/overview/useOverviewOptions';
import { LazyChart } from '@/components/report/chart/LazyChart'; import { LazyChart } from '@/components/report/chart/LazyChart';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@@ -12,10 +12,12 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
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 { ChevronRight, MoreHorizontal, PlusIcon, Trash } from 'lucide-react'; import { ChevronRight, MoreHorizontal, 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';
import { toast } from 'sonner';
import { import {
getDefaultIntervalByDates, getDefaultIntervalByDates,
@@ -33,7 +35,13 @@ export function ListReports({ reports }: ListReportsProps) {
const router = useRouter(); const router = useRouter();
const params = useAppParams<{ dashboardId: string }>(); const params = useAppParams<{ dashboardId: string }>();
const { range, startDate, endDate } = useOverviewOptions(); const { range, startDate, endDate } = useOverviewOptions();
const deletion = api.report.delete.useMutation({
onError: handleError,
onSuccess() {
router.refresh();
toast('Report deleted');
},
});
return ( return (
<> <>
<StickyBelowHeader className="flex items-center justify-between p-4"> <StickyBelowHeader className="flex items-center justify-between p-4">
@@ -42,7 +50,7 @@ export function ListReports({ reports }: ListReportsProps) {
icon={PlusIcon} icon={PlusIcon}
onClick={() => { onClick={() => {
router.push( router.push(
`/${params.organizationId}/${ `/${params.organizationSlug}/${
params.projectId params.projectId
}/reports?${new URLSearchParams({ }/reports?${new URLSearchParams({
dashboardId: params.dashboardId, dashboardId: params.dashboardId,
@@ -60,7 +68,7 @@ export function ListReports({ reports }: ListReportsProps) {
return ( return (
<div className="card" key={report.id}> <div className="card" key={report.id}>
<Link <Link
href={`/${params.organizationId}/${params.projectId}/reports/${report.id}`} href={`/${params.organizationSlug}/${params.projectId}/reports/${report.id}`}
className="flex items-center justify-between border-b border-border p-4 leading-none [&_svg]:hover:opacity-100" className="flex items-center justify-between border-b border-border p-4 leading-none [&_svg]:hover:opacity-100"
shallow shallow
> >
@@ -95,10 +103,10 @@ export function ListReports({ reports }: ListReportsProps) {
<DropdownMenuItem <DropdownMenuItem
className="text-destructive" className="text-destructive"
onClick={(event) => { onClick={(event) => {
// event.stopPropagation(); event.stopPropagation();
// deletion.mutate({ deletion.mutate({
// reportId: report.id, reportId: report.id,
// }); });
}} }}
> >
<Trash size={16} className="mr-2" /> <Trash size={16} className="mr-2" />

View File

@@ -1,4 +1,4 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { getDashboardById, getReportsByDashboardId } from '@openpanel/db'; import { getDashboardById, getReportsByDashboardId } from '@openpanel/db';
@@ -7,14 +7,14 @@ import { ListReports } from './list-reports';
interface PageProps { interface PageProps {
params: { params: {
organizationId: string; organizationSlug: string;
projectId: string; projectId: string;
dashboardId: string; dashboardId: string;
}; };
} }
export default async function Page({ export default async function Page({
params: { organizationId, projectId, dashboardId }, params: { organizationSlug, projectId, dashboardId },
}: PageProps) { }: PageProps) {
const [dashboard, reports] = await Promise.all([ const [dashboard, reports] = await Promise.all([
getDashboardById(dashboardId, projectId), getDashboardById(dashboardId, projectId),
@@ -26,7 +26,7 @@ export default async function Page({
} }
return ( return (
<PageLayout title={dashboard.name} organizationSlug={organizationId}> <PageLayout title={dashboard.name} organizationSlug={organizationSlug}>
<ListReports reports={reports} /> <ListReports reports={reports} />
</PageLayout> </PageLayout>
); );

View File

@@ -20,7 +20,7 @@ interface ListDashboardsProps {
export function ListDashboards({ dashboards }: ListDashboardsProps) { export function ListDashboards({ dashboards }: ListDashboardsProps) {
const router = useRouter(); const router = useRouter();
const params = useAppParams(); const params = useAppParams();
const { organizationId, projectId } = params; const { organizationSlug, projectId } = params;
const deletion = api.dashboard.delete.useMutation({ const deletion = api.dashboard.delete.useMutation({
onError: (error, variables) => { onError: (error, variables) => {
return handleErrorToastOptions({ return handleErrorToastOptions({
@@ -65,8 +65,8 @@ export function ListDashboards({ dashboards }: ListDashboardsProps) {
<Card key={item.id} hover> <Card key={item.id} hover>
<div> <div>
<Link <Link
href={`/${organizationId}/${projectId}/dashboards/${item.id}`} href={`/${organizationSlug}/${projectId}/dashboards/${item.id}`}
className="block flex flex-col p-4" className="flex flex-col p-4"
> >
<span className="font-medium">{item.name}</span> <span className="font-medium">{item.name}</span>
</Link> </Link>

View File

@@ -1,4 +1,4 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
import { getDashboardsByProjectId } from '@openpanel/db'; import { getDashboardsByProjectId } from '@openpanel/db';
@@ -8,17 +8,17 @@ import { ListDashboards } from './list-dashboards';
interface PageProps { interface PageProps {
params: { params: {
projectId: string; projectId: string;
organizationId: string; organizationSlug: string;
}; };
} }
export default async function Page({ export default async function Page({
params: { projectId, organizationId }, params: { projectId, organizationSlug },
}: PageProps) { }: PageProps) {
const dashboards = await getDashboardsByProjectId(projectId); const dashboards = await getDashboardsByProjectId(projectId);
return ( return (
<PageLayout title="Dashboards" organizationSlug={organizationId}> <PageLayout title="Dashboards" organizationSlug={organizationSlug}>
{dashboards.length > 0 && <HeaderDashboards />} {dashboards.length > 0 && <HeaderDashboards />}
<ListDashboards dashboards={dashboards} /> <ListDashboards dashboards={dashboards} />
</PageLayout> </PageLayout>

View File

@@ -1,4 +1,3 @@
import { Widget } from '@/components/widget';
import { escape } from 'sqlstring'; import { escape } from 'sqlstring';
import { db, getEvents } from '@openpanel/db'; import { db, getEvents } from '@openpanel/db';
@@ -12,7 +11,7 @@ interface Props {
export default async function EventConversionsListServer({ projectId }: Props) { export default async function EventConversionsListServer({ projectId }: Props) {
const conversions = await db.eventMeta.findMany({ const conversions = await db.eventMeta.findMany({
where: { where: {
project_id: projectId, projectId,
conversion: true, conversion: true,
}, },
}); });

View File

@@ -15,7 +15,7 @@ import { EventIcon } from './event-icon';
type EventListItemProps = IServiceCreateEventPayload; type EventListItemProps = IServiceCreateEventPayload;
export function EventListItem(props: EventListItemProps) { export function EventListItem(props: EventListItemProps) {
const { organizationId, projectId } = useAppParams(); const { organizationSlug, projectId } = useAppParams();
const { createdAt, name, path, duration, meta, profile } = props; const { createdAt, name, path, duration, meta, profile } = props;
const [isDetailsOpen, setIsDetailsOpen] = useState(false); const [isDetailsOpen, setIsDetailsOpen] = useState(false);
@@ -77,7 +77,7 @@ export function EventListItem(props: EventListItemProps) {
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
}} }}
href={`/${organizationId}/${projectId}/profiles/${profile?.id}`} href={`/${organizationSlug}/${projectId}/profiles/${profile?.id}`}
className="max-w-[80px] overflow-hidden text-ellipsis whitespace-nowrap text-sm text-muted-foreground hover:underline" className="max-w-[80px] overflow-hidden text-ellipsis whitespace-nowrap text-sm text-muted-foreground hover:underline"
> >
{profile?.firstName} {profile?.lastName} {profile?.firstName} {profile?.lastName}

View File

@@ -1,4 +1,4 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons'; import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons';
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer'; import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
import { import {
@@ -17,7 +17,7 @@ import { EventList } from './event-list';
interface PageProps { interface PageProps {
params: { params: {
projectId: string; projectId: string;
organizationId: string; organizationSlug: string;
}; };
searchParams: { searchParams: {
events?: string; events?: string;
@@ -31,7 +31,7 @@ const nuqsOptions = {
}; };
export default async function Page({ export default async function Page({
params: { projectId, organizationId }, params: { projectId, organizationSlug },
searchParams, searchParams,
}: PageProps) { }: PageProps) {
const filters = const filters =
@@ -56,7 +56,7 @@ export default async function Page({
]); ]);
return ( return (
<PageLayout title="Events" organizationSlug={organizationId}> <PageLayout title="Events" organizationSlug={organizationSlug}>
<StickyBelowHeader className="flex justify-between p-4"> <StickyBelowHeader className="flex justify-between p-4">
<OverviewFiltersDrawer <OverviewFiltersDrawer
mode="events" mode="events"

View File

@@ -83,49 +83,49 @@ export default function LayoutMenu({ dashboards }: LayoutMenuProps) {
<LinkWithIcon <LinkWithIcon
icon={WallpaperIcon} icon={WallpaperIcon}
label="Overview" label="Overview"
href={`/${params.organizationId}/${projectId}`} href={`/${params.organizationSlug}/${projectId}`}
/> />
<LinkWithIcon <LinkWithIcon
icon={LayoutPanelTopIcon} icon={LayoutPanelTopIcon}
label="Dashboards" label="Dashboards"
href={`/${params.organizationId}/${projectId}/dashboards`} href={`/${params.organizationSlug}/${projectId}/dashboards`}
/> />
<LinkWithIcon <LinkWithIcon
icon={GanttChartIcon} icon={GanttChartIcon}
label="Events" label="Events"
href={`/${params.organizationId}/${projectId}/events`} href={`/${params.organizationSlug}/${projectId}/events`}
/> />
<LinkWithIcon <LinkWithIcon
icon={UsersIcon} icon={UsersIcon}
label="Profiles" label="Profiles"
href={`/${params.organizationId}/${projectId}/profiles`} href={`/${params.organizationSlug}/${projectId}/profiles`}
/> />
<LinkWithIcon <LinkWithIcon
icon={CogIcon} icon={CogIcon}
label="Settings" label="Settings"
href={`/${params.organizationId}/${projectId}/settings/organization`} href={`/${params.organizationSlug}/${projectId}/settings/organization`}
/> />
{pathname?.includes('/settings/') && ( {pathname?.includes('/settings/') && (
<div className="flex flex-col gap-1 pl-7"> <div className="flex flex-col gap-1 pl-7">
<LinkWithIcon <LinkWithIcon
icon={BuildingIcon} icon={BuildingIcon}
label="Organization" label="Organization"
href={`/${params.organizationId}/${projectId}/settings/organization`} href={`/${params.organizationSlug}/${projectId}/settings/organization`}
/> />
<LinkWithIcon <LinkWithIcon
icon={WarehouseIcon} icon={WarehouseIcon}
label="Projects" label="Projects"
href={`/${params.organizationId}/${projectId}/settings/projects`} href={`/${params.organizationSlug}/${projectId}/settings/projects`}
/> />
<LinkWithIcon <LinkWithIcon
icon={UserIcon} icon={UserIcon}
label="Profile (yours)" label="Profile (yours)"
href={`/${params.organizationId}/${projectId}/settings/profile`} href={`/${params.organizationSlug}/${projectId}/settings/profile`}
/> />
<LinkWithIcon <LinkWithIcon
icon={BookmarkIcon} icon={BookmarkIcon}
label="References" label="References"
href={`/${params.organizationId}/${projectId}/settings/references`} href={`/${params.organizationSlug}/${projectId}/settings/references`}
/> />
</div> </div>
)} )}
@@ -145,7 +145,7 @@ export default function LayoutMenu({ dashboards }: LayoutMenuProps) {
</span> </span>
</div> </div>
} }
href={`/${item.organization_slug}/${item.project_id}/dashboards/${item.id}`} href={`/${item.organizationSlug}/${item.projectId}/dashboards/${item.id}`}
/> />
))} ))}
</div> </div>

View File

@@ -18,7 +18,7 @@ export default function LayoutOrganizationSelector({
const router = useRouter(); const router = useRouter();
const organization = organizations.find( const organization = organizations.find(
(item) => item.slug === params.organizationId (item) => item.slug === params.organizationSlug
); );
if (!organization) { if (!organization) {

View File

@@ -13,7 +13,7 @@ export default function LayoutProjectSelector({
projects, projects,
}: LayoutProjectSelectorProps) { }: LayoutProjectSelectorProps) {
const router = useRouter(); const router = useRouter();
const { organizationId, projectId } = useAppParams(); const { organizationSlug, projectId } = useAppParams();
const pathname = usePathname() || ''; const pathname = usePathname() || '';
return ( return (
@@ -24,12 +24,12 @@ export default function LayoutProjectSelector({
className="w-auto min-w-0 max-sm:max-w-[100px]" className="w-auto min-w-0 max-sm:max-w-[100px]"
placeholder={'Select project'} placeholder={'Select project'}
onChange={(value) => { onChange={(value) => {
if (organizationId && projectId) { if (organizationSlug && projectId) {
const split = pathname.replace(projectId, value).split('/'); const split = pathname.replace(projectId, value).split('/');
// slicing here will remove everything after /{orgId}/{projectId}/dashboards [slice here] /xxx/xxx/xxx // slicing here will remove everything after /{orgId}/{projectId}/dashboards [slice here] /xxx/xxx/xxx
router.push(split.slice(0, 4).join('/')); router.push(split.slice(0, 4).join('/'));
} else { } else {
router.push(`/${organizationId}/${value}`); router.push(`/${organizationSlug}/${value}`);
} }
}} }}
value={projectId} value={projectId}

View File

@@ -17,13 +17,13 @@ import LayoutOrganizationSelector from './layout-organization-selector';
interface LayoutSidebarProps { interface LayoutSidebarProps {
organizations: IServiceOrganization[]; organizations: IServiceOrganization[];
dashboards: IServiceDashboards; dashboards: IServiceDashboards;
organizationId: string; organizationSlug: string;
projectId: string; projectId: string;
} }
export function LayoutSidebar({ export function LayoutSidebar({
organizations, organizations,
dashboards, dashboards,
organizationId, organizationSlug,
projectId, projectId,
}: LayoutSidebarProps) { }: LayoutSidebarProps) {
const [active, setActive] = useState(false); const [active, setActive] = useState(false);
@@ -69,7 +69,7 @@ export function LayoutSidebar({
<div className="flex flex-col gap-2 bg-background p-4 pt-0"> <div className="flex flex-col gap-2 bg-background p-4 pt-0">
<Link <Link
className={cn('flex gap-2', buttonVariants())} className={cn('flex gap-2', buttonVariants())}
href={`/${organizationId}/${projectId}/reports`} href={`/${organizationSlug}/${projectId}/reports`}
> >
<PlusIcon size={16} /> <PlusIcon size={16} />
Create a report Create a report

View File

@@ -11,22 +11,22 @@ import { LayoutSidebar } from './layout-sidebar';
interface AppLayoutProps { interface AppLayoutProps {
children: React.ReactNode; children: React.ReactNode;
params: { params: {
organizationId: string; organizationSlug: string;
projectId: string; projectId: string;
}; };
} }
export default async function AppLayout({ export default async function AppLayout({
children, children,
params: { organizationId, projectId }, params: { organizationSlug, projectId },
}: AppLayoutProps) { }: AppLayoutProps) {
const [organizations, projects, dashboards] = await Promise.all([ const [organizations, projects, dashboards] = await Promise.all([
getCurrentOrganizations(), getCurrentOrganizations(),
getCurrentProjects(organizationId), getCurrentProjects(organizationSlug),
getDashboardsByProjectId(projectId), getDashboardsByProjectId(projectId),
]); ]);
if (!organizations.find((item) => item.slug === organizationId)) { if (!organizations.find((item) => item.slug === organizationSlug)) {
return ( return (
<FullPageEmptyState <FullPageEmptyState
title="Could not find organization" title="Could not find organization"
@@ -51,7 +51,7 @@ export default async function AppLayout({
return ( return (
<div id="dashboard"> <div id="dashboard">
<LayoutSidebar <LayoutSidebar
{...{ organizationId, projectId, organizations, dashboards }} {...{ organizationSlug, projectId, organizations, dashboards }}
/> />
<div className="transition-all lg:pl-72">{children}</div> <div className="transition-all lg:pl-72">{children}</div>
</div> </div>

View File

@@ -1,4 +1,4 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons'; import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons';
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer'; import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
import ServerLiveCounter from '@/components/overview/live-counter'; import ServerLiveCounter from '@/components/overview/live-counter';
@@ -10,7 +10,7 @@ import OverviewTopGeo from '@/components/overview/overview-top-geo';
import OverviewTopPages from '@/components/overview/overview-top-pages'; import OverviewTopPages from '@/components/overview/overview-top-pages';
import OverviewTopSources from '@/components/overview/overview-top-sources'; import OverviewTopSources from '@/components/overview/overview-top-sources';
import { db } from '@openpanel/db'; import { getShareByProjectId } from '@openpanel/db';
import OverviewMetrics from '../../../../components/overview/overview-metrics'; import OverviewMetrics from '../../../../components/overview/overview-metrics';
import { StickyBelowHeader } from './layout-sticky-below-header'; import { StickyBelowHeader } from './layout-sticky-below-header';
@@ -18,22 +18,18 @@ import { OverviewReportRange } from './overview-sticky-header';
interface PageProps { interface PageProps {
params: { params: {
organizationId: string; organizationSlug: string;
projectId: string; projectId: string;
}; };
} }
export default async function Page({ export default async function Page({
params: { organizationId, projectId }, params: { organizationSlug, projectId },
}: PageProps) { }: PageProps) {
const share = await db.shareOverview.findUnique({ const share = await getShareByProjectId(projectId);
where: {
project_id: projectId,
},
});
return ( return (
<PageLayout title="Overview" organizationSlug={organizationId}> <PageLayout title="Overview" organizationSlug={organizationSlug}>
<StickyBelowHeader> <StickyBelowHeader>
<div className="flex justify-between gap-2 p-4"> <div className="flex justify-between gap-2 p-4">
<div className="flex gap-2"> <div className="flex gap-2">

View File

@@ -1,4 +1,4 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons'; import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons';
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer'; import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
import { ProfileAvatar } from '@/components/profiles/profile-avatar'; import { ProfileAvatar } from '@/components/profiles/profile-avatar';
@@ -27,9 +27,9 @@ import { StickyBelowHeader } from '../../layout-sticky-below-header';
interface PageProps { interface PageProps {
params: { params: {
organizationSlug: string;
projectId: string; projectId: string;
profileId: string; profileId: string;
organizationId: string;
}; };
searchParams: { searchParams: {
events?: string; events?: string;
@@ -41,7 +41,7 @@ interface PageProps {
} }
export default async function Page({ export default async function Page({
params: { projectId, profileId, organizationId }, params: { projectId, profileId, organizationSlug },
searchParams, searchParams,
}: PageProps) { }: PageProps) {
const eventListOptions: GetEventListOptions = { const eventListOptions: GetEventListOptions = {
@@ -125,7 +125,7 @@ export default async function Page({
return ( return (
<PageLayout <PageLayout
organizationSlug={organizationId} organizationSlug={organizationSlug}
title={ title={
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<ProfileAvatar {...profile} size="sm" className="hidden sm:block" /> <ProfileAvatar {...profile} size="sm" className="hidden sm:block" />

View File

@@ -1,4 +1,4 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons'; import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons';
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer'; import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
import { eventQueryFiltersParser } from '@/hooks/useEventQueryFilters'; import { eventQueryFiltersParser } from '@/hooks/useEventQueryFilters';
@@ -11,7 +11,7 @@ import ProfileTopServer from './profile-top';
interface PageProps { interface PageProps {
params: { params: {
organizationId: string; organizationSlug: string;
projectId: string; projectId: string;
}; };
searchParams: { searchParams: {
@@ -25,11 +25,11 @@ const nuqsOptions = {
}; };
export default function Page({ export default function Page({
params: { organizationId, projectId }, params: { organizationSlug, projectId },
searchParams: { cursor, f }, searchParams: { cursor, f },
}: PageProps) { }: PageProps) {
return ( return (
<PageLayout title="Profiles" organizationSlug={organizationId}> <PageLayout title="Profiles" organizationSlug={organizationSlug}>
{/* <StickyBelowHeader className="flex justify-between p-4"> {/* <StickyBelowHeader className="flex justify-between p-4">
<OverviewFiltersDrawer <OverviewFiltersDrawer
projectId={projectId} projectId={projectId}
@@ -53,7 +53,7 @@ export default function Page({
<ProfileLastSeenServer projectId={projectId} /> <ProfileLastSeenServer projectId={projectId} />
<ProfileTopServer <ProfileTopServer
projectId={projectId} projectId={projectId}
organizationId={organizationId} organizationSlug={organizationSlug}
/> />
</div> </div>
</div> </div>

View File

@@ -21,7 +21,7 @@ interface ProfileListProps {
count: number; count: number;
} }
export function ProfileList({ data, count }: ProfileListProps) { export function ProfileList({ data, count }: ProfileListProps) {
const { organizationId, projectId } = useAppParams(); const { organizationSlug, projectId } = useAppParams();
const { cursor, setCursor } = useCursor(); const { cursor, setCursor } = useCursor();
return ( return (
<Widget> <Widget>
@@ -46,7 +46,7 @@ export function ProfileList({ data, count }: ProfileListProps) {
render(profile) { render(profile) {
return ( return (
<Link <Link
href={`/${organizationId}/${projectId}/profiles/${profile.id}`} href={`/${organizationSlug}/${projectId}/profiles/${profile.id}`}
className="flex items-center gap-2 font-medium" className="flex items-center gap-2 font-medium"
> >
<ProfileAvatar size="sm" {...profile} /> <ProfileAvatar size="sm" {...profile} />

View File

@@ -10,11 +10,11 @@ import { chQuery, getProfiles } from '@openpanel/db';
interface Props { interface Props {
projectId: string; projectId: string;
organizationId: string; organizationSlug: string;
} }
export default async function ProfileTopServer({ export default async function ProfileTopServer({
organizationId, organizationSlug,
projectId, projectId,
}: Props) { }: Props) {
// Days since last event from users // Days since last event from users
@@ -44,7 +44,7 @@ export default async function ProfileTopServer({
render(profile) { render(profile) {
return ( return (
<Link <Link
href={`/${organizationId}/${projectId}/profiles/${profile.id}`} href={`/${organizationSlug}/${projectId}/profiles/${profile.id}`}
className="flex items-center gap-2 font-medium" className="flex items-center gap-2 font-medium"
> >
<ProfileAvatar size="sm" {...profile} /> <ProfileAvatar size="sm" {...profile} />

View File

@@ -1,4 +1,4 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
import { Pencil } from 'lucide-react'; import { Pencil } from 'lucide-react';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
@@ -8,14 +8,14 @@ import ReportEditor from '../report-editor';
interface PageProps { interface PageProps {
params: { params: {
organizationSlug: string;
projectId: string; projectId: string;
reportId: string; reportId: string;
organizationId: string;
}; };
} }
export default async function Page({ export default async function Page({
params: { reportId, organizationId }, params: { reportId, organizationSlug },
}: PageProps) { }: PageProps) {
const report = await getReportById(reportId); const report = await getReportById(reportId);
@@ -25,7 +25,7 @@ export default async function Page({
return ( return (
<PageLayout <PageLayout
organizationSlug={organizationId} organizationSlug={organizationSlug}
title={ title={
<div className="flex cursor-pointer items-center gap-2"> <div className="flex cursor-pointer items-center gap-2">
{report.name} {report.name}

View File

@@ -1,19 +1,19 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
import { Pencil } from 'lucide-react'; import { Pencil } from 'lucide-react';
import ReportEditor from './report-editor'; import ReportEditor from './report-editor';
interface PageProps { interface PageProps {
params: { params: {
organizationId: string; organizationSlug: string;
projectId: string; projectId: string;
}; };
} }
export default function Page({ params: { organizationId } }: PageProps) { export default function Page({ params: { organizationSlug } }: PageProps) {
return ( return (
<PageLayout <PageLayout
organizationSlug={organizationId} organizationSlug={organizationSlug}
title={ title={
<div className="flex cursor-pointer items-center gap-2"> <div className="flex cursor-pointer items-center gap-2">
Unnamed report Unnamed report

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { StickyBelowHeader } from '@/app/(app)/[organizationId]/[projectId]/layout-sticky-below-header'; import { StickyBelowHeader } from '@/app/(app)/[organizationSlug]/[projectId]/layout-sticky-below-header';
import { ChartSwitch } from '@/components/report/chart'; import { ChartSwitch } from '@/components/report/chart';
import { ReportChartType } from '@/components/report/ReportChartType'; import { ReportChartType } from '@/components/report/ReportChartType';
import { ReportInterval } from '@/components/report/ReportInterval'; import { ReportInterval } from '@/components/report/ReportInterval';

View File

@@ -33,7 +33,7 @@ interface Props {
export default function CreateInvite({ projects }: Props) { export default function CreateInvite({ projects }: Props) {
const router = useRouter(); const router = useRouter();
const { organizationId: organizationSlug } = useAppParams(); const { organizationSlug } = useAppParams();
const { register, handleSubmit, formState, reset, control } = useForm<IForm>({ const { register, handleSubmit, formState, reset, control } = useForm<IForm>({
resolver: zodResolver(zInviteUser), resolver: zodResolver(zInviteUser),

View File

@@ -1,7 +1,7 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { getInvites, getOrganizationBySlug } from '@openpanel/db'; import { getOrganizationBySlug } from '@openpanel/db';
import EditOrganization from './edit-organization'; import EditOrganization from './edit-organization';
import InvitesServer from './invites'; import InvitesServer from './invites';
@@ -9,12 +9,12 @@ import MembersServer from './members';
interface PageProps { interface PageProps {
params: { params: {
organizationId: string; organizationSlug: string;
}; };
} }
export default async function Page({ export default async function Page({
params: { organizationId: organizationSlug }, params: { organizationSlug },
}: PageProps) { }: PageProps) {
const organization = await getOrganizationBySlug(organizationSlug); const organization = await getOrganizationBySlug(organizationSlug);

View File

@@ -1,4 +1,4 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
import { auth } from '@clerk/nextjs'; import { auth } from '@clerk/nextjs';
import { getUserById } from '@openpanel/db'; import { getUserById } from '@openpanel/db';
@@ -8,15 +8,17 @@ import { Logout } from './logout';
interface PageProps { interface PageProps {
params: { params: {
organizationId: string; organizationSlug: string;
}; };
} }
export default async function Page({ params: { organizationId } }: PageProps) { export default async function Page({
params: { organizationSlug },
}: PageProps) {
const { userId } = auth(); const { userId } = auth();
const profile = await getUserById(userId!); const profile = await getUserById(userId!);
return ( return (
<PageLayout title={profile.lastName} organizationSlug={organizationId}> <PageLayout title={profile.lastName} organizationSlug={organizationSlug}>
<div className="flex flex-col gap-4 p-4"> <div className="flex flex-col gap-4 p-4">
<EditProfile profile={profile} /> <EditProfile profile={profile} />
<Logout /> <Logout />

View File

@@ -1,9 +1,8 @@
'use client'; 'use client';
import { StickyBelowHeader } from '@/app/(app)/[organizationId]/[projectId]/layout-sticky-below-header'; import { StickyBelowHeader } from '@/app/(app)/[organizationSlug]/[projectId]/layout-sticky-below-header';
import { ClientActions } from '@/components/clients/client-actions'; import { ClientActions } from '@/components/clients/client-actions';
import { ProjectActions } from '@/components/projects/project-actions'; import { ProjectActions } from '@/components/projects/project-actions';
// import { columns } from '@/components/projects/table';
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
@@ -13,7 +12,6 @@ import {
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Tooltiper } from '@/components/ui/tooltip'; import { Tooltiper } from '@/components/ui/tooltip';
import { useAppParams } from '@/hooks/useAppParams';
import { pushModal } from '@/modals'; import { pushModal } from '@/modals';
import { InfoIcon, PlusIcon, PlusSquareIcon } from 'lucide-react'; import { InfoIcon, PlusIcon, PlusSquareIcon } from 'lucide-react';
@@ -24,20 +22,12 @@ interface ListProjectsProps {
clients: IServiceClientWithProject[]; clients: IServiceClientWithProject[];
} }
export default function ListProjects({ projects, clients }: ListProjectsProps) { export default function ListProjects({ projects, clients }: ListProjectsProps) {
const organizationId = useAppParams().organizationId;
return ( return (
<> <>
<StickyBelowHeader> <StickyBelowHeader>
<div className="flex items-center justify-between p-4"> <div className="flex items-center justify-between p-4">
<div /> <div />
<Button <Button icon={PlusIcon} onClick={() => pushModal('AddProject')}>
icon={PlusIcon}
onClick={() =>
pushModal('AddProject', {
organizationId,
})
}
>
<span className="max-sm:hidden">Create project</span> <span className="max-sm:hidden">Create project</span>
<span className="sm:hidden">Project</span> <span className="sm:hidden">Project</span>
</Button> </Button>
@@ -57,7 +47,7 @@ export default function ListProjects({ projects, clients }: ListProjectsProps) {
<Accordion type="single" collapsible className="-mx-4"> <Accordion type="single" collapsible className="-mx-4">
{projects.map((project) => { {projects.map((project) => {
const pClients = clients.filter( const pClients = clients.filter(
(client) => client.project_id === project.id (client) => client.projectId === project.id
); );
return ( return (
<AccordionItem <AccordionItem

View File

@@ -0,0 +1,29 @@
import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
import {
getClientsByOrganizationSlug,
getProjectsByOrganizationSlug,
} from '@openpanel/db';
import ListProjects from './list-projects';
interface PageProps {
params: {
organizationSlug: string;
};
}
export default async function Page({
params: { organizationSlug },
}: PageProps) {
const [projects, clients] = await Promise.all([
getProjectsByOrganizationSlug(organizationSlug),
getClientsByOrganizationSlug(organizationSlug),
]);
return (
<PageLayout title="Projects" organizationSlug={organizationSlug}>
<ListProjects projects={projects} clients={clients} />
</PageLayout>
);
}

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { StickyBelowHeader } from '@/app/(app)/[organizationId]/[projectId]/layout-sticky-below-header'; import { StickyBelowHeader } from '@/app/(app)/[organizationSlug]/[projectId]/layout-sticky-below-header';
import { DataTable } from '@/components/data-table'; import { DataTable } from '@/components/data-table';
import { columns } from '@/components/references/table'; import { columns } from '@/components/references/table';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';

View File

@@ -1,4 +1,4 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
import { getReferences } from '@openpanel/db'; import { getReferences } from '@openpanel/db';
@@ -6,24 +6,24 @@ import ListReferences from './list-references';
interface PageProps { interface PageProps {
params: { params: {
organizationId: string; organizationSlug: string;
projectId: string; projectId: string;
}; };
} }
export default async function Page({ export default async function Page({
params: { organizationId, projectId }, params: { organizationSlug, projectId },
}: PageProps) { }: PageProps) {
const references = await getReferences({ const references = await getReferences({
where: { where: {
project_id: projectId, projectId,
}, },
take: 50, take: 50,
skip: 0, skip: 0,
}); });
return ( return (
<PageLayout title="References" organizationSlug={organizationId}> <PageLayout title="References" organizationSlug={organizationSlug}>
<ListReferences data={references} /> <ListReferences data={references} />
</PageLayout> </PageLayout>
); );

View File

@@ -36,7 +36,7 @@ export function CreateProject() {
const onSubmit: SubmitHandler<IForm> = (values) => { const onSubmit: SubmitHandler<IForm> = (values) => {
mutation.mutate({ mutation.mutate({
name: values.name, name: values.name,
organizationId: params.organizationId, organizationSlug: params.organizationSlug,
}); });
}; };

View File

@@ -12,14 +12,16 @@ import { CreateProject } from './create-project';
interface PageProps { interface PageProps {
params: { params: {
organizationId: string; organizationSlug: string;
}; };
} }
export default async function Page({ params: { organizationId } }: PageProps) { export default async function Page({
params: { organizationSlug },
}: PageProps) {
const [organization, projects] = await Promise.all([ const [organization, projects] = await Promise.all([
getOrganizationBySlug(organizationId), getOrganizationBySlug(organizationSlug),
getCurrentProjects(organizationId), getCurrentProjects(organizationSlug),
]); ]);
if (!organization) { if (!organization) {
@@ -55,7 +57,7 @@ export default async function Page({ params: { organizationId } }: PageProps) {
} }
if (projects.length === 1 && projects[0]) { if (projects.length === 1 && projects[0]) {
return redirect(`/${organizationId}/${projects[0].id}`); return redirect(`/${organizationSlug}/${projects[0].id}`);
} }
return ( return (

View File

@@ -1,5 +1,5 @@
import { StickyBelowHeader } from '@/app/(app)/[organizationId]/[projectId]/layout-sticky-below-header'; import { StickyBelowHeader } from '@/app/(app)/[organizationSlug]/[projectId]/layout-sticky-below-header';
import { OverviewReportRange } from '@/app/(app)/[organizationId]/[projectId]/overview-sticky-header'; import { OverviewReportRange } from '@/app/(app)/[organizationSlug]/[projectId]/overview-sticky-header';
import { Logo } from '@/components/logo'; import { Logo } from '@/components/logo';
import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons'; import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons';
import ServerLiveCounter from '@/components/overview/live-counter'; import ServerLiveCounter from '@/components/overview/live-counter';
@@ -28,8 +28,8 @@ export default async function Page({ params: { id } }: PageProps) {
if (!share.public) { if (!share.public) {
return notFound(); return notFound();
} }
const projectId = share.project_id; const projectId = share.projectId;
const organization = await getOrganizationBySlug(share.organization_slug); const organization = await getOrganizationBySlug(share.organizationSlug);
return ( return (
<div className="bg-gradient-to-tl from-blue-950 to-blue-600 p-4 md:p-16"> <div className="bg-gradient-to-tl from-blue-950 to-blue-600 p-4 md:p-16">

View File

@@ -12,9 +12,9 @@ export async function POST(request: Request) {
data: access data: access
.filter((a): a is string => typeof a === 'string') .filter((a): a is string => typeof a === 'string')
.map((projectId) => ({ .map((projectId) => ({
organization_slug: payload.data.organization.slug!, organizationSlug: payload.data.organization.slug!,
project_id: projectId, projectId: projectId,
user_id: payload.data.public_user_data.user_id, userId: payload.data.public_user_data.user_id,
level: AccessLevel.read, level: AccessLevel.read,
})), })),
}); });
@@ -23,8 +23,8 @@ export async function POST(request: Request) {
if (payload.type === 'organizationMembership.deleted') { if (payload.type === 'organizationMembership.deleted') {
await db.projectAccess.deleteMany({ await db.projectAccess.deleteMany({
where: { where: {
organization_slug: payload.data.organization.slug!, organizationSlug: payload.data.organization.slug!,
user_id: payload.data.public_user_data.user_id, userId: payload.data.public_user_data.user_id,
}, },
}); });
} }

View File

@@ -58,9 +58,8 @@ export function OverviewShare({ data }: OverviewShareProps) {
<DropdownMenuItem <DropdownMenuItem
onClick={() => { onClick={() => {
mutation.mutate({ mutation.mutate({
...data,
public: false, public: false,
projectId: data?.project_id,
organizationId: data?.organization_slug,
password: null, password: null,
}); });
}} }}

View File

@@ -1,8 +1,7 @@
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
// eslint-disable-next-line
type AppParams = { type AppParams = {
organizationId: string; organizationSlug: string;
projectId: string; projectId: string;
}; };
@@ -10,7 +9,7 @@ export function useAppParams<T>() {
const params = useParams<T & AppParams>(); const params = useParams<T & AppParams>();
return { return {
...(params ?? {}), ...(params ?? {}),
organizationId: params?.organizationId, organizationSlug: params?.organizationSlug,
projectId: params?.projectId, projectId: params?.projectId,
} as T & AppParams; } as T & AppParams;
} }

View File

@@ -43,7 +43,7 @@ interface Props {
projectId: string; projectId: string;
} }
export default function AddClient(props: Props) { export default function AddClient(props: Props) {
const { organizationId, projectId } = useAppParams(); const { organizationSlug, projectId } = useAppParams();
const router = useRouter(); const router = useRouter();
const form = useForm<IForm>({ const form = useForm<IForm>({
resolver: zodResolver(validation), resolver: zodResolver(validation),
@@ -62,14 +62,14 @@ export default function AddClient(props: Props) {
}, },
}); });
const query = api.project.list.useQuery({ const query = api.project.list.useQuery({
organizationId, organizationSlug,
}); });
const onSubmit: SubmitHandler<IForm> = (values) => { const onSubmit: SubmitHandler<IForm> = (values) => {
mutation.mutate({ mutation.mutate({
name: values.name, name: values.name,
cors: values.tab === 'website' ? values.cors : null, cors: values.tab === 'website' ? values.cors : null,
projectId: values.projectId, projectId: values.projectId,
organizationId, organizationSlug,
}); });
}; };

View File

@@ -19,7 +19,7 @@ const validator = z.object({
type IForm = z.infer<typeof validator>; type IForm = z.infer<typeof validator>;
export default function AddDashboard() { export default function AddDashboard() {
const { projectId, organizationId: organizationSlug } = useAppParams(); const { projectId, organizationSlug } = useAppParams();
const router = useRouter(); const router = useRouter();
const { register, handleSubmit, formState } = useForm<IForm>({ const { register, handleSubmit, formState } = useForm<IForm>({

View File

@@ -1,6 +1,7 @@
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 { api, handleError } from '@/trpc/client'; 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';
@@ -16,10 +17,9 @@ const validator = z.object({
}); });
type IForm = z.infer<typeof validator>; type IForm = z.infer<typeof validator>;
interface AddProjectProps {
organizationId: string; export default function AddProject() {
} const { organizationSlug } = useAppParams();
export default function AddProject({ organizationId }: AddProjectProps) {
const router = useRouter(); const router = useRouter();
const mutation = api.project.create.useMutation({ const mutation = api.project.create.useMutation({
onError: handleError, onError: handleError,
@@ -45,7 +45,7 @@ export default function AddProject({ organizationId }: AddProjectProps) {
onSubmit={handleSubmit((values) => { onSubmit={handleSubmit((values) => {
mutation.mutate({ mutation.mutate({
...values, ...values,
organizationId, organizationSlug,
}); });
})} })}
> >

View File

@@ -1,12 +1,11 @@
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 { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { api, handleError } from '@/trpc/client'; 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 { useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { z } from 'zod'; import type { z } from 'zod';

View File

@@ -21,7 +21,7 @@ type EditReportProps = {
}; };
export default function EditReport({ form, onSubmit }: EditReportProps) { export default function EditReport({ form, onSubmit }: EditReportProps) {
const { register, handleSubmit, reset, formState } = useForm<IForm>({ const { register, handleSubmit, formState } = useForm<IForm>({
resolver: zodResolver(validator), resolver: zodResolver(validator),
defaultValues: form, defaultValues: form,
}); });

View File

@@ -8,10 +8,11 @@ import { popModal } from '..';
interface ModalContentProps { interface ModalContentProps {
children: React.ReactNode; children: React.ReactNode;
className?: string;
} }
export function ModalContent({ children }: ModalContentProps) { export function ModalContent({ children, className }: ModalContentProps) {
return <DialogContent>{children}</DialogContent>; return <DialogContent className={className}>{children}</DialogContent>;
} }
interface ModalHeaderProps { interface ModalHeaderProps {

View File

@@ -30,7 +30,7 @@ type IForm = z.infer<typeof validator>;
export default function SaveReport({ report }: SaveReportProps) { export default function SaveReport({ report }: SaveReportProps) {
const router = useRouter(); const router = useRouter();
const { organizationId: organizationSlug, projectId } = useAppParams(); const { organizationSlug, projectId } = useAppParams();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const dashboardId = searchParams?.get('dashboardId') ?? undefined; const dashboardId = searchParams?.get('dashboardId') ?? undefined;

View File

@@ -20,16 +20,16 @@ const validator = zShareOverview;
type IForm = z.infer<typeof validator>; type IForm = z.infer<typeof validator>;
export default function ShareOverviewModal() { export default function ShareOverviewModal() {
const { projectId, organizationId: organizationSlug } = useAppParams(); const { projectId, organizationSlug } = useAppParams();
const router = useRouter(); const router = useRouter();
const { register, handleSubmit, formState, control } = useForm<IForm>({ const { register, handleSubmit, control } = useForm<IForm>({
resolver: zodResolver(validator), resolver: zodResolver(validator),
defaultValues: { defaultValues: {
public: true, public: true,
password: '', password: '',
projectId, projectId,
organizationId: organizationSlug, organizationSlug,
}, },
}); });

View File

@@ -5,8 +5,13 @@ import dynamic from 'next/dynamic';
import { createPushModal } from 'pushmodal'; import { createPushModal } from 'pushmodal';
import type { ConfirmProps } from './Confirm'; import type { ConfirmProps } from './Confirm';
import { ModalContent } from './Modal/Container';
const Loading = () => <Loader className="mb-8 animate-spin" size={40} />; const Loading = () => (
<ModalContent className="flex items-center justify-center p-16">
<Loader className="animate-spin" size={40} />
</ModalContent>
);
const modals = { const modals = {
EditProject: dynamic(() => import('./EditProject'), { EditProject: dynamic(() => import('./EditProject'), {

View File

@@ -3,38 +3,9 @@ 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';
import { db, transformClient } from '@openpanel/db'; import { db } from '@openpanel/db';
export const clientRouter = createTRPCRouter({ export const clientRouter = createTRPCRouter({
list: protectedProcedure
.input(
z.object({
organizationId: z.string(),
})
)
.query(async ({ input: { organizationId } }) => {
return db.client.findMany({
where: {
organization_slug: organizationId,
},
include: {
project: true,
},
});
}),
get: protectedProcedure
.input(
z.object({
id: z.string(),
})
)
.query(({ input }) => {
return db.client.findUniqueOrThrow({
where: {
id: input.id,
},
});
}),
update: protectedProcedure update: protectedProcedure
.input( .input(
z.object({ z.object({
@@ -59,7 +30,7 @@ export const clientRouter = createTRPCRouter({
z.object({ z.object({
name: z.string(), name: z.string(),
projectId: z.string(), projectId: z.string(),
organizationId: z.string(), organizationSlug: z.string(),
cors: z.string().nullable(), cors: z.string().nullable(),
}) })
) )
@@ -67,8 +38,8 @@ export const clientRouter = createTRPCRouter({
const secret = randomUUID(); const secret = randomUUID();
const client = await db.client.create({ const client = await db.client.create({
data: { data: {
organization_slug: input.organizationId, organizationSlug: input.organizationSlug,
project_id: input.projectId, projectId: input.projectId,
name: input.name, name: input.name,
secret: input.cors ? null : await hashPassword(secret), secret: input.cors ? null : await hashPassword(secret),
cors: input.cors ? stripTrailingSlash(input.cors) : '*', cors: input.cors ? stripTrailingSlash(input.cors) : '*',
@@ -76,7 +47,7 @@ export const clientRouter = createTRPCRouter({
}); });
return { return {
...transformClient(client), ...client,
secret: input.cors ? null : secret, secret: input.cors ? null : secret,
}; };
}), }),

View File

@@ -28,8 +28,8 @@ export const dashboardRouter = createTRPCRouter({
return db.dashboard.create({ return db.dashboard.create({
data: { data: {
id: await getId('dashboard', name), id: await getId('dashboard', name),
project_id: projectId, projectId: projectId,
organization_slug: organizationSlug, organizationSlug: organizationSlug,
name, name,
}, },
}); });
@@ -63,7 +63,7 @@ export const dashboardRouter = createTRPCRouter({
if (forceDelete) { if (forceDelete) {
await db.report.deleteMany({ await db.report.deleteMany({
where: { where: {
dashboard_id: id, dashboardId: id,
}, },
}); });
} }

View File

@@ -22,12 +22,12 @@ export const eventRouter = createTRPCRouter({
.mutation(({ input: { projectId, name, icon, color, conversion } }) => { .mutation(({ input: { projectId, name, icon, color, conversion } }) => {
return db.eventMeta.upsert({ return db.eventMeta.upsert({
where: { where: {
name_project_id: { name_projectId: {
name, name,
project_id: projectId, projectId,
}, },
}, },
create: { project_id: projectId, name, icon, color, conversion }, create: { projectId, name, icon, color, conversion },
update: { icon, color, conversion }, update: { icon, color, conversion },
}); });
}), }),

View File

@@ -4,12 +4,7 @@ import { clerkClient } from '@clerk/nextjs';
import { z } from 'zod'; import { z } from 'zod';
import { hashPassword, stripTrailingSlash } from '@openpanel/common'; import { hashPassword, stripTrailingSlash } from '@openpanel/common';
import { import { db, transformOrganization } from '@openpanel/db';
db,
transformClient,
transformOrganization,
transformProject,
} from '@openpanel/db';
export const onboardingRouter = createTRPCRouter({ export const onboardingRouter = createTRPCRouter({
organziation: protectedProcedure organziation: protectedProcedure
@@ -30,7 +25,7 @@ export const onboardingRouter = createTRPCRouter({
const project = await db.project.create({ const project = await db.project.create({
data: { data: {
name: input.project, name: input.project,
organization_slug: org.slug, organizationSlug: org.slug,
}, },
}); });
@@ -38,19 +33,19 @@ export const onboardingRouter = createTRPCRouter({
const client = await db.client.create({ const client = await db.client.create({
data: { data: {
name: `${project.name} Client`, name: `${project.name} Client`,
organization_slug: org.slug, organizationSlug: org.slug,
project_id: project.id, projectId: project.id,
cors: input.cors ? stripTrailingSlash(input.cors) : '*', cors: input.cors ? stripTrailingSlash(input.cors) : '*',
secret: input.cors ? null : await hashPassword(secret), secret: input.cors ? null : await hashPassword(secret),
}, },
}); });
return { return {
client: transformClient({ client: {
...client, ...client,
secret: input.cors ? null : secret, secret: input.cors ? null : secret,
}), },
project: transformProject(project), project,
organization: transformOrganization(org), organization: transformOrganization(org),
}; };
} }

View File

@@ -77,15 +77,15 @@ export const organizationRouter = createTRPCRouter({
return db.$transaction([ return db.$transaction([
db.projectAccess.deleteMany({ db.projectAccess.deleteMany({
where: { where: {
user_id: input.userId, userId: input.userId,
organization_slug: input.organizationSlug, organizationSlug: input.organizationSlug,
}, },
}), }),
db.projectAccess.createMany({ db.projectAccess.createMany({
data: input.access.map((projectId) => ({ data: input.access.map((projectId) => ({
user_id: input.userId, userId: input.userId,
organization_slug: input.organizationSlug, organizationSlug: input.organizationSlug,
project_id: projectId, projectId: projectId,
level: 'read', level: 'read',
})), })),
}), }),

View File

@@ -8,12 +8,12 @@ export const projectRouter = createTRPCRouter({
list: protectedProcedure list: protectedProcedure
.input( .input(
z.object({ z.object({
organizationId: z.string().nullable(), organizationSlug: z.string().nullable(),
}) })
) )
.query(async ({ input: { organizationId } }) => { .query(async ({ input: { organizationSlug } }) => {
if (organizationId === null) return []; if (organizationSlug === null) return [];
return getProjectsByOrganizationSlug(organizationId); return getProjectsByOrganizationSlug(organizationSlug);
}), }),
update: protectedProcedure update: protectedProcedure
@@ -37,15 +37,15 @@ export const projectRouter = createTRPCRouter({
.input( .input(
z.object({ z.object({
name: z.string().min(1), name: z.string().min(1),
organizationId: z.string(), organizationSlug: z.string(),
}) })
) )
.mutation(async ({ input }) => { .mutation(async ({ input: { name, organizationSlug } }) => {
return db.project.create({ return db.project.create({
data: { data: {
id: await getId('project', input.name), id: await getId('project', name),
organization_slug: input.organizationId, organizationSlug: organizationSlug,
name: input.name, name: name,
}, },
}); });
}), }),

View File

@@ -19,7 +19,7 @@ export const referenceRouter = createTRPCRouter({
data: { data: {
title, title,
description, description,
project_id: projectId, projectId,
date: new Date(datetime), date: new Date(datetime),
}, },
}); });
@@ -47,7 +47,7 @@ export const referenceRouter = createTRPCRouter({
const { startDate, endDate } = getChartStartEndDate(input); const { startDate, endDate } = getChartStartEndDate(input);
return getReferences({ return getReferences({
where: { where: {
project_id: projectId, projectId,
date: { date: {
gte: new Date(startDate), gte: new Date(startDate),
lte: new Date(endDate), lte: new Date(endDate),

View File

@@ -20,14 +20,14 @@ export const reportRouter = createTRPCRouter({
}); });
return db.report.create({ return db.report.create({
data: { data: {
project_id: dashboard.project_id, projectId: dashboard.projectId,
dashboard_id: dashboardId, dashboardId,
name: report.name, name: report.name,
events: report.events, events: report.events,
interval: report.interval, interval: report.interval,
breakdowns: report.breakdowns, breakdowns: report.breakdowns,
chart_type: report.chartType, chartType: report.chartType,
line_type: report.lineType, lineType: report.lineType,
range: report.range, range: report.range,
formula: report.formula, formula: report.formula,
}, },
@@ -50,8 +50,8 @@ export const reportRouter = createTRPCRouter({
events: report.events, events: report.events,
interval: report.interval, interval: report.interval,
breakdowns: report.breakdowns, breakdowns: report.breakdowns,
chart_type: report.chartType, chartType: report.chartType,
line_type: report.lineType, lineType: report.lineType,
range: report.range, range: report.range,
formula: report.formula, formula: report.formula,
}, },

View File

@@ -12,12 +12,12 @@ export const shareRouter = createTRPCRouter({
.mutation(({ input }) => { .mutation(({ input }) => {
return db.shareOverview.upsert({ return db.shareOverview.upsert({
where: { where: {
project_id: input.projectId, projectId: input.projectId,
}, },
create: { create: {
id: uid.rnd(), id: uid.rnd(),
organization_slug: input.organizationId, organizationSlug: input.organizationSlug,
project_id: input.projectId, projectId: input.projectId,
public: input.public, public: input.public,
password: input.password || null, password: input.password || null,
}, },

View File

@@ -4,11 +4,10 @@ import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import { Heading2, Lead2 } from './copy'; import { Heading2, Lead2 } from './copy';
import { JoinWaitlist } from './join-waitlist';
export default function Footer() { export default function Footer() {
return ( return (
<footer className="bg-blue-darker relative relative mt-40 text-white"> <footer className="bg-blue-darker relative mt-40 text-white">
<div className="absolute inset-0 h-full w-full bg-[radial-gradient(circle,rgba(255,255,255,0.2)_0%,rgba(255,255,255,0)_100%)]"></div> <div className="absolute inset-0 h-full w-full bg-[radial-gradient(circle,rgba(255,255,255,0.2)_0%,rgba(255,255,255,0)_100%)]"></div>
<div className="container relative flex flex-col items-center text-center"> <div className="container relative flex flex-col items-center text-center">
<div className="my-24"> <div className="my-24">

View File

@@ -0,0 +1,97 @@
-- Project
-- organization_slug -> organizationSlug
ALTER TABLE
IF EXISTS "projects" RENAME COLUMN "organization_slug" TO "organizationSlug";
-- ProjectAccess
-- project_id -> projectId
-- organization_slug -> organizationSlug
-- user_id -> userId
ALTER TABLE
IF EXISTS "project_access" RENAME COLUMN "project_id" TO "projectId";
ALTER TABLE
IF EXISTS "project_access" RENAME COLUMN "organization_slug" TO "organizationSlug";
ALTER TABLE
IF EXISTS "project_access" RENAME COLUMN "user_id" TO "userId";
-- Event
-- project_id -> projectId
-- profile_id -> profileId
ALTER TABLE
IF EXISTS "events" RENAME COLUMN "project_id" TO "projectId";
ALTER TABLE
IF EXISTS "events" RENAME COLUMN "profile_id" TO "profileId";
-- Profile
-- external_id -> externalId
-- first_name -> firstName
-- last_name -> lastName
-- project_id -> projectId
ALTER TABLE
IF EXISTS "profiles" RENAME COLUMN "external_id" TO "externalId";
ALTER TABLE
IF EXISTS "profiles" RENAME COLUMN "first_name" TO "firstName";
ALTER TABLE
IF EXISTS "profiles" RENAME COLUMN "last_name" TO "lastName";
ALTER TABLE
IF EXISTS "profiles" RENAME COLUMN "project_id" TO "projectId";
-- Client
-- project_id -> projectId
-- organization_slug -> organizationSlug
ALTER TABLE
IF EXISTS "clients" RENAME COLUMN "project_id" TO "projectId";
ALTER TABLE
IF EXISTS "clients" RENAME COLUMN "organization_slug" TO "organizationSlug";
-- Dashboard
-- organization_slug -> organizationSlug
-- project_id -> projectId
ALTER TABLE
IF EXISTS "dashboards" RENAME COLUMN "organization_slug" TO "organizationSlug";
ALTER TABLE
IF EXISTS "dashboards" RENAME COLUMN "project_id" TO "projectId";
-- Report
-- chart_type -> chartType
-- line_type -> lineType
-- project_id -> projectId
-- dashboard_id -> dashboardId
ALTER TABLE
IF EXISTS "reports" RENAME COLUMN "chart_type" TO "chartType";
ALTER TABLE
IF EXISTS "reports" RENAME COLUMN "line_type" TO "lineType";
ALTER TABLE
IF EXISTS "reports" RENAME COLUMN "project_id" TO "projectId";
ALTER TABLE
IF EXISTS "reports" RENAME COLUMN "dashboard_id" TO "dashboardId";
-- ShareOverview
-- project_id -> projectId
-- organization_slug -> organizationSlug
ALTER TABLE
IF EXISTS "shares" RENAME COLUMN "project_id" TO "projectId";
ALTER TABLE
IF EXISTS "shares" RENAME COLUMN "organization_slug" TO "organizationSlug";
-- EventMeta (ta bort constraint)
-- project_id -> projectId
ALTER TABLE
IF EXISTS "event_meta" RENAME COLUMN "project_id" TO "projectId";
-- Reference
-- project_id -> projectId
ALTER TABLE
IF EXISTS "references" RENAME COLUMN "project_id" TO "projectId";

View File

@@ -0,0 +1,35 @@
-- RenameForeignKey
ALTER TABLE "clients" RENAME CONSTRAINT "clients_project_id_fkey" TO "clients_projectId_fkey";
-- RenameForeignKey
ALTER TABLE "dashboards" RENAME CONSTRAINT "dashboards_project_id_fkey" TO "dashboards_projectId_fkey";
-- RenameForeignKey
ALTER TABLE "event_meta" RENAME CONSTRAINT "event_meta_project_id_fkey" TO "event_meta_projectId_fkey";
-- RenameForeignKey
ALTER TABLE "events" RENAME CONSTRAINT "events_project_id_fkey" TO "events_projectId_fkey";
-- RenameForeignKey
ALTER TABLE "profiles" RENAME CONSTRAINT "profiles_project_id_fkey" TO "profiles_projectId_fkey";
-- RenameForeignKey
ALTER TABLE "project_access" RENAME CONSTRAINT "project_access_project_id_fkey" TO "project_access_projectId_fkey";
-- RenameForeignKey
ALTER TABLE "references" RENAME CONSTRAINT "references_project_id_fkey" TO "references_projectId_fkey";
-- RenameForeignKey
ALTER TABLE "reports" RENAME CONSTRAINT "reports_dashboard_id_fkey" TO "reports_dashboardId_fkey";
-- RenameForeignKey
ALTER TABLE "reports" RENAME CONSTRAINT "reports_project_id_fkey" TO "reports_projectId_fkey";
-- RenameForeignKey
ALTER TABLE "shares" RENAME CONSTRAINT "shares_project_id_fkey" TO "shares_projectId_fkey";
-- RenameIndex
ALTER INDEX "event_meta_name_project_id_key" RENAME TO "event_meta_name_projectId_key";
-- RenameIndex
ALTER INDEX "shares_project_id_key" RENAME TO "shares_projectId_key";

View File

@@ -11,23 +11,23 @@ datasource db {
} }
model Project { model Project {
id String @id @default(dbgenerated("gen_random_uuid()")) id String @id @default(dbgenerated("gen_random_uuid()"))
name String name String
organization_slug String organizationSlug String
events Event[] eventsCount Int @default(0)
eventsCount Int @default(0)
profiles Profile[]
clients Client[]
createdAt DateTime @default(now()) events Event[]
updatedAt DateTime @default(now()) @updatedAt profiles Profile[]
clients Client[]
reports Report[] reports Report[]
dashboards Dashboard[] dashboards Dashboard[]
share ShareOverview? share ShareOverview?
EventMeta EventMeta[] meta EventMeta[]
Reference Reference[] references Reference[]
access ProjectAccess[]
access ProjectAccess[] createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@map("projects") @@map("projects")
} }
@@ -39,14 +39,14 @@ enum AccessLevel {
} }
model ProjectAccess { model ProjectAccess {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
project_id String projectId String
project Project @relation(fields: [project_id], references: [id]) project Project @relation(fields: [projectId], references: [id])
organization_slug String organizationSlug String
user_id String userId String
level AccessLevel level AccessLevel
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
@@map("project_access") @@map("project_access")
} }
@@ -55,10 +55,10 @@ model Event {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String name String
properties Json properties Json
project_id String projectId String
project Project @relation(fields: [project_id], references: [id]) project Project @relation(fields: [projectId], references: [id])
profile_id String? profileId String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
@@ -75,29 +75,29 @@ model Salt {
} }
model Profile { model Profile {
id String @id id String @id
external_id String? externalId String?
first_name String? firstName String?
last_name String? lastName String?
email String? email String?
avatar String? avatar String?
properties Json properties Json
project_id String projectId String
project Project @relation(fields: [project_id], references: [id]) project Project @relation(fields: [projectId], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
@@map("profiles") @@map("profiles")
} }
model Client { model Client {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String name String
secret String? secret String?
project_id String projectId String
project Project @relation(fields: [project_id], references: [id]) project Project @relation(fields: [projectId], references: [id])
organization_slug String organizationSlug String
cors String @default("*") cors String @default("*")
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
@@ -124,12 +124,12 @@ enum ChartType {
} }
model Dashboard { model Dashboard {
id String @id @default(dbgenerated("gen_random_uuid()")) id String @id @default(dbgenerated("gen_random_uuid()"))
name String name String
organization_slug String organizationSlug String
project_id String projectId String
project Project @relation(fields: [project_id], references: [id]) project Project @relation(fields: [projectId], references: [id])
reports Report[] reports Report[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
@@ -149,19 +149,19 @@ model Report {
name String name String
interval Interval interval Interval
range String @default("1m") range String @default("1m")
chart_type ChartType chartType ChartType
line_type String @default("monotone") lineType String @default("monotone")
breakdowns Json breakdowns Json
events Json events Json
formula String? formula String?
unit String? unit String?
metric Metric @default(sum) metric Metric @default(sum)
project_id String projectId String
project Project @relation(fields: [project_id], references: [id]) project Project @relation(fields: [projectId], references: [id])
previous Boolean @default(false) previous Boolean @default(false)
dashboard_id String dashboardId String
dashboard Dashboard @relation(fields: [dashboard_id], references: [id]) dashboard Dashboard @relation(fields: [dashboardId], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
@@ -180,14 +180,14 @@ model Waitlist {
} }
model ShareOverview { model ShareOverview {
id String @unique id String @unique
project_id String @unique projectId String @unique
project Project @relation(fields: [project_id], references: [id]) project Project @relation(fields: [projectId], references: [id])
organization_slug String organizationSlug String
public Boolean @default(false) public Boolean @default(false)
password String? password String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
@@map("shares") @@map("shares")
} }
@@ -198,13 +198,13 @@ model EventMeta {
conversion Boolean? conversion Boolean?
color String? color String?
icon String? icon String?
project_id String projectId String
project Project @relation(fields: [project_id], references: [id]) project Project @relation(fields: [projectId], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
@@unique([name, project_id]) @@unique([name, projectId])
@@map("event_meta") @@map("event_meta")
} }
@@ -213,8 +213,8 @@ model Reference {
title String title String
description String? description String?
date DateTime @default(now()) date DateTime @default(now())
project_id String projectId String
project Project @relation(fields: [project_id], references: [id]) project Project @relation(fields: [projectId], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt

View File

@@ -1,38 +1,20 @@
import type { Client } from '../prisma-client'; import type { Client, Prisma } from '../prisma-client';
import { db } from '../prisma-client'; import { db } from '../prisma-client';
import { transformProject } from './project.service';
import type { IServiceProject } from './project.service';
export type IServiceClient = ReturnType<typeof transformClient>; export type IServiceClient = Client;
export type IServiceClientWithProject = IServiceClient & { export type IServiceClientWithProject = Prisma.ClientGetPayload<{
project: Exclude<IServiceProject, null>; include: {
}; project: true;
export function transformClient({ organization_slug, ...client }: Client) {
return {
...client,
organizationSlug: organization_slug,
}; };
} }>;
export async function getClientsByOrganizationId(organizationId: string) { export async function getClientsByOrganizationSlug(organizationSlug: string) {
const clients = await db.client.findMany({ return db.client.findMany({
where: { where: {
organization_slug: organizationId, organizationSlug,
}, },
include: { include: {
project: true, project: true,
}, },
}); });
return clients
.map((client) => {
return {
...transformClient(client),
project: transformProject(client.project),
};
})
.filter(
(client): client is IServiceClientWithProject => client.project !== null
);
} }

View File

@@ -1,15 +1,18 @@
import type { Dashboard, Prisma } from '../prisma-client';
import { db } from '../prisma-client'; import { db } from '../prisma-client';
export type IServiceDashboard = Awaited<ReturnType<typeof getDashboardById>>; export type IServiceDashboard = Dashboard;
export type IServiceDashboards = Awaited< export type IServiceDashboards = Prisma.DashboardGetPayload<{
ReturnType<typeof getDashboardsByProjectId> include: {
>; project: true;
};
}>[];
export async function getDashboardById(id: string, projectId: string) { export async function getDashboardById(id: string, projectId: string) {
const dashboard = await db.dashboard.findUnique({ const dashboard = await db.dashboard.findUnique({
where: { where: {
id, id,
project_id: projectId, projectId,
}, },
include: { include: {
project: true, project: true,
@@ -26,7 +29,7 @@ export async function getDashboardById(id: string, projectId: string) {
export function getDashboardsByProjectId(projectId: string) { export function getDashboardsByProjectId(projectId: string) {
return db.dashboard.findMany({ return db.dashboard.findMany({
where: { where: {
project_id: projectId, projectId,
}, },
include: { include: {
project: true, project: true,

View File

@@ -144,7 +144,7 @@ export async function getEvents(
name: { name: {
in: names, in: names,
}, },
project_id: events[0]?.project_id, projectId: events[0]?.project_id,
}, },
select: options.meta === true ? undefined : options.meta, select: options.meta === true ? undefined : options.meta,
}); });
@@ -265,7 +265,7 @@ export async function getEventList({
sb.where.projectId = `project_id = ${escape(projectId)}`; sb.where.projectId = `project_id = ${escape(projectId)}`;
if (profileId) { if (profileId) {
sb.where.deviceId = `device_id IN (SELECT device_id as did FROM openpanel.events WHERE profile_id = ${escape(profileId)} group by did)`; sb.where.deviceId = `device_id IN (SELECT device_id as did FROM events WHERE profile_id = ${escape(profileId)} group by did)`;
} }
if (events && events.length > 0) { if (events && events.length > 0) {
@@ -357,7 +357,7 @@ export function createBotEvent({
export function getConversionEventNames(projectId: string) { export function getConversionEventNames(projectId: string) {
return db.eventMeta.findMany({ return db.eventMeta.findMany({
where: { where: {
project_id: projectId, projectId,
conversion: true, conversion: true,
}, },
}); });

View File

@@ -11,7 +11,7 @@ import { db } from '../prisma-client';
export type IServiceOrganization = ReturnType<typeof transformOrganization>; export type IServiceOrganization = ReturnType<typeof transformOrganization>;
export type IServiceInvite = ReturnType<typeof transformInvite>; export type IServiceInvite = ReturnType<typeof transformInvite>;
export type IServiceMember = ReturnType<typeof transformMember>; export type IServiceMember = ReturnType<typeof transformMember>;
export type IServiceProjectAccess = ReturnType<typeof transformAccess>; export type IServiceProjectAccess = ProjectAccess;
export function transformOrganization(org: Organization) { export function transformOrganization(org: Organization) {
return { return {
@@ -21,15 +21,6 @@ export function transformOrganization(org: Organization) {
}; };
} }
export function transformAccess(access: ProjectAccess) {
return {
projectId: access.project_id,
userId: access.user_id,
level: access.level,
organizationSlug: access.organization_slug,
};
}
export async function getCurrentOrganizations() { export async function getCurrentOrganizations() {
const session = auth(); const session = auth();
const organizations = await clerkClient.users.getOrganizationMembershipList({ const organizations = await clerkClient.users.getOrganizationMembershipList({
@@ -53,7 +44,7 @@ export async function getOrganizationByProjectId(projectId: string) {
}); });
return clerkClient.organizations.getOrganization({ return clerkClient.organizations.getOrganization({
slug: project.organization_slug, slug: project.organizationSlug,
}); });
} }
@@ -110,7 +101,7 @@ export async function getMembers(organizationSlug: string) {
}), }),
db.projectAccess.findMany({ db.projectAccess.findMany({
where: { where: {
organization_slug: organizationSlug, organizationSlug,
}, },
}), }),
]); ]);
@@ -118,11 +109,11 @@ export async function getMembers(organizationSlug: string) {
return members return members
.map((member) => { .map((member) => {
const projectAccess = access.filter( const projectAccess = access.filter(
(item) => item.user_id === member.publicUserData?.userId (item) => item.userId === member.publicUserData?.userId
); );
return { return {
...member, ...member,
access: projectAccess.map(transformAccess), access: projectAccess,
}; };
}) })
.map(transformMember); .map(transformMember);

View File

@@ -1,17 +1,9 @@
import { auth } from '@clerk/nextjs'; import { auth } from '@clerk/nextjs';
import { project } from 'ramda';
import type { Project } from '../prisma-client'; import type { Project } from '../prisma-client';
import { db } from '../prisma-client'; import { db } from '../prisma-client';
export type IServiceProject = ReturnType<typeof transformProject>; export type IServiceProject = Project;
export function transformProject({ organization_slug, ...project }: Project) {
return {
organizationSlug: organization_slug,
...project,
};
}
export async function getProjectById(id: string) { export async function getProjectById(id: string) {
const res = await db.project.findUnique({ const res = await db.project.findUnique({
@@ -24,46 +16,32 @@ export async function getProjectById(id: string) {
return null; return null;
} }
return transformProject(res); return res;
} }
export async function getProjectsByOrganizationSlug(slug: string) { export async function getProjectsByOrganizationSlug(organizationSlug: string) {
const res = await db.project.findMany({ return db.project.findMany({
where: { where: {
organization_slug: slug, organizationSlug,
}, },
orderBy: { orderBy: {
createdAt: 'desc', createdAt: 'desc',
}, },
}); });
return res.map(transformProject);
} }
export async function getCurrentProjects(slug: string) { export async function getCurrentProjects(organizationSlug: string) {
const session = auth(); const session = auth();
if (!session.userId) { if (!session.userId) {
return []; return [];
} }
const access = await db.projectAccess.findMany({ return db.project.findMany({
where: { where: {
organization_slug: slug, organizationSlug,
user_id: session.userId, },
include: {
access: true,
}, },
}); });
const res = await db.project.findMany({
where: {
organization_slug: slug,
},
});
if (access.length === 0) {
return res.map(transformProject);
}
return res
.filter((project) => access.some((a) => a.project_id === project.id))
.map(transformProject);
} }

View File

@@ -1,19 +1,7 @@
import type { Prisma, Reference } from '../prisma-client'; import type { Prisma, Reference } from '../prisma-client';
import { db } from '../prisma-client'; import { db } from '../prisma-client';
export type IServiceReference = Omit<Reference, 'project_id'> & { export type IServiceReference = Reference;
projectId: string;
};
export function transformReference({
project_id,
...item
}: Reference): IServiceReference {
return {
...item,
projectId: project_id,
};
}
export async function getReferenceById(id: string) { export async function getReferenceById(id: string) {
const reference = await db.reference.findUnique({ const reference = await db.reference.findUnique({
@@ -26,7 +14,7 @@ export async function getReferenceById(id: string) {
return null; return null;
} }
return transformReference(reference); return reference;
} }
export async function getReferences({ export async function getReferences({
@@ -38,11 +26,9 @@ export async function getReferences({
take?: number; take?: number;
skip?: number; skip?: number;
}) { }) {
const references = await db.reference.findMany({ return db.reference.findMany({
where, where,
take: take ?? 50, take: take ?? 50,
skip, skip,
}); });
return references.map(transformReference);
} }

View File

@@ -45,11 +45,11 @@ export function transformReport(
): IChartInput & { id: string } { ): IChartInput & { id: string } {
return { return {
id: report.id, id: report.id,
projectId: report.project_id, projectId: report.projectId,
events: (report.events as IChartEvent[]).map(transformReportEvent), events: (report.events as IChartEvent[]).map(transformReportEvent),
breakdowns: report.breakdowns as IChartBreakdown[], breakdowns: report.breakdowns as IChartBreakdown[],
chartType: report.chart_type, chartType: report.chartType,
lineType: (report.line_type as IChartLineType) ?? lineTypes.monotone, lineType: (report.lineType as IChartLineType) ?? lineTypes.monotone,
interval: report.interval, interval: report.interval,
name: report.name || 'Untitled', name: report.name || 'Untitled',
range: (report.range as IChartRange) ?? timeRanges['1m'], range: (report.range as IChartRange) ?? timeRanges['1m'],
@@ -64,7 +64,7 @@ export function getReportsByDashboardId(dashboardId: string) {
return db.report return db.report
.findMany({ .findMany({
where: { where: {
dashboard_id: dashboardId, dashboardId,
}, },
}) })
.then((reports) => reports.map(transformReport)); .then((reports) => reports.map(transformReport));

View File

@@ -10,3 +10,11 @@ export function getShareOverviewById(id: string) {
}, },
}); });
} }
export function getShareByProjectId(projectId: string) {
return db.shareOverview.findUnique({
where: {
projectId,
},
});
}

View File

@@ -85,7 +85,7 @@ export const zInviteUser = z.object({
}); });
export const zShareOverview = z.object({ export const zShareOverview = z.object({
organizationId: z.string(), organizationSlug: z.string(),
projectId: z.string(), projectId: z.string(),
password: z.string().nullable(), password: z.string().nullable(),
public: z.boolean(), public: z.boolean(),

8
pnpm-lock.yaml generated
View File

@@ -301,8 +301,8 @@ importers:
specifier: ^0.1.3 specifier: ^0.1.3
version: 0.1.3 version: 0.1.3
pushmodal: pushmodal:
specifier: ^0.0.8 specifier: ^1.0.0
version: 0.0.8(@radix-ui/react-dialog@1.0.5)(react-dom@18.2.0)(react@18.2.0) version: 1.0.0(@radix-ui/react-dialog@1.0.5)(react-dom@18.2.0)(react@18.2.0)
ramda: ramda:
specifier: ^0.29.1 specifier: ^0.29.1
version: 0.29.1 version: 0.29.1
@@ -14709,8 +14709,8 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
/pushmodal@0.0.8(@radix-ui/react-dialog@1.0.5)(react-dom@18.2.0)(react@18.2.0): /pushmodal@1.0.0(@radix-ui/react-dialog@1.0.5)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Fmsx9Fadnv51WPcXvX7x4N8BbLxMDdSStpfJ1WY4pyjqXEEht+3H51TwjZJc5PkGMURdsvi7py76SFgsmrujnw==} resolution: {integrity: sha512-34JSZHJHGTcLqBgYk9Fyiw5vBYJZrcgoDE7GfHehKKzxBt/Ro2bSLTIGRnzQ+NRv389GxH6WXCBUH+6VJ1wvTg==}
peerDependencies: peerDependencies:
'@radix-ui/react-dialog': ^1.0.0 '@radix-ui/react-dialog': ^1.0.0
react: ^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 react: ^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0