create for series
This commit is contained in:
parent
6fcfae4cc0
commit
055465ac7b
@ -1,4 +1,5 @@
|
||||
import type { Page } from '@/types/nostr'
|
||||
import { Card } from './ui'
|
||||
import { t } from '@/lib/i18n'
|
||||
import { useNostrAuth } from '@/hooks/useNostrAuth'
|
||||
import { useEffect, useState } from 'react'
|
||||
@ -28,10 +29,10 @@ function LockedPagesView({ pagesCount }: { pagesCount: number }): React.ReactEle
|
||||
return (
|
||||
<div className="space-y-6 mt-6">
|
||||
<h3 className="text-xl font-semibold text-neon-cyan">{t('article.pages.title')}</h3>
|
||||
<div className="border border-neon-cyan/30 rounded-lg p-6 bg-cyber-dark text-center">
|
||||
<Card variant="default" className="text-center">
|
||||
<p className="text-cyber-accent mb-2">{t('article.pages.locked.title')}</p>
|
||||
<p className="text-sm text-cyber-accent/70">{t('article.pages.locked.message', { count: pagesCount })}</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -98,7 +99,7 @@ function isUserPurchase({
|
||||
|
||||
function PageDisplay({ page }: { page: Page }): React.ReactElement {
|
||||
return (
|
||||
<div className="border border-neon-cyan/30 rounded-lg p-4 bg-cyber-dark">
|
||||
<Card variant="default" className="mb-0">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h4 className="font-semibold text-neon-cyan">
|
||||
{t('page.number', { number: page.number })}
|
||||
@ -124,6 +125,6 @@ function PageDisplay({ page }: { page: Page }): React.ReactElement {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { nostrAuthService } from '@/lib/nostrAuth'
|
||||
import { objectCache } from '@/lib/objectCache'
|
||||
import { Button, ErrorState } from './ui'
|
||||
|
||||
async function updateCache(): Promise<void> {
|
||||
const state = nostrAuthService.getState()
|
||||
@ -28,14 +29,6 @@ async function updateCache(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function ErrorMessage({ error }: { error: string }): React.ReactElement {
|
||||
return (
|
||||
<div className="bg-red-900/20 border border-red-400/50 rounded-lg p-4 mb-4">
|
||||
<p className="text-red-400">{error}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SuccessMessage(): React.ReactElement {
|
||||
return (
|
||||
<div className="bg-green-900/20 border border-green-400/50 rounded-lg p-4 mb-4">
|
||||
@ -78,12 +71,6 @@ function createUpdateHandler(
|
||||
}
|
||||
}
|
||||
|
||||
function Spinner(): React.ReactElement {
|
||||
return (
|
||||
<div className="inline-block animate-spin rounded-full h-4 w-4 border-2 border-neon-cyan border-t-transparent mr-2" />
|
||||
)
|
||||
}
|
||||
|
||||
export function CacheUpdateManager(): React.ReactElement {
|
||||
const router = useRouter()
|
||||
const [updating, setUpdating] = useState(false)
|
||||
@ -102,20 +89,21 @@ export function CacheUpdateManager(): React.ReactElement {
|
||||
Cela permet de récupérer les dernières versions de vos publications, séries et profil.
|
||||
</p>
|
||||
|
||||
{error && <ErrorMessage error={error} />}
|
||||
{error && <ErrorState message={error} />}
|
||||
{success && <SuccessMessage />}
|
||||
{!isConnected && <NotConnectedMessage />}
|
||||
|
||||
<button
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
void handleUpdateCache()
|
||||
}}
|
||||
disabled={updating || !isConnected}
|
||||
className="w-full py-3 px-6 bg-neon-cyan/20 hover:bg-neon-cyan/30 text-neon-cyan rounded-lg font-medium transition-all border border-neon-cyan/50 hover:shadow-glow-cyan disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
|
||||
loading={updating}
|
||||
className="w-full"
|
||||
>
|
||||
{updating && <Spinner />}
|
||||
{updating ? 'Mise à jour en cours...' : 'Mettre à jour le cache'}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { useState } from 'react'
|
||||
import { CreateAccountModal } from '../CreateAccountModal'
|
||||
import { RecoveryStep } from '../CreateAccountModalSteps'
|
||||
import { UnlockAccountModal } from '../UnlockAccountModal'
|
||||
import { Button, Card } from '../ui'
|
||||
import { t } from '@/lib/i18n'
|
||||
|
||||
export function NoAccountView(): React.ReactElement {
|
||||
@ -50,20 +51,12 @@ export function NoAccountView(): React.ReactElement {
|
||||
function NoAccountActionButtons(params: { onGenerate: () => void; onImport: () => void }): React.ReactElement {
|
||||
return (
|
||||
<div className="flex flex-col gap-3 w-full max-w-xs">
|
||||
<button
|
||||
type="button"
|
||||
onClick={params.onGenerate}
|
||||
className="px-6 py-2 bg-neon-cyan/20 hover:bg-neon-cyan/30 text-neon-cyan rounded-lg font-medium transition-all border border-neon-cyan/50 hover:shadow-glow-cyan"
|
||||
>
|
||||
<Button variant="primary" size="large" onClick={params.onGenerate}>
|
||||
{t('account.create.generateButton')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={params.onImport}
|
||||
className="px-6 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg font-medium transition-colors"
|
||||
>
|
||||
</Button>
|
||||
<Button variant="secondary" size="large" onClick={params.onImport}>
|
||||
{t('account.create.importButton')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -76,7 +69,7 @@ function NoAccountCard(params: {
|
||||
modals: React.ReactElement
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<div className="border border-neon-cyan/20 rounded-lg p-6 bg-cyber-dark/50">
|
||||
<Card variant="default" className="bg-cyber-dark/50">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<p className="text-center text-cyber-accent mb-2">Créez un compte ou importez votre clé secrète pour commencer</p>
|
||||
{params.error ? <p className="text-sm text-red-400">{params.error}</p> : null}
|
||||
@ -84,7 +77,7 @@ function NoAccountCard(params: {
|
||||
{params.generating ? <p className="text-cyber-accent text-sm">Génération du compte...</p> : null}
|
||||
{params.modals}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,19 +1,21 @@
|
||||
import React from 'react'
|
||||
import { Button, ErrorState } from '@/components/ui'
|
||||
import { Button, ErrorState, Input, Textarea, Card } from '@/components/ui'
|
||||
import { t } from '@/lib/i18n'
|
||||
import type { ReviewFormController } from './useReviewFormController'
|
||||
|
||||
export function ReviewFormView(params: { ctrl: ReviewFormController; onCancel?: () => void }): React.ReactElement {
|
||||
return (
|
||||
<form
|
||||
onSubmit={(e) => void params.ctrl.handleSubmit(e)}
|
||||
className="border border-neon-cyan/30 rounded-lg p-4 bg-cyber-dark space-y-4"
|
||||
>
|
||||
<ReviewFormHeader />
|
||||
<ReviewFormFields ctrl={params.ctrl} />
|
||||
{params.ctrl.error ? <ErrorBox message={params.ctrl.error} /> : null}
|
||||
<ReviewFormActions loading={params.ctrl.loading} onCancel={params.onCancel} />
|
||||
</form>
|
||||
<Card variant="default" className="space-y-4">
|
||||
<form
|
||||
onSubmit={(e) => void params.ctrl.handleSubmit(e)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<ReviewFormHeader />
|
||||
<ReviewFormFields ctrl={params.ctrl} />
|
||||
{params.ctrl.error ? <ErrorBox message={params.ctrl.error} /> : null}
|
||||
<ReviewFormActions loading={params.ctrl.loading} onCancel={params.onCancel} />
|
||||
</form>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@ -94,20 +96,16 @@ function TextInput(params: {
|
||||
placeholder: string
|
||||
optionalLabel?: string
|
||||
}): React.ReactElement {
|
||||
const labelText = params.optionalLabel ? `${params.label} ${params.optionalLabel}` : params.label
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={params.id} className="block text-sm font-medium text-cyber-accent mb-1">
|
||||
{params.label} {params.optionalLabel ? <span className="text-cyber-accent/50">{params.optionalLabel}</span> : null}
|
||||
</label>
|
||||
<input
|
||||
id={params.id}
|
||||
type="text"
|
||||
value={params.value}
|
||||
onChange={(e) => params.onChange(e.target.value)}
|
||||
placeholder={params.placeholder}
|
||||
className="w-full px-3 py-2 bg-cyber-darker border border-neon-cyan/30 rounded text-cyber-accent focus:border-neon-cyan focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
id={params.id}
|
||||
type="text"
|
||||
label={labelText}
|
||||
value={params.value}
|
||||
onChange={(e) => params.onChange(e.target.value)}
|
||||
placeholder={params.placeholder}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -123,23 +121,23 @@ function TextAreaInput(params: {
|
||||
optionalLabel?: string
|
||||
helpText?: string
|
||||
}): React.ReactElement {
|
||||
let labelText = params.label
|
||||
if (params.requiredMark) {
|
||||
labelText = `${labelText} *`
|
||||
}
|
||||
if (params.optionalLabel) {
|
||||
labelText = `${labelText} ${params.optionalLabel}`
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={params.id} className="block text-sm font-medium text-cyber-accent mb-1">
|
||||
{params.label}{' '}
|
||||
{params.requiredMark ? <span className="text-red-400">*</span> : null}{' '}
|
||||
{params.optionalLabel ? <span className="text-cyber-accent/50">{params.optionalLabel}</span> : null}
|
||||
</label>
|
||||
<textarea
|
||||
id={params.id}
|
||||
value={params.value}
|
||||
onChange={(e) => params.onChange(e.target.value)}
|
||||
placeholder={params.placeholder}
|
||||
rows={params.rows}
|
||||
required={params.required}
|
||||
className="w-full px-3 py-2 bg-cyber-darker border border-neon-cyan/30 rounded text-cyber-accent focus:border-neon-cyan focus:outline-none"
|
||||
/>
|
||||
{params.helpText ? <p className="text-xs text-cyber-accent/70 mt-1">{params.helpText}</p> : null}
|
||||
</div>
|
||||
<Textarea
|
||||
id={params.id}
|
||||
label={labelText}
|
||||
value={params.value}
|
||||
onChange={(e) => params.onChange(e.target.value)}
|
||||
placeholder={params.placeholder}
|
||||
rows={params.rows}
|
||||
required={params.required}
|
||||
{...(params.helpText ? { helperText: params.helpText } : {})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,19 +1,21 @@
|
||||
import React from 'react'
|
||||
import { Button, ErrorState } from '@/components/ui'
|
||||
import { Button, ErrorState, Textarea, Card } from '@/components/ui'
|
||||
import { t } from '@/lib/i18n'
|
||||
import type { ReviewTipFormController } from './useReviewTipFormController'
|
||||
|
||||
export function ReviewTipFormView(params: { ctrl: ReviewTipFormController; onCancel?: () => void }): React.ReactElement {
|
||||
return (
|
||||
<form
|
||||
onSubmit={(e) => void params.ctrl.handleSubmit(e)}
|
||||
className="border border-neon-cyan/30 rounded-lg p-4 bg-cyber-dark space-y-4"
|
||||
>
|
||||
<ReviewTipFormHeader split={params.ctrl.split} />
|
||||
<ReviewTipTextField value={params.ctrl.text} onChange={params.ctrl.setText} />
|
||||
{params.ctrl.error ? <ErrorBox message={params.ctrl.error} /> : null}
|
||||
<ReviewTipFormActions amount={params.ctrl.split.total} loading={params.ctrl.loading} onCancel={params.onCancel} />
|
||||
</form>
|
||||
<Card variant="default" className="space-y-4">
|
||||
<form
|
||||
onSubmit={(e) => void params.ctrl.handleSubmit(e)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<ReviewTipFormHeader split={params.ctrl.split} />
|
||||
<ReviewTipTextField value={params.ctrl.text} onChange={params.ctrl.setText} />
|
||||
{params.ctrl.error ? <ErrorBox message={params.ctrl.error} /> : null}
|
||||
<ReviewTipFormActions amount={params.ctrl.split.total} loading={params.ctrl.loading} onCancel={params.onCancel} />
|
||||
</form>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@ -34,20 +36,15 @@ function ReviewTipFormHeader(params: { split: { total: number; reviewer: number;
|
||||
|
||||
function ReviewTipTextField(params: { value: string; onChange: (value: string) => void }): React.ReactElement {
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor="review-tip-text" className="block text-sm font-medium text-cyber-accent mb-1">
|
||||
{t('reviewTip.form.text.label')} <span className="text-cyber-accent/50">({t('common.optional')})</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="review-tip-text"
|
||||
value={params.value}
|
||||
onChange={(e) => params.onChange(e.target.value)}
|
||||
placeholder={t('reviewTip.form.text.placeholder')}
|
||||
rows={3}
|
||||
className="w-full px-3 py-2 bg-cyber-darker border border-neon-cyan/30 rounded text-cyber-accent focus:border-neon-cyan focus:outline-none"
|
||||
/>
|
||||
<p className="text-xs text-cyber-accent/70 mt-1">{t('reviewTip.form.text.help')}</p>
|
||||
</div>
|
||||
<Textarea
|
||||
id="review-tip-text"
|
||||
label={`${t('reviewTip.form.text.label')} (${t('common.optional')})`}
|
||||
value={params.value}
|
||||
onChange={(e) => params.onChange(e.target.value)}
|
||||
placeholder={t('reviewTip.form.text.placeholder')}
|
||||
rows={3}
|
||||
helperText={t('reviewTip.form.text.help')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user