imporve billing more + supporter prompt on self-hosting
This commit is contained in:
@@ -8,7 +8,7 @@ export function FeedbackButton() {
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant={'outline'}
|
variant={'outline'}
|
||||||
className="text-left justify-start"
|
className="text-left justify-start text-[13px]"
|
||||||
icon={SparklesIcon}
|
icon={SparklesIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
op.track('feedback_button_clicked');
|
op.track('feedback_button_clicked');
|
||||||
|
|||||||
147
apps/start/src/components/organization/supporter-prompt.tsx
Normal file
147
apps/start/src/components/organization/supporter-prompt.tsx
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import { Button, LinkButton } from '@/components/ui/button';
|
||||||
|
import { useAppContext } from '@/hooks/use-app-context';
|
||||||
|
import { useCookieStore } from '@/hooks/use-cookie-store';
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import {
|
||||||
|
AwardIcon,
|
||||||
|
HeartIcon,
|
||||||
|
type LucideIcon,
|
||||||
|
MessageCircleIcon,
|
||||||
|
RocketIcon,
|
||||||
|
SparklesIcon,
|
||||||
|
XIcon,
|
||||||
|
ZapIcon,
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
const PERKS = [
|
||||||
|
{
|
||||||
|
icon: RocketIcon,
|
||||||
|
text: 'Latest Docker Images',
|
||||||
|
description: 'Bleeding-edge builds on every commit',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: MessageCircleIcon,
|
||||||
|
text: 'Prioritized Support',
|
||||||
|
description: 'Get help faster with priority Discord support',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: SparklesIcon,
|
||||||
|
text: 'Feature Requests',
|
||||||
|
description: 'Your ideas get prioritized in our roadmap',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: AwardIcon,
|
||||||
|
text: 'Exclusive Discord Role',
|
||||||
|
description: 'Special badge and recognition in our community',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ZapIcon,
|
||||||
|
text: 'Early Access',
|
||||||
|
description: 'Try new features before public release',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: HeartIcon,
|
||||||
|
text: 'Direct Impact',
|
||||||
|
description: 'Your support directly funds development',
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
function PerkPoint({
|
||||||
|
icon: Icon,
|
||||||
|
text,
|
||||||
|
description,
|
||||||
|
}: {
|
||||||
|
icon: LucideIcon;
|
||||||
|
text: string;
|
||||||
|
description: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="row gap-4 items-center">
|
||||||
|
<Icon className="size-4" />
|
||||||
|
<div className="flex-1 min-w-0 col gap-1.5">
|
||||||
|
<h3 className="font-medium text-sm">{text}</h3>
|
||||||
|
<p className="text-xs text-muted-foreground">{description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SupporterPrompt() {
|
||||||
|
const { isSelfHosted } = useAppContext();
|
||||||
|
const [supporterPromptClosed, setSupporterPromptClosed] = useCookieStore(
|
||||||
|
'supporter-prompt-closed',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isSelfHosted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
{!supporterPromptClosed && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: 100, scale: 0.95 }}
|
||||||
|
animate={{ opacity: 1, x: 0, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, x: 100, scale: 0.95 }}
|
||||||
|
transition={{
|
||||||
|
type: 'spring',
|
||||||
|
stiffness: 300,
|
||||||
|
damping: 30,
|
||||||
|
}}
|
||||||
|
className="fixed bottom-0 right-0 z-50 p-4 max-w-md"
|
||||||
|
>
|
||||||
|
<div className="bg-card border p-6 rounded-lg shadow-lg col gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="row items-center justify-between">
|
||||||
|
<h2 className="text-xl font-semibold">Support OpenPanel</h2>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="rounded-full"
|
||||||
|
onClick={() => setSupporterPromptClosed(true)}
|
||||||
|
>
|
||||||
|
<XIcon className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Help us build the future of open analytics
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col gap-3">
|
||||||
|
{PERKS.map((perk) => (
|
||||||
|
<PerkPoint
|
||||||
|
key={perk.text}
|
||||||
|
icon={perk.icon}
|
||||||
|
text={perk.text}
|
||||||
|
description={perk.description}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2">
|
||||||
|
<LinkButton
|
||||||
|
className="w-full"
|
||||||
|
href="https://buy.polar.sh/polar_cl_Az1CruNFzQB2bYdMOZmGHqTevW317knWqV44W1FqZmV"
|
||||||
|
>
|
||||||
|
Become a Supporter
|
||||||
|
</LinkButton>
|
||||||
|
<p className="text-xs text-muted-foreground text-center mt-4">
|
||||||
|
Starting at $20/month • Cancel anytime •{' '}
|
||||||
|
<a
|
||||||
|
href="https://openpanel.dev/supporter"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-primary underline-offset-4 hover:underline"
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -135,9 +135,12 @@ export function SidebarContainer({
|
|||||||
<ProfileToggle />
|
<ProfileToggle />
|
||||||
</div>
|
</div>
|
||||||
{isSelfHosted && (
|
{isSelfHosted && (
|
||||||
<div className={cn('text-sm w-full text-left mt-2')}>
|
<a
|
||||||
Self-hosted instance
|
href="https://openpanel.dev/supporter"
|
||||||
</div>
|
className="text-center text-sm w-full mt-2 border rounded p-2 font-medium block hover:underline hover:text-primary outline-none"
|
||||||
|
>
|
||||||
|
Self-hosted instance, support us!
|
||||||
|
</a>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,12 +5,20 @@ import { pick } from 'ramda';
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { z } from 'zod';
|
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 COOKIE_EVENT_NAME = '__cookie-change';
|
||||||
|
|
||||||
const setCookieFn = createServerFn({ method: 'POST' })
|
const setCookieFn = createServerFn({ method: 'POST' })
|
||||||
.inputValidator(z.object({ key: z.enum(VALID_COOKIES), value: z.string() }))
|
.inputValidator(z.object({ key: z.enum(VALID_COOKIES), value: z.string() }))
|
||||||
.handler(({ data: { key, value } }) => {
|
.handler(({ data: { key, value } }) => {
|
||||||
|
if (!VALID_COOKIES.includes(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setCookie(key, value);
|
setCookie(key, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default function SaveReport({
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { organizationId, projectId } = useAppParams();
|
const { organizationId, projectId } = useAppParams();
|
||||||
const searchParams = useSearch({
|
const searchParams = useSearch({
|
||||||
from: '/_app/$organizationId/$projectId_/reports',
|
from: '/_app/$organizationId/$projectId/reports',
|
||||||
shouldThrow: false,
|
shouldThrow: false,
|
||||||
});
|
});
|
||||||
const dashboardId = searchParams?.dashboardId;
|
const dashboardId = searchParams?.dashboardId;
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ import { Route as AppOrganizationIdProjectIdSettingsTabsIndexRouteImport } from
|
|||||||
import { Route as AppOrganizationIdProjectIdProfilesTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.profiles._tabs.index'
|
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 AppOrganizationIdProjectIdNotificationsTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.notifications._tabs.index'
|
||||||
import { Route as AppOrganizationIdProjectIdEventsTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.events._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 AppOrganizationIdProjectIdSettingsTabsEventsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.events'
|
||||||
import { Route as AppOrganizationIdProjectIdSettingsTabsDetailsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.details'
|
import { Route as AppOrganizationIdProjectIdSettingsTabsDetailsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.details'
|
||||||
import { Route as AppOrganizationIdProjectIdSettingsTabsClientsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.clients'
|
import { Route as AppOrganizationIdProjectIdSettingsTabsClientsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.clients'
|
||||||
@@ -391,9 +391,9 @@ const AppOrganizationIdProjectIdEventsTabsIndexRoute =
|
|||||||
} as any)
|
} as any)
|
||||||
const AppOrganizationIdProjectIdSettingsTabsImportsRoute =
|
const AppOrganizationIdProjectIdSettingsTabsImportsRoute =
|
||||||
AppOrganizationIdProjectIdSettingsTabsImportsRouteImport.update({
|
AppOrganizationIdProjectIdSettingsTabsImportsRouteImport.update({
|
||||||
id: '/$projectId_/settings/_tabs/imports',
|
id: '/imports',
|
||||||
path: '/$projectId/settings/imports',
|
path: '/imports',
|
||||||
getParentRoute: () => AppOrganizationIdRoute,
|
getParentRoute: () => AppOrganizationIdProjectIdSettingsTabsRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const AppOrganizationIdProjectIdSettingsTabsEventsRoute =
|
const AppOrganizationIdProjectIdSettingsTabsEventsRoute =
|
||||||
AppOrganizationIdProjectIdSettingsTabsEventsRouteImport.update({
|
AppOrganizationIdProjectIdSettingsTabsEventsRouteImport.update({
|
||||||
@@ -651,7 +651,7 @@ export interface FileRoutesById {
|
|||||||
'/_app/$organizationId/$projectId/settings/_tabs/clients': typeof AppOrganizationIdProjectIdSettingsTabsClientsRoute
|
'/_app/$organizationId/$projectId/settings/_tabs/clients': typeof AppOrganizationIdProjectIdSettingsTabsClientsRoute
|
||||||
'/_app/$organizationId/$projectId/settings/_tabs/details': typeof AppOrganizationIdProjectIdSettingsTabsDetailsRoute
|
'/_app/$organizationId/$projectId/settings/_tabs/details': typeof AppOrganizationIdProjectIdSettingsTabsDetailsRoute
|
||||||
'/_app/$organizationId/$projectId/settings/_tabs/events': typeof AppOrganizationIdProjectIdSettingsTabsEventsRoute
|
'/_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/events/_tabs/': typeof AppOrganizationIdProjectIdEventsTabsIndexRoute
|
||||||
'/_app/$organizationId/$projectId/notifications/_tabs/': typeof AppOrganizationIdProjectIdNotificationsTabsIndexRoute
|
'/_app/$organizationId/$projectId/notifications/_tabs/': typeof AppOrganizationIdProjectIdNotificationsTabsIndexRoute
|
||||||
'/_app/$organizationId/$projectId/profiles/_tabs/': typeof AppOrganizationIdProjectIdProfilesTabsIndexRoute
|
'/_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/clients'
|
||||||
| '/_app/$organizationId/$projectId/settings/_tabs/details'
|
| '/_app/$organizationId/$projectId/settings/_tabs/details'
|
||||||
| '/_app/$organizationId/$projectId/settings/_tabs/events'
|
| '/_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/events/_tabs/'
|
||||||
| '/_app/$organizationId/$projectId/notifications/_tabs/'
|
| '/_app/$organizationId/$projectId/notifications/_tabs/'
|
||||||
| '/_app/$organizationId/$projectId/profiles/_tabs/'
|
| '/_app/$organizationId/$projectId/profiles/_tabs/'
|
||||||
@@ -1225,12 +1225,12 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AppOrganizationIdProjectIdEventsTabsIndexRouteImport
|
preLoaderRoute: typeof AppOrganizationIdProjectIdEventsTabsIndexRouteImport
|
||||||
parentRoute: typeof AppOrganizationIdProjectIdEventsTabsRoute
|
parentRoute: typeof AppOrganizationIdProjectIdEventsTabsRoute
|
||||||
}
|
}
|
||||||
'/_app/$organizationId/$projectId_/settings/_tabs/imports': {
|
'/_app/$organizationId/$projectId/settings/_tabs/imports': {
|
||||||
id: '/_app/$organizationId/$projectId_/settings/_tabs/imports'
|
id: '/_app/$organizationId/$projectId/settings/_tabs/imports'
|
||||||
path: '/$projectId/settings/imports'
|
path: '/imports'
|
||||||
fullPath: '/$organizationId/$projectId/settings/imports'
|
fullPath: '/$organizationId/$projectId/settings/imports'
|
||||||
preLoaderRoute: typeof AppOrganizationIdProjectIdSettingsTabsImportsRouteImport
|
preLoaderRoute: typeof AppOrganizationIdProjectIdSettingsTabsImportsRouteImport
|
||||||
parentRoute: typeof AppOrganizationIdRoute
|
parentRoute: typeof AppOrganizationIdProjectIdSettingsTabsRoute
|
||||||
}
|
}
|
||||||
'/_app/$organizationId/$projectId/settings/_tabs/events': {
|
'/_app/$organizationId/$projectId/settings/_tabs/events': {
|
||||||
id: '/_app/$organizationId/$projectId/settings/_tabs/events'
|
id: '/_app/$organizationId/$projectId/settings/_tabs/events'
|
||||||
@@ -1487,6 +1487,7 @@ interface AppOrganizationIdProjectIdSettingsTabsRouteChildren {
|
|||||||
AppOrganizationIdProjectIdSettingsTabsClientsRoute: typeof AppOrganizationIdProjectIdSettingsTabsClientsRoute
|
AppOrganizationIdProjectIdSettingsTabsClientsRoute: typeof AppOrganizationIdProjectIdSettingsTabsClientsRoute
|
||||||
AppOrganizationIdProjectIdSettingsTabsDetailsRoute: typeof AppOrganizationIdProjectIdSettingsTabsDetailsRoute
|
AppOrganizationIdProjectIdSettingsTabsDetailsRoute: typeof AppOrganizationIdProjectIdSettingsTabsDetailsRoute
|
||||||
AppOrganizationIdProjectIdSettingsTabsEventsRoute: typeof AppOrganizationIdProjectIdSettingsTabsEventsRoute
|
AppOrganizationIdProjectIdSettingsTabsEventsRoute: typeof AppOrganizationIdProjectIdSettingsTabsEventsRoute
|
||||||
|
AppOrganizationIdProjectIdSettingsTabsImportsRoute: typeof AppOrganizationIdProjectIdSettingsTabsImportsRoute
|
||||||
AppOrganizationIdProjectIdSettingsTabsIndexRoute: typeof AppOrganizationIdProjectIdSettingsTabsIndexRoute
|
AppOrganizationIdProjectIdSettingsTabsIndexRoute: typeof AppOrganizationIdProjectIdSettingsTabsIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1498,6 +1499,8 @@ const AppOrganizationIdProjectIdSettingsTabsRouteChildren: AppOrganizationIdProj
|
|||||||
AppOrganizationIdProjectIdSettingsTabsDetailsRoute,
|
AppOrganizationIdProjectIdSettingsTabsDetailsRoute,
|
||||||
AppOrganizationIdProjectIdSettingsTabsEventsRoute:
|
AppOrganizationIdProjectIdSettingsTabsEventsRoute:
|
||||||
AppOrganizationIdProjectIdSettingsTabsEventsRoute,
|
AppOrganizationIdProjectIdSettingsTabsEventsRoute,
|
||||||
|
AppOrganizationIdProjectIdSettingsTabsImportsRoute:
|
||||||
|
AppOrganizationIdProjectIdSettingsTabsImportsRoute,
|
||||||
AppOrganizationIdProjectIdSettingsTabsIndexRoute:
|
AppOrganizationIdProjectIdSettingsTabsIndexRoute:
|
||||||
AppOrganizationIdProjectIdSettingsTabsIndexRoute,
|
AppOrganizationIdProjectIdSettingsTabsIndexRoute,
|
||||||
}
|
}
|
||||||
@@ -1655,7 +1658,6 @@ interface AppOrganizationIdRouteChildren {
|
|||||||
AppOrganizationIdIndexRoute: typeof AppOrganizationIdIndexRoute
|
AppOrganizationIdIndexRoute: typeof AppOrganizationIdIndexRoute
|
||||||
AppOrganizationIdIntegrationsRoute: typeof AppOrganizationIdIntegrationsRouteWithChildren
|
AppOrganizationIdIntegrationsRoute: typeof AppOrganizationIdIntegrationsRouteWithChildren
|
||||||
AppOrganizationIdMembersRoute: typeof AppOrganizationIdMembersRouteWithChildren
|
AppOrganizationIdMembersRoute: typeof AppOrganizationIdMembersRouteWithChildren
|
||||||
AppOrganizationIdProjectIdSettingsTabsImportsRoute: typeof AppOrganizationIdProjectIdSettingsTabsImportsRoute
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppOrganizationIdRouteChildren: AppOrganizationIdRouteChildren = {
|
const AppOrganizationIdRouteChildren: AppOrganizationIdRouteChildren = {
|
||||||
@@ -1666,8 +1668,6 @@ const AppOrganizationIdRouteChildren: AppOrganizationIdRouteChildren = {
|
|||||||
AppOrganizationIdIntegrationsRoute:
|
AppOrganizationIdIntegrationsRoute:
|
||||||
AppOrganizationIdIntegrationsRouteWithChildren,
|
AppOrganizationIdIntegrationsRouteWithChildren,
|
||||||
AppOrganizationIdMembersRoute: AppOrganizationIdMembersRouteWithChildren,
|
AppOrganizationIdMembersRoute: AppOrganizationIdMembersRouteWithChildren,
|
||||||
AppOrganizationIdProjectIdSettingsTabsImportsRoute:
|
|
||||||
AppOrganizationIdProjectIdSettingsTabsImportsRoute,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppOrganizationIdRouteWithChildren =
|
const AppOrganizationIdRouteWithChildren =
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import {
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_app/$organizationId/$projectId_/settings/_tabs/imports',
|
'/_app/$organizationId/$projectId/settings/_tabs/imports',
|
||||||
)({
|
)({
|
||||||
component: ImportsSettings,
|
component: ImportsSettings,
|
||||||
});
|
});
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import FullPageLoadingState from '@/components/full-page-loading-state';
|
import FullPageLoadingState from '@/components/full-page-loading-state';
|
||||||
|
import SupporterPrompt from '@/components/organization/supporter-prompt';
|
||||||
import { LinkButton } from '@/components/ui/button';
|
import { LinkButton } from '@/components/ui/button';
|
||||||
import { useTRPC } from '@/integrations/trpc/react';
|
import { useTRPC } from '@/integrations/trpc/react';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
@@ -136,6 +137,7 @@ function Component() {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
<SupporterPrompt />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import { importQueue } from '@openpanel/queue';
|
|||||||
import { zCreateImport } from '@openpanel/validation';
|
import { zCreateImport } from '@openpanel/validation';
|
||||||
|
|
||||||
import { getProjectAccess } from '../access';
|
import { getProjectAccess } from '../access';
|
||||||
import { TRPCAccessError } from '../errors';
|
import {
|
||||||
|
TRPCAccessError,
|
||||||
|
TRPCBadRequestError,
|
||||||
|
TRPCNotFoundError,
|
||||||
|
} from '../errors';
|
||||||
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
||||||
|
|
||||||
export const importRouter = createTRPCRouter({
|
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
|
// Create import record
|
||||||
const importRecord = await db.import.create({
|
const importRecord = await db.import.create({
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -1364,7 +1364,7 @@ importers:
|
|||||||
packages/sdks/astro:
|
packages/sdks/astro:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@openpanel/web':
|
'@openpanel/web':
|
||||||
specifier: workspace:1.0.1-local
|
specifier: workspace:1.0.2-local
|
||||||
version: link:../web
|
version: link:../web
|
||||||
devDependencies:
|
devDependencies:
|
||||||
astro:
|
astro:
|
||||||
@@ -1402,10 +1402,10 @@ importers:
|
|||||||
packages/sdks/nextjs:
|
packages/sdks/nextjs:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@openpanel/web':
|
'@openpanel/web':
|
||||||
specifier: workspace:1.0.1-local
|
specifier: workspace:1.0.2-local
|
||||||
version: link:../web
|
version: link:../web
|
||||||
next:
|
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)
|
version: 15.0.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
react:
|
react:
|
||||||
specifier: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
specifier: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
@@ -30999,7 +30999,7 @@ snapshots:
|
|||||||
|
|
||||||
postcss@8.4.31:
|
postcss@8.4.31:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.7
|
nanoid: 3.3.11
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user