Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ contracts/out/
/blob-report/
/playwright/.cache/
/playwright/.auth/

*storybook.log
storybook-static
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep the ts extension.

File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { SpinnerIcon } from '@phosphor-icons/react/dist/ssr';
import type { AccessKeyBucketScope, ListBucketsResponse } from '@filone/shared';

import { apiRequest } from '../lib/api.js';
import { Checkbox } from './Checkbox.js';
import { Icon } from './Icon.js';
import { Checkbox } from './Checkbox/index.js';
import { Icon } from './Icon/index.js';
Comment on lines +7 to +8
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

./Checkbox/index.js and ./Icon/index.js do not exist (these components are Checkbox.tsx/Icon.tsx in this folder). These import paths will fail at build time; import from ./Checkbox and ./Icon instead.

Suggested change
import { Checkbox } from './Checkbox/index.js';
import { Icon } from './Icon/index.js';
import { Checkbox } from './Checkbox';
import { Icon } from './Icon';

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +8
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that all these components import from <Component>/index.js , but the actual extension file is index.ts. Also I don't think it's necessary to explicitly specify that import is coming from index.{ts|js} file.


type AccessKeyBucketScopeFieldsProps = {
bucketScope: AccessKeyBucketScope;
Expand Down Expand Up @@ -89,7 +89,7 @@ export function AccessKeyBucketScopeFields({
{loading && (
<div className="flex items-center justify-center py-6" role="status">
<span className="text-brand-700 animate-spin">
<Icon component={SpinnerIcon} size={20} />
<Icon component={SpinnerIcon} size="md" />
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Icon’s size prop is a number (defaults to 24). Passing size="md" will fail typechecking and may render incorrectly. Use a numeric size (e.g. 20) or extend Icon to support named sizes consistently.

Suggested change
<Icon component={SpinnerIcon} size="md" />
<Icon component={SpinnerIcon} size={20} />

Copilot uses AI. Check for mistakes.
</span>
<span className="sr-only">Loading buckets</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AccessKeyPermission } from '@filone/shared';
import { Checkbox } from './Checkbox';
import { Checkbox } from './Checkbox/index.js';
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

./Checkbox/index.js does not exist (Checkbox is Checkbox.tsx in this folder). This import path will fail at build time; import from ./Checkbox (or the actual file that exists) instead.

Suggested change
import { Checkbox } from './Checkbox/index.js';
import { Checkbox } from './Checkbox';

Copilot uses AI. Check for mistakes.

type PermissionOption = {
value: AccessKeyPermission;
Expand Down
85 changes: 20 additions & 65 deletions packages/website/src/components/AccessKeysTable.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,13 @@
import { useEffect, useRef, useState } from 'react';

import {
CopySimpleIcon,
DotsThreeIcon,
KeyIcon,
PlusIcon,
TrashIcon,
} from '@phosphor-icons/react/dist/ssr';
import { DotsThreeIcon, KeyIcon, PlusIcon, TrashIcon } from '@phosphor-icons/react/dist/ssr';

import type { AccessKey } from '@filone/shared';

import { Badge } from './Badge/index.js';
import { Button } from './Button';
import { CopyButton } from './CopyButton';
import { formatDate } from '../lib/time.js';
Comment on lines +7 to 10
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Badge and CopyButton are imported here but don’t exist anywhere under packages/website/src/components (no Badge/CopyButton file or directory). This will fail to compile; either add these components or keep the prior inline implementations / existing components.

Copilot uses AI. Check for mistakes.
import { useCopyToClipboard } from '../lib/use-copy-to-clipboard.js';

// ---------------------------------------------------------------------------
// Sub-components
// ---------------------------------------------------------------------------

function StatusBadge({ status }: { status: AccessKey['status'] }) {
if (status === 'active') {
return (
<span className="rounded-full border border-green-200 bg-green-50 px-2 py-0.5 text-xs font-medium text-green-700">
Active
</span>
);
}
return (
<span className="rounded-full border border-zinc-200 bg-zinc-100 px-2 py-0.5 text-xs font-medium text-zinc-600">
Inactive
</span>
);
}

function PermissionBadge({ permission }: { permission: string }) {
return (
<span className="rounded-full border border-zinc-200 bg-zinc-100 px-2 py-0.5 text-[10px] font-medium uppercase text-zinc-600">
{permission}
</span>
);
}

function BucketBadge({ name }: { name: string }) {
return (
<span className="rounded-full bg-zinc-100 px-2.5 py-0.5 text-[10px] font-normal text-zinc-800">
{name}
</span>
);
}

function CopyButton({ value }: { value: string }) {
const { copied, copy } = useCopyToClipboard();

return (
<button
type="button"
onClick={() => void copy(value)}
title={copied ? 'Copied' : 'Copy'}
aria-label={copied ? 'Copied to clipboard' : 'Copy to clipboard'}
className="rounded p-0.5 text-zinc-400 hover:bg-zinc-100 hover:text-zinc-700"
>
<CopySimpleIcon size={12} />
</button>
);
}

function ActionMenu({ onDelete }: { onDelete: () => void }) {
const [open, setOpen] = useState(false);
Expand Down Expand Up @@ -159,7 +103,8 @@ export function AccessKeysTable({
<p className="mb-1 text-sm font-medium text-zinc-900">{emptyTitle}</p>
<p className="mb-4 text-sm text-zinc-500">{emptyDescription}</p>
{onCreateOpen && (
<Button variant="filled" icon={PlusIcon} onClick={onCreateOpen}>
<Button variant="default" onClick={onCreateOpen}>
<PlusIcon className="size-4" />
Comment on lines +106 to +107
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Button variants in this codebase are primary|ghost|tertiary|filled; variant="default" will not compile. Use a supported variant, and if you need an icon, pass it via the existing icon prop (or extend the Button API consistently).

Suggested change
<Button variant="default" onClick={onCreateOpen}>
<PlusIcon className="size-4" />
<Button variant="primary" icon={PlusIcon} onClick={onCreateOpen}>

Copilot uses AI. Check for mistakes.
Create your first key
</Button>
)}
Expand Down Expand Up @@ -204,7 +149,7 @@ export function AccessKeysTable({
<p className="text-sm font-medium text-zinc-900">{key.keyName}</p>
<div className="flex items-center gap-1">
<p className="font-mono text-xs text-zinc-500">{key.accessKeyId}</p>
<CopyButton value={key.accessKeyId} />
<CopyButton value={key.accessKeyId} size={12} className="rounded p-0.5" />
</div>
</td>

Expand All @@ -213,9 +158,15 @@ export function AccessKeysTable({
<td className="px-4 py-3">
<div className="flex flex-wrap gap-1">
{key.bucketScope === 'all' ? (
<BucketBadge name="All Buckets" />
<Badge className="border-transparent text-[10px] font-normal">
All Buckets
</Badge>
) : (
(key.buckets ?? []).map((b) => <BucketBadge key={b} name={b} />)
(key.buckets ?? []).map((b) => (
<Badge key={b} className="border-transparent text-[10px] font-normal">
{b}
</Badge>
))
)}
</div>
</td>
Expand All @@ -226,15 +177,19 @@ export function AccessKeysTable({
<td className="px-4 py-3">
<div className="flex flex-wrap gap-1">
{(key.permissions ?? []).map((p) => (
<PermissionBadge key={p} permission={p} />
<Badge key={p} className="text-[10px] uppercase">
{p}
</Badge>
))}
</div>
</td>
)}

{/* Status */}
<td className="px-4 py-3">
<StatusBadge status={key.status} />
<Badge variant={key.status === 'active' ? 'success' : 'default'}>
{key.status === 'active' ? 'Active' : 'Inactive'}
</Badge>
</td>

{/* Last Used */}
Expand Down
35 changes: 20 additions & 15 deletions packages/website/src/components/AddBucketKeyModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { expiresAtFromForm } from '../lib/time.js';
import { AccessKeyExpirationFields } from './AccessKeyExpirationFields.js';
import type { ExpirationOption } from './AccessKeyExpirationFields.js';
import { AccessKeyPermissionsFields } from './AccessKeyPermissionsFields.js';
import { Button } from './Button.js';
import { Input } from './Input.js';
import { Modal, ModalBody, ModalFooter, ModalHeader } from './Modal/index.js';
import { Button } from './Button';
import { Input } from './Input/index.js';
import { Label } from './Label/index.js';
import { Dialog, DialogBody, DialogFooter, DialogHeader } from './Dialog';
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These imports reference paths/components that do not exist in packages/website/src/components (there is no Input/ directory, and no Label or Dialog components in this package). This will fail to compile; import the existing Input/Button/Modal components or add the missing components/files.

Suggested change
import { Dialog, DialogBody, DialogFooter, DialogHeader } from './Dialog';
import {
Modal as Dialog,
ModalBody as DialogBody,
ModalFooter as DialogFooter,
ModalHeader as DialogHeader,
} from './Modal';

Copilot uses AI. Check for mistakes.
import { SaveCredentialsModal } from './SaveCredentialsModal.js';
import { useToast } from './Toast/index.js';

Expand Down Expand Up @@ -107,19 +108,23 @@ export function AddBucketKeyModal({
const canSubmit = keyName.trim().length > 0 && permissions.length > 0 && !creating;

return (
<Modal open={open} onClose={handleClose} size="md">
<ModalHeader onClose={handleClose}>Create API key for {bucketName}</ModalHeader>
<ModalBody>
<Dialog open={open} onClose={handleClose} size="md">
<DialogHeader onClose={handleClose}>Create API key for {bucketName}</DialogHeader>
<DialogBody>
<div className="flex flex-col gap-4">
{/* Key name */}
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-zinc-700">Key name</label>
<Input value={keyName} onChange={setKeyName} placeholder="e.g., Production API Key" />
<Label>Key name</Label>
<Input
value={keyName}
onChange={(e) => setKeyName(e.target.value)}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Input’s onChange prop receives a string value (not a DOM event). Passing (e) => setKeyName(e.target.value) will throw at runtime/typecheck. Use onChange={setKeyName} (or onChange={(value) => setKeyName(value)}) with the existing Input component API.

Suggested change
onChange={(e) => setKeyName(e.target.value)}
onChange={setKeyName}

Copilot uses AI. Check for mistakes.
placeholder="e.g., Production API Key"
/>
</div>

{/* Permissions */}
<div className="flex flex-col gap-2">
<label className="text-sm font-medium text-zinc-700">Permissions</label>
<Label>Permissions</Label>
<AccessKeyPermissionsFields value={permissions} onChange={setPermissions} />
{permissions.length === 0 && (
<p className="text-xs text-red-600">Select at least one permission.</p>
Expand All @@ -128,7 +133,7 @@ export function AddBucketKeyModal({

{/* Expiration */}
<div className="flex flex-col gap-2">
<label className="text-sm font-medium text-zinc-700">Expiration</label>
<Label>Expiration</Label>
<AccessKeyExpirationFields
value={expiration}
customDate={customDate}
Expand All @@ -137,17 +142,17 @@ export function AddBucketKeyModal({
/>
</div>
</div>
</ModalBody>
<ModalFooter>
</DialogBody>
<DialogFooter>
<div className="flex justify-end gap-2">
<Button variant="ghost" onClick={handleClose}>
Cancel
</Button>
<Button variant="filled" disabled={!canSubmit} onClick={handleCreate}>
<Button variant="default" disabled={!canSubmit} onClick={handleCreate}>
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Button variants are limited to primary|ghost|tertiary|filled in this codebase; variant="default" will not compile. Use an existing variant (e.g. filled) or update Button.tsx to introduce default consistently.

Suggested change
<Button variant="default" disabled={!canSubmit} onClick={handleCreate}>
<Button variant="filled" disabled={!canSubmit} onClick={handleCreate}>

Copilot uses AI. Check for mistakes.
{creating ? 'Creating...' : 'Create & add key'}
</Button>
</div>
</ModalFooter>
</Modal>
</DialogFooter>
</Dialog>
);
}
35 changes: 31 additions & 4 deletions packages/website/src/components/BaseLink.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,44 @@
import type { AnchorHTMLAttributes } from 'react';
import { Component } from 'react';
import type { AnchorHTMLAttributes, ReactNode } from 'react';

import { Link } from '@tanstack/react-router';

export type BaseLinkProps = {
href: string;
children?: React.ReactNode;
children?: ReactNode;
className?: string;
} & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>;

function isInternalLink(href: string): boolean {
return href.startsWith('/') || href.startsWith('#');
}

/** Falls back to a plain <a> if no router context is available (e.g. Storybook). */
class RouterLinkSafe extends Component<
{ to: string; children?: ReactNode } & Record<string, unknown>,
{ hasError: boolean }
Comment on lines +17 to +19
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RouterLinkSafe types ...rest as Record<string, unknown> and then spreads it into <a {...rest}> / <Link {...rest}>. This is not assignable to those components’ prop types (e.g. className?: unknown), and will typically fail TypeScript checking. Type RouterLinkSafe props as the same anchor-attribute shape you pass from BaseLink (e.g. Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>) instead of a generic Record.

Copilot uses AI. Check for mistakes.
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
const { to, children, ...rest } = this.props;
if (this.state.hasError) {
return (
<a href={to} {...rest}>
{children}
</a>
);
}
return (
<Link to={to} {...rest}>
{children}
</Link>
);
}
}

export function BaseLink({ href, children, ...rest }: BaseLinkProps) {
if (href.startsWith('mailto:')) {
return (
Expand All @@ -23,9 +50,9 @@ export function BaseLink({ href, children, ...rest }: BaseLinkProps) {

if (isInternalLink(href)) {
return (
<Link to={href} {...rest}>
<RouterLinkSafe to={href} {...rest}>
{children}
</Link>
</RouterLinkSafe>
);
}

Expand Down
24 changes: 12 additions & 12 deletions packages/website/src/components/SidebarNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
ChatCircleIcon,
} from '@phosphor-icons/react/dist/ssr';
import { Link, useMatchRoute } from '@tanstack/react-router';
import { ProgressBar } from './ProgressBar.js';
import { Button } from './Button.js';
import { ProgressBar } from './ProgressBar/index.js';
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

./ProgressBar/index.js does not exist (ProgressBar is ProgressBar.tsx in this folder). This import path will fail at build time; import from ./ProgressBar (or the actual file that exists) instead.

Suggested change
import { ProgressBar } from './ProgressBar/index.js';
import { ProgressBar } from './ProgressBar';

Copilot uses AI. Check for mistakes.
import { Button } from './Button';
import { SubscriptionStatus, getUsageLimits, formatBytes } from '@filone/shared';
import type { BillingInfo, UsageResponse } from '@filone/shared';
import { getBilling, getUsage } from '../lib/api.js';
Expand Down Expand Up @@ -167,8 +167,8 @@ export function SidebarNav({ collapsed, onToggle }: SidebarNavProps) {
</div>
</div>
<div className="mt-3">
<Button variant="filled" href="/billing" className="w-full justify-center text-xs">
Upgrade
<Button variant="default" asChild className="w-full justify-center text-xs">
<Link to="/billing">Upgrade</Link>
</Button>
Comment on lines +170 to 172
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Button does not support variant="default" or the asChild prop (see components/Button.tsx, variants are primary|ghost|tertiary|filled). This usage won’t compile and also changes navigation behavior. Use href="/billing" with a supported variant (or extend Button to support asChild + the new variant everywhere).

Copilot uses AI. Check for mistakes.
</div>
</div>
Expand All @@ -182,8 +182,8 @@ export function SidebarNav({ collapsed, onToggle }: SidebarNavProps) {
upgrade or download your data.
</p>
<div className="mt-3">
<Button variant="filled" href="/billing" className="w-full justify-center text-xs">
Upgrade
<Button variant="default" asChild className="w-full justify-center text-xs">
<Link to="/billing">Upgrade</Link>
</Button>
Comment on lines +185 to 187
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Button does not support variant="default" or the asChild prop (variants are primary|ghost|tertiary|filled). This usage won’t compile; use href + a supported variant, or update Button to support these props consistently.

Copilot uses AI. Check for mistakes.
</div>
</div>
Expand All @@ -197,8 +197,8 @@ export function SidebarNav({ collapsed, onToggle }: SidebarNavProps) {
reactivate or download your data.
</p>
<div className="mt-3">
<Button variant="filled" href="/billing" className="w-full justify-center text-xs">
Reactivate
<Button variant="default" asChild className="w-full justify-center text-xs">
<Link to="/billing">Reactivate</Link>
</Button>
Comment on lines +200 to 202
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Button does not support variant="default" or the asChild prop (variants are primary|ghost|tertiary|filled). This usage won’t compile; use href + a supported variant, or update Button to support these props consistently.

Copilot uses AI. Check for mistakes.
</div>
</div>
Expand All @@ -212,8 +212,8 @@ export function SidebarNav({ collapsed, onToggle }: SidebarNavProps) {
{graceDays !== null ? ` ${graceDays} days remaining.` : ''}
</p>
<div className="mt-3">
<Button variant="filled" href="/billing" className="w-full justify-center text-xs">
Update payment
<Button variant="default" asChild className="w-full justify-center text-xs">
<Link to="/billing">Update payment</Link>
</Button>
Comment on lines +215 to 217
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Button does not support variant="default" or the asChild prop (variants are primary|ghost|tertiary|filled). This usage won’t compile; use href + a supported variant, or update Button to support these props consistently.

Copilot uses AI. Check for mistakes.
</div>
</div>
Expand All @@ -226,8 +226,8 @@ export function SidebarNav({ collapsed, onToggle }: SidebarNavProps) {
Account canceled. Reactivate to regain access.
</p>
<div className="mt-3">
<Button variant="filled" href="/billing" className="w-full justify-center text-xs">
Reactivate
<Button variant="default" asChild className="w-full justify-center text-xs">
<Link to="/billing">Reactivate</Link>
</Button>
Comment on lines +229 to 231
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Button does not support variant="default" or the asChild prop (variants are primary|ghost|tertiary|filled). This usage won’t compile; use href + a supported variant, or update Button to support these props consistently.

Copilot uses AI. Check for mistakes.
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions packages/website/src/lib/use-file-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ export function useFileUpload({ bucketName, tags, onSuccess }: UseFileUploadOpti
}
}, []);

const handleObjectNameChange = useCallback((value: string) => {
const handleObjectNameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
userEditedName.current = true;
setObjectName(value);
setObjectName(e.target.value);
}, []);
Comment on lines +45 to 48
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Input components in this codebase call onChange with a string value (see components/Input.tsx), but handleObjectNameChange was changed to expect a ChangeEvent. This will break call sites like UploadObjectPage that pass handleObjectNameChange directly to <Input onChange={...}>. Keep this callback signature as (value: string) => void (or update Input + all call sites consistently).

Copilot uses AI. Check for mistakes.

const handleUpload = useCallback(async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/website/src/styles.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@import 'tailwindcss';
@import '@fontsource-variable/inconsolata';
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new @import '@fontsource-variable/inconsolata'; requires adding @fontsource-variable/inconsolata to the relevant package.json dependencies/devDependencies (currently it’s not declared anywhere in the repo). Without that, installs/builds will fail with a missing module error.

Suggested change
@import '@fontsource-variable/inconsolata';

Copilot uses AI. Check for mistakes.
@import './styles/globals.css';
3 changes: 2 additions & 1 deletion packages/website/src/styles/globals.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@import './button.css';
@import './modal.css';
@import './state-card.css';
@import './table.css';
@import './tabs.css';
Expand Down Expand Up @@ -33,6 +32,8 @@
@theme inline {
--font-sans: var(--font-funnel-sans);
--font-heading: var(--font-aspekta);
--font-mono:
'Inconsolata Variable', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}

@utility brand-outline {
Expand Down