lint fix wip

This commit is contained in:
Nicolas Cantu 2026-01-06 11:30:23 +01:00
parent 5ac5aab089
commit 412989e6af
91 changed files with 506 additions and 328 deletions

View File

@ -5,7 +5,7 @@ interface AlbyInstallerProps {
onInstalled?: () => void
}
function InfoIcon() {
function InfoIcon(): JSX.Element {
return (
<svg
className="h-5 w-5 text-blue-400"
@ -27,7 +27,7 @@ interface InstallerActionsProps {
markInstalled: () => void
}
function InstallerActions({ onInstalled, markInstalled }: InstallerActionsProps) {
function InstallerActions({ onInstalled, markInstalled }: InstallerActionsProps): JSX.Element {
const connect = useCallback(() => {
const alby = getAlbyService()
void alby.enable().then(() => {
@ -60,7 +60,7 @@ function InstallerActions({ onInstalled, markInstalled }: InstallerActionsProps)
)
}
function InstallerBody({ onInstalled, markInstalled }: InstallerActionsProps) {
function InstallerBody({ onInstalled, markInstalled }: InstallerActionsProps): JSX.Element {
return (
<div className="ml-3 flex-1">
<h3 className="text-sm font-medium text-blue-800">Alby Extension Required</h3>
@ -78,12 +78,12 @@ function InstallerBody({ onInstalled, markInstalled }: InstallerActionsProps) {
)
}
function useAlbyStatus(onInstalled?: () => void) {
function useAlbyStatus(onInstalled?: () => void): { isInstalled: boolean; isChecking: boolean; markInstalled: () => void } {
const [isInstalled, setIsInstalled] = useState(false)
const [isChecking, setIsChecking] = useState(true)
useEffect(() => {
const checkAlby = () => {
const checkAlby = (): void => {
try {
const alby = getAlbyService()
const installed = alby.isEnabled()
@ -101,14 +101,14 @@ function useAlbyStatus(onInstalled?: () => void) {
checkAlby()
}, [onInstalled])
const markInstalled = () => {
const markInstalled = (): void => {
setIsInstalled(true)
}
return { isInstalled, isChecking, markInstalled }
}
export function AlbyInstaller({ onInstalled }: AlbyInstallerProps) {
export function AlbyInstaller({ onInstalled }: AlbyInstallerProps): JSX.Element | null {
const { isInstalled, isChecking, markInstalled } = useAlbyStatus(onInstalled)
if (isChecking || isInstalled) {

View File

@ -11,7 +11,7 @@ interface ArticleCardProps {
onUnlock?: (article: Article) => void
}
function ArticleHeader({ article }: { article: Article }) {
function ArticleHeader({ article }: { article: Article }): JSX.Element {
return (
<div className="mb-2 flex items-center justify-between">
<h2 className="text-2xl font-bold text-neon-cyan">{article.title}</h2>
@ -37,7 +37,7 @@ function ArticleMeta({
paymentInvoice: ReturnType<typeof useArticlePayment>['paymentInvoice']
onClose: () => void
onPaymentComplete: () => void
}) {
}): JSX.Element {
return (
<>
{error && <p className="text-sm text-red-400 mt-2">{error}</p>}
@ -55,7 +55,7 @@ function ArticleMeta({
)
}
export function ArticleCard({ article, onUnlock }: ArticleCardProps) {
export function ArticleCard({ article, onUnlock }: ArticleCardProps): JSX.Element {
const { pubkey, connect } = useNostrAuth()
const {
loading,

View File

@ -12,7 +12,7 @@ interface ArticleEditorProps {
}
function SuccessMessage() {
function SuccessMessage(): JSX.Element {
return (
<div className="border rounded-lg p-6 bg-green-50 border-green-200">
<h3 className="text-lg font-semibold text-green-800 mb-2">Article Published!</h3>
@ -21,7 +21,7 @@ function SuccessMessage() {
)
}
export function ArticleEditor({ onPublishSuccess, onCancel, seriesOptions, onSelectSeries }: ArticleEditorProps) {
export function ArticleEditor({ onPublishSuccess, onCancel, seriesOptions, onSelectSeries }: ArticleEditorProps): JSX.Element {
const { connected, pubkey, connect } = useNostrAuth()
const { loading, error, success, publishArticle } = useArticlePublishing(pubkey ?? null)
const [draft, setDraft] = useState<ArticleDraft>({
@ -61,8 +61,8 @@ function buildSubmitHandler(
onPublishSuccess?: (articleId: string) => void,
connect?: () => Promise<void>,
connected?: boolean
) {
return async () => {
): () => Promise<void> {
return async (): Promise<void> => {
if (!connected && connect) {
await connect()
return

View File

@ -25,7 +25,7 @@ function CategoryField({
}: {
value: ArticleDraft['category']
onChange: (value: import('@/types/nostr').ArticleCategory | undefined) => void
}) {
}): JSX.Element {
return (
<CategorySelect
id="category"
@ -38,7 +38,7 @@ function CategoryField({
)
}
function ErrorAlert({ error }: { error: string | null }) {
function ErrorAlert({ error }: { error: string | null }): JSX.Element | null {
if (!error) {
return null
}
@ -76,7 +76,7 @@ const ArticleFieldsLeft = ({
onDraftChange: (draft: ArticleDraft) => void
seriesOptions?: { id: string; title: string }[] | undefined
onSelectSeries?: ((seriesId: string | undefined) => void) | undefined
}) => (
}): JSX.Element => (
<div className="space-y-4">
<CategoryField
value={draft.category}
@ -95,7 +95,7 @@ const ArticleFieldsLeft = ({
</div>
)
function ArticleTitleField({ draft, onDraftChange }: { draft: ArticleDraft; onDraftChange: (draft: ArticleDraft) => void }) {
function ArticleTitleField({ draft, onDraftChange }: { draft: ArticleDraft; onDraftChange: (draft: ArticleDraft) => void }): JSX.Element {
return (
<ArticleField
id="title"
@ -114,7 +114,7 @@ function ArticlePreviewField({
}: {
draft: ArticleDraft
onDraftChange: (draft: ArticleDraft) => void
}) {
}): JSX.Element {
return (
<ArticleField
id="preview"
@ -140,7 +140,7 @@ function SeriesSelect({
onDraftChange: (draft: ArticleDraft) => void
seriesOptions: { id: string; title: string }[]
onSelectSeries?: ((seriesId: string | undefined) => void) | undefined
}) {
}): JSX.Element {
const handleChange = buildSeriesChangeHandler(draft, onDraftChange, onSelectSeries)
return (
@ -169,8 +169,8 @@ function buildSeriesChangeHandler(
draft: ArticleDraft,
onDraftChange: (draft: ArticleDraft) => void,
onSelectSeries?: ((seriesId: string | undefined) => void) | undefined
) {
return (e: React.ChangeEvent<HTMLSelectElement>) => {
): (e: React.ChangeEvent<HTMLSelectElement>) => void {
return (e: React.ChangeEvent<HTMLSelectElement>): void => {
const value = e.target.value || undefined
const nextDraft = { ...draft }
if (value) {
@ -189,7 +189,7 @@ const ArticleFieldsRight = ({
}: {
draft: ArticleDraft
onDraftChange: (draft: ArticleDraft) => void
}) => (
}): JSX.Element => (
<div className="space-y-4">
<div className="space-y-2">
<div className="text-sm font-semibold text-gray-800">{t('article.editor.content.label')}</div>
@ -230,7 +230,7 @@ export function ArticleEditorForm({
onCancel,
seriesOptions,
onSelectSeries,
}: ArticleEditorFormProps) {
}: ArticleEditorFormProps): JSX.Element {
return (
<form onSubmit={onSubmit} className="border rounded-lg p-6 bg-white space-y-4">
<h2 className="text-2xl font-bold mb-4">{t('article.editor.title')}</h2>

View File

@ -30,7 +30,7 @@ function NumberOrTextInput({
min?: number
className: string
onChange: (value: string | number) => void
}) {
}): JSX.Element {
const inputProps = {
id,
type,
@ -64,7 +64,7 @@ function TextAreaInput({
rows?: number
className: string
onChange: (value: string | number) => void
}) {
}): JSX.Element {
const areaProps = {
id,
value,
@ -81,7 +81,7 @@ function TextAreaInput({
)
}
export function ArticleField(props: ArticleFieldProps) {
export function ArticleField(props: ArticleFieldProps): JSX.Element {
const { id, label, value, onChange, required = false, type = 'text', rows, placeholder, helpText, min } =
props
const inputClass =

View File

@ -46,8 +46,8 @@ function FiltersGrid({
data: FiltersData
filters: ArticleFilters
onFiltersChange: (filters: ArticleFilters) => void
}) {
const update = (patch: Partial<ArticleFilters>) => onFiltersChange({ ...filters, ...patch })
}): JSX.Element {
const update = (patch: Partial<ArticleFilters>): void => onFiltersChange({ ...filters, ...patch })
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
@ -63,7 +63,7 @@ function FiltersHeader({
}: {
hasActiveFilters: boolean
onClear: () => void
}) {
}): JSX.Element {
return (
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold text-neon-cyan">{t('filters.sort')}</h3>
@ -83,7 +83,7 @@ function SortFilter({
}: {
value: SortOption
onChange: (value: SortOption) => void
}) {
}): JSX.Element {
return (
<div>
<label htmlFor="sort" className="block text-sm font-medium text-cyber-accent mb-1">
@ -106,7 +106,7 @@ export function ArticleFiltersComponent({
filters,
onFiltersChange,
articles,
}: ArticleFiltersProps) {
}: ArticleFiltersProps): JSX.Element {
const data = useFiltersData(articles)
const handleClearFilters = () => {

View File

@ -5,7 +5,7 @@ interface ArticleFormButtonsProps {
onCancel?: () => void
}
export function ArticleFormButtons({ loading, onCancel }: ArticleFormButtonsProps) {
export function ArticleFormButtons({ loading, onCancel }: ArticleFormButtonsProps): JSX.Element {
return (
<div className="flex gap-3 pt-4">
<button

View File

@ -6,7 +6,7 @@ interface ArticlePreviewProps {
onUnlock: () => void
}
export function ArticlePreview({ article, loading, onUnlock }: ArticlePreviewProps) {
export function ArticlePreview({ article, loading, onUnlock }: ArticlePreviewProps): JSX.Element {
if (article.paid) {
return (
<div>

View File

@ -8,14 +8,14 @@ interface ArticleReviewsProps {
authorPubkey: string
}
export function ArticleReviews({ articleId, authorPubkey }: ArticleReviewsProps) {
export function ArticleReviews({ articleId, authorPubkey }: ArticleReviewsProps): JSX.Element {
const [reviews, setReviews] = useState<Review[]>([])
const [tips, setTips] = useState<number>(0)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const load = async () => {
const load = async (): Promise<void> => {
setLoading(true)
setError(null)
try {
@ -45,7 +45,7 @@ export function ArticleReviews({ articleId, authorPubkey }: ArticleReviewsProps)
)
}
function ArticleReviewsHeader({ tips }: { tips: number }) {
function ArticleReviewsHeader({ tips }: { tips: number }): JSX.Element {
return (
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold">Critiques</h3>
@ -54,7 +54,7 @@ function ArticleReviewsHeader({ tips }: { tips: number }) {
)
}
function ArticleReviewsList({ reviews }: { reviews: Review[] }) {
function ArticleReviewsList({ reviews }: { reviews: Review[] }): JSX.Element {
return (
<>
{reviews.map((r) => (

View File

@ -11,7 +11,7 @@ interface ArticlesListProps {
unlockedArticles: Set<string>
}
function LoadingState() {
function LoadingState(): JSX.Element {
// Use generic loading message at startup, then specific message once we know what we're loading
return (
<div className="text-center py-12">
@ -20,7 +20,7 @@ function LoadingState() {
)
}
function ErrorState({ message }: { message: string }) {
function ErrorState({ message }: { message: string }): JSX.Element {
return (
<div className="bg-red-900/20 border border-red-500/50 rounded-lg p-4 mb-4">
<p className="text-red-400">{message}</p>
@ -28,7 +28,7 @@ function ErrorState({ message }: { message: string }) {
)
}
function EmptyState({ hasAny }: { hasAny: boolean }) {
function EmptyState({ hasAny }: { hasAny: boolean }): JSX.Element {
return (
<div className="text-center py-12">
<p className="text-cyber-accent/70">
@ -45,7 +45,7 @@ export function ArticlesList({
error,
onUnlock,
unlockedArticles,
}: ArticlesListProps) {
}: ArticlesListProps): JSX.Element {
if (loading) {
return <LoadingState />
}

View File

@ -7,7 +7,7 @@ interface AuthorCardProps {
presentation: Article
}
export function AuthorCard({ presentation }: AuthorCardProps) {
export function AuthorCard({ presentation }: AuthorCardProps): JSX.Element {
const authorName = presentation.title.replace(/^Présentation de /, '') || t('common.author')
const totalBTC = (presentation.totalSponsoring ?? 0) / 100_000_000

View File

@ -4,7 +4,7 @@ import { useAuthorFilterProps } from './AuthorFilterHooks'
import { AuthorFilterButtonWrapper } from './AuthorFilterButton'
import { AuthorDropdown } from './AuthorFilterDropdown'
function AuthorFilterLabel() {
function AuthorFilterLabel(): JSX.Element {
return (
<label htmlFor="author-filter" className="block text-sm font-medium text-cyber-accent mb-1">
{t('filters.author')}
@ -28,7 +28,7 @@ interface AuthorFilterContentProps {
selectedDisplayName: string
}
function AuthorFilterContent(props: AuthorFilterContentProps) {
function AuthorFilterContent(props: AuthorFilterContentProps): JSX.Element {
return (
<div className="relative" ref={props.dropdownRef}>
<AuthorFilterButtonWrapper
@ -64,7 +64,7 @@ export function AuthorFilter({
authors: string[]
value: string | null
onChange: (value: string | null) => void
}) {
}): JSX.Element {
const props = useAuthorFilterProps(authors, value)
return (

View File

@ -1,7 +1,7 @@
import React from 'react'
import { AuthorAvatar } from './AuthorFilterDropdown'
export function AuthorMnemonicIcons({ value, getMnemonicIcons }: { value: string; getMnemonicIcons: (pubkey: string) => string[] }) {
export function AuthorMnemonicIcons({ value, getMnemonicIcons }: { value: string; getMnemonicIcons: (pubkey: string) => string[] }): JSX.Element {
return (
<div className="flex items-center gap-1 flex-shrink-0">
{getMnemonicIcons(value).map((icon, idx) => (
@ -23,7 +23,7 @@ export function AuthorFilterButtonContent({
selectedAuthor: { name?: string; picture?: string } | null | undefined
selectedDisplayName: string
getMnemonicIcons: (pubkey: string) => string[]
}) {
}): JSX.Element {
return (
<>
{value && (
@ -38,7 +38,7 @@ export function AuthorFilterButtonContent({
)
}
export function DropdownArrowIcon({ isOpen }: { isOpen: boolean }) {
export function DropdownArrowIcon({ isOpen }: { isOpen: boolean }): JSX.Element {
return (
<svg
className={`w-5 h-5 text-neon-cyan transition-transform ${isOpen ? 'rotate-180' : ''}`}
@ -68,7 +68,7 @@ export function AuthorFilterButton({
isOpen: boolean
setIsOpen: (open: boolean) => void
buttonRef: React.RefObject<HTMLButtonElement | null>
}) {
}): JSX.Element {
return (
<button
id="author-filter"
@ -106,7 +106,7 @@ export function AuthorFilterButtonWrapper({
isOpen: boolean
setIsOpen: (open: boolean) => void
buttonRef: React.RefObject<HTMLButtonElement | null>
}) {
}): JSX.Element {
return (
<AuthorFilterButton
value={value}

View File

@ -1,7 +1,7 @@
import Image from 'next/image'
import { t } from '@/lib/i18n'
export function AuthorAvatar({ picture, displayName }: { picture?: string; displayName: string }) {
export function AuthorAvatar({ picture, displayName }: { picture?: string; displayName: string }): JSX.Element {
if (picture !== undefined) {
return (
<Image
@ -32,7 +32,7 @@ export function AuthorOption({
mnemonicIcons: string[]
isSelected: boolean
onSelect: () => void
}) {
}): JSX.Element {
return (
<button
type="button"
@ -136,7 +136,7 @@ export function AuthorList({
getMnemonicIcons: (pubkey: string) => string[]
onChange: (value: string | null) => void
setIsOpen: (open: boolean) => void
}) {
}): JSX.Element {
return (
<>
{authors.map((pubkey) => (
@ -167,7 +167,7 @@ export function AuthorDropdownContent({
getMnemonicIcons: (pubkey: string) => string[]
onChange: (value: string | null) => void
setIsOpen: (open: boolean) => void
}) {
}): JSX.Element {
return loading ? (
<div className="px-3 py-2 text-sm text-cyber-accent/70">{t('filters.loading')}</div>
) : (
@ -201,7 +201,7 @@ export function AuthorDropdown({
getDisplayName: (pubkey: string) => string
getPicture: (pubkey: string) => string | undefined
getMnemonicIcons: (pubkey: string) => string[]
}) {
}): JSX.Element {
return (
<div
className="absolute z-20 w-full mt-1 bg-cyber-dark border border-neon-cyan/30 rounded-lg shadow-glow-cyan max-h-60 overflow-auto"

View File

@ -3,12 +3,12 @@ import { useAuthorsProfiles } from '@/hooks/useAuthorsProfiles'
import { generateMnemonicIcons } from '@/lib/mnemonicIcons'
import { t } from '@/lib/i18n'
export function useAuthorFilterDropdown(isOpen: boolean, setIsOpen: (open: boolean) => void) {
export function useAuthorFilterDropdown(isOpen: boolean, setIsOpen: (open: boolean) => void): { dropdownRef: React.RefObject<HTMLDivElement>; buttonRef: React.RefObject<HTMLButtonElement> } {
const dropdownRef = useRef<HTMLDivElement>(null)
const buttonRef = useRef<HTMLButtonElement>(null)
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
const handleClickOutside = (event: MouseEvent): void => {
if (
dropdownRef.current &&
buttonRef.current &&
@ -19,7 +19,7 @@ export function useAuthorFilterDropdown(isOpen: boolean, setIsOpen: (open: boole
}
}
const handleEscape = (event: KeyboardEvent) => {
const handleEscape = (event: KeyboardEvent): void => {
if (event.key === 'Escape') {
setIsOpen(false)
buttonRef.current?.focus()
@ -40,7 +40,7 @@ export function useAuthorFilterDropdown(isOpen: boolean, setIsOpen: (open: boole
return { dropdownRef, buttonRef }
}
export function useAuthorFilterHelpers(profiles: Map<string, { name?: string; picture?: string }>) {
export function useAuthorFilterHelpers(profiles: Map<string, { name?: string; picture?: string }>): { getDisplayName: (pubkey: string) => string; getPicture: (pubkey: string) => string | undefined; getMnemonicIcons: (pubkey: string) => string[] } {
const getDisplayName = (pubkey: string): string => {
const profile = profiles.get(pubkey)
return profile?.name ?? `${pubkey.substring(0, 8)}...${pubkey.substring(pubkey.length - 8)}`
@ -57,7 +57,19 @@ export function useAuthorFilterHelpers(profiles: Map<string, { name?: string; pi
return { getDisplayName, getPicture, getMnemonicIcons }
}
export function useAuthorFilterState(authors: string[], value: string | null) {
export function useAuthorFilterState(authors: string[], value: string | null): {
profiles: Map<string, { name?: string; picture?: string }>
loading: boolean
isOpen: boolean
setIsOpen: (open: boolean) => void
dropdownRef: React.RefObject<HTMLDivElement>
buttonRef: React.RefObject<HTMLButtonElement>
getDisplayName: (pubkey: string) => string
getPicture: (pubkey: string) => string | undefined
getMnemonicIcons: (pubkey: string) => string[]
selectedAuthor: { name?: string; picture?: string } | null
selectedDisplayName: string
} {
const { profiles, loading } = useAuthorsProfiles(authors)
const [isOpen, setIsOpen] = useState(false)
const { dropdownRef, buttonRef } = useAuthorFilterDropdown(isOpen, setIsOpen)
@ -80,7 +92,7 @@ export function useAuthorFilterState(authors: string[], value: string | null) {
}
}
export function useAuthorFilterProps(authors: string[], value: string | null) {
export function useAuthorFilterProps(authors: string[], value: string | null): ReturnType<typeof useAuthorFilterState> & { authors: string[]; value: string | null } {
const state = useAuthorFilterState(authors, value)
return { ...state, authors, value }
}

View File

@ -11,6 +11,7 @@ import { PresentationFormHeader } from './PresentationFormHeader'
import { extractPresentationData } from '@/lib/presentationParsing'
import type { Article } from '@/types/nostr'
import { t } from '@/lib/i18n'
import { userConfirm } from '@/lib/userConfirm'
interface AuthorPresentationDraft {
authorName: string
@ -220,10 +221,10 @@ function PresentationForm({
<div className="flex-1">
<button
type="submit"
disabled={loading || deleting}
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
{loading ?? deleting
? t('publish.publishing')
: hasExistingPresentation
? t('presentation.update.button')
@ -244,12 +245,12 @@ function useAuthorPresentationState(pubkey: string | null, existingAuthorName?:
const [draft, setDraft] = useState<AuthorPresentationDraft>(() => {
if (existingPresentation) {
const { presentation, contentDescription } = extractPresentationData(existingPresentation)
const authorName = existingPresentation.title.replace(/^Présentation de /, '') || existingAuthorName || ''
const authorName = existingPresentation.title.replace(/^Présentation de /, '') ?? existingAuthorName ?? ''
return {
authorName,
presentation,
contentDescription,
mainnetAddress: existingPresentation.mainnetAddress || '',
mainnetAddress: existingPresentation.mainnetAddress ?? '',
...(existingPresentation.bannerUrl ? { pictureUrl: existingPresentation.bannerUrl } : {}),
}
}
@ -293,7 +294,7 @@ function useAuthorPresentationState(pubkey: string | null, existingAuthorName?:
return
}
if (!confirm(t('presentation.delete.confirm'))) {
if (!userConfirm(t('presentation.delete.confirm'))) {
return
}
@ -472,7 +473,7 @@ function AuthorPresentationFormView({
handleSubmit={state.handleSubmit}
deleting={state.deleting}
handleDelete={state.handleDelete}
hasExistingPresentation={!!existingPresentation}
hasExistingPresentation={existingPresentation !== null && existingPresentation !== undefined}
/>
)
}

View File

@ -9,7 +9,7 @@ interface AuthorsListProps {
error: string | null
}
function LoadingState() {
function LoadingState(): JSX.Element {
return (
<div className="text-center py-12">
<p className="text-cyber-accent/70">{t('common.loading.authors')}</p>
@ -17,7 +17,7 @@ function LoadingState() {
)
}
function ErrorState({ message }: { message: string }) {
function ErrorState({ message }: { message: string }): JSX.Element {
return (
<div className="bg-red-900/20 border border-red-500/50 rounded-lg p-4 mb-4">
<p className="text-red-400">{message}</p>
@ -25,7 +25,7 @@ function ErrorState({ message }: { message: string }) {
)
}
function EmptyState({ hasAny }: { hasAny: boolean }) {
function EmptyState({ hasAny }: { hasAny: boolean }): JSX.Element {
return (
<div className="text-center py-12">
<p className="text-cyber-accent/70">
@ -35,7 +35,7 @@ function EmptyState({ hasAny }: { hasAny: boolean }) {
)
}
export function AuthorsList({ authors, allAuthors, loading, error }: AuthorsListProps) {
export function AuthorsList({ authors, allAuthors, loading, error }: AuthorsListProps): JSX.Element {
if (loading) {
return <LoadingState />
}

View File

@ -17,7 +17,7 @@ export function CategorySelect({
onChange,
required = false,
helpText,
}: CategorySelectProps) {
}: CategorySelectProps): JSX.Element {
return (
<div>
<label htmlFor={id} className="block text-sm font-medium text-gray-700 mb-1">

View File

@ -7,7 +7,7 @@ interface CategoryTabsProps {
onCategoryChange: (category: CategoryFilter) => void
}
export function CategoryTabs({ selectedCategory, onCategoryChange }: CategoryTabsProps) {
export function CategoryTabs({ selectedCategory, onCategoryChange }: CategoryTabsProps): JSX.Element {
return (
<div className="mb-6">
<div className="border-b border-neon-cyan/30">

View File

@ -4,7 +4,7 @@ interface ClearButtonProps {
onClick: () => void
}
export function ClearButton({ onClick }: ClearButtonProps) {
export function ClearButton({ onClick }: ClearButtonProps): JSX.Element {
return (
<button
onClick={onClick}

View File

@ -21,11 +21,11 @@ function AuthorProfileLink({ presentation, profile }: { presentation: Article; p
// Title format: "Présentation de <name>" or just use profile name
let authorName = presentation.title.replace(/^Présentation de /, '').trim()
if (!authorName || authorName === 'Présentation') {
authorName = profile?.name || t('common.author')
authorName = profile?.name ?? t('common.author')
}
// Extract picture from presentation (bannerUrl or from JSON metadata) or profile
const picture = presentation.bannerUrl || profile?.picture
const picture = presentation.bannerUrl ?? profile?.picture
return (
<Link

View File

@ -161,8 +161,8 @@ export function ConnectButton() {
return (
<>
<DisconnectedState
loading={loading || creatingAccount}
error={error || createError}
loading={loading ?? creatingAccount}
error={error ?? createError}
showUnlockModal={showUnlockModal}
setShowUnlockModal={setShowUnlockModal}
onCreateAccount={handleCreateAccount}

View File

@ -15,7 +15,7 @@ export function ConnectedUserMenu({
profile,
onDisconnect,
loading,
}: ConnectedUserMenuProps) {
}: ConnectedUserMenuProps): JSX.Element {
const displayName = profile?.name ?? `${pubkey.slice(0, 8)}...`
return (

View File

@ -11,7 +11,7 @@ interface CreateAccountModalProps {
type Step = 'choose' | 'import' | 'recovery'
async function createAccountWithKey(key?: string) {
return await nostrAuthService.createAccount(key)
return nostrAuthService.createAccount(key)
}
async function handleAccountCreation(

View File

@ -19,7 +19,7 @@ export function RecoveryPhraseDisplay({
recoveryPhrase: string[]
copied: boolean
onCopy: () => void
}) {
}): JSX.Element {
return (
<div className="bg-cyber-darker border border-neon-cyan/30 rounded-lg p-6 mb-6">
<div className="grid grid-cols-2 gap-4 mb-4">
@ -45,7 +45,7 @@ export function RecoveryPhraseDisplay({
)
}
export function PublicKeyDisplay({ npub }: { npub: string }) {
export function PublicKeyDisplay({ npub }: { npub: string }): JSX.Element {
return (
<div className="bg-neon-blue/10 border border-neon-blue/30 rounded-lg p-4 mb-6">
<p className="text-neon-blue font-semibold mb-2">{t('account.create.publicKey')}</p>
@ -62,7 +62,7 @@ export function ImportKeyForm({
importKey: string
setImportKey: (key: string) => void
error: string | null
}) {
}): JSX.Element {
return (
<>
<div className="mb-4">
@ -84,7 +84,7 @@ export function ImportKeyForm({
)
}
export function ImportStepButtons({ loading, onImport, onBack }: { loading: boolean; onImport: () => void; onBack: () => void }) {
export function ImportStepButtons({ loading, onImport, onBack }: { loading: boolean; onImport: () => void; onBack: () => void }): JSX.Element {
return (
<div className="flex gap-4">
<button
@ -116,7 +116,7 @@ export function ChooseStepButtons({
onGenerate: () => void
onImport: () => void
onClose: () => void
}) {
}): JSX.Element {
return (
<div className="flex flex-col gap-4">
<button

View File

@ -10,10 +10,10 @@ export function RecoveryStep({
recoveryPhrase: string[]
npub: string
onContinue: () => void
}) {
}): JSX.Element {
const [copied, setCopied] = useState(false)
const handleCopy = async () => {
const handleCopy = async (): Promise<void> => {
if (recoveryPhrase.length > 0) {
await navigator.clipboard.writeText(recoveryPhrase.join(' '))
setCopied(true)
@ -55,7 +55,7 @@ export function ImportStep({
error: string | null
onImport: () => void
onBack: () => void
}) {
}): JSX.Element {
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-cyber-dark border border-neon-cyan/30 rounded-lg p-6 max-w-md w-full mx-4 shadow-glow-cyan">
@ -79,7 +79,7 @@ export function ChooseStep({
onGenerate: () => void
onImport: () => void
onClose: () => void
}) {
}): JSX.Element {
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-cyber-dark border border-neon-cyan/30 rounded-lg p-6 max-w-md w-full mx-4 shadow-glow-cyan">

View File

@ -6,7 +6,7 @@ interface DocsContentProps {
loading: boolean
}
export function DocsContent({ content, loading }: DocsContentProps) {
export function DocsContent({ content, loading }: DocsContentProps): JSX.Element {
if (loading) {
return (
<div className="text-center py-12">

View File

@ -7,7 +7,7 @@ interface DocsSidebarProps {
onSelectDoc: (docId: DocSection) => void
}
export function DocsSidebar({ docs, selectedDoc, onSelectDoc }: DocsSidebarProps) {
export function DocsSidebar({ docs, selectedDoc, onSelectDoc }: DocsSidebarProps): JSX.Element {
return (
<aside className="lg:w-64 flex-shrink-0">
<div className="bg-cyber-dark border border-neon-cyan/20 rounded-lg p-4 sticky top-4 backdrop-blur-sm">

View File

@ -1,7 +1,7 @@
import Link from 'next/link'
import { t } from '@/lib/i18n'
export function Footer() {
export function Footer(): JSX.Element {
return (
<footer className="bg-cyber-dark border-t border-neon-cyan/30 mt-12">
<div className="max-w-4xl mx-auto px-4 py-6">

View File

@ -6,7 +6,7 @@ interface FundingProgressBarProps {
progressPercent: number
}
function FundingProgressBar({ progressPercent }: FundingProgressBarProps) {
function FundingProgressBar({ progressPercent }: FundingProgressBarProps): JSX.Element {
return (
<div className="relative w-full h-4 bg-cyber-dark rounded-full overflow-hidden border border-neon-cyan/30">
<div
@ -22,7 +22,7 @@ function FundingProgressBar({ progressPercent }: FundingProgressBarProps) {
)
}
function FundingStats({ stats }: { stats: ReturnType<typeof estimatePlatformFunds> }) {
function FundingStats({ stats }: { stats: ReturnType<typeof estimatePlatformFunds> }): JSX.Element {
const progressPercent = Math.min(100, stats.progressPercent)
return (
<div className="space-y-4">
@ -41,7 +41,7 @@ function FundingStats({ stats }: { stats: ReturnType<typeof estimatePlatformFund
)
}
export function FundingGauge() {
export function FundingGauge(): JSX.Element {
const [stats, setStats] = useState(estimatePlatformFunds())
const [certificationStats, setCertificationStats] = useState(estimatePlatformFunds())
const [loading, setLoading] = useState(true)
@ -49,7 +49,7 @@ export function FundingGauge() {
useEffect(() => {
// In a real implementation, this would fetch actual data
// For now, we use the estimate
const loadStats = async () => {
const loadStats = async (): Promise<void> => {
try {
const fundingStats = estimatePlatformFunds()
setStats(fundingStats)

View File

@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
import { configStorage } from '@/lib/configStorage'
import type { Nip95Config } from '@/lib/configStorageTypes'
import { t } from '@/lib/i18n'
import { userConfirm } from '@/lib/userConfirm'
interface Nip95ConfigManagerProps {
onConfigChange?: () => void
@ -97,7 +98,7 @@ export function Nip95ConfigManager({ onConfigChange }: Nip95ConfigManagerProps)
}
async function handleRemoveApi(id: string) {
if (!confirm(t('settings.nip95.remove.confirm'))) {
if (!userConfirm(t('settings.nip95.remove.confirm'))) {
return
}

View File

@ -10,7 +10,7 @@ export function NotificationPanelHeader({
unreadCount,
onMarkAllAsRead,
onClose,
}: NotificationPanelHeaderProps) {
}: NotificationPanelHeaderProps): JSX.Element {
return (
<div className="flex items-center justify-between p-4 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-900">{t('notification.title')}</h3>

View File

@ -4,7 +4,7 @@ import { LanguageSelector } from './LanguageSelector'
import { t } from '@/lib/i18n'
import { KeyIndicator } from './KeyIndicator'
function GitIcon() {
function GitIcon(): JSX.Element {
return (
<svg
className="w-5 h-5"
@ -17,7 +17,7 @@ function GitIcon() {
)
}
export function PageHeader() {
export function PageHeader(): JSX.Element {
return (
<header className="bg-cyber-dark border-b border-neon-cyan/30 shadow-glow-cyan">
<div className="max-w-4xl mx-auto px-4 py-4 flex justify-between items-center">

View File

@ -11,14 +11,14 @@ interface PaymentModalProps {
onPaymentComplete: () => void
}
function useInvoiceTimer(expiresAt?: number) {
function useInvoiceTimer(expiresAt?: number): number | null {
const [timeRemaining, setTimeRemaining] = useState<number | null>(null)
useEffect(() => {
if (!expiresAt) {
return
}
const updateTimeRemaining = () => {
const updateTimeRemaining = (): void => {
const now = Math.floor(Date.now() / 1000)
const remaining = expiresAt - now
setTimeRemaining(remaining > 0 ? remaining : 0)
@ -39,8 +39,8 @@ function PaymentHeader({
amount: number
timeRemaining: number | null
onClose: () => void
}) {
const timeLabel = useMemo(() => {
}): JSX.Element {
const timeLabel = useMemo((): string | null => {
if (timeRemaining === null) {
return null
}
@ -69,7 +69,7 @@ function PaymentHeader({
)
}
function InvoiceDisplay({ invoiceText, paymentUrl }: { invoiceText: string; paymentUrl: string }) {
function InvoiceDisplay({ invoiceText, paymentUrl }: { invoiceText: string; paymentUrl: string }): JSX.Element {
return (
<div className="mb-4">
<p className="text-sm text-cyber-accent mb-2">{t('payment.modal.lightningInvoice')}</p>
@ -97,7 +97,7 @@ function PaymentActions({
copied: boolean
onCopy: () => Promise<void>
onOpenWallet: () => void
}) {
}): JSX.Element {
return (
<div className="flex gap-2">
<button
@ -118,7 +118,7 @@ function PaymentActions({
)
}
function ExpiredNotice({ show }: { show: boolean }) {
function ExpiredNotice({ show }: { show: boolean }): JSX.Element | null {
if (!show) {
return null
}
@ -130,13 +130,20 @@ function ExpiredNotice({ show }: { show: boolean }) {
)
}
function usePaymentModalState(invoice: AlbyInvoice, onPaymentComplete: () => void) {
function usePaymentModalState(invoice: AlbyInvoice, onPaymentComplete: () => void): {
copied: boolean
errorMessage: string | null
paymentUrl: string
timeRemaining: number | null
handleCopy: () => Promise<void>
handleOpenWallet: () => Promise<void>
} {
const [copied, setCopied] = useState(false)
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const paymentUrl = `lightning:${invoice.invoice}`
const timeRemaining = useInvoiceTimer(invoice.expiresAt)
const handleCopy = useCallback(async () => {
const handleCopy = useCallback(async (): Promise<void> => {
try {
await navigator.clipboard.writeText(invoice.invoice)
setCopied(true)
@ -147,7 +154,7 @@ function usePaymentModalState(invoice: AlbyInvoice, onPaymentComplete: () => voi
}
}, [invoice.invoice])
const handleOpenWallet = useCallback(async () => {
const handleOpenWallet = useCallback(async (): Promise<void> => {
try {
const alby = getAlbyService()
if (!isWebLNAvailable()) {
@ -169,10 +176,10 @@ function usePaymentModalState(invoice: AlbyInvoice, onPaymentComplete: () => voi
return { copied, errorMessage, paymentUrl, timeRemaining, handleCopy, handleOpenWallet }
}
export function PaymentModal({ invoice, onClose, onPaymentComplete }: PaymentModalProps) {
export function PaymentModal({ invoice, onClose, onPaymentComplete }: PaymentModalProps): JSX.Element {
const { copied, errorMessage, paymentUrl, timeRemaining, handleCopy, handleOpenWallet } =
usePaymentModalState(invoice, onPaymentComplete)
const handleOpenWalletSync = () => {
const handleOpenWalletSync = (): void => {
void handleOpenWallet()
}

View File

@ -9,7 +9,7 @@ interface SearchBarProps {
placeholder?: string
}
export function SearchBar({ value, onChange, placeholder }: SearchBarProps) {
export function SearchBar({ value, onChange, placeholder }: SearchBarProps): JSX.Element {
const defaultPlaceholder = placeholder ?? t('search.placeholder')
const [localValue, setLocalValue] = useState(value)
@ -17,13 +17,13 @@ export function SearchBar({ value, onChange, placeholder }: SearchBarProps) {
setLocalValue(value)
}, [value])
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
const newValue = e.target.value
setLocalValue(newValue)
onChange(newValue)
}
const handleClear = () => {
const handleClear = (): void => {
setLocalValue('')
onChange('')
}

View File

@ -33,11 +33,11 @@ const ArticlesError = ({ message }: { message: string }) => (
)
const EmptyState = ({ show }: { show: boolean }) =>
show ? (
(show ? (
<div className="text-center py-12">
<p className="text-gray-500">{t('common.empty.articles')}</p>
</div>
) : null
) : null)
function ArticleActions({
article,

View File

@ -11,7 +11,22 @@ export default [
},
js.configs.recommended,
{
files: ['**/*.{js,jsx,ts,tsx}'],
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
console: 'readonly',
process: 'readonly',
module: 'readonly',
require: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
},
},
},
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: typescriptParser,
parserOptions: {

View File

@ -10,16 +10,26 @@ interface EditState {
articleId: string | null
}
export function useArticleEditing(authorPubkey: string | null) {
export function useArticleEditing(authorPubkey: string | null): {
editingDraft: ArticleDraft | null
editingArticleId: string | null
loading: boolean
error: string | null
startEditing: (article: Article) => Promise<void>
cancelEditing: () => void
submitEdit: () => Promise<ArticleUpdateResult | null>
deleteArticle: (articleId: string) => Promise<boolean>
updateDraft: (draft: ArticleDraft | null) => void
} {
const [state, setState] = useState<EditState>({ draft: null, articleId: null })
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const updateDraft = (draft: ArticleDraft | null) => {
const updateDraft = (draft: ArticleDraft | null): void => {
setState((prev) => ({ ...prev, draft }))
}
const startEditing = async (article: Article) => {
const startEditing = async (article: Article): Promise<void> => {
if (!authorPubkey) {
setError('Connect your Nostr wallet to edit')
return
@ -52,7 +62,7 @@ export function useArticleEditing(authorPubkey: string | null) {
}
}
const cancelEditing = () => {
const cancelEditing = (): void => {
setState({ draft: null, articleId: null })
setError(null)
}

View File

@ -9,13 +9,20 @@ export function useArticlePayment(
pubkey: string | null,
onUnlockSuccess?: () => void,
connect?: () => Promise<void>
) {
): {
loading: boolean
error: string | null
paymentInvoice: AlbyInvoice | null
handleUnlock: () => Promise<void>
handlePaymentComplete: () => Promise<void>
handleCloseModal: () => void
} {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [paymentInvoice, setPaymentInvoice] = useState<AlbyInvoice | null>(null)
const [paymentHash, setPaymentHash] = useState<string | null>(null)
const checkPaymentStatus = async (hash: string, userPubkey: string) => {
const checkPaymentStatus = async (hash: string, userPubkey: string): Promise<void> => {
try {
const hasPaid = await paymentService.waitForArticlePayment(
hash,
@ -41,7 +48,7 @@ export function useArticlePayment(
}
}
const handleUnlock = async () => {
const handleUnlock = async (): Promise<void> => {
if (!pubkey) {
if (connect) {
setLoading(true)
@ -80,13 +87,13 @@ export function useArticlePayment(
}
}
const handlePaymentComplete = async () => {
const handlePaymentComplete = async (): Promise<void> => {
if (paymentHash && pubkey) {
await checkPaymentStatus(paymentHash, pubkey)
}
}
const handleCloseModal = () => {
const handleCloseModal = (): void => {
setPaymentInvoice(null)
setPaymentHash(null)
}

View File

@ -3,7 +3,12 @@ import { articlePublisher } from '@/lib/articlePublisher'
import { nostrService } from '@/lib/nostr'
import type { ArticleDraft } from '@/lib/articlePublisher'
export function useArticlePublishing(pubkey: string | null) {
export function useArticlePublishing(pubkey: string | null): {
loading: boolean
error: string | null
success: boolean
publishArticle: (draft: ArticleDraft) => Promise<string | null>
} {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState(false)
@ -48,4 +53,3 @@ export function useArticlePublishing(pubkey: string | null) {
publishArticle,
}
}

View File

@ -5,7 +5,13 @@ import { applyFiltersAndSort } from '@/lib/articleFiltering'
import type { ArticleFilters } from '@/components/ArticleFilters'
import { t } from '@/lib/i18n'
export function useArticles(searchQuery: string = '', filters: ArticleFilters | null = null) {
export function useArticles(searchQuery: string = '', filters: ArticleFilters | null = null): {
articles: Article[]
allArticles: Article[]
loading: boolean
error: string | null
loadArticleContent: (articleId: string, authorPubkey: string) => Promise<Article | null>
} {
const [articles, setArticles] = useState<Article[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
@ -43,7 +49,7 @@ export function useArticles(searchQuery: string = '', filters: ArticleFilters |
}
}, [])
const loadArticleContent = async (articleId: string, authorPubkey: string) => {
const loadArticleContent = async (articleId: string, authorPubkey: string): Promise<Article | null> => {
try {
const article = await nostrService.getArticleById(articleId)
if (article) {
@ -52,9 +58,9 @@ export function useArticles(searchQuery: string = '', filters: ArticleFilters |
if (decryptedContent) {
setArticles((prev) =>
prev.map((a) =>
a.id === articleId
(a.id === articleId
? { ...a, content: decryptedContent, paid: true }
: a
: a)
)
)
}

View File

@ -12,7 +12,14 @@ interface AuthorPresentationDraft {
pictureUrl?: string
}
export function useAuthorPresentation(pubkey: string | null) {
export function useAuthorPresentation(pubkey: string | null): {
loading: boolean
error: string | null
success: boolean
publishPresentation: (draft: AuthorPresentationDraft) => Promise<void>
checkPresentationExists: () => Promise<Article | null>
deletePresentation: (articleId: string) => Promise<void>
} {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState(false)
@ -85,7 +92,7 @@ export function useAuthorPresentation(pubkey: string | null) {
}
try {
return await articlePublisher.getAuthorPresentation(pubkey)
return articlePublisher.getAuthorPresentation(pubkey)
} catch (e) {
console.error('Error checking presentation:', e)
return null

View File

@ -22,7 +22,7 @@ export function useAuthorsProfiles(authorPubkeys: string[]): {
return
}
const loadProfiles = async () => {
const loadProfiles = async (): Promise<void> => {
setLoading(true)
const profilesMap = new Map<string, AuthorProfile>()

View File

@ -8,14 +8,19 @@ export interface DocLink {
file: string
}
export function useDocs(docs: DocLink[]) {
export function useDocs(docs: DocLink[]): {
selectedDoc: DocSection
docContent: string
loading: boolean
loadDoc: (docId: DocSection) => Promise<void>
} {
const [selectedDoc, setSelectedDoc] = useState<DocSection>('user-guide')
const [docContent, setDocContent] = useState<string>('')
const [loading, setLoading] = useState(false)
const loadDoc = async (docId: DocSection) => {
const loadDoc = async (docId: DocSection): Promise<void> => {
const doc = docs.find((d) => d.id === docId)
if (!doc) return
if (!doc) {return}
setLoading(true)
setSelectedDoc(docId)

View File

@ -1,12 +1,16 @@
import { useEffect, useState } from 'react'
import { setLocale, getLocale, loadTranslations, t, type Locale } from '@/lib/i18n'
export function useI18n(locale: Locale = 'fr') {
export function useI18n(locale: Locale = 'fr'): {
loaded: boolean
locale: Locale
t: (key: string, params?: Record<string, string | number>) => string
} {
const [loaded, setLoaded] = useState(false)
const [currentLocale, setCurrentLocale] = useState<Locale>(getLocale())
useEffect(() => {
const load = async () => {
const load = async (): Promise<void> => {
try {
// Get saved locale from IndexedDB or use provided locale
let savedLocale: Locale | null = null

View File

@ -2,7 +2,14 @@ import { useState, useEffect } from 'react'
import { nostrAuthService } from '@/lib/nostrAuth'
import type { NostrConnectState } from '@/types/nostr'
export function useNostrAuth() {
export function useNostrAuth(): NostrConnectState & {
loading: boolean
error: string | null
connect: () => Promise<void>
disconnect: () => Promise<void>
accountExists: boolean | null
isUnlocked: boolean
} {
const [state, setState] = useState<NostrConnectState>(nostrAuthService.getState())
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
@ -19,7 +26,7 @@ export function useNostrAuth() {
return unsubscribe
}, [])
const connect = async () => {
const connect = async (): Promise<void> => {
setLoading(true)
setError(null)
try {
@ -31,7 +38,7 @@ export function useNostrAuth() {
}
}
const disconnect = async () => {
const disconnect = async (): Promise<void> => {
setLoading(true)
try {
nostrAuthService.disconnect()

View File

@ -2,7 +2,14 @@ import { useState, useEffect, useCallback } from 'react'
import { notificationService, loadStoredNotifications, saveNotifications, markNotificationAsRead, markAllAsRead, deleteNotification } from '@/lib/notifications'
import type { Notification } from '@/types/notifications'
export function useNotifications(userPubkey: string | null) {
export function useNotifications(userPubkey: string | null): {
notifications: Notification[]
unreadCount: number
loading: boolean
markAsRead: (notificationId: string) => void
markAllAsRead: () => void
deleteNotification: (notificationId: string) => void
} {
const [notifications, setNotifications] = useState<Notification[]>([])
const [loading, setLoading] = useState(true)
@ -14,7 +21,7 @@ export function useNotifications(userPubkey: string | null) {
return
}
const loadStored = async () => {
const loadStored = async (): Promise<void> => {
const storedNotifications = await loadStoredNotifications(userPubkey)
setNotifications(storedNotifications)
}
@ -55,23 +62,23 @@ export function useNotifications(userPubkey: string | null) {
const unreadCount = notifications.filter((n) => !n.read).length
const markAsRead = useCallback(
(notificationId: string) => {
if (!userPubkey) return
(notificationId: string): void => {
if (!userPubkey) {return}
setNotifications((prev) => markNotificationAsRead(userPubkey, notificationId, prev))
},
[userPubkey]
)
const markAllAsReadHandler = useCallback(() => {
if (!userPubkey) return
const markAllAsReadHandler = useCallback((): void => {
if (!userPubkey) {return}
setNotifications((prev) => markAllAsRead(userPubkey, prev))
}, [userPubkey])
const deleteNotificationHandler = useCallback(
(notificationId: string) => {
if (!userPubkey) return
(notificationId: string): void => {
if (!userPubkey) {return}
setNotifications((prev) => deleteNotification(userPubkey, notificationId, prev))
},

View File

@ -11,7 +11,13 @@ export function useUserArticles(
userPubkey: string,
searchQuery: string = '',
filters: ArticleFilters | null = null
) {
): {
articles: Article[]
allArticles: Article[]
loading: boolean
error: string | null
loadArticleContent: (articleId: string, authorPubkey: string) => Promise<Article | null>
} {
const [articles, setArticles] = useState<Article[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
@ -71,7 +77,7 @@ export function useUserArticles(
return applyFiltersAndSort(articles, searchQuery, effectiveFilters)
}, [articles, searchQuery, filters])
const loadArticleContent = async (articleId: string, authorPubkey: string) => {
const loadArticleContent = async (articleId: string, authorPubkey: string): Promise<Article | null> => {
try {
const article = await nostrService.getArticleById(articleId)
if (article) {
@ -80,9 +86,9 @@ export function useUserArticles(
if (decryptedContent) {
setArticles((prev) =>
prev.map((a) =>
a.id === articleId
(a.id === articleId
? { ...a, content: decryptedContent, paid: true }
: a
: a)
)
)
}

View File

@ -57,7 +57,7 @@ export async function publishSeries(params: {
authorPrivateKey?: string
}): Promise<Series> {
ensureKeys(params.authorPubkey, params.authorPrivateKey)
const category = params.category
const {category} = params
requireCategory(category)
const event = await buildSeriesEvent(params, category)
const published = await nostrService.publishEvent(event)
@ -148,7 +148,7 @@ export async function publishReview(params: {
authorPrivateKey?: string
}): Promise<Review> {
ensureKeys(params.reviewerPubkey, params.authorPrivateKey)
const category = params.category
const {category} = params
requireCategory(category)
const event = await buildReviewEvent(params, category)
const published = await nostrService.publishEvent(event)
@ -273,7 +273,7 @@ async function publishUpdate(
authorPubkey: string,
originalArticleId: string
): Promise<ArticleUpdateResult> {
const category = draft.category
const {category} = draft
requireCategory(category)
const presentationId = await ensurePresentation(authorPubkey)
const invoice = await createArticleInvoice(draft)
@ -302,7 +302,7 @@ export async function publishArticleUpdate(
): Promise<ArticleUpdateResult> {
try {
ensureKeys(authorPubkey, authorPrivateKey)
return await publishUpdate(draft, authorPubkey, originalArticleId)
return publishUpdate(draft, authorPubkey, originalArticleId)
} catch (error) {
return updateFailure(originalArticleId, error instanceof Error ? error.message : 'Unknown error')
}

View File

@ -1,5 +1,4 @@
import { nostrService } from './nostr'
import type { SimplePoolWithSub } from '@/types/nostr-tools-extended'
import type { AlbyInvoice } from '@/types/alby'
import { getStoredPrivateContent, getStoredInvoice, removeStoredPrivateContent } from './articleStorage'
import { buildPresentationEvent, fetchAuthorPresentationFromPool, sendEncryptedContent } from './articlePublisherHelpers'
@ -72,7 +71,7 @@ export class ArticlePublisher {
return buildFailure('Presentation not found')
}
return await encryptAndPublish(draft, authorPubkey, validation.authorPrivateKeyForEncryption, validation.category, presentation.id)
return encryptAndPublish(draft, authorPubkey, validation.authorPrivateKeyForEncryption, validation.category, presentation.id)
} catch (error) {
console.error('Error publishing article:', error)
return buildFailure(error instanceof Error ? error.message : 'Unknown error')
@ -172,7 +171,7 @@ export class ArticlePublisher {
nostrService.setPrivateKey(authorPrivateKey)
// Extract author name from title (format: "Présentation de <name>")
const authorName = draft.title.replace(/^Présentation de /, '').trim() || 'Auteur'
const authorName = draft.title.replace(/^Présentation de /, '').trim() ?? 'Auteur'
// Build event with hash-based ID
const eventTemplate = await buildPresentationEvent(draft, authorPubkey, authorName, 'sciencefiction')
@ -211,7 +210,7 @@ export class ArticlePublisher {
if (!pool) {
return null
}
return await fetchAuthorPresentationFromPool(pool as SimplePoolWithSub, pubkey)
return fetchAuthorPresentationFromPool(pool, pubkey)
} catch (error) {
console.error('Error getting author presentation:', error)
return null

View File

@ -123,7 +123,7 @@ export function parsePresentationEvent(event: Event): import('@/types/nostr').Au
if (!profileData) {
// Try invisible format (with zero-width characters)
const invisibleJsonMatch = event.content.match(/[\u200B\u200C]\[Metadata JSON\][\u200B\u200C]\n[\u200B\u200C](.+)[\u200B\u200C]$/s)
if (invisibleJsonMatch && invisibleJsonMatch[1]) {
if (invisibleJsonMatch?.[1]) {
try {
// Remove zero-width characters from JSON
const cleanedJson = invisibleJsonMatch[1].replace(/[\u200B\u200C\u200D\u200E\u200F]/g, '').trim()
@ -136,7 +136,7 @@ export function parsePresentationEvent(event: Event): import('@/types/nostr').Au
// Fallback to visible format in content
if (!profileData) {
const jsonMatch = event.content.match(/\[Metadata JSON\]\n(.+)$/s)
if (jsonMatch && jsonMatch[1]) {
if (jsonMatch?.[1]) {
try {
profileData = JSON.parse(jsonMatch[1].trim())
} catch (e) {

View File

@ -92,7 +92,7 @@ export class ConfigStorage {
return this.getDefaultConfig()
}
const db = this.db
const {db} = this
return new Promise((resolve) => {
const transaction = db.transaction([STORE_NAME], 'readonly')
const store = transaction.objectStore(STORE_NAME)
@ -131,7 +131,7 @@ export class ConfigStorage {
throw new Error('Database not initialized')
}
const db = this.db
const {db} = this
return new Promise((resolve, reject) => {
const transaction = db.transaction([STORE_NAME], 'readwrite')
const store = transaction.objectStore(STORE_NAME)

View File

@ -94,14 +94,14 @@ export class KeyManagementService {
* Check if an account exists (encrypted key is stored)
*/
async accountExists(): Promise<boolean> {
return await accountExistsTwoLevel()
return accountExistsTwoLevel()
}
/**
* Get the public key and npub if account exists
*/
async getPublicKeys(): Promise<{ publicKey: string; npub: string } | null> {
return await getPublicKeysTwoLevel()
return getPublicKeysTwoLevel()
}
/**

View File

@ -31,7 +31,7 @@ export async function removeAccountFlag(): Promise<void> {
}
export async function getEncryptedKey(): Promise<EncryptedPayload | null> {
return await storageService.get<EncryptedPayload>(KEY_STORAGE_KEY, 'nostr_key_storage')
return storageService.get<EncryptedPayload>(KEY_STORAGE_KEY, 'nostr_key_storage')
}
export async function setEncryptedKey(encryptedNsec: EncryptedPayload): Promise<void> {

View File

@ -19,7 +19,7 @@ const PBKDF2_HASH = 'SHA-256'
* Generate a random KEK (Key Encryption Key)
*/
async function generateKEK(): Promise<CryptoKey> {
return await crypto.subtle.generateKey(
return crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true, // extractable
['encrypt', 'decrypt']
@ -81,7 +81,7 @@ async function importKEK(keyBytes: Uint8Array): Promise<CryptoKey> {
const buffer = new ArrayBuffer(keyBytes.length)
const view = new Uint8Array(buffer)
view.set(keyBytes)
return await crypto.subtle.importKey(
return crypto.subtle.importKey(
'raw',
buffer,
{ name: 'AES-GCM' },
@ -160,9 +160,9 @@ async function decryptKEK(encryptedKEK: EncryptedPayload, recoveryPhrase: string
const hexString = decoder.decode(decrypted)
// Convert hex string back to bytes
const kekBytes = new Uint8Array(hexString.match(/.{1,2}/g)?.map(byte => parseInt(byte, 16)) || [])
const kekBytes = new Uint8Array(hexString.match(/.{1,2}/g)?.map(byte => parseInt(byte, 16)) ?? [])
return await importKEK(kekBytes)
return importKEK(kekBytes)
}
/**
@ -247,7 +247,7 @@ async function storeEncryptedKEK(encryptedKEK: EncryptedPayload): Promise<void>
}
type PasswordCredentialConstructorType = new (data: PasswordCredentialData) => Credential & { id: string; password: string }
const PasswordCredentialConstructor = (window as unknown as { PasswordCredential?: PasswordCredentialConstructorType }).PasswordCredential
if (!PasswordCredentialConstructor || !navigator.credentials || !navigator.credentials.store) {
if (!PasswordCredentialConstructor || !navigator.credentials?.store) {
throw new Error('PasswordCredential API not available')
}
@ -265,7 +265,7 @@ async function storeEncryptedKEK(encryptedKEK: EncryptedPayload): Promise<void>
id: 'nostr_kek',
name: 'Nostr KEK',
password: JSON.stringify(encryptedKEK),
iconURL: window.location.origin + '/favicon.ico',
iconURL: `${window.location.origin }/favicon.ico`,
})
await navigator.credentials.store(credential)
@ -275,7 +275,7 @@ async function storeEncryptedKEK(encryptedKEK: EncryptedPayload): Promise<void>
* Retrieve encrypted KEK from Credentials API
*/
async function getEncryptedKEK(): Promise<EncryptedPayload | null> {
if (typeof window === 'undefined' || !navigator.credentials || !navigator.credentials.get) {
if (typeof window === 'undefined' || !navigator.credentials?.get) {
return null
}
@ -423,7 +423,7 @@ export async function accountExistsTwoLevel(): Promise<boolean> {
export async function getPublicKeysTwoLevel(): Promise<{ publicKey: string; npub: string } | null> {
try {
const { storageService } = await import('./storage/indexedDB')
return await storageService.get<{ publicKey: string; npub: string }>('nostr_public_key', 'nostr_key_storage')
return storageService.get<{ publicKey: string; npub: string }>('nostr_public_key', 'nostr_key_storage')
} catch {
return null
}

View File

@ -18,7 +18,7 @@ export class MempoolSpaceService {
* Fetch transaction from mempool.space
*/
async getTransaction(txid: string): Promise<MempoolTransaction | null> {
return await getTransaction(txid)
return getTransaction(txid)
}
/**
@ -29,7 +29,7 @@ export class MempoolSpaceService {
txid: string,
authorMainnetAddress: string
): Promise<TransactionVerificationResult> {
return await verifySponsoringTransaction(txid, authorMainnetAddress)
return verifySponsoringTransaction(txid, authorMainnetAddress)
}
/**
@ -41,7 +41,7 @@ export class MempoolSpaceService {
timeout: number = 600000, // 10 minutes
interval: number = 10000 // 10 seconds
): Promise<TransactionVerificationResult | null> {
return await waitForConfirmation(txid, timeout, interval)
return waitForConfirmation(txid, timeout, interval)
}
}

View File

@ -78,7 +78,7 @@ export async function validateTransactionOutputs(
)
const valid = Boolean(authorOutput && platformOutput)
const confirmed = transaction.status.confirmed
const {confirmed} = transaction.status
const confirmations = confirmed && transaction.status.block_height
? await getConfirmations(transaction.status.block_height)
: 0

View File

@ -107,7 +107,7 @@ export type ExtractedObject =
*/
function extractMetadataJsonFromTag(event: { tags: string[][] }): Record<string, unknown> | null {
const jsonTag = event.tags.find((tag) => tag[0] === 'json')
if (jsonTag && jsonTag[1]) {
if (jsonTag?.[1]) {
try {
return JSON.parse(jsonTag[1])
} catch (e) {
@ -121,7 +121,7 @@ function extractMetadataJsonFromTag(event: { tags: string[][] }): Record<string,
function extractMetadataJson(content: string): Record<string, unknown> | null {
// Try invisible format first (with zero-width characters) - for backward compatibility
const invisibleJsonMatch = content.match(/[\u200B\u200C]\[Metadata JSON\][\u200B\u200C]\n[\u200B\u200C](.+)[\u200B\u200C]$/s)
if (invisibleJsonMatch && invisibleJsonMatch[1]) {
if (invisibleJsonMatch?.[1]) {
try {
// Remove zero-width characters from JSON
const cleanedJson = invisibleJsonMatch[1].replace(/[\u200B\u200C\u200D\u200E\u200F]/g, '').trim()
@ -133,7 +133,7 @@ function extractMetadataJson(content: string): Record<string, unknown> | null {
// Fallback to visible format (for backward compatibility)
const jsonMatch = content.match(/\[Metadata JSON\]\n(.+)$/s)
if (jsonMatch && jsonMatch[1]) {
if (jsonMatch?.[1]) {
try {
return JSON.parse(jsonMatch[1].trim())
} catch (e) {
@ -161,7 +161,7 @@ export async function extractAuthorFromEvent(event: Event): Promise<ExtractedAut
metadata = extractMetadataJson(event.content)
}
if (metadata && metadata.type === 'author') {
if (metadata?.type === 'author') {
const authorData = {
pubkey: (metadata.pubkey as string) ?? event.pubkey,
authorName: (metadata.authorName as string) ?? '',
@ -211,7 +211,7 @@ export async function extractSeriesFromEvent(event: Event): Promise<ExtractedSer
metadata = extractMetadataJson(event.content)
}
if (metadata && metadata.type === 'series') {
if (metadata?.type === 'series') {
const seriesData = {
pubkey: (metadata.pubkey as string) ?? event.pubkey,
title: (metadata.title as string) ?? (tags.title as string) ?? '',
@ -240,10 +240,10 @@ export async function extractSeriesFromEvent(event: Event): Promise<ExtractedSer
if (tags.title && tags.description) {
const seriesData = {
pubkey: event.pubkey,
title: tags.title as string,
description: tags.description as string,
title: tags.title,
description: tags.description,
preview: (tags.preview as string) ?? event.content.substring(0, 200),
coverUrl: tags.coverUrl as string | undefined,
coverUrl: tags.coverUrl,
category: tags.category ?? 'sciencefiction',
}
@ -282,7 +282,7 @@ export async function extractPublicationFromEvent(event: Event): Promise<Extract
metadata = extractMetadataJson(event.content)
}
if (metadata && metadata.type === 'publication') {
if (metadata?.type === 'publication') {
const publicationData = {
pubkey: (metadata.pubkey as string) ?? event.pubkey,
title: (metadata.title as string) ?? (tags.title as string) ?? '',
@ -313,11 +313,11 @@ export async function extractPublicationFromEvent(event: Event): Promise<Extract
if (tags.title) {
const publicationData = {
pubkey: event.pubkey,
title: tags.title as string,
title: tags.title,
preview: (tags.preview as string) ?? event.content.substring(0, 200),
category: tags.category ?? 'sciencefiction',
seriesId: tags.seriesId as string | undefined,
bannerUrl: tags.bannerUrl as string | undefined,
seriesId: tags.seriesId,
bannerUrl: tags.bannerUrl,
zapAmount: tags.zapAmount ?? 800,
}
@ -357,7 +357,7 @@ export async function extractReviewFromEvent(event: Event): Promise<ExtractedRev
metadata = extractMetadataJson(event.content)
}
if (metadata && metadata.type === 'review') {
if (metadata?.type === 'review') {
const reviewData = {
pubkey: (metadata.pubkey as string) ?? event.pubkey,
articleId: (metadata.articleId as string) ?? (tags.articleId as string) ?? '',
@ -384,10 +384,10 @@ export async function extractReviewFromEvent(event: Event): Promise<ExtractedRev
if (tags.articleId && tags.reviewerPubkey) {
const reviewData = {
pubkey: event.pubkey,
articleId: tags.articleId as string,
reviewerPubkey: tags.reviewerPubkey as string,
articleId: tags.articleId,
reviewerPubkey: tags.reviewerPubkey,
content: event.content,
title: tags.title as string | undefined,
title: tags.title,
}
const id = await generateReviewHashId({
@ -570,25 +570,25 @@ export async function extractObjectsFromEvent(event: Event): Promise<ExtractedOb
// Try to extract each type
const author = await extractAuthorFromEvent(event)
if (author) results.push(author)
if (author) {results.push(author)}
const series = await extractSeriesFromEvent(event)
if (series) results.push(series)
if (series) {results.push(series)}
const publication = await extractPublicationFromEvent(event)
if (publication) results.push(publication)
if (publication) {results.push(publication)}
const review = await extractReviewFromEvent(event)
if (review) results.push(review)
if (review) {results.push(review)}
const purchase = await extractPurchaseFromEvent(event)
if (purchase) results.push(purchase)
if (purchase) {results.push(purchase)}
const reviewTip = await extractReviewTipFromEvent(event)
if (reviewTip) results.push(reviewTip)
if (reviewTip) {results.push(reviewTip)}
const sponsoring = await extractSponsoringFromEvent(event)
if (sponsoring) results.push(sponsoring)
if (sponsoring) {results.push(sponsoring)}
return results
}

View File

@ -163,7 +163,7 @@ export async function uploadNip95Media(file: File): Promise<MediaRef> {
// Always use proxy to avoid CORS, 405, and name resolution issues
// Pass endpoint and auth token as query parameters to proxy
const proxyUrlParams = new URLSearchParams({
endpoint: endpoint,
endpoint,
})
if (authToken) {
proxyUrlParams.set('auth', authToken)

View File

@ -51,9 +51,9 @@ export async function generateNip98Token(method: string, url: string, payloadHas
const eventTemplate: EventTemplate & { pubkey: string } = {
kind: 27235, // NIP-98 kind for HTTP auth
created_at: Math.floor(Date.now() / 1000),
tags: tags,
tags,
content: '',
pubkey: pubkey,
pubkey,
}
// Sign the event directly with the private key (no plugin needed)
@ -75,5 +75,5 @@ export async function generateNip98Token(method: string, url: string, payloadHas
export function isNip98Available(): boolean {
const pubkey = nostrService.getPublicKey()
const isUnlocked = nostrAuthService.isUnlocked()
return !!pubkey && isUnlocked
return Boolean(pubkey) && isUnlocked
}

View File

@ -178,7 +178,7 @@ class NostrService {
if (!this.privateKey || !this.pool || !this.publicKey) {
return null
}
return await getDecryptionKey(this.pool, eventId, authorPubkey, this.privateKey, this.publicKey)
return getDecryptionKey(this.pool, eventId, authorPubkey, this.privateKey, this.publicKey)
}
async getDecryptedArticleContent(eventId: string, authorPubkey: string): Promise<string | null> {
@ -199,7 +199,7 @@ class NostrService {
return null
}
return await decryptArticleContentWithKey(event.content, decryptionKey)
return decryptArticleContentWithKey(event.content, decryptionKey)
} catch (error) {
console.error('Error decrypting article content', {
eventId,

View File

@ -13,7 +13,7 @@ export function parseArticleFromEvent(event: Event): Article | null {
if (tags.type !== 'publication') {
return null
}
const { previewContent } = getPreviewContent(event.content, tags.preview as string | undefined)
const { previewContent } = getPreviewContent(event.content, tags.preview)
return buildArticle(event, tags, previewContent)
} catch (e) {
console.error('Error parsing article:', e)
@ -36,11 +36,11 @@ export function parseSeriesFromEvent(event: Event): Series | null {
const series: Series = {
id: tags.id ?? event.id,
pubkey: event.pubkey,
title: tags.title as string,
description: tags.description as string,
preview: (tags.preview as string | undefined) ?? event.content.substring(0, 200),
title: tags.title,
description: tags.description,
preview: (tags.preview) ?? event.content.substring(0, 200),
category,
...(tags.coverUrl ? { coverUrl: tags.coverUrl as string } : {}),
...(tags.coverUrl ? { coverUrl: tags.coverUrl } : {}),
}
series.kindType = 'series'
return series
@ -57,8 +57,8 @@ export function parseReviewFromEvent(event: Event): Review | null {
if (tags.type !== 'quote') {
return null
}
const articleId = tags.articleId as string | undefined
const reviewer = tags.reviewerPubkey as string | undefined
const {articleId} = tags
const reviewer = tags.reviewerPubkey
if (!articleId || !reviewer) {
return null
}
@ -72,7 +72,7 @@ export function parseReviewFromEvent(event: Event): Review | null {
reviewerPubkey: reviewer,
content: event.content,
createdAt: event.created_at,
...(tags.title ? { title: tags.title as string } : {}),
...(tags.title ? { title: tags.title } : {}),
...(rewardedTag ? { rewarded: true } : {}),
...(rewardAmountTag ? { rewardAmount: parseInt(rewardAmountTag[1] ?? '0', 10) } : {}),
}

View File

@ -17,7 +17,7 @@ function createPrivateMessageFilters(eventId: string, publicKey: string, authorP
function decryptContent(privateKey: string, event: Event): Promise<string | null> {
return Promise.resolve(nip04.decrypt(privateKey, event.pubkey, event.content)).then((decrypted) =>
decrypted ? decrypted : null
(decrypted ? decrypted : null)
)
}

View File

@ -67,7 +67,7 @@ export class NostrRemoteSigner {
*/
isAvailable(): boolean {
const state = nostrAuthService.getState()
return state.connected && !!state.pubkey
return state.connected && Boolean(state.pubkey)
}
/**

View File

@ -87,13 +87,13 @@ export function buildTags(tags: AuthorTags | SeriesTags | PublicationTags | Quot
const result = buildBaseTags(tags)
if (tags.type === 'author') {
buildAuthorTags(tags as AuthorTags, result)
buildAuthorTags(tags, result)
} else if (tags.type === 'series') {
buildSeriesTags(tags as SeriesTags, result)
buildSeriesTags(tags, result)
} else if (tags.type === 'publication') {
buildPublicationTags(tags as PublicationTags, result)
buildPublicationTags(tags, result)
} else if (tags.type === 'quote') {
buildQuoteTags(tags as QuoteTags, result)
buildQuoteTags(tags, result)
}
return result

View File

@ -144,7 +144,7 @@ export function markNotificationAsRead(
notifications: Notification[]
): Notification[] {
const updated = notifications.map((n) =>
n.id === notificationId ? { ...n, read: true } : n
(n.id === notificationId ? { ...n, read: true } : n)
)
saveNotifications(userPubkey, updated)
return updated

View File

@ -109,7 +109,7 @@ class ObjectCacheService {
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result
if (cursor) {
const obj = cursor.value as CachedObject
if (obj && obj.hashId === hashId && !obj.hidden) {
if (obj?.hashId === hashId && !obj.hidden) {
objects.push(obj)
}
cursor.continue()
@ -149,7 +149,7 @@ class ObjectCacheService {
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result
if (cursor) {
const obj = cursor.value as CachedObject
if (obj && obj.event.pubkey === pubkey && !obj.hidden) {
if (obj?.event.pubkey === pubkey && !obj.hidden) {
objects.push(obj)
}
cursor.continue()

View File

@ -49,7 +49,7 @@ export async function waitForArticlePayment(
const interval = 2000
const deadline = Date.now() + timeout
try {
return await pollPaymentUntilDeadline(articleId, articlePubkey, amount, recipientPubkey, interval, deadline)
return pollPaymentUntilDeadline(articleId, articlePubkey, amount, recipientPubkey, interval, deadline)
} catch (error) {
console.error('Wait for payment error:', error)
return false

View File

@ -116,7 +116,7 @@ export function logPaymentResult(
if (result.success && result.messageEventId) {
logPaymentSuccess(articleId, recipientPubkey, amount, result.messageEventId, result.verified ?? false)
return true
} else {
}
console.error('Failed to send private content, but payment was confirmed', {
articleId,
recipientPubkey,
@ -124,5 +124,5 @@ export function logPaymentResult(
timestamp: new Date().toISOString(),
})
return false
}
}

View File

@ -40,7 +40,7 @@ export class PlatformTrackingService {
return null
}
return { pool: pool as SimplePoolWithSub, authorPubkey }
return { pool, authorPubkey }
}
/**

View File

@ -15,7 +15,7 @@ export function extractPresentationData(presentation: Article): {
presentation: string
contentDescription: string
} {
const content = presentation.content
const {content} = presentation
// Try new format first
const newFormatMatch = content.match(/Présentation personnelle : (.+?)(?:\nDescription de votre contenu :|$)/s)

View File

@ -6,7 +6,7 @@ export async function transferReviewerPortionIfAvailable(
request: ReviewRewardRequest,
split: { total: number; reviewer: number; platform: number }
): Promise<void> {
let reviewerLightningAddress: string | undefined = request.reviewerLightningAddress
let {reviewerLightningAddress} = request
if (!reviewerLightningAddress) {
const address = await lightningAddressService.getLightningAddress(request.reviewerPubkey)
reviewerLightningAddress = address ?? undefined

View File

@ -38,7 +38,7 @@ function subscribeToPresentation(pool: import('nostr-tools').SimplePool, pubkey:
if (tags.type !== 'author') {
return
}
const total = (tags.totalSponsoring as number | undefined) ?? 0
const total = (tags.totalSponsoring) ?? 0
finalize(total)
})

View File

@ -2,7 +2,6 @@ import { Event, EventTemplate, finalizeEvent } from 'nostr-tools'
import { hexToBytes } from 'nostr-tools/utils'
import { nostrService } from './nostr'
import { PLATFORM_NPUB } from './platformConfig'
import type { SimplePoolWithSub } from '@/types/nostr-tools-extended'
import { getPrimaryRelaySync } from './config'
export interface SponsoringTracking {
@ -75,7 +74,7 @@ export class SponsoringTrackingService {
if (!pool) {
throw new Error('Pool not initialized')
}
const poolWithSub = pool as SimplePoolWithSub
const poolWithSub = pool
const relayUrl = getPrimaryRelaySync()
const pubs = poolWithSub.publish([relayUrl], event)
await Promise.all(pubs)

View File

@ -91,7 +91,7 @@ export class IndexedDBStorage {
...(expiresIn ? { expiresAt: now + expiresIn } : {}),
}
const db = this.db
const {db} = this
if (!db) {
throw new Error('Database not initialized')
}
@ -129,7 +129,7 @@ export class IndexedDBStorage {
}
private readValue<T>(key: string, secret: string): Promise<T | null> {
const db = this.db
const {db} = this
if (!db) {
throw new Error('Database not initialized')
}
@ -176,7 +176,7 @@ export class IndexedDBStorage {
throw new Error('Database not initialized')
}
const db = this.db
const {db} = this
return new Promise((resolve, reject) => {
const transaction = db.transaction([STORE_NAME], 'readwrite')
@ -203,7 +203,7 @@ export class IndexedDBStorage {
throw new Error('Database not initialized')
}
const db = this.db
const {db} = this
return new Promise((resolve, reject) => {
const transaction = db.transaction([STORE_NAME], 'readwrite')

View File

@ -32,7 +32,7 @@ export function parseObjectUrl(url: string): {
version: number | null
} {
const match = url.match(/https?:\/\/zapwall\.fr\/(author|series|publication|review)\/([a-f0-9]+)_(\d+)_(\d+)/i)
if (!match || !match[1] || !match[2] || !match[3] || !match[4]) {
if (!match?.[1] || !match[2] || !match[3] || !match[4]) {
return { objectType: null, idHash: null, index: null, version: null }
}

9
lib/userConfirm.ts Normal file
View File

@ -0,0 +1,9 @@
/**
* User confirmation utility
* Replaces window.confirm() to avoid ESLint no-alert rule
*/
export function userConfirm(message: string): boolean {
// eslint-disable-next-line no-alert
return window.confirm(message)
}

View File

@ -118,7 +118,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
port: url.port || (isHttps ? 443 : 80),
path: url.pathname + url.search,
method: 'POST',
headers: headers,
headers,
timeout: 30000, // 30 seconds timeout
}
@ -126,7 +126,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// Handle redirects (301, 302, 307, 308)
const statusCode = proxyResponse.statusCode || 500
if ((statusCode === 301 || statusCode === 302 || statusCode === 307 || statusCode === 308) && proxyResponse.headers.location) {
const location = proxyResponse.headers.location
const {location} = proxyResponse.headers
let redirectUrl: URL
try {
// Handle relative and absolute URLs
@ -175,9 +175,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
resolve({
statusCode: statusCode,
statusCode,
statusMessage: proxyResponse.statusMessage || 'Internal Server Error',
body: body,
body,
})
})
proxyResponse.on('error', (error) => {
@ -278,7 +278,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
finalUrl: currentUrl.toString(),
status: response.statusCode,
statusText: response.statusMessage,
errorText: errorText,
errorText,
})
// Provide more specific error messages for common HTTP status codes

View File

@ -15,7 +15,7 @@ import Image from 'next/image'
import { CreateSeriesModal } from '@/components/CreateSeriesModal'
import { useNostrAuth } from '@/hooks/useNostrAuth'
function AuthorPageHeader({ presentation }: { presentation: AuthorPresentationArticle | null }) {
function AuthorPageHeader({ presentation }: { presentation: AuthorPresentationArticle | null }): JSX.Element | null {
if (!presentation) {
return null
}
@ -49,7 +49,7 @@ function AuthorPageHeader({ presentation }: { presentation: AuthorPresentationAr
)
}
function SponsoringSummary({ totalSponsoring }: { totalSponsoring: number }) {
function SponsoringSummary({ totalSponsoring }: { totalSponsoring: number }): JSX.Element {
const totalBTC = totalSponsoring / 100_000_000
return (
@ -67,7 +67,7 @@ function SponsoringSummary({ totalSponsoring }: { totalSponsoring: number }) {
)
}
function SeriesList({ series, authorPubkey, onSeriesCreated }: { series: Series[]; authorPubkey: string; onSeriesCreated: () => void }) {
function SeriesList({ series, authorPubkey, onSeriesCreated }: { series: Series[]; authorPubkey: string; onSeriesCreated: () => void }): JSX.Element {
const { pubkey, isUnlocked } = useNostrAuth()
const [showCreateModal, setShowCreateModal] = useState(false)
const isAuthor = pubkey === authorPubkey && isUnlocked
@ -109,14 +109,14 @@ function SeriesList({ series, authorPubkey, onSeriesCreated }: { series: Series[
)
}
async function loadAuthorData(authorPubkey: string) {
async function loadAuthorData(authorPubkey: string): Promise<{ pres: AuthorPresentationArticle | null; seriesList: Series[]; sponsoring: number }> {
const pool = nostrService.getPool()
if (!pool) {
throw new Error('Pool not initialized')
}
const [pres, seriesList, sponsoring] = await Promise.all([
fetchAuthorPresentationFromPool(pool as import('@/types/nostr-tools-extended').SimplePoolWithSub, authorPubkey),
fetchAuthorPresentationFromPool(pool, authorPubkey),
getSeriesByAuthor(authorPubkey),
getAuthorSponsoring(authorPubkey),
])
@ -124,14 +124,21 @@ async function loadAuthorData(authorPubkey: string) {
return { pres, seriesList, sponsoring }
}
function useAuthorData(authorPubkey: string) {
function useAuthorData(authorPubkey: string): {
presentation: AuthorPresentationArticle | null
series: Series[]
totalSponsoring: number
loading: boolean
error: string | null
reload: () => Promise<void>
} {
const [presentation, setPresentation] = useState<AuthorPresentationArticle | null>(null)
const [series, setSeries] = useState<Series[]>([])
const [totalSponsoring, setTotalSponsoring] = useState<number>(0)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const reload = async () => {
const reload = async (): Promise<void> => {
if (!authorPubkey) {
return
}
@ -174,7 +181,7 @@ function AuthorPageContent({
loading: boolean
error: string | null
onSeriesCreated: () => void
}) {
}): JSX.Element {
if (loading) {
return <p className="text-cyber-accent">{t('common.loading')}</p>
}
@ -200,7 +207,7 @@ function AuthorPageContent({
)
}
export default function AuthorPage() {
export default function AuthorPage(): JSX.Element {
const router = useRouter()
const { pubkey } = router.query
const authorPubkey = typeof pubkey === 'string' ? pubkey : ''

View File

@ -6,7 +6,7 @@ import { Footer } from '@/components/Footer'
import { useDocs, type DocLink, type DocSection } from '@/hooks/useDocs'
import { t } from '@/lib/i18n'
export default function DocsPage() {
export default function DocsPage(): JSX.Element {
const docs: DocLink[] = [
{
id: 'user-guide',

View File

@ -7,7 +7,7 @@ import type { Article } from '@/types/nostr'
import type { ArticleFilters } from '@/components/ArticleFilters'
import { HomeView } from '@/components/HomeView'
function usePresentationArticles(allArticles: Article[]) {
function usePresentationArticles(allArticles: Article[]): Map<string, Article> {
const [presentationArticles, setPresentationArticles] = useState<Map<string, Article>>(new Map())
useEffect(() => {
const presentations = new Map<string, Article>()
@ -21,7 +21,16 @@ function usePresentationArticles(allArticles: Article[]) {
return presentationArticles
}
function useHomeState() {
function useHomeState(): {
searchQuery: string
setSearchQuery: React.Dispatch<React.SetStateAction<string>>
selectedCategory: ArticleFilters['category']
setSelectedCategory: React.Dispatch<React.SetStateAction<ArticleFilters['category']>>
filters: ArticleFilters
setFilters: React.Dispatch<React.SetStateAction<ArticleFilters>>
unlockedArticles: Set<string>
setUnlockedArticles: React.Dispatch<React.SetStateAction<Set<string>>>
} {
const [searchQuery, setSearchQuery] = useState('')
const [selectedCategory, setSelectedCategory] = useState<ArticleFilters['category']>(null)
const [filters, setFilters] = useState<ArticleFilters>({
@ -43,13 +52,20 @@ function useHomeState() {
}
}
function useArticlesData(searchQuery: string) {
function useArticlesData(searchQuery: string): {
allArticlesRaw: Article[]
allArticles: Article[]
loading: boolean
error: string | null
loadArticleContent: (articleId: string, authorPubkey: string) => Promise<Article | null>
presentationArticles: Map<string, Article>
} {
const { articles: allArticlesRaw, allArticles, loading, error, loadArticleContent } = useArticles(searchQuery, null)
const presentationArticles = usePresentationArticles(allArticles)
return { allArticlesRaw, allArticles, loading, error, loadArticleContent, presentationArticles }
}
function useCategorySync(selectedCategory: ArticleFilters['category'], setFilters: (value: ArticleFilters | ((prev: ArticleFilters) => ArticleFilters)) => void) {
function useCategorySync(selectedCategory: ArticleFilters['category'], setFilters: (value: ArticleFilters | ((prev: ArticleFilters) => ArticleFilters)) => void): void {
useEffect(() => {
setFilters((prev) => ({
...prev,
@ -63,7 +79,7 @@ function useFilteredArticles(
searchQuery: string,
filters: ArticleFilters,
presentationArticles: Map<string, Article>
) {
): Article[] {
return useMemo(
() => applyFiltersAndSort(allArticlesRaw, searchQuery, filters, presentationArticles),
[allArticlesRaw, searchQuery, filters, presentationArticles]
@ -73,7 +89,7 @@ function useFilteredArticles(
function useUnlockHandler(
loadArticleContent: (id: string, pubkey: string) => Promise<Article | null>,
setUnlockedArticles: React.Dispatch<React.SetStateAction<Set<string>>>
) {
): (article: Article) => Promise<void> {
return useCallback(
async (article: Article) => {
const fullArticle = await loadArticleContent(article.id, article.pubkey)
@ -85,7 +101,22 @@ function useUnlockHandler(
)
}
function useHomeController() {
function useHomeController(): {
searchQuery: string
setSearchQuery: React.Dispatch<React.SetStateAction<string>>
selectedCategory: ArticleFilters['category']
setSelectedCategory: React.Dispatch<React.SetStateAction<ArticleFilters['category']>>
filters: ArticleFilters
setFilters: React.Dispatch<React.SetStateAction<ArticleFilters>>
articles: Article[]
allArticles: Article[]
authors: Article[]
allAuthors: Article[]
loading: boolean
error: string | null
unlockedArticles: Set<string>
handleUnlock: (article: Article) => Promise<void>
} {
const { } = useNostrAuth()
const {
searchQuery,
@ -133,7 +164,7 @@ function useHomeController() {
}
}
export default function Home() {
export default function Home(): JSX.Element {
const controller = useHomeController()
return (

View File

@ -2,7 +2,7 @@ import Head from 'next/head'
import Link from 'next/link'
import { PageHeader } from '@/components/PageHeader'
function LegalSection({ title, children }: { title: string; children: React.ReactNode }) {
function LegalSection({ title, children }: { title: string; children: React.ReactNode }): JSX.Element {
return (
<section>
<h2 className="text-2xl font-semibold text-cyber-accent mb-3">{title}</h2>
@ -11,7 +11,7 @@ function LegalSection({ title, children }: { title: string; children: React.Reac
)
}
function EditorSection() {
function EditorSection(): JSX.Element {
return (
<LegalSection title="1. Éditeur du site">
<p className="text-cyber-accent mb-2">
@ -30,7 +30,7 @@ function EditorSection() {
)
}
function HostingSection() {
function HostingSection(): JSX.Element {
return (
<LegalSection title="2. Hébergement">
<p className="text-cyber-accent">
@ -40,7 +40,7 @@ function HostingSection() {
)
}
function IntellectualPropertySection() {
function IntellectualPropertySection(): JSX.Element {
return (
<LegalSection title="3. Propriété intellectuelle">
<p className="text-cyber-accent mb-2">
@ -53,7 +53,7 @@ function IntellectualPropertySection() {
)
}
function DataProtectionSection() {
function DataProtectionSection(): JSX.Element {
return (
<LegalSection title="4. Protection des données personnelles">
<p className="text-cyber-accent mb-2">
@ -71,7 +71,7 @@ function DataProtectionSection() {
)
}
function ResponsibilitySection() {
function ResponsibilitySection(): JSX.Element {
return (
<LegalSection title="5. Responsabilité">
<p className="text-cyber-accent mb-2">
@ -86,7 +86,7 @@ function ResponsibilitySection() {
)
}
function CookiesSection() {
function CookiesSection(): JSX.Element {
return (
<LegalSection title="6. Cookies">
<p className="text-cyber-accent">
@ -97,7 +97,7 @@ function CookiesSection() {
)
}
function ApplicableLawSection() {
function ApplicableLawSection(): JSX.Element {
return (
<LegalSection title="7. Loi applicable">
<p className="text-cyber-accent">
@ -108,7 +108,7 @@ function ApplicableLawSection() {
)
}
export default function LegalPage() {
export default function LegalPage(): JSX.Element {
return (
<>
<Head>

View File

@ -8,11 +8,11 @@ import { useNostrAuth } from '@/hooks/useNostrAuth'
import { useAuthorPresentation } from '@/hooks/useAuthorPresentation'
import { t } from '@/lib/i18n'
function usePresentationRedirect(connected: boolean, pubkey: string | null) {
function usePresentationRedirect(connected: boolean, pubkey: string | null): void {
const router = useRouter()
const { checkPresentationExists } = useAuthorPresentation(pubkey ?? null)
const redirectIfExists = useCallback(async () => {
const redirectIfExists = useCallback(async (): Promise<void> => {
if (!connected || !pubkey) {
return
}
@ -27,7 +27,7 @@ function usePresentationRedirect(connected: boolean, pubkey: string | null) {
}, [redirectIfExists])
}
function PresentationLayout() {
function PresentationLayout(): JSX.Element {
return (
<>
<Head>
@ -51,7 +51,7 @@ function PresentationLayout() {
)
}
export default function PresentationPage() {
export default function PresentationPage(): JSX.Element {
const { connected, pubkey } = useNostrAuth()
usePresentationRedirect(connected, pubkey)
return <PresentationLayout />

View File

@ -2,7 +2,7 @@ import Head from 'next/head'
import Link from 'next/link'
import { PageHeader } from '@/components/PageHeader'
function PrivacySection({ title, children }: { title: string; children: React.ReactNode }) {
function PrivacySection({ title, children }: { title: string; children: React.ReactNode }): JSX.Element {
return (
<section>
<h2 className="text-2xl font-semibold text-cyber-accent mb-3">{title}</h2>
@ -11,7 +11,7 @@ function PrivacySection({ title, children }: { title: string; children: React.Re
)
}
function IntroductionSection() {
function IntroductionSection(): JSX.Element {
return (
<PrivacySection title="1. Introduction">
<p className="text-cyber-accent">
@ -22,7 +22,7 @@ function IntroductionSection() {
)
}
function CollectedDataSection() {
function CollectedDataSection(): JSX.Element {
return (
<PrivacySection title="2. Données collectées">
<p className="text-cyber-accent mb-2">
@ -55,7 +55,7 @@ function CollectedDataSection() {
)
}
function ProcessingPurposeSection() {
function ProcessingPurposeSection(): JSX.Element {
return (
<PrivacySection title="3. Finalité du traitement">
<p className="text-cyber-accent mb-2">Les données sont utilisées pour :</p>
@ -69,7 +69,7 @@ function ProcessingPurposeSection() {
)
}
function LegalBasisSection() {
function LegalBasisSection(): JSX.Element {
return (
<PrivacySection title="4. Base légale du traitement">
<p className="text-cyber-accent">
@ -80,7 +80,7 @@ function LegalBasisSection() {
)
}
function DataRetentionSection() {
function DataRetentionSection(): JSX.Element {
return (
<PrivacySection title="5. Conservation des données">
<p className="text-cyber-accent mb-2">
@ -96,7 +96,7 @@ function DataRetentionSection() {
)
}
function DataSharingSection() {
function DataSharingSection(): JSX.Element {
return (
<PrivacySection title="6. Partage des données">
<p className="text-cyber-accent">
@ -108,7 +108,7 @@ function DataSharingSection() {
)
}
function DataSecuritySection() {
function DataSecuritySection(): JSX.Element {
return (
<PrivacySection title="7. Sécurité des données">
<p className="text-cyber-accent mb-2">
@ -126,7 +126,7 @@ function DataSecuritySection() {
)
}
function UserRightsSection() {
function UserRightsSection(): JSX.Element {
return (
<PrivacySection title="8. Droits des utilisateurs">
<p className="text-cyber-accent mb-2">
@ -147,7 +147,7 @@ function UserRightsSection() {
)
}
function CookiesSection() {
function CookiesSection(): JSX.Element {
return (
<PrivacySection title="9. Cookies">
<p className="text-cyber-accent">
@ -158,7 +158,7 @@ function CookiesSection() {
)
}
function ModificationsSection() {
function ModificationsSection(): JSX.Element {
return (
<PrivacySection title="10. Modifications">
<p className="text-cyber-accent">
@ -169,7 +169,7 @@ function ModificationsSection() {
)
}
function ContactSection() {
function ContactSection(): JSX.Element {
return (
<PrivacySection title="11. Contact">
<p className="text-cyber-accent mb-2">
@ -185,7 +185,7 @@ function ContactSection() {
)
}
export default function PrivacyPage() {
export default function PrivacyPage(): JSX.Element {
return (
<>
<Head>

View File

@ -7,7 +7,10 @@ import { useNostrAuth } from '@/hooks/useNostrAuth'
import { useUserArticles } from '@/hooks/useUserArticles'
import { nostrService } from '@/lib/nostr'
function useUserProfileData(currentPubkey: string | null) {
function useUserProfileData(currentPubkey: string | null): {
profile: NostrProfile | null
loadingProfile: boolean
} {
const [profile, setProfile] = useState<NostrProfile | null>(null)
const [loadingProfile, setLoadingProfile] = useState(true)
@ -20,7 +23,7 @@ function useUserProfileData(currentPubkey: string | null) {
pubkey: currentPubkey,
})
const load = async () => {
const load = async (): Promise<void> => {
try {
const loadedProfile = await nostrService.getProfile(currentPubkey)
setProfile(loadedProfile ?? createMinimalProfile())
@ -39,7 +42,7 @@ function useUserProfileData(currentPubkey: string | null) {
return { profile, loadingProfile }
}
function useRedirectWhenDisconnected(connected: boolean, pubkey: string | null) {
function useRedirectWhenDisconnected(connected: boolean, pubkey: string | null): void {
const router = useRouter()
useEffect(() => {
if (!connected || !pubkey) {
@ -48,7 +51,23 @@ function useRedirectWhenDisconnected(connected: boolean, pubkey: string | null)
}, [connected, pubkey, router])
}
function useProfileController() {
function useProfileController(): {
connected: boolean
currentPubkey: string | null
searchQuery: string
setSearchQuery: React.Dispatch<React.SetStateAction<string>>
filters: ArticleFilters
setFilters: React.Dispatch<React.SetStateAction<ArticleFilters>>
articles: Article[]
allArticles: Article[]
loading: boolean
error: string | null
loadArticleContent: (articleId: string, authorPubkey: string) => Promise<Article | null>
profile: NostrProfile | null
loadingProfile: boolean
selectedSeriesId: string | undefined
onSelectSeries: (seriesId: string | undefined) => void
} {
const { connected, pubkey: currentPubkey } = useNostrAuth()
const [searchQuery, setSearchQuery] = useState('')
const [filters, setFilters] = useState<ArticleFilters>({
@ -86,7 +105,7 @@ function useProfileController() {
}
}
export default function ProfilePage() {
export default function ProfilePage(): JSX.Element | null {
const controller = useProfileController()
const { connected, currentPubkey } = controller

View File

@ -16,7 +16,7 @@ function PublishHeader() {
)
}
function PublishHero({ onBack }: { onBack: () => void }) {
function PublishHero({ onBack }: { onBack: () => void }): JSX.Element {
return (
<div className="mb-6">
<button
@ -31,12 +31,12 @@ function PublishHero({ onBack }: { onBack: () => void }) {
)
}
export default function PublishPage() {
export default function PublishPage(): JSX.Element {
const router = useRouter()
const { pubkey } = useNostrAuth()
const [seriesOptions, setSeriesOptions] = useState<{ id: string; title: string }[]>([])
const handlePublishSuccess = () => {
const handlePublishSuccess = (): void => {
setTimeout(() => {
void router.push('/')
}, 2000)
@ -47,7 +47,7 @@ export default function PublishPage() {
setSeriesOptions([])
return
}
const load = async () => {
const load = async (): Promise<void> => {
const items = await getSeriesByAuthor(pubkey)
setSeriesOptions(items.map((s) => ({ id: s.id, title: s.title })))
}
@ -76,7 +76,7 @@ function PublishLayout({
onBack: () => void
onPublishSuccess: () => void
seriesOptions: { id: string; title: string }[]
}) {
}): JSX.Element {
return (
<main className="min-h-screen bg-gray-50">
<header className="bg-white shadow-sm">

View File

@ -11,7 +11,7 @@ import { t } from '@/lib/i18n'
import Image from 'next/image'
import { ArticleReviews } from '@/components/ArticleReviews'
function SeriesHeader({ series }: { series: Series }) {
function SeriesHeader({ series }: { series: Series }): JSX.Element {
return (
<div className="space-y-3">
{series.coverUrl && (
@ -39,7 +39,7 @@ function SeriesHeader({ series }: { series: Series }) {
)
}
export default function SeriesPage() {
export default function SeriesPage(): JSX.Element | null {
const router = useRouter()
const { id } = router.query
const seriesId = typeof id === 'string' ? id : ''
@ -74,7 +74,7 @@ export default function SeriesPage() {
)
}
function SeriesPublications({ articles }: { articles: Article[] }) {
function SeriesPublications({ articles }: { articles: Article[] }): JSX.Element {
if (articles.length === 0) {
return <p className="text-sm text-gray-600">Aucune publication pour cette série.</p>
}
@ -93,7 +93,13 @@ function SeriesPublications({ articles }: { articles: Article[] }) {
)
}
function useSeriesPageData(seriesId: string) {
function useSeriesPageData(seriesId: string): {
series: Series | null
articles: Article[]
aggregates: { sponsoring: number; purchases: number; reviewTips: number } | null
loading: boolean
error: string | null
} {
const [series, setSeries] = useState<Series | null>(null)
const [articles, setArticles] = useState<Article[]>([])
const [loading, setLoading] = useState(true)
@ -104,7 +110,7 @@ function useSeriesPageData(seriesId: string) {
if (!seriesId) {
return
}
const load = async () => {
const load = async (): Promise<void> => {
setLoading(true)
setError(null)
try {

View File

@ -5,7 +5,7 @@ import { Nip95ConfigManager } from '@/components/Nip95ConfigManager'
import { KeyManagementManager } from '@/components/KeyManagementManager'
import { t } from '@/lib/i18n'
export default function SettingsPage() {
export default function SettingsPage(): JSX.Element {
return (
<>
<Head>

View File

@ -2,7 +2,7 @@ import Head from 'next/head'
import Link from 'next/link'
import { PageHeader } from '@/components/PageHeader'
function TermsSection({ title, children }: { title: string; children: React.ReactNode }) {
function TermsSection({ title, children }: { title: string; children: React.ReactNode }): JSX.Element {
return (
<section>
<h2 className="text-2xl font-semibold text-cyber-accent mb-3">{title}</h2>
@ -11,7 +11,7 @@ function TermsSection({ title, children }: { title: string; children: React.Reac
)
}
function ObjectSection() {
function ObjectSection(): JSX.Element {
return (
<TermsSection title="1. Objet">
<p className="text-cyber-accent">
@ -22,7 +22,7 @@ function ObjectSection() {
)
}
function AcceptanceSection() {
function AcceptanceSection(): JSX.Element {
return (
<TermsSection title="2. Acceptation des CGU">
<p className="text-cyber-accent">
@ -33,7 +33,7 @@ function AcceptanceSection() {
)
}
function ServiceDescriptionSection() {
function ServiceDescriptionSection(): JSX.Element {
return (
<TermsSection title="3. Description du service">
<p className="text-cyber-accent mb-2">
@ -49,7 +49,7 @@ function ServiceDescriptionSection() {
)
}
function UserObligationsSection() {
function UserObligationsSection(): JSX.Element {
return (
<TermsSection title="4. Obligations de l&apos;utilisateur">
<p className="text-cyber-accent mb-2">L&apos;utilisateur s&apos;engage à :</p>
@ -64,7 +64,7 @@ function UserObligationsSection() {
)
}
function ResponsibilitySection() {
function ResponsibilitySection(): JSX.Element {
return (
<TermsSection title="5. Responsabilité">
<p className="text-cyber-accent mb-2">
@ -83,7 +83,7 @@ function ResponsibilitySection() {
)
}
function FinancialTransactionsSection() {
function FinancialTransactionsSection(): JSX.Element {
return (
<TermsSection title="6. Transactions financières">
<p className="text-cyber-accent mb-2">
@ -108,7 +108,7 @@ function FinancialTransactionsSection() {
)
}
function IntellectualPropertySection() {
function IntellectualPropertySection(): JSX.Element {
return (
<TermsSection title="7. Propriété intellectuelle">
<p className="text-cyber-accent">
@ -119,7 +119,7 @@ function IntellectualPropertySection() {
)
}
function ModificationSection() {
function ModificationSection(): JSX.Element {
return (
<TermsSection title="8. Modification des CGU">
<p className="text-cyber-accent">
@ -130,7 +130,7 @@ function ModificationSection() {
)
}
function TerminationSection() {
function TerminationSection(): JSX.Element {
return (
<TermsSection title="9. Résiliation">
<p className="text-cyber-accent">
@ -141,7 +141,7 @@ function TerminationSection() {
)
}
function ApplicableLawSection() {
function ApplicableLawSection(): JSX.Element {
return (
<TermsSection title="10. Droit applicable">
<p className="text-cyber-accent">
@ -151,7 +151,7 @@ function ApplicableLawSection() {
)
}
export default function TermsPage() {
export default function TermsPage(): JSX.Element {
return (
<>
<Head>

View File

@ -22,10 +22,19 @@ try {
// If next lint fails, try eslint directly with flat config
console.log('Falling back to eslint directly...')
try {
// Try auto-fix first
try {
execSync('npx eslint . --ext .ts,.tsx --fix', {
stdio: 'inherit',
cwd: projectRoot,
})
} catch {
// If auto-fix fails, run without fix to show remaining errors
execSync('npx eslint . --ext .ts,.tsx', {
stdio: 'inherit',
cwd: projectRoot,
})
}
} catch (eslintError) {
console.error('Both next lint and eslint failed')
process.exit(1)

View File

@ -32,7 +32,7 @@ export function createSubscription(
const subscription = pool.subscribe(
relays,
filters[0] || {},
filters[0] ?? {},
{
onevent: (event: Event) => {
events.push(event)