series building
This commit is contained in:
parent
4787bd5410
commit
4a619c9576
@ -1,5 +1,6 @@
|
|||||||
import type { Article } from '@/types/nostr'
|
import type { Article } from '@/types/nostr'
|
||||||
import { ArticleCard } from './ArticleCard'
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
import { t } from '@/lib/i18n'
|
||||||
|
|
||||||
interface ArticlesListProps {
|
interface ArticlesListProps {
|
||||||
articles: Article[]
|
articles: Article[]
|
||||||
@ -13,7 +14,7 @@ interface ArticlesListProps {
|
|||||||
function LoadingState() {
|
function LoadingState() {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<p className="text-cyber-accent/70">Loading articles...</p>
|
<p className="text-cyber-accent/70">{t('common.loading.articles')}</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useState, useCallback, useEffect, type FormEvent } from 'react'
|
import { useState, useCallback, useEffect, type FormEvent } from 'react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
import { useNostrAuth } from '@/hooks/useNostrAuth'
|
import { useNostrAuth } from '@/hooks/useNostrAuth'
|
||||||
import { useAuthorPresentation } from '@/hooks/useAuthorPresentation'
|
import { useAuthorPresentation } from '@/hooks/useAuthorPresentation'
|
||||||
import { ArticleField } from './ArticleField'
|
import { ArticleField } from './ArticleField'
|
||||||
@ -8,6 +9,8 @@ import { RecoveryStep } from './CreateAccountModalSteps'
|
|||||||
import { UnlockAccountModal } from './UnlockAccountModal'
|
import { UnlockAccountModal } from './UnlockAccountModal'
|
||||||
import { ImageUploadField } from './ImageUploadField'
|
import { ImageUploadField } from './ImageUploadField'
|
||||||
import { PresentationFormHeader } from './PresentationFormHeader'
|
import { PresentationFormHeader } from './PresentationFormHeader'
|
||||||
|
import { extractPresentationData } from '@/lib/presentationParsing'
|
||||||
|
import type { Article } from '@/types/nostr'
|
||||||
import { t } from '@/lib/i18n'
|
import { t } from '@/lib/i18n'
|
||||||
|
|
||||||
interface AuthorPresentationDraft {
|
interface AuthorPresentationDraft {
|
||||||
@ -170,6 +173,19 @@ const PresentationFields = ({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function DeleteButton({ onDelete, deleting }: { onDelete: () => void; deleting: boolean }) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onDelete}
|
||||||
|
disabled={deleting}
|
||||||
|
className="px-4 py-2 bg-red-500/20 hover:bg-red-500/30 text-red-400 rounded-lg text-sm font-medium transition-all border border-red-500/50 hover:shadow-glow-red disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{deleting ? t('presentation.delete.deleting') : t('presentation.delete.button')}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function PresentationForm({
|
function PresentationForm({
|
||||||
draft,
|
draft,
|
||||||
setDraft,
|
setDraft,
|
||||||
@ -177,6 +193,9 @@ function PresentationForm({
|
|||||||
error,
|
error,
|
||||||
loading,
|
loading,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
deleting,
|
||||||
|
handleDelete,
|
||||||
|
hasExistingPresentation,
|
||||||
}: {
|
}: {
|
||||||
draft: AuthorPresentationDraft
|
draft: AuthorPresentationDraft
|
||||||
setDraft: (next: AuthorPresentationDraft) => void
|
setDraft: (next: AuthorPresentationDraft) => void
|
||||||
@ -184,6 +203,9 @@ function PresentationForm({
|
|||||||
error: string | null
|
error: string | null
|
||||||
loading: boolean
|
loading: boolean
|
||||||
handleSubmit: (e: FormEvent<HTMLFormElement>) => Promise<void>
|
handleSubmit: (e: FormEvent<HTMLFormElement>) => Promise<void>
|
||||||
|
deleting: boolean
|
||||||
|
handleDelete: () => void
|
||||||
|
hasExistingPresentation: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
@ -195,27 +217,59 @@ function PresentationForm({
|
|||||||
<PresentationFormHeader />
|
<PresentationFormHeader />
|
||||||
<PresentationFields draft={draft} onChange={setDraft} />
|
<PresentationFields draft={draft} onChange={setDraft} />
|
||||||
<ValidationError message={validationError ?? error} />
|
<ValidationError message={validationError ?? error} />
|
||||||
<ArticleFormButtons loading={loading} />
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex-1">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading || deleting}
|
||||||
|
className="w-full px-4 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 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{loading || deleting
|
||||||
|
? t('publish.publishing')
|
||||||
|
: hasExistingPresentation
|
||||||
|
? t('presentation.update.button')
|
||||||
|
: t('publish.button')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{hasExistingPresentation && (
|
||||||
|
<DeleteButton onDelete={handleDelete} deleting={deleting} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function useAuthorPresentationState(pubkey: string | null, existingAuthorName?: string) {
|
function useAuthorPresentationState(pubkey: string | null, existingAuthorName?: string, existingPresentation?: Article | null) {
|
||||||
const { loading, error, success, publishPresentation } = useAuthorPresentation(pubkey)
|
const { loading, error, success, publishPresentation, deletePresentation } = useAuthorPresentation(pubkey)
|
||||||
const [draft, setDraft] = useState<AuthorPresentationDraft>({
|
const router = useRouter()
|
||||||
|
const [draft, setDraft] = useState<AuthorPresentationDraft>(() => {
|
||||||
|
if (existingPresentation) {
|
||||||
|
const { presentation, contentDescription } = extractPresentationData(existingPresentation)
|
||||||
|
const authorName = existingPresentation.title.replace(/^Présentation de /, '') || existingAuthorName || ''
|
||||||
|
return {
|
||||||
|
authorName,
|
||||||
|
presentation,
|
||||||
|
contentDescription,
|
||||||
|
mainnetAddress: existingPresentation.mainnetAddress || '',
|
||||||
|
pictureUrl: existingPresentation.bannerUrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
authorName: existingAuthorName ?? '',
|
authorName: existingAuthorName ?? '',
|
||||||
presentation: '',
|
presentation: '',
|
||||||
contentDescription: '',
|
contentDescription: '',
|
||||||
mainnetAddress: '',
|
mainnetAddress: '',
|
||||||
|
}
|
||||||
})
|
})
|
||||||
const [validationError, setValidationError] = useState<string | null>(null)
|
const [validationError, setValidationError] = useState<string | null>(null)
|
||||||
|
const [deleting, setDeleting] = useState(false)
|
||||||
|
|
||||||
// Update authorName when profile changes
|
// Update authorName when profile changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (existingAuthorName && existingAuthorName !== draft.authorName) {
|
if (existingAuthorName && existingAuthorName !== draft.authorName && !existingPresentation) {
|
||||||
setDraft((prev) => ({ ...prev, authorName: existingAuthorName }))
|
setDraft((prev) => ({ ...prev, authorName: existingAuthorName }))
|
||||||
}
|
}
|
||||||
}, [existingAuthorName])
|
}, [existingAuthorName, existingPresentation])
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
async (e: FormEvent<HTMLFormElement>) => {
|
async (e: FormEvent<HTMLFormElement>) => {
|
||||||
@ -235,7 +289,28 @@ function useAuthorPresentationState(pubkey: string | null, existingAuthorName?:
|
|||||||
[draft, publishPresentation]
|
[draft, publishPresentation]
|
||||||
)
|
)
|
||||||
|
|
||||||
return { loading, error, success, draft, setDraft, validationError, handleSubmit }
|
const handleDelete = useCallback(async () => {
|
||||||
|
if (!existingPresentation?.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm(t('presentation.delete.confirm'))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setDeleting(true)
|
||||||
|
setValidationError(null)
|
||||||
|
try {
|
||||||
|
await deletePresentation(existingPresentation.id)
|
||||||
|
await router.push('/')
|
||||||
|
} catch (e) {
|
||||||
|
setValidationError(e instanceof Error ? e.message : t('presentation.delete.error'))
|
||||||
|
} finally {
|
||||||
|
setDeleting(false)
|
||||||
|
}
|
||||||
|
}, [existingPresentation, deletePresentation, router])
|
||||||
|
|
||||||
|
return { loading, error, success, draft, setDraft, validationError, handleSubmit, deleting, handleDelete, existingPresentation }
|
||||||
}
|
}
|
||||||
|
|
||||||
function NoAccountActionButtons({
|
function NoAccountActionButtons({
|
||||||
@ -348,11 +423,42 @@ function AuthorPresentationFormView({
|
|||||||
pubkey: string | null
|
pubkey: string | null
|
||||||
profile: { name?: string; pubkey: string } | null
|
profile: { name?: string; pubkey: string } | null
|
||||||
}) {
|
}) {
|
||||||
const state = useAuthorPresentationState(pubkey, profile?.name)
|
const { checkPresentationExists } = useAuthorPresentation(pubkey)
|
||||||
|
const [existingPresentation, setExistingPresentation] = useState<Article | null>(null)
|
||||||
|
const [loadingPresentation, setLoadingPresentation] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const load = async () => {
|
||||||
|
if (!pubkey) {
|
||||||
|
setLoadingPresentation(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const presentation = await checkPresentationExists()
|
||||||
|
setExistingPresentation(presentation)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading presentation:', e)
|
||||||
|
} finally {
|
||||||
|
setLoadingPresentation(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void load()
|
||||||
|
}, [pubkey, checkPresentationExists])
|
||||||
|
|
||||||
|
const state = useAuthorPresentationState(pubkey, profile?.name, existingPresentation)
|
||||||
|
|
||||||
if (!pubkey) {
|
if (!pubkey) {
|
||||||
return <NoAccountView />
|
return <NoAccountView />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (loadingPresentation) {
|
||||||
|
return (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<p className="text-cyber-accent/70">{t('common.loading')}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (state.success) {
|
if (state.success) {
|
||||||
return <SuccessNotice pubkey={pubkey} />
|
return <SuccessNotice pubkey={pubkey} />
|
||||||
}
|
}
|
||||||
@ -365,6 +471,9 @@ function AuthorPresentationFormView({
|
|||||||
error={state.error}
|
error={state.error}
|
||||||
loading={state.loading}
|
loading={state.loading}
|
||||||
handleSubmit={state.handleSubmit}
|
handleSubmit={state.handleSubmit}
|
||||||
|
deleting={state.deleting}
|
||||||
|
handleDelete={state.handleDelete}
|
||||||
|
hasExistingPresentation={!!existingPresentation}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { Article } from '@/types/nostr'
|
import type { Article } from '@/types/nostr'
|
||||||
import { AuthorCard } from './AuthorCard'
|
import { AuthorCard } from './AuthorCard'
|
||||||
|
import { t } from '@/lib/i18n'
|
||||||
|
|
||||||
interface AuthorsListProps {
|
interface AuthorsListProps {
|
||||||
authors: Article[]
|
authors: Article[]
|
||||||
@ -11,7 +12,7 @@ interface AuthorsListProps {
|
|||||||
function LoadingState() {
|
function LoadingState() {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<p className="text-cyber-accent/70">Loading authors...</p>
|
<p className="text-cyber-accent/70">{t('common.loading.authors')}</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
import Image from 'next/image'
|
||||||
import { useNostrAuth } from '@/hooks/useNostrAuth'
|
import { useNostrAuth } from '@/hooks/useNostrAuth'
|
||||||
import { useAuthorPresentation } from '@/hooks/useAuthorPresentation'
|
import { useAuthorPresentation } from '@/hooks/useAuthorPresentation'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { t } from '@/lib/i18n'
|
import { t } from '@/lib/i18n'
|
||||||
|
import type { Article } from '@/types/nostr'
|
||||||
|
|
||||||
const buttonClassName = 'px-4 py-2 bg-neon-cyan/20 hover:bg-neon-cyan/30 text-neon-cyan rounded-lg text-sm font-medium transition-all border border-neon-cyan/50 hover:shadow-glow-cyan'
|
const buttonClassName = 'px-4 py-2 bg-neon-cyan/20 hover:bg-neon-cyan/30 text-neon-cyan rounded-lg text-sm font-medium transition-all border border-neon-cyan/50 hover:shadow-glow-cyan'
|
||||||
|
|
||||||
@ -30,19 +32,51 @@ function LoadingButton() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AuthorProfileLink({ presentation, profile }: { presentation: Article; profile: { name?: string; picture?: string } | null }) {
|
||||||
|
const authorName = presentation.title.replace(/^Présentation de /, '') || profile?.name || 'Auteur'
|
||||||
|
const picture = presentation.bannerUrl || profile?.picture
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={`/author/${presentation.pubkey}`}
|
||||||
|
className="flex items-center gap-2 px-3 py-2 bg-cyber-dark/50 hover:bg-cyber-dark border border-neon-cyan/20 hover:border-neon-cyan/40 rounded-lg transition-all"
|
||||||
|
>
|
||||||
|
{picture ? (
|
||||||
|
<div className="relative w-8 h-8 rounded-full overflow-hidden border border-neon-cyan/30 flex-shrink-0">
|
||||||
|
<Image
|
||||||
|
src={picture}
|
||||||
|
alt={authorName}
|
||||||
|
fill
|
||||||
|
className="object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="w-8 h-8 rounded-full bg-cyber-light border border-neon-cyan/30 flex items-center justify-center flex-shrink-0">
|
||||||
|
<span className="text-xs text-neon-cyan font-medium">{authorName.charAt(0).toUpperCase()}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<span className="text-sm text-neon-cyan font-medium truncate max-w-[120px]">{authorName}</span>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function ConditionalPublishButton() {
|
export function ConditionalPublishButton() {
|
||||||
const { connected, pubkey } = useNostrAuth()
|
const { connected, pubkey, profile } = useNostrAuth()
|
||||||
const { checkPresentationExists } = useAuthorPresentation(pubkey ?? null)
|
const { checkPresentationExists } = useAuthorPresentation(pubkey ?? null)
|
||||||
const [hasPresentation, setHasPresentation] = useState<boolean | null>(null)
|
const [presentation, setPresentation] = useState<Article | null>(null)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const check = async () => {
|
const check = async () => {
|
||||||
if (!connected || !pubkey) {
|
if (!connected || !pubkey) {
|
||||||
setHasPresentation(null)
|
setPresentation(null)
|
||||||
|
setLoading(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const presentation = await checkPresentationExists()
|
setLoading(true)
|
||||||
setHasPresentation(presentation !== null)
|
const pres = await checkPresentationExists()
|
||||||
|
setPresentation(pres)
|
||||||
|
setLoading(false)
|
||||||
}
|
}
|
||||||
void check()
|
void check()
|
||||||
}, [connected, pubkey, checkPresentationExists])
|
}, [connected, pubkey, checkPresentationExists])
|
||||||
@ -51,13 +85,14 @@ export function ConditionalPublishButton() {
|
|||||||
return <CreateAuthorPageLink />
|
return <CreateAuthorPageLink />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasPresentation === null) {
|
if (loading) {
|
||||||
return <LoadingButton />
|
return <LoadingButton />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasPresentation) {
|
if (!presentation) {
|
||||||
return <CreateAuthorPageLink />
|
return <CreateAuthorPageLink />
|
||||||
}
|
}
|
||||||
|
|
||||||
return <PublishLink />
|
// If presentation exists, show author profile link instead of publish button
|
||||||
|
return <AuthorProfileLink presentation={presentation} profile={profile} />
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,3 +54,4 @@ Aucun déploiement spécial nécessaire. Les modifications sont purement fronten
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -92,11 +92,38 @@ export function useAuthorPresentation(pubkey: string | null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deletePresentation = async (articleId: string): Promise<void> => {
|
||||||
|
if (!pubkey) {
|
||||||
|
throw new Error('Clé publique non disponible')
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const privateKey = nostrService.getPrivateKey()
|
||||||
|
if (!privateKey) {
|
||||||
|
throw new Error('Clé privée requise pour supprimer. Veuillez vous connecter avec un portefeuille Nostr qui fournit des capacités de signature.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { deleteArticleEvent } = await import('@/lib/articleMutations')
|
||||||
|
await deleteArticleEvent(articleId, pubkey, privateKey)
|
||||||
|
} catch (e) {
|
||||||
|
const errorMessage = e instanceof Error ? e.message : 'Erreur inconnue'
|
||||||
|
console.error('Error deleting presentation:', e)
|
||||||
|
setError(errorMessage)
|
||||||
|
throw e
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
success,
|
success,
|
||||||
publishPresentation,
|
publishPresentation,
|
||||||
checkPresentationExists,
|
checkPresentationExists,
|
||||||
|
deletePresentation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
lib/presentationParsing.ts
Normal file
30
lib/presentationParsing.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import type { Article } from '@/types/nostr'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract presentation data from article content
|
||||||
|
* Content format: "${presentation}\n\n---\n\nDescription du contenu :\n${contentDescription}"
|
||||||
|
*/
|
||||||
|
export function extractPresentationData(presentation: Article): {
|
||||||
|
presentation: string
|
||||||
|
contentDescription: string
|
||||||
|
} {
|
||||||
|
const content = presentation.content
|
||||||
|
const separator = '\n\n---\n\nDescription du contenu :\n'
|
||||||
|
const separatorIndex = content.indexOf(separator)
|
||||||
|
|
||||||
|
if (separatorIndex === -1) {
|
||||||
|
// Fallback: return content as presentation if separator not found
|
||||||
|
return {
|
||||||
|
presentation: content,
|
||||||
|
contentDescription: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const presentationText = content.substring(0, separatorIndex)
|
||||||
|
const contentDescription = content.substring(separatorIndex + separator.length)
|
||||||
|
|
||||||
|
return {
|
||||||
|
presentation: presentationText,
|
||||||
|
contentDescription,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -83,6 +83,11 @@ presentation.field.mainnetAddress.placeholder=1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
|
|||||||
presentation.field.mainnetAddress.help=Bitcoin mainnet address where you will receive sponsoring payments (0.046 BTC excluding fees per sponsoring)
|
presentation.field.mainnetAddress.help=Bitcoin mainnet address where you will receive sponsoring payments (0.046 BTC excluding fees per sponsoring)
|
||||||
presentation.validation.invalidAddress=Invalid Bitcoin address (must start with 1, 3 or bc1)
|
presentation.validation.invalidAddress=Invalid Bitcoin address (must start with 1, 3 or bc1)
|
||||||
presentation.fallback.user=User
|
presentation.fallback.user=User
|
||||||
|
presentation.update.button=Update author page
|
||||||
|
presentation.delete.button=Delete author page
|
||||||
|
presentation.delete.confirm=Are you sure you want to delete your author page? This action is irreversible.
|
||||||
|
presentation.delete.deleting=Deleting...
|
||||||
|
presentation.delete.error=Error deleting author page
|
||||||
|
|
||||||
# Filters
|
# Filters
|
||||||
filters.clear=Clear all
|
filters.clear=Clear all
|
||||||
@ -102,6 +107,8 @@ footer.privacy=Privacy Policy
|
|||||||
|
|
||||||
# Common
|
# Common
|
||||||
common.loading=Loading...
|
common.loading=Loading...
|
||||||
|
common.loading.articles=Loading articles...
|
||||||
|
common.loading.authors=Loading authors...
|
||||||
common.error=Error
|
common.error=Error
|
||||||
common.back=Back
|
common.back=Back
|
||||||
common.open=Open
|
common.open=Open
|
||||||
|
|||||||
@ -83,6 +83,11 @@ presentation.field.mainnetAddress.placeholder=1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
|
|||||||
presentation.field.mainnetAddress.help=Adresse Bitcoin mainnet où vous recevrez les paiements de sponsoring (0.046 BTC hors frais par sponsoring)
|
presentation.field.mainnetAddress.help=Adresse Bitcoin mainnet où vous recevrez les paiements de sponsoring (0.046 BTC hors frais par sponsoring)
|
||||||
presentation.validation.invalidAddress=Adresse Bitcoin invalide (doit commencer par 1, 3 ou bc1)
|
presentation.validation.invalidAddress=Adresse Bitcoin invalide (doit commencer par 1, 3 ou bc1)
|
||||||
presentation.fallback.user=Utilisateur
|
presentation.fallback.user=Utilisateur
|
||||||
|
presentation.update.button=Mettre à jour la page auteur
|
||||||
|
presentation.delete.button=Supprimer la page auteur
|
||||||
|
presentation.delete.confirm=Êtes-vous sûr de vouloir supprimer votre page auteur ? Cette action est irréversible.
|
||||||
|
presentation.delete.deleting=Suppression...
|
||||||
|
presentation.delete.error=Erreur lors de la suppression de la page auteur
|
||||||
|
|
||||||
# Filters
|
# Filters
|
||||||
filters.clear=Effacer tout
|
filters.clear=Effacer tout
|
||||||
@ -102,6 +107,8 @@ footer.privacy=Politique de confidentialité
|
|||||||
|
|
||||||
# Common
|
# Common
|
||||||
common.loading=Chargement...
|
common.loading=Chargement...
|
||||||
|
common.loading.articles=Chargement des articles...
|
||||||
|
common.loading.authors=Chargement des auteurs...
|
||||||
common.error=Erreur
|
common.error=Erreur
|
||||||
common.back=Retour
|
common.back=Retour
|
||||||
common.open=Ouvrir
|
common.open=Ouvrir
|
||||||
|
|||||||
@ -18,7 +18,7 @@ function usePresentationRedirect(connected: boolean, pubkey: string | null) {
|
|||||||
}
|
}
|
||||||
const presentation = await checkPresentationExists()
|
const presentation = await checkPresentationExists()
|
||||||
if (presentation) {
|
if (presentation) {
|
||||||
await router.push('/')
|
await router.push(`/author/${pubkey}`)
|
||||||
}
|
}
|
||||||
}, [checkPresentationExists, connected, pubkey, router])
|
}, [checkPresentationExists, connected, pubkey, router])
|
||||||
|
|
||||||
|
|||||||
@ -83,6 +83,11 @@ presentation.field.mainnetAddress.placeholder=1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
|
|||||||
presentation.field.mainnetAddress.help=Bitcoin mainnet address where you will receive sponsoring payments (0.046 BTC excluding fees per sponsoring)
|
presentation.field.mainnetAddress.help=Bitcoin mainnet address where you will receive sponsoring payments (0.046 BTC excluding fees per sponsoring)
|
||||||
presentation.validation.invalidAddress=Invalid Bitcoin address (must start with 1, 3 or bc1)
|
presentation.validation.invalidAddress=Invalid Bitcoin address (must start with 1, 3 or bc1)
|
||||||
presentation.fallback.user=User
|
presentation.fallback.user=User
|
||||||
|
presentation.update.button=Update author page
|
||||||
|
presentation.delete.button=Delete author page
|
||||||
|
presentation.delete.confirm=Are you sure you want to delete your author page? This action is irreversible.
|
||||||
|
presentation.delete.deleting=Deleting...
|
||||||
|
presentation.delete.error=Error deleting author page
|
||||||
|
|
||||||
# Filters
|
# Filters
|
||||||
filters.clear=Clear all
|
filters.clear=Clear all
|
||||||
@ -102,6 +107,8 @@ footer.privacy=Privacy Policy
|
|||||||
|
|
||||||
# Common
|
# Common
|
||||||
common.loading=Loading...
|
common.loading=Loading...
|
||||||
|
common.loading.articles=Loading articles...
|
||||||
|
common.loading.authors=Loading authors...
|
||||||
common.error=Error
|
common.error=Error
|
||||||
common.back=Back
|
common.back=Back
|
||||||
common.open=Open
|
common.open=Open
|
||||||
|
|||||||
@ -83,6 +83,11 @@ presentation.field.mainnetAddress.placeholder=1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
|
|||||||
presentation.field.mainnetAddress.help=Adresse Bitcoin mainnet où vous recevrez les paiements de sponsoring (0.046 BTC hors frais par sponsoring)
|
presentation.field.mainnetAddress.help=Adresse Bitcoin mainnet où vous recevrez les paiements de sponsoring (0.046 BTC hors frais par sponsoring)
|
||||||
presentation.validation.invalidAddress=Adresse Bitcoin invalide (doit commencer par 1, 3 ou bc1)
|
presentation.validation.invalidAddress=Adresse Bitcoin invalide (doit commencer par 1, 3 ou bc1)
|
||||||
presentation.fallback.user=Utilisateur
|
presentation.fallback.user=Utilisateur
|
||||||
|
presentation.update.button=Mettre à jour la page auteur
|
||||||
|
presentation.delete.button=Supprimer la page auteur
|
||||||
|
presentation.delete.confirm=Êtes-vous sûr de vouloir supprimer votre page auteur ? Cette action est irréversible.
|
||||||
|
presentation.delete.deleting=Suppression...
|
||||||
|
presentation.delete.error=Erreur lors de la suppression de la page auteur
|
||||||
|
|
||||||
# Filters
|
# Filters
|
||||||
filters.clear=Effacer tout
|
filters.clear=Effacer tout
|
||||||
@ -102,6 +107,8 @@ footer.privacy=Politique de confidentialité
|
|||||||
|
|
||||||
# Common
|
# Common
|
||||||
common.loading=Chargement...
|
common.loading=Chargement...
|
||||||
|
common.loading.articles=Chargement des articles...
|
||||||
|
common.loading.authors=Chargement des auteurs...
|
||||||
common.error=Erreur
|
common.error=Erreur
|
||||||
common.back=Retour
|
common.back=Retour
|
||||||
common.open=Ouvrir
|
common.open=Ouvrir
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user