diff --git a/apps/web/src/components/settings/AddSesSettings.tsx b/apps/web/src/components/settings/AddSesSettings.tsx index 38d176f..b5f0116 100644 --- a/apps/web/src/components/settings/AddSesSettings.tsx +++ b/apps/web/src/components/settings/AddSesSettings.tsx @@ -22,11 +22,8 @@ import { isLocalhost } from "~/utils/client"; const FormSchema = z.object({ region: z.string(), unsendUrl: z.string().url(), - sendRate: z.preprocess((val) => Number(val), z.number()), - transactionalQuota: z.preprocess( - (val) => Number(val), - z.number().min(0).max(100) - ), + sendRate: z.coerce.number(), + transactionalQuota: z.coerce.number().min(0).max(100), }); type SesSettingsProps = { diff --git a/apps/web/src/hooks/useInterval.ts b/apps/web/src/hooks/useInterval.ts index 52aadeb..67beba2 100644 --- a/apps/web/src/hooks/useInterval.ts +++ b/apps/web/src/hooks/useInterval.ts @@ -1,7 +1,7 @@ import { useEffect, useRef } from "react"; export function useInterval(callback: () => void, delay: number | null) { - const savedCallback = useRef<() => void>(); + const savedCallback = useRef<(() => void) | null>(null); // Remember the latest callback. useEffect(() => { diff --git a/apps/web/src/server/billing/payments.ts b/apps/web/src/server/billing/payments.ts index f1a0296..13ea9b4 100644 --- a/apps/web/src/server/billing/payments.ts +++ b/apps/web/src/server/billing/payments.ts @@ -125,13 +125,21 @@ export async function syncStripeData(customerId: string) { return; } + if (!subscription.items.data[0]) { + return; + } + await db.subscription.upsert({ where: { id: subscription.id }, update: { status: subscription.status, priceId: subscription.items.data[0]?.price?.id || "", - currentPeriodEnd: new Date(subscription.current_period_end * 1000), - currentPeriodStart: new Date(subscription.current_period_start * 1000), + currentPeriodEnd: new Date( + subscription.items.data[0]?.current_period_end * 1000 + ), + currentPeriodStart: new Date( + subscription.items.data[0]?.current_period_start * 1000 + ), cancelAtPeriodEnd: subscription.cancel_at ? new Date(subscription.cancel_at * 1000) : null, @@ -142,8 +150,12 @@ export async function syncStripeData(customerId: string) { id: subscription.id, status: subscription.status, priceId: subscription.items.data[0]?.price?.id || "", - currentPeriodEnd: new Date(subscription.current_period_end * 1000), - currentPeriodStart: new Date(subscription.current_period_start * 1000), + currentPeriodEnd: new Date( + subscription.items.data[0]?.current_period_end * 1000 + ), + currentPeriodStart: new Date( + subscription.items.data[0]?.current_period_start * 1000 + ), cancelAtPeriodEnd: subscription.cancel_at ? new Date(subscription.cancel_at * 1000) : null, diff --git a/apps/web/src/server/billing/usage.ts b/apps/web/src/server/billing/usage.ts index 4f74858..0618555 100644 --- a/apps/web/src/server/billing/usage.ts +++ b/apps/web/src/server/billing/usage.ts @@ -6,7 +6,7 @@ const METER_EVENT_NAME = "unsend_usage"; export async function sendUsageToStripe(customerId: string, usage: number) { const stripe = new Stripe(env.STRIPE_SECRET_KEY!, { - apiVersion: "2025-01-27.acacia", + apiVersion: "2025-03-31.basil", }); const meterEvent = await stripe.billing.meterEvents.create({ diff --git a/packages/email-editor/src/extensions/SlashCommand.tsx b/packages/email-editor/src/extensions/SlashCommand.tsx index ca8f2c2..e0f2a28 100644 --- a/packages/email-editor/src/extensions/SlashCommand.tsx +++ b/packages/email-editor/src/extensions/SlashCommand.tsx @@ -44,7 +44,7 @@ export type SlashCommandItem = { title: string; description: string; searchTerms: string[]; - icon: JSX.Element; + icon: ReactNode; command: (options: CommandProps) => void; }; diff --git a/packages/email-editor/src/menus/LinkMenu.tsx b/packages/email-editor/src/menus/LinkMenu.tsx index 196c905..ee8d1f8 100644 --- a/packages/email-editor/src/menus/LinkMenu.tsx +++ b/packages/email-editor/src/menus/LinkMenu.tsx @@ -5,7 +5,7 @@ import { MenuProps } from "../types"; import { LinkPreviewPanel } from "../components/panels/LinkPreviewPanel"; import { LinkEditorPanel } from "../components/panels/LinkEditorPanel"; -export const LinkMenu = ({ editor, appendTo }: MenuProps): JSX.Element => { +export const LinkMenu = ({ editor, appendTo }: MenuProps): React.ReactNode => { const [showEdit, setShowEdit] = useState(false); const shouldShow = useCallback(() => { diff --git a/packages/email-editor/src/renderer.tsx b/packages/email-editor/src/renderer.tsx index e895579..df1598b 100644 --- a/packages/email-editor/src/renderer.tsx +++ b/packages/email-editor/src/renderer.tsx @@ -10,7 +10,7 @@ import { Link, Heading, Hr, - Button, + Butan as Button, Img, Preview, Row, @@ -271,17 +271,17 @@ export class EmailRenderer { } // `renderMark` will call the method of the corresponding mark type - private renderMark(node: JSONContent): JSX.Element { + private renderMark(node: JSONContent): React.ReactNode { // It will wrap the text with the corresponding mark type const text = node.text || <> ; const marks = node.marks || []; - return marks.reduce( + return marks.reduce( (acc, mark) => { const type = mark.type; if (type in this) { // @ts-expect-error - `this` is not assignable to type 'never' - return this[type]?.(mark, acc) as JSX.Element; + return this[type]?.(mark, acc) as React.ReactNode; } throw new Error(`Mark type "${type}" is not supported.`); @@ -293,7 +293,7 @@ export class EmailRenderer { private getMappedContent( node: JSONContent, options?: NodeOptions - ): JSX.Element[] { + ): React.ReactNode[] { return node.content ?.map((childNode) => { const component = this.renderNode(childNode, options); @@ -303,18 +303,18 @@ export class EmailRenderer { return {component}; }) - .filter((n) => n !== null) as JSX.Element[]; + .filter((n) => n !== null) as React.ReactNode[]; } private renderNode( node: JSONContent, options: NodeOptions = {} - ): JSX.Element | null { + ): React.ReactNode | null { const type = node.type || ""; if (type in this) { // @ts-expect-error - `this` is not assignable to type 'never' - return this[type]?.(node, options) as JSX.Element; + return this[type]?.(node, options) as React.ReactNode; } throw new Error(`Node type "${type}" is not supported.`); @@ -323,7 +323,7 @@ export class EmailRenderer { private unsubscribeFooter( node: JSONContent, options?: NodeOptions - ): JSX.Element { + ): React.ReactNode { return ( {text}; } - private bold(_: MarkType, text: JSX.Element): JSX.Element { + private bold(_: MarkType, text: React.ReactNode): React.ReactNode { return {text}; } - private italic(_: MarkType, text: JSX.Element): JSX.Element { + private italic(_: MarkType, text: React.ReactNode): React.ReactNode { return {text}; } - private underline(_: MarkType, text: JSX.Element): JSX.Element { + private underline(_: MarkType, text: React.ReactNode): React.ReactNode { return {text}; } - private strike(_: MarkType, text: JSX.Element): JSX.Element { + private strike(_: MarkType, text: React.ReactNode): React.ReactNode { return {text}; } - private textStyle(mark: MarkType, text: JSX.Element): JSX.Element { + private textStyle(mark: MarkType, text: React.ReactNode): React.ReactNode { const { attrs } = mark; const { fontSize, fontWeight, color } = attrs || {}; return {text}; } - private link(mark: MarkType, text: JSX.Element): JSX.Element { + private link(mark: MarkType, text: React.ReactNode): React.ReactNode { const { attrs } = mark; let href = attrs?.href || "#"; const target = attrs?.target || "_blank"; @@ -425,7 +425,7 @@ export class EmailRenderer { ); } - private heading(node: JSONContent, options?: NodeOptions): JSX.Element { + private heading(node: JSONContent, options?: NodeOptions): React.ReactNode { const { attrs } = node; const { next, prev } = options || {}; @@ -456,7 +456,7 @@ export class EmailRenderer { ); } - private variable(node: JSONContent, _?: NodeOptions): JSX.Element { + private variable(node: JSONContent, _?: NodeOptions): React.ReactNode { const { id: variable, fallback } = node.attrs || {}; let formattedVariable = this.variableFormatter({ @@ -474,7 +474,7 @@ export class EmailRenderer { return <>{formattedVariable}; } - private horizontalRule(_: JSONContent, __?: NodeOptions): JSX.Element { + private horizontalRule(_: JSONContent, __?: NodeOptions): React.ReactNode { return (
    ; } - private logo(node: JSONContent, options?: NodeOptions): JSX.Element { + private logo(node: JSONContent, options?: NodeOptions): React.ReactNode { const { attrs } = node; const { src, @@ -645,7 +645,7 @@ export class EmailRenderer { ); } - private image(node: JSONContent, options?: NodeOptions): JSX.Element { + private image(node: JSONContent, options?: NodeOptions): React.ReactNode { const { attrs } = node; const { src, @@ -711,7 +711,10 @@ export class EmailRenderer { ); } - private blockquote(node: JSONContent, options?: NodeOptions): JSX.Element { + private blockquote( + node: JSONContent, + options?: NodeOptions + ): React.ReactNode { const { next, prev } = options || {}; const isNextSpacer = next?.type === "spacer"; const isPrevSpacer = prev?.type === "spacer"; @@ -734,7 +737,7 @@ export class EmailRenderer { ); } - private code(_: MarkType, text: JSX.Element): JSX.Element { + private code(_: MarkType, text: React.ReactNode): React.ReactNode { return (