Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e3ce03b
Replace limits cards with inline limit and alert controls
sarimrmalik Apr 13, 2026
151d688
Refactor each icon into its own file with a common wrapper for shared…
sarimrmalik Apr 14, 2026
e25ca40
Refactor Alert and Limit ASCII icons to use div wrappers instead of S…
sarimrmalik Apr 14, 2026
a2ec03e
Rollback icon refactor since out of scope of this branch
sarimrmalik Apr 14, 2026
adee037
Add ASCII icon components for alert and limit with styled text repres…
sarimrmalik Apr 14, 2026
80f766d
Refactor billing limits management by removing the old LimitForm comp…
sarimrmalik Apr 14, 2026
f8c7998
Refactor dashboard limits management by removing the old limit-sectio…
sarimrmalik Apr 14, 2026
fa3856c
Enhance UsageAlertForm and UsageLimitForm to improve cancel functiona…
sarimrmalik Apr 14, 2026
a510283
Add SetUsageLimitDialog component for setting API usage limits
sarimrmalik Apr 14, 2026
65c7b0c
Refactor UsageAlertForm and UsageLimitForm to use Input components fo…
sarimrmalik Apr 14, 2026
262247b
Refactor RemoveUsageLimitDialog and UsageLimitForm to enhance user in…
sarimrmalik Apr 14, 2026
039e670
Refactor UsageLimits component to improve loading state handling. Con…
sarimrmalik Apr 14, 2026
7189e3d
Run biome format
sarimrmalik Apr 14, 2026
85c4cc7
Add mount check in UsageAlertForm to prevent initial effect execution
sarimrmalik Apr 14, 2026
307ef38
Add currency validation and formatting utilities with comprehensive t…
sarimrmalik Apr 14, 2026
58f76f7
Refactor currency-related tests and forms for improved consistency an…
sarimrmalik Apr 15, 2026
ab005a1
Fix top padding
sarimrmalik Apr 17, 2026
05220fe
Refactor dialog components and forms for improved user experience and…
sarimrmalik Apr 17, 2026
3d5758a
Enhance hover effects in UsageAlertSection and UsageLimitSection for …
sarimrmalik Apr 20, 2026
a9896ee
Add focus behavior for input fields in UsageAlertForm and UsageLimitForm
sarimrmalik Apr 20, 2026
34d823b
Update dialog positioning and overflow behavior for improved layout a…
sarimrmalik Apr 20, 2026
b83fa3c
Refactor AlertAsciiIcon and LimitAsciiIcon components to utilize a ne…
sarimrmalik Apr 20, 2026
a0e6d06
Reduce gap between items in UsageAlertForm and UsageLimitForm for imp…
sarimrmalik Apr 20, 2026
c424e39
Update success toast messages in usage limit and alert forms for impr…
sarimrmalik Apr 20, 2026
0213f76
Merge remote-tracking branch 'origin/main' into refactor/limits-page
sarimrmalik Apr 20, 2026
e3cc931
Merge remote-tracking branch 'origin/main' into refactor/limits-page
sarimrmalik Apr 23, 2026
770fc1b
refactor: update button variants and loading states in usage limit an…
sarimrmalik Apr 23, 2026
0ac36aa
refactor: remove early return for team check in UsageLimits component
sarimrmalik Apr 24, 2026
7bd5c58
refactor: reorganize currency input utilities and update usage forms
sarimrmalik Apr 24, 2026
2495346
fix: adjust dialog content width for better responsiveness
sarimrmalik Apr 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ next-env.d.ts

# AI agents and related files
CLAUDE.md
.cursor
.agent


Expand Down
33 changes: 33 additions & 0 deletions src/__test__/unit/currency.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, it } from 'vitest'
import {
CurrencyInputSchema,
sanitizeCurrencyInput,
} from '@/features/dashboard/limits/currency-input'

describe('sanitizeCurrencyInput', () => {
it.each([
['$1,250', '1250'],
['1250', '1250'],
['abc', ''],
['', ''],
])('returns %p -> %p', (value: string, expected: string) => {
expect(sanitizeCurrencyInput(value)).toBe(expected)
})
})

describe('CurrencyInputSchema', () => {
it.each(['100', '1', ' 100 ', '999999'])('accepts %p', (value: string) => {
expect(CurrencyInputSchema.safeParse(value).success).toBe(true)
})

it.each([
'',
' ',
'12.50',
'1,250',
'abc',
'0',
])('rejects %p', (value: string) => {
expect(CurrencyInputSchema.safeParse(value).success).toBe(false)
})
})
13 changes: 4 additions & 9 deletions src/app/dashboard/[teamSlug]/limits/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import UsageLimits from '@/features/dashboard/limits/usage-limits'
import { Page } from '@/features/dashboard/layouts/page'
import { UsageLimits } from '@/features/dashboard/limits/usage-limits'
import { HydrateClient, prefetch, trpc } from '@/trpc/server'
import Frame from '@/ui/frame'

interface LimitsPageProps {
params: Promise<{ teamSlug: string }>
Expand All @@ -13,14 +13,9 @@ export default async function LimitsPage({ params }: LimitsPageProps) {

return (
<HydrateClient>
<Frame
classNames={{
frame: 'flex flex-col gap-4 max-md:border-none',
wrapper: 'w-full max-md:p-0',
}}
>
<Page>
<UsageLimits />
</Frame>
</Page>
</HydrateClient>
)
}
2 changes: 1 addition & 1 deletion src/features/dashboard/layouts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface PageProps {
}

export const Page = ({ children, className }: PageProps) => (
<div className={cn('mx-auto w-full max-w-[900px]', className)}>
<div className={cn('mx-auto w-full max-w-[900px] p-3 md:p-0', className)}>
{children}
</div>
)
2 changes: 1 addition & 1 deletion src/features/dashboard/layouts/wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function DefaultDashboardLayout({
}) {
return (
<div className="flex-1 overflow-y-auto">
<div className="container mx-auto p-0 md:p-8 2xl:p-24 h-min w-full">
<div className="container mx-auto h-min w-full p-0 md:p-10 2xl:p-24">
<CatchErrorBoundary
classNames={{
wrapper: 'h-full w-full',
Expand Down
72 changes: 72 additions & 0 deletions src/features/dashboard/limits/alert-ascii-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { AsciiIcon } from './ascii-icon'

const INACTIVE_LINES = [
' ',
' ',
' ',
' ',
' -********- ',
' -*-- -*- ',
' -*- -*- ',
' **- -** ',
' -*- -*- ',
' -**----------**- ',
' ----**----**---- ',
' -******- ',
' ',
' ',
' ',
' ',
]

const ACTIVE_LINES = [
' ',
' ',
' ',
' ',
' -********- ',
[
{ text: ' -*-- ' },
{ className: 'text-accent-main-highlight', text: '-' },
{ text: '*- ' },
],
' -*- -*- ',
[
{ text: ' *' },
{ className: 'text-accent-main-highlight', text: '*' },
{ text: '- -' },
{ className: 'text-accent-main-highlight', text: '*' },
{ text: '* ' },
],
' -*- -*- ',
[
{ text: ' -*' },
{ className: 'text-accent-main-highlight', text: '*' },
{ text: '----------**- ' },
],
[
{ text: ' ----**' },
{ className: 'text-accent-main-highlight', text: '-' },
{ text: '---**---- ' },
],
' -******- ',
' ',
' ',
' ',
' ',
]

interface AlertAsciiIconProps {
active?: boolean
className?: string
}

export const AlertAsciiIcon = ({
active = false,
className,
}: AlertAsciiIconProps) => (
<AsciiIcon
className={className}
lines={active ? ACTIVE_LINES : INACTIVE_LINES}
/>
)
40 changes: 0 additions & 40 deletions src/features/dashboard/limits/alert-card.tsx

This file was deleted.

52 changes: 52 additions & 0 deletions src/features/dashboard/limits/ascii-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { type CSSProperties, Fragment } from 'react'
import { cn } from '@/lib/utils'

type AsciiLineSegment = {
className?: string
text: string
}

type AsciiLine = string | readonly AsciiLineSegment[]

const TEXT_STYLE = {
fontFamily: 'var(--font-mono)',
fontFeatureSettings: "'ss03' 1",
fontSize: '3.802px',
fontWeight: 600,
letterSpacing: '-0.038px',
lineHeight: '4px',
} satisfies CSSProperties

interface AsciiIconProps {
className?: string
lines: readonly AsciiLine[]
}

const renderAsciiLine = (line: AsciiLine) => {
if (typeof line === 'string') return line

return line.map((segment, index) => (
<Fragment key={`${segment.text}-${index}`}>
{segment.className ? (
<span className={segment.className}>{segment.text}</span>
) : (
segment.text
)}
</Fragment>
))
}

export const AsciiIcon = ({ className, lines }: AsciiIconProps) => (
<div className={cn('relative size-[72px]', className)}>
<div
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 whitespace-nowrap text-fg uppercase"
style={TEXT_STYLE}
>
{lines.map((line, index) => (
<p key={index} className="m-0 whitespace-pre">
{renderAsciiLine(line)}
</p>
))}
</div>
</div>
)
14 changes: 14 additions & 0 deletions src/features/dashboard/limits/currency-input.ts
Comment thread
sarimrmalik marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { z } from 'zod'

// Validates a string as a positive whole USD amount. Example: "1250" -> valid, "abc" -> invalid.
const CurrencyInputSchema = z
.string()
.trim()
.min(1, 'Enter a value.')
.regex(/^\d+$/, 'Enter a whole USD amount.')
.refine((value) => Number(value) >= 1, 'Value must be at least 1.')

// Removes non-digits from a USD draft value. Example: "$1,250" -> "1250".
const sanitizeCurrencyInput = (value: string) => value.replace(/\D+/g, '')

export { CurrencyInputSchema, sanitizeCurrencyInput }
22 changes: 22 additions & 0 deletions src/features/dashboard/limits/focus-block-input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { MouseEvent } from 'react'

const INTERACTIVE_ELEMENT_SELECTOR =
'button, input, textarea, select, a, [role="button"], [data-no-input-focus]'

// Focuses the first editable input in a block, e.g. block click -> input focused, button click -> unchanged.
const focusBlockInputOnMouseDown = (event: MouseEvent<HTMLElement>) => {
if (!(event.target instanceof Element)) return
if (event.target.closest(INTERACTIVE_ELEMENT_SELECTOR)) return

const input = event.currentTarget.querySelector('input')
if (!(input instanceof HTMLInputElement)) return
if (input.disabled || input.readOnly) return

event.preventDefault()
input.focus()

const cursorPosition = input.value.length
input.setSelectionRange(cursorPosition, cursorPosition)
}

export { focusBlockInputOnMouseDown }
70 changes: 70 additions & 0 deletions src/features/dashboard/limits/limit-ascii-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { AsciiIcon } from './ascii-icon'

const INACTIVE_LINES = [
' ',
' ',
' ',
' ',
' ---**--- ',
' -**------**- ',
' -**- - -**- ',
' ** -**- ** ',
' *- --- -* ',
' ** ** ',
' -**- -**- ',
' -- -- ',
' ',
' ',
' ',
' ',
]

const ACTIVE_LINES = [
' ',
' ',
' ',
' ',
' ---**--- ',
[
{ text: ' -**-----' },
{ className: 'text-accent-secondary-error-highlight', text: '-' },
{ text: '**- ' },
],
' -**- - -**- ',
[
{ text: ' ' },
{ className: 'text-accent-main-highlight', text: '**' },
{ text: ' -**- ** ' },
],
[
{ text: ' *- -' },
{ className: 'text-accent-main-highlight', text: '-' },
{ text: '- -* ' },
],
' ** ** ',
' -**- -**- ',
[
{ text: ' -- ' },
{ className: 'text-accent-main-highlight', text: '-' },
{ text: '- ' },
],
' ',
' ',
' ',
' ',
]

interface LimitAsciiIconProps {
active?: boolean
className?: string
}

export const LimitAsciiIcon = ({
active = false,
className,
}: LimitAsciiIconProps) => (
<AsciiIcon
className={className}
lines={active ? ACTIVE_LINES : INACTIVE_LINES}
/>
)
Loading
Loading