create for series
This commit is contained in:
parent
fb0457b8d6
commit
d027cb32e1
@ -10,15 +10,9 @@ interface SeriesCardProps {
|
||||
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 (
|
||||
<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 && (
|
||||
<div className="relative w-full h-40 mb-3 rounded-lg overflow-hidden border border-neon-cyan/20">
|
||||
<Image
|
||||
@ -48,6 +42,17 @@ export function SeriesCard({ series, onSelect, selected }: SeriesCardProps): Rea
|
||||
{t('series.view')}
|
||||
</Link>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -175,29 +175,31 @@ function SponsoringFormView(params: {
|
||||
onCancel?: () => void
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<form onSubmit={params.onSubmit} className="border border-neon-cyan/30 rounded-lg p-4 bg-cyber-dark space-y-4">
|
||||
<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>
|
||||
<Textarea
|
||||
id="sponsoring-text"
|
||||
label={`${t('sponsoring.form.text.label')} (${t('common.optional')})`}
|
||||
value={params.text}
|
||||
onChange={(e) => params.setText(e.target.value)}
|
||||
placeholder={t('sponsoring.form.text.placeholder')}
|
||||
rows={3}
|
||||
helperText={t('sponsoring.form.text.help')}
|
||||
/>
|
||||
{params.error && <ErrorState message={params.error} />}
|
||||
<div className="flex gap-2">
|
||||
<Button type="submit" variant="success" disabled={params.loading} loading={params.loading}>
|
||||
{params.loading ? t('common.loading') : t('sponsoring.form.submit')}
|
||||
</Button>
|
||||
{params.onCancel && (
|
||||
<Button type="button" variant="ghost" onClick={params.onCancel}>
|
||||
{t('common.cancel')}
|
||||
<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>
|
||||
<p className="text-sm text-cyber-accent/70">{t('sponsoring.form.description', { amount: '0.046' })}</p>
|
||||
<Textarea
|
||||
id="sponsoring-text"
|
||||
label={`${t('sponsoring.form.text.label')} (${t('common.optional')})`}
|
||||
value={params.text}
|
||||
onChange={(e) => params.setText(e.target.value)}
|
||||
placeholder={t('sponsoring.form.text.placeholder')}
|
||||
rows={3}
|
||||
helperText={t('sponsoring.form.text.help')}
|
||||
/>
|
||||
{params.error && <ErrorState message={params.error} />}
|
||||
<div className="flex gap-2">
|
||||
<Button type="submit" variant="success" disabled={params.loading} loading={params.loading}>
|
||||
{params.loading ? t('common.loading') : t('sponsoring.form.submit')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
{params.onCancel && (
|
||||
<Button type="button" variant="ghost" onClick={params.onCancel}>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ArticleCard } from './ArticleCard'
|
||||
import { Button } from './ui'
|
||||
import { Button, ErrorState } from './ui'
|
||||
import type { Article } from '@/types/nostr'
|
||||
import { memo } from 'react'
|
||||
import Link from 'next/link'
|
||||
@ -28,8 +28,8 @@ const ArticlesLoading = (): React.ReactElement => (
|
||||
)
|
||||
|
||||
const ArticlesError = ({ message }: { message: string }): React.ReactElement => (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
|
||||
<p className="text-red-800">{message}</p>
|
||||
<div className="mb-4">
|
||||
<ErrorState message={message} />
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import { CreateSeriesModal } from '@/components/CreateSeriesModal'
|
||||
import { SeriesCard } from '@/components/SeriesCard'
|
||||
import { Button } from '@/components/ui'
|
||||
import { Button, Card, EmptyState } from '@/components/ui'
|
||||
import { useNostrAuth } from '@/hooks/useNostrAuth'
|
||||
import { t } from '@/lib/i18n'
|
||||
import type { Series } from '@/types/nostr'
|
||||
@ -46,9 +46,9 @@ function SeriesListHeader(params: { isAuthor: boolean; onCreate: () => void }):
|
||||
function SeriesGrid(params: { series: Series[] }): React.ReactElement {
|
||||
if (params.series.length === 0) {
|
||||
return (
|
||||
<div className="bg-cyber-dark/50 border border-neon-cyan/20 rounded-lg p-6">
|
||||
<p className="text-cyber-accent">{t('series.empty')}</p>
|
||||
</div>
|
||||
<Card variant="default" className="bg-cyber-dark/50">
|
||||
<EmptyState title={t('series.empty')} />
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import { Button } from '@/components/ui'
|
||||
import { Button, Card } from '@/components/ui'
|
||||
import { SponsoringForm } from '@/components/SponsoringForm'
|
||||
import { t } from '@/lib/i18n'
|
||||
import type { AuthorPresentationArticle } from '@/types/nostr'
|
||||
@ -15,11 +15,11 @@ export function SponsoringSummary({ totalSponsoring, author, onSponsor }: Sponso
|
||||
const [showForm, setShowForm] = useState(false)
|
||||
|
||||
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)} />
|
||||
<SponsoringTotals totalBTC={totalBTC} totalSats={totalSponsoring} />
|
||||
<SponsoringFormPanel show={showForm} author={author} onClose={() => setShowForm(false)} onSponsor={onSponsor} />
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FormEvent } from 'react'
|
||||
import { PresentationFormHeader } from '../PresentationFormHeader'
|
||||
import { Button, ErrorState } from '../ui'
|
||||
import { Button, Card, ErrorState } from '../ui'
|
||||
import { t } from '@/lib/i18n'
|
||||
import { PresentationFields } from './fields'
|
||||
import type { AuthorPresentationDraft } from './types'
|
||||
@ -19,30 +19,32 @@ export interface PresentationFormProps {
|
||||
|
||||
export function PresentationForm(props: PresentationFormProps): React.ReactElement {
|
||||
return (
|
||||
<form
|
||||
onSubmit={(e: FormEvent<HTMLFormElement>) => {
|
||||
void props.handleSubmit(e)
|
||||
}}
|
||||
className="border border-neon-cyan/20 rounded-lg p-6 bg-cyber-dark space-y-4"
|
||||
>
|
||||
<PresentationFormHeader />
|
||||
<PresentationFields draft={props.draft} onChange={props.setDraft} />
|
||||
<ValidationError message={props.validationError ?? props.error} />
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex-1">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
disabled={props.loading || props.deleting}
|
||||
loading={props.loading || props.deleting}
|
||||
className="w-full"
|
||||
>
|
||||
{getSubmitLabel({ loading: props.loading, deleting: props.deleting, hasExistingPresentation: props.hasExistingPresentation })}
|
||||
</Button>
|
||||
<Card variant="default" className="space-y-4">
|
||||
<form
|
||||
onSubmit={(e: FormEvent<HTMLFormElement>) => {
|
||||
void props.handleSubmit(e)
|
||||
}}
|
||||
className="space-y-4"
|
||||
>
|
||||
<PresentationFormHeader />
|
||||
<PresentationFields draft={props.draft} onChange={props.setDraft} />
|
||||
<ValidationError message={props.validationError ?? props.error} />
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex-1">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
disabled={props.loading || props.deleting}
|
||||
loading={props.loading || props.deleting}
|
||||
className="w-full"
|
||||
>
|
||||
{getSubmitLabel({ loading: props.loading, deleting: props.deleting, hasExistingPresentation: props.hasExistingPresentation })}
|
||||
</Button>
|
||||
</div>
|
||||
{props.hasExistingPresentation ? <DeleteButton onDelete={props.handleDelete} deleting={props.deleting} /> : null}
|
||||
</div>
|
||||
{props.hasExistingPresentation ? <DeleteButton onDelete={props.handleDelete} deleting={props.deleting} /> : null}
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { ImageUploadField } from '../ImageUploadField'
|
||||
import { Button, Input, Textarea } from '../ui'
|
||||
import { Button, Input, Textarea, ErrorState } from '../ui'
|
||||
import { t } from '@/lib/i18n'
|
||||
import type { SeriesDraft } from './createSeriesModalTypes'
|
||||
import type { CreateSeriesModalController } from './useCreateSeriesModalController'
|
||||
@ -136,11 +136,7 @@ function SeriesError({ error }: { error: string | null }): React.ReactElement |
|
||||
if (!error) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<div className="p-4 bg-red-900/30 border border-red-500/50 rounded text-red-300">
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
)
|
||||
return <ErrorState message={error} />
|
||||
}
|
||||
|
||||
function SeriesActions(params: { loading: boolean; canPublish: boolean; onClose: () => void }): React.ReactElement {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Button } from '@/components/ui'
|
||||
import { Button, ErrorState } from '@/components/ui'
|
||||
import { t } from '@/lib/i18n'
|
||||
import type { KeyManagementManagerActions } from './useKeyManagementManager'
|
||||
import type { KeyManagementManagerState } from './keyManagementController'
|
||||
@ -34,8 +34,8 @@ function KeyManagementErrorBanner(params: { error: string | null }): React.React
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<div className="bg-red-900/20 border border-red-400/50 rounded-lg p-4 mb-4">
|
||||
<p className="text-red-400">{params.error}</p>
|
||||
<div className="mb-4">
|
||||
<ErrorState message={params.error} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { Nip95Config } from '@/lib/configStorageTypes'
|
||||
import { Button } from '../ui'
|
||||
import { Button, Card, Input } from '../ui'
|
||||
import { t } from '@/lib/i18n'
|
||||
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 {
|
||||
return (
|
||||
<div className="bg-cyber-dark border border-neon-cyan/30 rounded p-4 space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-cyber-accent mb-2">{t('settings.nip95.add.url')}</label>
|
||||
<input
|
||||
type="url"
|
||||
value={params.newUrl}
|
||||
onChange={(e) => params.onNewUrlChange(e.target.value)}
|
||||
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>
|
||||
<Card variant="default" className="space-y-4">
|
||||
<Input
|
||||
type="url"
|
||||
label={t('settings.nip95.add.url')}
|
||||
value={params.newUrl}
|
||||
onChange={(e) => params.onNewUrlChange(e.target.value)}
|
||||
placeholder={t('settings.nip95.add.placeholder')}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
@ -92,7 +89,7 @@ function AddForm(params: { newUrl: string; onNewUrlChange: (value: string) => vo
|
||||
{t('settings.nip95.add.cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Button } from '../ui'
|
||||
import { Button, Card, Input } from '../ui'
|
||||
import { t } from '@/lib/i18n'
|
||||
import { RelayList } from './RelayList'
|
||||
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 {
|
||||
return (
|
||||
<div className="bg-cyber-dark border border-neon-cyan/30 rounded p-4 space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-cyber-accent mb-2">{t('settings.relay.add.url')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={params.newUrl}
|
||||
onChange={(e) => params.onNewUrlChange(e.target.value)}
|
||||
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>
|
||||
<Card variant="default" className="space-y-4">
|
||||
<Input
|
||||
type="text"
|
||||
label={t('settings.relay.add.url')}
|
||||
value={params.newUrl}
|
||||
onChange={(e) => params.onNewUrlChange(e.target.value)}
|
||||
placeholder={t('settings.relay.add.placeholder')}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
@ -85,7 +82,7 @@ function AddForm(params: { newUrl: string; onNewUrlChange: (value: string) => vo
|
||||
{t('settings.relay.add.cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user