/** * Component: Hardcover Shelf Form * Documentation: documentation/frontend/components.md */ 'use client'; import React from 'react'; import { Input } from './Input'; // --------------------------------------------------------------------------- // Status option definitions // --------------------------------------------------------------------------- const STATUS_OPTIONS = [ { id: '1', label: 'Want to Read', description: 'Books saved to read later', icon: ( ), }, { id: '2', label: 'Currently Reading', description: 'Books actively being read', icon: ( ), }, { id: '3', label: 'Read', description: 'Books already finished', icon: ( ), }, { id: '4', label: 'Did Not Finish', description: 'Books started but set aside', icon: ( ), }, ] as const; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface HardcoverFormProps { apiToken: string; setApiToken: (v: string) => void; listType: 'status' | 'custom'; setListType: (v: 'status' | 'custom') => void; statusId: string; setStatusId: (v: string) => void; customListId: string; setCustomListId: (v: string) => void; validationError: string; setValidationError: (v: string) => void; isLoading: boolean; success: boolean; } // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- export function HardcoverForm({ apiToken, setApiToken, listType, setListType, statusId, setStatusId, customListId, setCustomListId, validationError, setValidationError, isLoading, success, }: HardcoverFormProps) { const disabled = isLoading || success; const isTokenError = validationError === 'Hardcover API Token is required'; const isListError = !isTokenError && !!validationError; return (
{/* API Token */}
Get your token
{ setApiToken(e.target.value); if (isTokenError) setValidationError(''); }} placeholder="Paste your Hardcover API token" disabled={disabled} className={[ 'block w-full rounded-lg border px-4 py-2 text-sm transition-colors', 'focus:outline-none focus:ring-2 focus:ring-indigo-500/40 focus:border-indigo-500/60', 'disabled:opacity-50 disabled:cursor-not-allowed', 'bg-white dark:bg-gray-800/60 text-gray-900 dark:text-white', 'placeholder-gray-400 dark:placeholder-gray-500', isTokenError ? 'border-red-400 dark:border-red-500' : 'border-gray-200 dark:border-gray-700', ].join(' ')} /> {isTokenError && (

{validationError}

)}

Found under{' '} Settings → API {' '}on hardcover.app. Stored securely and never shared.

{/* Divider */}
{/* List Type Selection */}

Which list should we watch?

Choose a reading status or one of your custom lists.

setListType('status')} disabled={disabled} icon={ } title="Reading Status" subtitle="Want to Read, Reading, Read, etc." /> setListType('custom')} disabled={disabled} icon={ } title="Custom List" subtitle="A list you created on Hardcover" />
{/* Status picker or Custom list input */} {listType === 'status' ? (

Status to sync

{STATUS_OPTIONS.map((opt) => ( setStatusId(opt.id)} disabled={disabled} /> ))}
) : (
{ setCustomListId(e.target.value); if (isListError) setValidationError(''); }} placeholder="https://hardcover.app/@username/lists/..." error={isListError ? validationError : ''} disabled={disabled} />

Paste the list URL from Hardcover, or enter just the slug (e.g.{' '} my-audiobooks ) or a numeric ID.

)}
); } // --------------------------------------------------------------------------- // Sub-components // --------------------------------------------------------------------------- function ListTypeCard({ active, onClick, disabled, icon, title, subtitle, }: { active: boolean; onClick: () => void; disabled: boolean; icon: React.ReactNode; title: string; subtitle: string; }) { return ( ); } function StatusRow({ opt, selected, onSelect, disabled, }: { opt: typeof STATUS_OPTIONS[number]; selected: boolean; onSelect: () => void; disabled: boolean; }) { return ( ); }