diff --git a/apps/start/src/components/feedback-button.tsx b/apps/start/src/components/feedback-button.tsx index d7745261..0bc70e97 100644 --- a/apps/start/src/components/feedback-button.tsx +++ b/apps/start/src/components/feedback-button.tsx @@ -8,7 +8,7 @@ export function FeedbackButton() { return ( + +

+ Help us build the future of open analytics +

+ + +
+ {PERKS.map((perk) => ( + + ))} +
+ +
+ + Become a Supporter + +

+ Starting at $20/month • Cancel anytime •{' '} + + Learn more + +

+
+ + + )} + + ); +} diff --git a/apps/start/src/components/sidebar.tsx b/apps/start/src/components/sidebar.tsx index e816acd4..5562247d 100644 --- a/apps/start/src/components/sidebar.tsx +++ b/apps/start/src/components/sidebar.tsx @@ -135,9 +135,12 @@ export function SidebarContainer({ {isSelfHosted && ( -
- Self-hosted instance -
+ + Self-hosted instance, support us! + )} diff --git a/apps/start/src/hooks/use-cookie-store.tsx b/apps/start/src/hooks/use-cookie-store.tsx index ec775a1b..e3717b0e 100644 --- a/apps/start/src/hooks/use-cookie-store.tsx +++ b/apps/start/src/hooks/use-cookie-store.tsx @@ -5,12 +5,20 @@ import { pick } from 'ramda'; import { useEffect, useMemo, useRef, useState } from 'react'; import { z } from 'zod'; -const VALID_COOKIES = ['ui-theme', 'chartType', 'range'] as const; +const VALID_COOKIES = [ + 'ui-theme', + 'chartType', + 'range', + 'supporter-prompt-closed', +] as const; const COOKIE_EVENT_NAME = '__cookie-change'; const setCookieFn = createServerFn({ method: 'POST' }) .inputValidator(z.object({ key: z.enum(VALID_COOKIES), value: z.string() })) .handler(({ data: { key, value } }) => { + if (!VALID_COOKIES.includes(key)) { + return; + } setCookie(key, value); }); diff --git a/apps/start/src/modals/save-report.tsx b/apps/start/src/modals/save-report.tsx index d99aec2d..eb8eb3fb 100644 --- a/apps/start/src/modals/save-report.tsx +++ b/apps/start/src/modals/save-report.tsx @@ -40,7 +40,7 @@ export default function SaveReport({ const queryClient = useQueryClient(); const { organizationId, projectId } = useAppParams(); const searchParams = useSearch({ - from: '/_app/$organizationId/$projectId_/reports', + from: '/_app/$organizationId/$projectId/reports', shouldThrow: false, }); const dashboardId = searchParams?.dashboardId; diff --git a/apps/start/src/routeTree.gen.ts b/apps/start/src/routeTree.gen.ts index 5a3d1608..735a873a 100644 --- a/apps/start/src/routeTree.gen.ts +++ b/apps/start/src/routeTree.gen.ts @@ -57,7 +57,7 @@ import { Route as AppOrganizationIdProjectIdSettingsTabsIndexRouteImport } from import { Route as AppOrganizationIdProjectIdProfilesTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.profiles._tabs.index' import { Route as AppOrganizationIdProjectIdNotificationsTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.notifications._tabs.index' import { Route as AppOrganizationIdProjectIdEventsTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.events._tabs.index' -import { Route as AppOrganizationIdProjectIdSettingsTabsImportsRouteImport } from './routes/_app.$organizationId.$projectId_.settings._tabs.imports' +import { Route as AppOrganizationIdProjectIdSettingsTabsImportsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.imports' import { Route as AppOrganizationIdProjectIdSettingsTabsEventsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.events' import { Route as AppOrganizationIdProjectIdSettingsTabsDetailsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.details' import { Route as AppOrganizationIdProjectIdSettingsTabsClientsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.clients' @@ -391,9 +391,9 @@ const AppOrganizationIdProjectIdEventsTabsIndexRoute = } as any) const AppOrganizationIdProjectIdSettingsTabsImportsRoute = AppOrganizationIdProjectIdSettingsTabsImportsRouteImport.update({ - id: '/$projectId_/settings/_tabs/imports', - path: '/$projectId/settings/imports', - getParentRoute: () => AppOrganizationIdRoute, + id: '/imports', + path: '/imports', + getParentRoute: () => AppOrganizationIdProjectIdSettingsTabsRoute, } as any) const AppOrganizationIdProjectIdSettingsTabsEventsRoute = AppOrganizationIdProjectIdSettingsTabsEventsRouteImport.update({ @@ -651,7 +651,7 @@ export interface FileRoutesById { '/_app/$organizationId/$projectId/settings/_tabs/clients': typeof AppOrganizationIdProjectIdSettingsTabsClientsRoute '/_app/$organizationId/$projectId/settings/_tabs/details': typeof AppOrganizationIdProjectIdSettingsTabsDetailsRoute '/_app/$organizationId/$projectId/settings/_tabs/events': typeof AppOrganizationIdProjectIdSettingsTabsEventsRoute - '/_app/$organizationId/$projectId_/settings/_tabs/imports': typeof AppOrganizationIdProjectIdSettingsTabsImportsRoute + '/_app/$organizationId/$projectId/settings/_tabs/imports': typeof AppOrganizationIdProjectIdSettingsTabsImportsRoute '/_app/$organizationId/$projectId/events/_tabs/': typeof AppOrganizationIdProjectIdEventsTabsIndexRoute '/_app/$organizationId/$projectId/notifications/_tabs/': typeof AppOrganizationIdProjectIdNotificationsTabsIndexRoute '/_app/$organizationId/$projectId/profiles/_tabs/': typeof AppOrganizationIdProjectIdProfilesTabsIndexRoute @@ -832,7 +832,7 @@ export interface FileRouteTypes { | '/_app/$organizationId/$projectId/settings/_tabs/clients' | '/_app/$organizationId/$projectId/settings/_tabs/details' | '/_app/$organizationId/$projectId/settings/_tabs/events' - | '/_app/$organizationId/$projectId_/settings/_tabs/imports' + | '/_app/$organizationId/$projectId/settings/_tabs/imports' | '/_app/$organizationId/$projectId/events/_tabs/' | '/_app/$organizationId/$projectId/notifications/_tabs/' | '/_app/$organizationId/$projectId/profiles/_tabs/' @@ -1225,12 +1225,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppOrganizationIdProjectIdEventsTabsIndexRouteImport parentRoute: typeof AppOrganizationIdProjectIdEventsTabsRoute } - '/_app/$organizationId/$projectId_/settings/_tabs/imports': { - id: '/_app/$organizationId/$projectId_/settings/_tabs/imports' - path: '/$projectId/settings/imports' + '/_app/$organizationId/$projectId/settings/_tabs/imports': { + id: '/_app/$organizationId/$projectId/settings/_tabs/imports' + path: '/imports' fullPath: '/$organizationId/$projectId/settings/imports' preLoaderRoute: typeof AppOrganizationIdProjectIdSettingsTabsImportsRouteImport - parentRoute: typeof AppOrganizationIdRoute + parentRoute: typeof AppOrganizationIdProjectIdSettingsTabsRoute } '/_app/$organizationId/$projectId/settings/_tabs/events': { id: '/_app/$organizationId/$projectId/settings/_tabs/events' @@ -1487,6 +1487,7 @@ interface AppOrganizationIdProjectIdSettingsTabsRouteChildren { AppOrganizationIdProjectIdSettingsTabsClientsRoute: typeof AppOrganizationIdProjectIdSettingsTabsClientsRoute AppOrganizationIdProjectIdSettingsTabsDetailsRoute: typeof AppOrganizationIdProjectIdSettingsTabsDetailsRoute AppOrganizationIdProjectIdSettingsTabsEventsRoute: typeof AppOrganizationIdProjectIdSettingsTabsEventsRoute + AppOrganizationIdProjectIdSettingsTabsImportsRoute: typeof AppOrganizationIdProjectIdSettingsTabsImportsRoute AppOrganizationIdProjectIdSettingsTabsIndexRoute: typeof AppOrganizationIdProjectIdSettingsTabsIndexRoute } @@ -1498,6 +1499,8 @@ const AppOrganizationIdProjectIdSettingsTabsRouteChildren: AppOrganizationIdProj AppOrganizationIdProjectIdSettingsTabsDetailsRoute, AppOrganizationIdProjectIdSettingsTabsEventsRoute: AppOrganizationIdProjectIdSettingsTabsEventsRoute, + AppOrganizationIdProjectIdSettingsTabsImportsRoute: + AppOrganizationIdProjectIdSettingsTabsImportsRoute, AppOrganizationIdProjectIdSettingsTabsIndexRoute: AppOrganizationIdProjectIdSettingsTabsIndexRoute, } @@ -1655,7 +1658,6 @@ interface AppOrganizationIdRouteChildren { AppOrganizationIdIndexRoute: typeof AppOrganizationIdIndexRoute AppOrganizationIdIntegrationsRoute: typeof AppOrganizationIdIntegrationsRouteWithChildren AppOrganizationIdMembersRoute: typeof AppOrganizationIdMembersRouteWithChildren - AppOrganizationIdProjectIdSettingsTabsImportsRoute: typeof AppOrganizationIdProjectIdSettingsTabsImportsRoute } const AppOrganizationIdRouteChildren: AppOrganizationIdRouteChildren = { @@ -1666,8 +1668,6 @@ const AppOrganizationIdRouteChildren: AppOrganizationIdRouteChildren = { AppOrganizationIdIntegrationsRoute: AppOrganizationIdIntegrationsRouteWithChildren, AppOrganizationIdMembersRoute: AppOrganizationIdMembersRouteWithChildren, - AppOrganizationIdProjectIdSettingsTabsImportsRoute: - AppOrganizationIdProjectIdSettingsTabsImportsRoute, } const AppOrganizationIdRouteWithChildren = diff --git a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.imports.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.imports.tsx similarity index 99% rename from apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.imports.tsx rename to apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.imports.tsx index 123e7875..35b0c101 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId_.settings._tabs.imports.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.imports.tsx @@ -33,7 +33,7 @@ import { import { toast } from 'sonner'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId_/settings/_tabs/imports', + '/_app/$organizationId/$projectId/settings/_tabs/imports', )({ component: ImportsSettings, }); diff --git a/apps/start/src/routes/_app.$organizationId.tsx b/apps/start/src/routes/_app.$organizationId.tsx index 59aad41c..9b7c8c6c 100644 --- a/apps/start/src/routes/_app.$organizationId.tsx +++ b/apps/start/src/routes/_app.$organizationId.tsx @@ -1,4 +1,5 @@ import FullPageLoadingState from '@/components/full-page-loading-state'; +import SupporterPrompt from '@/components/organization/supporter-prompt'; import { LinkButton } from '@/components/ui/button'; import { useTRPC } from '@/integrations/trpc/react'; import { cn } from '@/utils/cn'; @@ -136,6 +137,7 @@ function Component() { )} + ); } diff --git a/packages/trpc/src/routers/import.ts b/packages/trpc/src/routers/import.ts index d77effa1..a3e7e253 100644 --- a/packages/trpc/src/routers/import.ts +++ b/packages/trpc/src/routers/import.ts @@ -5,7 +5,11 @@ import { importQueue } from '@openpanel/queue'; import { zCreateImport } from '@openpanel/validation'; import { getProjectAccess } from '../access'; -import { TRPCAccessError } from '../errors'; +import { + TRPCAccessError, + TRPCBadRequestError, + TRPCNotFoundError, +} from '../errors'; import { createTRPCRouter, protectedProcedure } from '../trpc'; export const importRouter = createTRPCRouter({ @@ -69,6 +73,28 @@ export const importRouter = createTRPCRouter({ ); } + const organization = await db.organization.findFirst({ + where: { + projects: { + some: { + id: input.projectId, + }, + }, + }, + }); + + if (!organization) { + throw TRPCNotFoundError( + 'Could not start import, organization not found', + ); + } + + if (!organization.isActive) { + throw TRPCBadRequestError( + 'You cannot start an import without an active subscription!', + ); + } + // Create import record const importRecord = await db.import.create({ data: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b6e6377..28941df1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1364,7 +1364,7 @@ importers: packages/sdks/astro: dependencies: '@openpanel/web': - specifier: workspace:1.0.1-local + specifier: workspace:1.0.2-local version: link:../web devDependencies: astro: @@ -1402,10 +1402,10 @@ importers: packages/sdks/nextjs: dependencies: '@openpanel/web': - specifier: workspace:1.0.1-local + specifier: workspace:1.0.2-local version: link:../web next: - specifier: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 + specifier: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 version: 15.0.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: specifier: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -30999,7 +30999,7 @@ snapshots: postcss@8.4.31: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1