v0.6.12: billing, blogs UI

This commit is contained in:
Waleed
2026-03-26 01:19:23 -07:00
committed by GitHub
5 changed files with 67 additions and 32 deletions

View File

@@ -60,6 +60,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string
sizes='(max-width: 768px) 100vw, 450px'
priority
itemProp='image'
unoptimized
/>
</div>
</div>
@@ -143,6 +144,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string
className='h-[160px] w-full object-cover'
sizes='(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw'
loading='lazy'
unoptimized
/>
<div className='p-3'>
<div className='mb-1 text-[#999] text-xs'>

View File

@@ -64,6 +64,7 @@ export default async function AuthorPage({ params }: { params: Promise<{ id: str
width={600}
height={315}
className='h-[160px] w-full object-cover transition-transform group-hover:scale-[1.02]'
unoptimized
/>
<div className='p-3'>
<div className='mb-1 text-[#999] text-xs'>

View File

@@ -32,6 +32,7 @@ export function PostGrid({ posts }: { posts: Post[] }) {
src={p.ogImage}
alt={p.title}
sizes='(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw'
unoptimized
priority={index < 6}
loading={index < 6 ? undefined : 'lazy'}
fill

View File

@@ -1,6 +1,6 @@
'use client'
import { useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import {
Button,
Input,
@@ -39,10 +39,55 @@ export function CreditBalance({
const [validationError, setValidationError] = useState<string | null>(null)
const [requestId, setRequestId] = useState<string | null>(null)
const purchaseCredits = usePurchaseCredits()
const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const dollarAmount = Number.parseInt(amount, 10) || 0
const creditPreview = dollarsToCredits(dollarAmount)
const clearCloseTimeout = () => {
if (closeTimeoutRef.current) {
clearTimeout(closeTimeoutRef.current)
closeTimeoutRef.current = null
}
}
const resetModalState = () => {
setAmount('')
setValidationError(null)
purchaseCredits.reset()
}
const openModal = () => {
clearCloseTimeout()
resetModalState()
setRequestId(crypto.randomUUID())
setIsOpen(true)
}
const closeModal = () => {
clearCloseTimeout()
setIsOpen(false)
setRequestId(null)
resetModalState()
}
useEffect(() => {
return () => {
clearCloseTimeout()
}
}, [])
const handleOpenChange = (open: boolean) => {
if (open) {
openModal()
return
}
if (!purchaseCredits.isPending) {
closeModal()
}
}
const handleAmountChange = (value: string) => {
const numericValue = value.replace(/[^0-9]/g, '')
setAmount(numericValue)
@@ -68,27 +113,16 @@ export function CreditBalance({
{ amount: numAmount, requestId },
{
onSuccess: () => {
setTimeout(() => {
setIsOpen(false)
onPurchaseComplete?.()
onPurchaseComplete?.()
clearCloseTimeout()
closeTimeoutRef.current = setTimeout(() => {
closeModal()
}, 1500)
},
}
)
}
const handleOpenChange = (open: boolean) => {
setIsOpen(open)
if (open) {
setRequestId(crypto.randomUUID())
} else {
setAmount('')
setValidationError(null)
purchaseCredits.reset()
setRequestId(null)
}
}
const displayError = validationError || purchaseCredits.error?.message
return (
@@ -103,9 +137,7 @@ export function CreditBalance({
{canPurchase && (
<Modal open={isOpen} onOpenChange={handleOpenChange}>
<ModalTrigger asChild>
<Button variant='active' className='h-[32px] text-[13px]'>
Add Credits
</Button>
<Button variant='active'>Add Credits</Button>
</ModalTrigger>
<ModalContent size='sm'>
<ModalHeader>Add Credits</ModalHeader>

View File

@@ -192,9 +192,9 @@ function CreditPlanCard({
return (
<article className='flex flex-1 flex-col overflow-hidden rounded-[6px] border border-[var(--border-1)] bg-[var(--surface-5)]'>
<div className='flex items-center justify-between gap-[8px] px-[14px] py-[10px]'>
<div className='flex min-h-[44px] items-center justify-between gap-[8px] px-[14px] py-[10px]'>
<span className='font-medium text-[14px] text-[var(--text-primary)]'>{name}</span>
<div className='flex items-baseline gap-[4px]'>
<div className='flex shrink-0 items-baseline gap-[4px] whitespace-nowrap'>
<span className='font-medium text-[14px] text-[var(--text-primary)]'>
${isAnnual ? discountedMonthly : dollars}
</span>
@@ -207,7 +207,7 @@ function CreditPlanCard({
</div>
</div>
<div className='flex items-center gap-[12px] border-[var(--border-1)] border-t bg-[var(--surface-4)] px-[14px] py-[10px]'>
<div className='flex items-center gap-[12px] rounded-t-[8px] border-[var(--border-1)] border-t bg-[var(--surface-4)] px-[14px] py-[10px]'>
<div className='flex flex-col'>
<span className='font-semibold text-[18px] text-[var(--text-primary)]'>
{credits.toLocaleString()}
@@ -242,13 +242,13 @@ function CreditPlanCard({
</div>
)}
<div className='border-[var(--border-1)] border-t bg-[var(--surface-4)] px-[14px] py-[14px]'>
<div className='flex min-h-[60px] items-center border-[var(--border-1)] border-t bg-[var(--surface-4)] px-[14px] py-[14px]'>
{isCurrentPlan ? (
<Button onClick={onManagePlan} className='w-full' variant='default'>
<Button onClick={onManagePlan} className='h-[32px] w-full' variant='default'>
{isCancelledAtPeriodEnd ? 'Restore Subscription' : 'Manage plan'}
</Button>
) : (
<Button onClick={onButtonClick} className='w-full' variant='primary'>
<Button onClick={onButtonClick} className='h-[32px] w-full' variant='primary'>
{buttonText}
</Button>
)}
@@ -933,9 +933,9 @@ export function Subscription() {
{/* Billing details section */}
{(subscription.isPaid || (!isLoading && isTeamAdmin)) && (
<div className='flex flex-col'>
<div className='flex flex-col gap-[16px]'>
{subscription.isPaid && permissions.canViewUsageInfo && (
<div className='py-[2px]'>
<div>
<CreditBalance
balance={subscriptionData?.data?.creditBalance ?? 0}
canPurchase={hasUsablePaidAccess && permissions.canEditUsageLimit}
@@ -952,7 +952,7 @@ export function Subscription() {
subscriptionData?.data?.periodEnd &&
!permissions.showTeamMemberView &&
!permissions.isEnterpriseMember && (
<div className='flex items-center justify-between border-[var(--border-1)] border-t pt-[16px]'>
<div className='flex items-center justify-between gap-[16px]'>
<Label>{isCancelledAtPeriodEnd ? 'Access Until' : 'Next Billing Date'}</Label>
<span className='text-[13px] text-[var(--text-secondary)]'>
{new Date(subscriptionData.data.periodEnd).toLocaleDateString()}
@@ -961,7 +961,7 @@ export function Subscription() {
)}
{subscription.isPaid && permissions.canViewUsageInfo && (
<div className='border-[var(--border-1)] border-t pt-[16px]'>
<div>
<BillingUsageNotificationsToggle />
</div>
)}
@@ -969,11 +969,10 @@ export function Subscription() {
{subscription.isPaid &&
!permissions.showTeamMemberView &&
!permissions.isEnterpriseMember && (
<div className='flex items-center justify-between border-[var(--border-1)] border-t pt-[16px]'>
<div className='flex items-center justify-between gap-[16px]'>
<Label>Invoices</Label>
<Button
variant='active'
size='sm'
disabled={openBillingPortal.isPending}
onClick={() => {
const portalWindow = window.open('', '_blank')
@@ -1008,7 +1007,7 @@ export function Subscription() {
)}
{!isLoading && isTeamAdmin && (
<div className='flex items-center justify-between border-[var(--border-1)] border-t pt-[16px]'>
<div className='flex items-center justify-between gap-[16px]'>
<div className='flex items-center gap-[6px]'>
<Label htmlFor='billed-account'>Billed Account</Label>
<Tooltip.Root>