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