feat(subscriptions): added polar as payment provider for subscriptions

* feature(dashboard): add polar / subscription

* wip(payments): manage subscription

* wip(payments): add free product, faq and some other improvements

* fix(root): change node to bundler in tsconfig

* wip(payments): display current subscription

* feat(dashboard): schedule project for deletion

* wip(payments): support custom products/subscriptions

* wip(payments): fix polar scripts

* wip(payments): add json package to dockerfiles
This commit is contained in:
Carl-Gerhard Lindesvärd
2025-02-26 11:24:00 +01:00
committed by GitHub
parent 86bf9dd064
commit 168ebc3430
105 changed files with 3395 additions and 463 deletions

View File

@@ -1,5 +1,9 @@
'use client';
import { cn } from '@/utils/cn';
import { motion } from 'framer-motion';
import Link from 'next/link';
import { useState } from 'react';
export function PageTabs({
children,
@@ -27,15 +31,23 @@ export function PageTabsLink({
isActive?: boolean;
}) {
return (
<Link
className={cn(
'inline-block opacity-100 transition-transform hover:translate-y-[-1px]',
isActive ? 'opacity-100' : 'opacity-50',
<div className="relative">
<Link
className={cn(
'inline-block opacity-100 transition-transform hover:translate-y-[-1px]',
isActive ? 'opacity-100' : 'opacity-50',
)}
href={href}
>
{children}
</Link>
{isActive && (
<motion.div
className="rounded-full absolute -bottom-1 left-0 right-0 h-0.5 bg-primary"
layoutId={'page-tabs-link'}
/>
)}
href={href}
>
{children}
</Link>
</div>
);
}

View File

@@ -105,7 +105,6 @@ export function Chart({ data }: Props) {
}, [series]);
const yAxisProps = useYAxisProps({
data: [data.metrics.max],
hide: hideYAxis,
});
const xAxisProps = useXAxisProps({

View File

@@ -22,12 +22,7 @@ export function getYAxisWidth(value: string | undefined | null) {
return charLength * value.length + charLength;
}
export const useYAxisProps = ({
data,
hide,
tickFormatter,
}: {
data: number[];
export const useYAxisProps = (options?: {
hide?: boolean;
tickFormatter?: (value: number) => string;
}) => {
@@ -38,12 +33,14 @@ export const useYAxisProps = ({
return {
...AXIS_FONT_PROPS,
width: hide ? 0 : width,
width: options?.hide ? 0 : width,
axisLine: false,
tickLine: false,
allowDecimals: false,
tickFormatter: (value: number) => {
const tick = tickFormatter ? tickFormatter(value) : number.short(value);
const tick = options?.tickFormatter
? options.tickFormatter(value)
: number.short(value);
const newWidth = getYAxisWidth(tick);
ref.current.push(newWidth);
setWidthDebounced(Math.max(...ref.current));

View File

@@ -49,7 +49,6 @@ export function Chart({ data }: Props) {
const { series, setVisibleSeries } = useVisibleSeries(data);
const rechartData = useRechartDataModel(series);
const yAxisProps = useYAxisProps({
data: [data.metrics.max],
hide: hideYAxis,
});
const xAxisProps = useXAxisProps({

View File

@@ -110,7 +110,6 @@ export function Chart({ data }: Props) {
const xAxisProps = useXAxisProps({ interval, hide: hideXAxis });
const yAxisProps = useYAxisProps({
data: [data.metrics.max],
hide: hideYAxis,
});
return (

View File

@@ -15,7 +15,6 @@ import {
} from 'recharts';
import { average, round } from '@openpanel/common';
import { fix } from 'mathjs';
import { useXAxisProps, useYAxisProps } from '../common/axis';
import { useReportChartContext } from '../context';
import { RetentionTooltip } from './tooltip';
@@ -33,7 +32,6 @@ export function Chart({ data }: Props) {
const xAxisProps = useXAxisProps({ interval, hide: hideXAxis });
const yAxisProps = useYAxisProps({
data: [100],
hide: hideYAxis,
tickFormatter: (value) => `${value}%`,
});

View File

@@ -11,7 +11,7 @@ const AccordionItem = React.forwardRef<
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn('border-b', className)}
className={cn('border-b [&:last-child]:border-b-0', className)}
{...props}
/>
));

View File

@@ -11,6 +11,8 @@ const alertVariants = cva(
default: 'bg-card text-foreground',
destructive:
'border-destructive text-destructive dark:border-destructive [&>svg]:text-destructive',
warning:
'bg-orange-400/10 border-orange-400 text-orange-600 dark:border-orange-400 [&>svg]:text-orange-400',
},
},
defaultVariants: {

View File

@@ -10,12 +10,12 @@ import Link from 'next/link';
import * as React from 'react';
const buttonVariants = cva(
'inline-flex flex-shrink-0 select-none items-center justify-center whitespace-nowrap rounded-md font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
'inline-flex flex-shrink-0 select-none items-center justify-center whitespace-nowrap rounded-md font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:translate-y-[-1px]',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
cta: 'bg-highlight text-white hover:bg-highlight',
cta: 'bg-highlight text-white hover:bg-highlight/80',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:

View File

@@ -86,10 +86,7 @@ const DialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
'text-lg font-semibold leading-none tracking-tight',
className,
)}
className={cn('text-lg font-semibold tracking-tight', className)}
{...props}
/>
));
@@ -101,7 +98,7 @@ const DialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn(' text-muted-foreground', className)}
className={cn(' text-muted-foreground mt-2', className)}
{...props}
/>
));

View File

@@ -4,6 +4,7 @@ interface Props<T> {
columns: {
name: string;
render: (item: T) => React.ReactNode;
className?: string;
}[];
keyExtractor: (item: T) => string;
data: T[];
@@ -41,7 +42,9 @@ export function WidgetTable<T>({
<WidgetTableHead>
<tr>
{columns.map((column) => (
<th key={column.name}>{column.name}</th>
<th key={column.name} className={cn(column.className)}>
{column.name}
</th>
))}
</tr>
</WidgetTableHead>
@@ -49,10 +52,14 @@ export function WidgetTable<T>({
{data.map((item) => (
<tr
key={keyExtractor(item)}
className="border-b border-border text-right last:border-0 [&_td:first-child]:text-left [&_td]:p-4"
className={
'border-b border-border text-right last:border-0 [&_td:first-child]:text-left [&_td]:p-4'
}
>
{columns.map((column) => (
<td key={column.name}>{column.render(item)}</td>
<td key={column.name} className={cn(column.className)}>
{column.render(item)}
</td>
))}
</tr>
))}