create for series

This commit is contained in:
Nicolas Cantu 2026-01-14 01:27:54 +01:00
parent fb0457b8d6
commit d027cb32e1
10 changed files with 99 additions and 100 deletions

View File

@ -10,15 +10,9 @@ interface SeriesCardProps {
selected?: boolean selected?: boolean
} }
export function SeriesCard({ series, onSelect, selected }: SeriesCardProps): React.ReactElement { function SeriesCardContent({ series, onSelect }: { series: Series; onSelect: (seriesId: string | undefined) => void }): React.ReactElement {
return ( return (
<div <>
className={`border rounded-lg p-4 bg-cyber-dark transition-all ${
selected
? 'border-neon-cyan ring-1 ring-neon-cyan/50 shadow-glow-cyan'
: 'border-neon-cyan/30 hover:border-neon-cyan/50 hover:shadow-glow-cyan'
}`}
>
{series.coverUrl && ( {series.coverUrl && (
<div className="relative w-full h-40 mb-3 rounded-lg overflow-hidden border border-neon-cyan/20"> <div className="relative w-full h-40 mb-3 rounded-lg overflow-hidden border border-neon-cyan/20">
<Image <Image
@ -48,6 +42,17 @@ export function SeriesCard({ series, onSelect, selected }: SeriesCardProps): Rea
{t('series.view')} {t('series.view')}
</Link> </Link>
</div> </div>
</>
)
}
export function SeriesCard({ series, onSelect, selected }: SeriesCardProps): React.ReactElement {
const cardClasses = selected
? 'border-neon-cyan ring-1 ring-neon-cyan/50 shadow-glow-cyan'
: 'border-neon-cyan/30 hover:border-neon-cyan/50 hover:shadow-glow-cyan'
return (
<div className={`border rounded-lg p-4 bg-cyber-dark transition-all ${cardClasses}`}>
<SeriesCardContent series={series} onSelect={onSelect} />
</div> </div>
) )
} }

View File

@ -175,7 +175,8 @@ function SponsoringFormView(params: {
onCancel?: () => void onCancel?: () => void
}): React.ReactElement { }): React.ReactElement {
return ( return (
<form onSubmit={params.onSubmit} className="border border-neon-cyan/30 rounded-lg p-4 bg-cyber-dark space-y-4"> <Card variant="default" className="space-y-4">
<form onSubmit={params.onSubmit} className="space-y-4">
<h3 className="text-lg font-semibold text-neon-cyan">{t('sponsoring.form.title')}</h3> <h3 className="text-lg font-semibold text-neon-cyan">{t('sponsoring.form.title')}</h3>
<p className="text-sm text-cyber-accent/70">{t('sponsoring.form.description', { amount: '0.046' })}</p> <p className="text-sm text-cyber-accent/70">{t('sponsoring.form.description', { amount: '0.046' })}</p>
<Textarea <Textarea
@ -199,5 +200,6 @@ function SponsoringFormView(params: {
)} )}
</div> </div>
</form> </form>
</Card>
) )
} }

View File

@ -1,5 +1,5 @@
import { ArticleCard } from './ArticleCard' import { ArticleCard } from './ArticleCard'
import { Button } from './ui' import { Button, ErrorState } from './ui'
import type { Article } from '@/types/nostr' import type { Article } from '@/types/nostr'
import { memo } from 'react' import { memo } from 'react'
import Link from 'next/link' import Link from 'next/link'
@ -28,8 +28,8 @@ const ArticlesLoading = (): React.ReactElement => (
) )
const ArticlesError = ({ message }: { message: string }): React.ReactElement => ( const ArticlesError = ({ message }: { message: string }): React.ReactElement => (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4"> <div className="mb-4">
<p className="text-red-800">{message}</p> <ErrorState message={message} />
</div> </div>
) )

View File

@ -2,7 +2,7 @@ import Link from 'next/link'
import { useState } from 'react' import { useState } from 'react'
import { CreateSeriesModal } from '@/components/CreateSeriesModal' import { CreateSeriesModal } from '@/components/CreateSeriesModal'
import { SeriesCard } from '@/components/SeriesCard' import { SeriesCard } from '@/components/SeriesCard'
import { Button } from '@/components/ui' import { Button, Card, EmptyState } from '@/components/ui'
import { useNostrAuth } from '@/hooks/useNostrAuth' import { useNostrAuth } from '@/hooks/useNostrAuth'
import { t } from '@/lib/i18n' import { t } from '@/lib/i18n'
import type { Series } from '@/types/nostr' import type { Series } from '@/types/nostr'
@ -46,9 +46,9 @@ function SeriesListHeader(params: { isAuthor: boolean; onCreate: () => void }):
function SeriesGrid(params: { series: Series[] }): React.ReactElement { function SeriesGrid(params: { series: Series[] }): React.ReactElement {
if (params.series.length === 0) { if (params.series.length === 0) {
return ( return (
<div className="bg-cyber-dark/50 border border-neon-cyan/20 rounded-lg p-6"> <Card variant="default" className="bg-cyber-dark/50">
<p className="text-cyber-accent">{t('series.empty')}</p> <EmptyState title={t('series.empty')} />
</div> </Card>
) )
} }

View File

@ -1,5 +1,5 @@
import { useState } from 'react' import { useState } from 'react'
import { Button } from '@/components/ui' import { Button, Card } from '@/components/ui'
import { SponsoringForm } from '@/components/SponsoringForm' import { SponsoringForm } from '@/components/SponsoringForm'
import { t } from '@/lib/i18n' import { t } from '@/lib/i18n'
import type { AuthorPresentationArticle } from '@/types/nostr' import type { AuthorPresentationArticle } from '@/types/nostr'
@ -15,11 +15,11 @@ export function SponsoringSummary({ totalSponsoring, author, onSponsor }: Sponso
const [showForm, setShowForm] = useState(false) const [showForm, setShowForm] = useState(false)
return ( return (
<div className="bg-cyber-dark/50 border border-neon-cyan/20 rounded-lg p-6 mb-8"> <Card variant="default" className="bg-cyber-dark/50 mb-8">
<SponsoringSummaryHeader showSponsorButton={author !== null} onSponsorClick={() => setShowForm(true)} /> <SponsoringSummaryHeader showSponsorButton={author !== null} onSponsorClick={() => setShowForm(true)} />
<SponsoringTotals totalBTC={totalBTC} totalSats={totalSponsoring} /> <SponsoringTotals totalBTC={totalBTC} totalSats={totalSponsoring} />
<SponsoringFormPanel show={showForm} author={author} onClose={() => setShowForm(false)} onSponsor={onSponsor} /> <SponsoringFormPanel show={showForm} author={author} onClose={() => setShowForm(false)} onSponsor={onSponsor} />
</div> </Card>
) )
} }

View File

@ -1,6 +1,6 @@
import type { FormEvent } from 'react' import type { FormEvent } from 'react'
import { PresentationFormHeader } from '../PresentationFormHeader' import { PresentationFormHeader } from '../PresentationFormHeader'
import { Button, ErrorState } from '../ui' import { Button, Card, ErrorState } from '../ui'
import { t } from '@/lib/i18n' import { t } from '@/lib/i18n'
import { PresentationFields } from './fields' import { PresentationFields } from './fields'
import type { AuthorPresentationDraft } from './types' import type { AuthorPresentationDraft } from './types'
@ -19,11 +19,12 @@ export interface PresentationFormProps {
export function PresentationForm(props: PresentationFormProps): React.ReactElement { export function PresentationForm(props: PresentationFormProps): React.ReactElement {
return ( return (
<Card variant="default" className="space-y-4">
<form <form
onSubmit={(e: FormEvent<HTMLFormElement>) => { onSubmit={(e: FormEvent<HTMLFormElement>) => {
void props.handleSubmit(e) void props.handleSubmit(e)
}} }}
className="border border-neon-cyan/20 rounded-lg p-6 bg-cyber-dark space-y-4" className="space-y-4"
> >
<PresentationFormHeader /> <PresentationFormHeader />
<PresentationFields draft={props.draft} onChange={props.setDraft} /> <PresentationFields draft={props.draft} onChange={props.setDraft} />
@ -43,6 +44,7 @@ export function PresentationForm(props: PresentationFormProps): React.ReactEleme
{props.hasExistingPresentation ? <DeleteButton onDelete={props.handleDelete} deleting={props.deleting} /> : null} {props.hasExistingPresentation ? <DeleteButton onDelete={props.handleDelete} deleting={props.deleting} /> : null}
</div> </div>
</form> </form>
</Card>
) )
} }

View File

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import { ImageUploadField } from '../ImageUploadField' import { ImageUploadField } from '../ImageUploadField'
import { Button, Input, Textarea } from '../ui' import { Button, Input, Textarea, ErrorState } from '../ui'
import { t } from '@/lib/i18n' import { t } from '@/lib/i18n'
import type { SeriesDraft } from './createSeriesModalTypes' import type { SeriesDraft } from './createSeriesModalTypes'
import type { CreateSeriesModalController } from './useCreateSeriesModalController' import type { CreateSeriesModalController } from './useCreateSeriesModalController'
@ -136,11 +136,7 @@ function SeriesError({ error }: { error: string | null }): React.ReactElement |
if (!error) { if (!error) {
return null return null
} }
return ( return <ErrorState message={error} />
<div className="p-4 bg-red-900/30 border border-red-500/50 rounded text-red-300">
<p>{error}</p>
</div>
)
} }
function SeriesActions(params: { loading: boolean; canPublish: boolean; onClose: () => void }): React.ReactElement { function SeriesActions(params: { loading: boolean; canPublish: boolean; onClose: () => void }): React.ReactElement {

View File

@ -1,4 +1,4 @@
import { Button } from '@/components/ui' import { Button, ErrorState } from '@/components/ui'
import { t } from '@/lib/i18n' import { t } from '@/lib/i18n'
import type { KeyManagementManagerActions } from './useKeyManagementManager' import type { KeyManagementManagerActions } from './useKeyManagementManager'
import type { KeyManagementManagerState } from './keyManagementController' import type { KeyManagementManagerState } from './keyManagementController'
@ -34,8 +34,8 @@ function KeyManagementErrorBanner(params: { error: string | null }): React.React
return null return null
} }
return ( return (
<div className="bg-red-900/20 border border-red-400/50 rounded-lg p-4 mb-4"> <div className="mb-4">
<p className="text-red-400">{params.error}</p> <ErrorState message={params.error} />
</div> </div>
) )
} }

View File

@ -1,5 +1,5 @@
import type { Nip95Config } from '@/lib/configStorageTypes' import type { Nip95Config } from '@/lib/configStorageTypes'
import { Button } from '../ui' import { Button, Card, Input } from '../ui'
import { t } from '@/lib/i18n' import { t } from '@/lib/i18n'
import { Nip95ApiList } from './Nip95ApiList' import { Nip95ApiList } from './Nip95ApiList'
@ -65,17 +65,14 @@ function Header(params: { showAddForm: boolean; onToggleAddForm: () => void }):
function AddForm(params: { newUrl: string; onNewUrlChange: (value: string) => void; onAdd: () => void; onCancel: () => void }): React.ReactElement { function AddForm(params: { newUrl: string; onNewUrlChange: (value: string) => void; onAdd: () => void; onCancel: () => void }): React.ReactElement {
return ( return (
<div className="bg-cyber-dark border border-neon-cyan/30 rounded p-4 space-y-4"> <Card variant="default" className="space-y-4">
<div> <Input
<label className="block text-sm font-medium text-cyber-accent mb-2">{t('settings.nip95.add.url')}</label>
<input
type="url" type="url"
label={t('settings.nip95.add.url')}
value={params.newUrl} value={params.newUrl}
onChange={(e) => params.onNewUrlChange(e.target.value)} onChange={(e) => params.onNewUrlChange(e.target.value)}
placeholder={t('settings.nip95.add.placeholder')} placeholder={t('settings.nip95.add.placeholder')}
className="w-full px-4 py-2 bg-cyber-darker border border-cyber-accent/30 rounded text-cyber-light focus:border-neon-cyan focus:outline-none"
/> />
</div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button
type="button" type="button"
@ -92,7 +89,7 @@ function AddForm(params: { newUrl: string; onNewUrlChange: (value: string) => vo
{t('settings.nip95.add.cancel')} {t('settings.nip95.add.cancel')}
</Button> </Button>
</div> </div>
</div> </Card>
) )
} }

View File

@ -1,4 +1,4 @@
import { Button } from '../ui' import { Button, Card, Input } from '../ui'
import { t } from '@/lib/i18n' import { t } from '@/lib/i18n'
import { RelayList } from './RelayList' import { RelayList } from './RelayList'
import type { RelayManagerContentProps } from './types' import type { RelayManagerContentProps } from './types'
@ -58,17 +58,14 @@ function Header(params: { showAddForm: boolean; onToggleAddForm: () => void }):
function AddForm(params: { newUrl: string; onNewUrlChange: (value: string) => void; onAdd: () => void; onCancel: () => void }): React.ReactElement { function AddForm(params: { newUrl: string; onNewUrlChange: (value: string) => void; onAdd: () => void; onCancel: () => void }): React.ReactElement {
return ( return (
<div className="bg-cyber-dark border border-neon-cyan/30 rounded p-4 space-y-4"> <Card variant="default" className="space-y-4">
<div> <Input
<label className="block text-sm font-medium text-cyber-accent mb-2">{t('settings.relay.add.url')}</label>
<input
type="text" type="text"
label={t('settings.relay.add.url')}
value={params.newUrl} value={params.newUrl}
onChange={(e) => params.onNewUrlChange(e.target.value)} onChange={(e) => params.onNewUrlChange(e.target.value)}
placeholder={t('settings.relay.add.placeholder')} placeholder={t('settings.relay.add.placeholder')}
className="w-full px-4 py-2 bg-cyber-darker border border-cyber-accent/30 rounded text-cyber-light focus:border-neon-cyan focus:outline-none"
/> />
</div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button
type="button" type="button"
@ -85,7 +82,7 @@ function AddForm(params: { newUrl: string; onNewUrlChange: (value: string) => vo
{t('settings.relay.add.cancel')} {t('settings.relay.add.cancel')}
</Button> </Button>
</div> </div>
</div> </Card>
) )
} }