mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
v0.6.12: billing, blogs UI
This commit is contained in:
@@ -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'>
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user