lint fix wip
This commit is contained in:
parent
ccf2fdf759
commit
cc0f3816fa
@ -1,143 +0,0 @@
|
|||||||
# Résumé du déploiement - zapwall.fr
|
|
||||||
|
|
||||||
## ✅ Actions effectuées
|
|
||||||
|
|
||||||
1. **Déploiement initial** : Application déployée sur `/var/www/zapwall.fr`
|
|
||||||
2. **Service systemd** : Service `zapwall.service` créé et actif
|
|
||||||
3. **Configuration nginx** : Reverse proxy configuré dans le conteneur Docker
|
|
||||||
4. **HTTPS** : Configuration HTTPS avec redirection automatique HTTP → HTTPS
|
|
||||||
5. **Firewall** : Ports 80 et 443 ouverts
|
|
||||||
|
|
||||||
## 📍 État actuel
|
|
||||||
|
|
||||||
- **Service** : ✅ Actif sur le port 3001
|
|
||||||
- **Répertoire** : `/var/www/zapwall.fr` (correct)
|
|
||||||
- **HTTPS** : ✅ Configuré avec certificats auto-signés
|
|
||||||
- **Certificats Let's Encrypt** : ⚠️ Non obtenus (bug certbot avec Python 3.11)
|
|
||||||
|
|
||||||
## 🔧 Problèmes identifiés et solutions
|
|
||||||
|
|
||||||
### 1. Certificats Let's Encrypt
|
|
||||||
|
|
||||||
**Problème** : Certbot présente un bug (AttributeError) avec Python 3.11
|
|
||||||
|
|
||||||
**Solutions** :
|
|
||||||
- Utiliser certbot via snap (recommandé) :
|
|
||||||
```bash
|
|
||||||
ssh debian@92.243.27.35
|
|
||||||
sudo snap install certbot --classic
|
|
||||||
sudo docker stop lecoffre_nginx_test
|
|
||||||
sudo certbot certonly --standalone -d zapwall.fr --non-interactive --agree-tos --email admin@zapwall.fr
|
|
||||||
sudo docker start lecoffre_nginx_test
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Configuration nginx
|
|
||||||
|
|
||||||
**Vérification** : La configuration pointe bien vers le port 3001 (correct)
|
|
||||||
- `proxy_pass http://172.17.0.1:3001;` ✅
|
|
||||||
|
|
||||||
**Note** : La configuration principale nginx.conf contient aussi une config pour `test-lecoffreio.4nkweb.com`, mais elle ne devrait pas interférer car elle utilise un `server_name` différent.
|
|
||||||
|
|
||||||
## 📝 Mise à jour du site depuis Git
|
|
||||||
|
|
||||||
### Méthode recommandée : Script automatique
|
|
||||||
|
|
||||||
Depuis votre machine locale, dans le répertoire du projet :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Mise à jour depuis la branche actuelle
|
|
||||||
./update-from-git.sh
|
|
||||||
|
|
||||||
# Ou spécifier une branche
|
|
||||||
./update-from-git.sh main
|
|
||||||
```
|
|
||||||
|
|
||||||
Le script :
|
|
||||||
1. Transfère les fichiers depuis votre dépôt Git local
|
|
||||||
2. Installe les dépendances (`npm ci`)
|
|
||||||
3. Construit l'application (`npm run build`)
|
|
||||||
4. Redémarre le service
|
|
||||||
5. Vérifie que tout fonctionne
|
|
||||||
|
|
||||||
### Méthode manuelle
|
|
||||||
|
|
||||||
Si vous préférez faire manuellement :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Transférer les fichiers
|
|
||||||
tar --exclude='node_modules' \
|
|
||||||
--exclude='.next' \
|
|
||||||
--exclude='.git' \
|
|
||||||
--exclude='*.tsbuildinfo' \
|
|
||||||
--exclude='.env*.local' \
|
|
||||||
--exclude='.cursor' \
|
|
||||||
-czf - . | ssh debian@92.243.27.35 "cd /var/www/zapwall.fr && tar -xzf -"
|
|
||||||
|
|
||||||
# 2. Sur le serveur
|
|
||||||
ssh debian@92.243.27.35
|
|
||||||
cd /var/www/zapwall.fr
|
|
||||||
npm ci
|
|
||||||
npm run build
|
|
||||||
sudo systemctl restart zapwall
|
|
||||||
```
|
|
||||||
|
|
||||||
### Initialiser un dépôt Git sur le serveur (optionnel)
|
|
||||||
|
|
||||||
Si vous voulez pouvoir faire `git pull` directement sur le serveur :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ssh debian@92.243.35
|
|
||||||
cd /var/www/zapwall.fr
|
|
||||||
git init
|
|
||||||
git remote add origin https://git.4nkweb.com/4nk/story-research-zapwall.git
|
|
||||||
git fetch origin
|
|
||||||
git checkout main # ou la branche souhaitée
|
|
||||||
```
|
|
||||||
|
|
||||||
Ensuite, vous pourrez utiliser :
|
|
||||||
```bash
|
|
||||||
ssh debian@92.243.27.35 'cd /var/www/zapwall.fr && git pull && npm ci && npm run build && sudo systemctl restart zapwall'
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔍 Commandes de vérification
|
|
||||||
|
|
||||||
### Vérifier le service
|
|
||||||
```bash
|
|
||||||
ssh debian@92.243.27.35 'sudo systemctl status zapwall'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Voir les logs
|
|
||||||
```bash
|
|
||||||
ssh debian@92.243.27.35 'sudo journalctl -u zapwall -f'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Vérifier le port
|
|
||||||
```bash
|
|
||||||
ssh debian@92.243.27.35 'sudo ss -tuln | grep 3001'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Vérifier la configuration nginx
|
|
||||||
```bash
|
|
||||||
ssh debian@92.243.27.35 'sudo docker exec lecoffre_nginx_test cat /etc/nginx/conf.d/zapwall.fr.conf'
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📚 Fichiers de documentation créés
|
|
||||||
|
|
||||||
- `README-DEPLOYMENT.md` : Guide complet de déploiement et mise à jour
|
|
||||||
- `update-from-git.sh` : Script de mise à jour automatique
|
|
||||||
- `fix-nginx-config.sh` : Script de correction de la configuration
|
|
||||||
- `check-deployment-status.sh` : Script de vérification de l'état
|
|
||||||
|
|
||||||
## 🚀 Prochaines étapes recommandées
|
|
||||||
|
|
||||||
1. **Obtenir les certificats Let's Encrypt** via snap (voir ci-dessus)
|
|
||||||
2. **Configurer le renouvellement automatique** des certificats
|
|
||||||
3. **Tester l'accès** au site en HTTPS
|
|
||||||
4. **Configurer un dépôt Git sur le serveur** pour faciliter les mises à jour
|
|
||||||
|
|
||||||
## ⚠️ Notes importantes
|
|
||||||
|
|
||||||
- Le site fonctionne actuellement avec des certificats auto-signés (avertissement navigateur)
|
|
||||||
- Les modifications de code nécessitent un rebuild et un redémarrage du service
|
|
||||||
- Le service doit être actif pour que le site soit accessible
|
|
||||||
- Nginx fait un reverse proxy vers le port 3001 où tourne l'application Next.js
|
|
||||||
@ -109,7 +109,7 @@ export function ArticleFiltersComponent({
|
|||||||
}: ArticleFiltersProps): React.ReactElement {
|
}: ArticleFiltersProps): React.ReactElement {
|
||||||
const data = useFiltersData(articles)
|
const data = useFiltersData(articles)
|
||||||
|
|
||||||
const handleClearFilters = () => {
|
const handleClearFilters = (): void => {
|
||||||
onFiltersChange({
|
onFiltersChange({
|
||||||
authorPubkey: null,
|
authorPubkey: null,
|
||||||
sortBy: 'newest',
|
sortBy: 'newest',
|
||||||
|
|||||||
@ -64,7 +64,7 @@ export function AllAuthorsOption({
|
|||||||
value: string | null
|
value: string | null
|
||||||
onChange: (value: string | null) => void
|
onChange: (value: string | null) => void
|
||||||
setIsOpen: (open: boolean) => void
|
setIsOpen: (open: boolean) => void
|
||||||
}) {
|
}): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@ -23,7 +23,7 @@ interface AuthorPresentationDraft {
|
|||||||
|
|
||||||
const ADDRESS_PATTERN = /^(1|3|bc1)[a-zA-Z0-9]{25,62}$/
|
const ADDRESS_PATTERN = /^(1|3|bc1)[a-zA-Z0-9]{25,62}$/
|
||||||
|
|
||||||
function SuccessNotice({ pubkey }: { pubkey: string | null }) {
|
function SuccessNotice({ pubkey }: { pubkey: string | null }): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<div className="border border-neon-green/50 rounded-lg p-6 bg-neon-green/10">
|
<div className="border border-neon-green/50 rounded-lg p-6 bg-neon-green/10">
|
||||||
<h3 className="text-lg font-semibold text-neon-green mb-2">{t('presentation.success')}</h3>
|
<h3 className="text-lg font-semibold text-neon-green mb-2">{t('presentation.success')}</h3>
|
||||||
@ -44,7 +44,7 @@ function SuccessNotice({ pubkey }: { pubkey: string | null }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ValidationError({ message }: { message: string | null }) {
|
function ValidationError({ message }: { message: string | null }): React.ReactElement | null {
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -61,7 +61,7 @@ function PresentationField({
|
|||||||
}: {
|
}: {
|
||||||
draft: AuthorPresentationDraft
|
draft: AuthorPresentationDraft
|
||||||
onChange: (next: AuthorPresentationDraft) => void
|
onChange: (next: AuthorPresentationDraft) => void
|
||||||
}) {
|
}): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<ArticleField
|
<ArticleField
|
||||||
id="presentation"
|
id="presentation"
|
||||||
@ -83,7 +83,7 @@ function ContentDescriptionField({
|
|||||||
}: {
|
}: {
|
||||||
draft: AuthorPresentationDraft
|
draft: AuthorPresentationDraft
|
||||||
onChange: (next: AuthorPresentationDraft) => void
|
onChange: (next: AuthorPresentationDraft) => void
|
||||||
}) {
|
}): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<ArticleField
|
<ArticleField
|
||||||
id="contentDescription"
|
id="contentDescription"
|
||||||
@ -105,7 +105,7 @@ function MainnetAddressField({
|
|||||||
}: {
|
}: {
|
||||||
draft: AuthorPresentationDraft
|
draft: AuthorPresentationDraft
|
||||||
onChange: (next: AuthorPresentationDraft) => void
|
onChange: (next: AuthorPresentationDraft) => void
|
||||||
}) {
|
}): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<ArticleField
|
<ArticleField
|
||||||
id="mainnetAddress"
|
id="mainnetAddress"
|
||||||
@ -126,7 +126,7 @@ function AuthorNameField({
|
|||||||
}: {
|
}: {
|
||||||
draft: AuthorPresentationDraft
|
draft: AuthorPresentationDraft
|
||||||
onChange: (next: AuthorPresentationDraft) => void
|
onChange: (next: AuthorPresentationDraft) => void
|
||||||
}) {
|
}): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<ArticleField
|
<ArticleField
|
||||||
id="authorName"
|
id="authorName"
|
||||||
@ -147,7 +147,7 @@ function PictureField({
|
|||||||
}: {
|
}: {
|
||||||
draft: AuthorPresentationDraft
|
draft: AuthorPresentationDraft
|
||||||
onChange: (next: AuthorPresentationDraft) => void
|
onChange: (next: AuthorPresentationDraft) => void
|
||||||
}) {
|
}): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<ImageUploadField
|
<ImageUploadField
|
||||||
id="picture"
|
id="picture"
|
||||||
@ -163,7 +163,7 @@ const PresentationFields = ({
|
|||||||
}: {
|
}: {
|
||||||
draft: AuthorPresentationDraft
|
draft: AuthorPresentationDraft
|
||||||
onChange: (next: AuthorPresentationDraft) => void
|
onChange: (next: AuthorPresentationDraft) => void
|
||||||
}) => (
|
}): React.ReactElement => (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<AuthorNameField draft={draft} onChange={onChange} />
|
<AuthorNameField draft={draft} onChange={onChange} />
|
||||||
<PictureField draft={draft} onChange={onChange} />
|
<PictureField draft={draft} onChange={onChange} />
|
||||||
@ -173,7 +173,7 @@ const PresentationFields = ({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
function DeleteButton({ onDelete, deleting }: { onDelete: () => void; deleting: boolean }) {
|
function DeleteButton({ onDelete, deleting }: { onDelete: () => void; deleting: boolean }): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -206,7 +206,7 @@ function PresentationForm({
|
|||||||
deleting: boolean
|
deleting: boolean
|
||||||
handleDelete: () => void
|
handleDelete: () => void
|
||||||
hasExistingPresentation: boolean
|
hasExistingPresentation: boolean
|
||||||
}) {
|
}): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={(e: FormEvent<HTMLFormElement>) => {
|
onSubmit={(e: FormEvent<HTMLFormElement>) => {
|
||||||
@ -239,7 +239,17 @@ function PresentationForm({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function useAuthorPresentationState(pubkey: string | null, existingAuthorName?: string, existingPresentation?: Article | null) {
|
function useAuthorPresentationState(pubkey: string | null, existingAuthorName?: string, existingPresentation?: Article | null): {
|
||||||
|
draft: AuthorPresentationDraft
|
||||||
|
setDraft: (next: AuthorPresentationDraft) => void
|
||||||
|
validationError: string | null
|
||||||
|
error: string | null
|
||||||
|
loading: boolean
|
||||||
|
handleSubmit: (e: FormEvent<HTMLFormElement>) => Promise<void>
|
||||||
|
deleting: boolean
|
||||||
|
handleDelete: () => Promise<void>
|
||||||
|
success: boolean
|
||||||
|
} {
|
||||||
const { loading, error, success, publishPresentation, deletePresentation } = useAuthorPresentation(pubkey)
|
const { loading, error, success, publishPresentation, deletePresentation } = useAuthorPresentation(pubkey)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [draft, setDraft] = useState<AuthorPresentationDraft>(() => {
|
const [draft, setDraft] = useState<AuthorPresentationDraft>(() => {
|
||||||
@ -310,7 +320,7 @@ function useAuthorPresentationState(pubkey: string | null, existingAuthorName?:
|
|||||||
}
|
}
|
||||||
}, [existingPresentation, deletePresentation, router])
|
}, [existingPresentation, deletePresentation, router])
|
||||||
|
|
||||||
return { loading, error, success, draft, setDraft, validationError, handleSubmit, deleting, handleDelete, existingPresentation }
|
return { loading, error, success, draft, setDraft, validationError, handleSubmit, deleting, handleDelete }
|
||||||
}
|
}
|
||||||
|
|
||||||
function NoAccountActionButtons({
|
function NoAccountActionButtons({
|
||||||
@ -319,7 +329,7 @@ function NoAccountActionButtons({
|
|||||||
}: {
|
}: {
|
||||||
onGenerate: () => void
|
onGenerate: () => void
|
||||||
onImport: () => void
|
onImport: () => void
|
||||||
}) {
|
}): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3 w-full max-w-xs">
|
<div className="flex flex-col gap-3 w-full max-w-xs">
|
||||||
<button
|
<button
|
||||||
@ -338,7 +348,7 @@ function NoAccountActionButtons({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function NoAccountView() {
|
function NoAccountView(): React.ReactElement {
|
||||||
const [showImportModal, setShowImportModal] = useState(false)
|
const [showImportModal, setShowImportModal] = useState(false)
|
||||||
const [showRecoveryStep, setShowRecoveryStep] = useState(false)
|
const [showRecoveryStep, setShowRecoveryStep] = useState(false)
|
||||||
const [showUnlockModal, setShowUnlockModal] = useState(false)
|
const [showUnlockModal, setShowUnlockModal] = useState(false)
|
||||||
@ -347,7 +357,7 @@ function NoAccountView() {
|
|||||||
const [generating, setGenerating] = useState(false)
|
const [generating, setGenerating] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
const handleGenerate = async () => {
|
const handleGenerate = async (): Promise<void> => {
|
||||||
setGenerating(true)
|
setGenerating(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
try {
|
try {
|
||||||
@ -363,12 +373,12 @@ function NoAccountView() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRecoveryContinue = () => {
|
const handleRecoveryContinue = (): void => {
|
||||||
setShowRecoveryStep(false)
|
setShowRecoveryStep(false)
|
||||||
setShowUnlockModal(true)
|
setShowUnlockModal(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUnlockSuccess = () => {
|
const handleUnlockSuccess = (): void => {
|
||||||
setShowUnlockModal(false)
|
setShowUnlockModal(false)
|
||||||
setRecoveryPhrase([])
|
setRecoveryPhrase([])
|
||||||
setNpub('')
|
setNpub('')
|
||||||
@ -422,13 +432,13 @@ function AuthorPresentationFormView({
|
|||||||
}: {
|
}: {
|
||||||
pubkey: string | null
|
pubkey: string | null
|
||||||
profile: { name?: string; pubkey: string } | null
|
profile: { name?: string; pubkey: string } | null
|
||||||
}) {
|
}): React.ReactElement {
|
||||||
const { checkPresentationExists } = useAuthorPresentation(pubkey)
|
const { checkPresentationExists } = useAuthorPresentation(pubkey)
|
||||||
const [existingPresentation, setExistingPresentation] = useState<Article | null>(null)
|
const [existingPresentation, setExistingPresentation] = useState<Article | null>(null)
|
||||||
const [loadingPresentation, setLoadingPresentation] = useState(true)
|
const [loadingPresentation, setLoadingPresentation] = useState(true)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const load = async () => {
|
const load = async (): Promise<void> => {
|
||||||
if (!pubkey) {
|
if (!pubkey) {
|
||||||
setLoadingPresentation(false)
|
setLoadingPresentation(false)
|
||||||
return
|
return
|
||||||
@ -478,7 +488,7 @@ function AuthorPresentationFormView({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function useAutoLoadPubkey(accountExists: boolean | null, pubkey: string | null, connect: () => Promise<void>) {
|
function useAutoLoadPubkey(accountExists: boolean | null, pubkey: string | null, connect: () => Promise<void>): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (accountExists === true && !pubkey) {
|
if (accountExists === true && !pubkey) {
|
||||||
void connect()
|
void connect()
|
||||||
@ -486,7 +496,7 @@ function useAutoLoadPubkey(accountExists: boolean | null, pubkey: string | null,
|
|||||||
}, [accountExists, pubkey, connect])
|
}, [accountExists, pubkey, connect])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AuthorPresentationEditor() {
|
export function AuthorPresentationEditor(): React.ReactElement {
|
||||||
const { pubkey, profile, accountExists, connect } = useNostrAuth()
|
const { pubkey, profile, accountExists, connect } = useNostrAuth()
|
||||||
useAutoLoadPubkey(accountExists, pubkey ?? null, connect)
|
useAutoLoadPubkey(accountExists, pubkey ?? null, connect)
|
||||||
return <AuthorPresentationFormView pubkey={pubkey ?? null} profile={profile} />
|
return <AuthorPresentationFormView pubkey={pubkey ?? null} profile={profile} />
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { t } from '@/lib/i18n'
|
import { t } from '@/lib/i18n'
|
||||||
|
|
||||||
export function RecoveryWarning() {
|
export function RecoveryWarning(): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<div className="bg-yellow-900/20 border border-yellow-400/50 rounded-lg p-4 mb-6">
|
<div className="bg-yellow-900/20 border border-yellow-400/50 rounded-lg p-4 mb-6">
|
||||||
<p className="text-yellow-400 font-semibold mb-2">{t('account.create.recovery.warning.title')}</p>
|
<p className="text-yellow-400 font-semibold mb-2">{t('account.create.recovery.warning.title')}</p>
|
||||||
|
|||||||
@ -27,7 +27,7 @@ interface HomeViewProps {
|
|||||||
unlockedArticles: Set<string>
|
unlockedArticles: Set<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
function HomeHead() {
|
function HomeHead(): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<Head>
|
<Head>
|
||||||
<title>zapwall.fr</title>
|
<title>zapwall.fr</title>
|
||||||
|
|||||||
@ -39,6 +39,10 @@ export function SyncProgressBar(): React.ReactElement | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void loadSyncStatus()
|
||||||
|
}, [])
|
||||||
|
|
||||||
async function startSync(): Promise<void> {
|
async function startSync(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const state = nostrAuthService.getState()
|
const state = nostrAuthService.getState()
|
||||||
@ -47,7 +51,7 @@ export function SyncProgressBar(): React.ReactElement | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsSyncing(true)
|
setIsSyncing(true)
|
||||||
setSyncProgress({ currentDay: 0, totalDays, completed: false })
|
setSyncProgress({ currentStep: 0, totalSteps: 6, completed: false })
|
||||||
|
|
||||||
await syncUserContentToCache(state.pubkey, (progress) => {
|
await syncUserContentToCache(state.pubkey, (progress) => {
|
||||||
setSyncProgress(progress)
|
setSyncProgress(progress)
|
||||||
@ -78,8 +82,8 @@ export function SyncProgressBar(): React.ReactElement | null {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const progressPercentage = syncProgress && totalDays > 0
|
const progressPercentage = syncProgress && syncProgress.totalSteps > 0
|
||||||
? Math.min(100, (syncProgress.currentDay / totalDays) * 100)
|
? Math.min(100, (syncProgress.currentStep / syncProgress.totalSteps) * 100)
|
||||||
: 0
|
: 0
|
||||||
|
|
||||||
const formatDate = (timestamp: number): string => {
|
const formatDate = (timestamp: number): string => {
|
||||||
@ -133,8 +137,8 @@ export function SyncProgressBar(): React.ReactElement | null {
|
|||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<span className="text-cyber-accent">
|
<span className="text-cyber-accent">
|
||||||
{t('settings.sync.progress', {
|
{t('settings.sync.progress', {
|
||||||
current: syncProgress.currentDay,
|
current: syncProgress.currentStep,
|
||||||
total: syncProgress.totalDays,
|
total: syncProgress.totalSteps,
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-neon-cyan font-semibold">
|
<span className="text-neon-cyan font-semibold">
|
||||||
|
|||||||
@ -160,7 +160,7 @@ export class AlbyService {
|
|||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const checkPayment = () => {
|
const checkPayment = (): void => {
|
||||||
try {
|
try {
|
||||||
const status = this.checkPaymentStatus(paymentHash)
|
const status = this.checkPaymentStatus(paymentHash)
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,12 @@ export function prepareKeyData(storedContent: { content: string; decryptionKey?:
|
|||||||
return storedContent.content // Fallback to old behavior if keys are not available
|
return storedContent.content // Fallback to old behavior if keys are not available
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildPrivateMessageEvent(recipientPubkey: string, articleId: string, encryptedKey: string) {
|
export function buildPrivateMessageEvent(recipientPubkey: string, articleId: string, encryptedKey: string): {
|
||||||
|
kind: number
|
||||||
|
created_at: number
|
||||||
|
tags: string[][]
|
||||||
|
content: string
|
||||||
|
} {
|
||||||
return {
|
return {
|
||||||
kind: 4,
|
kind: 4,
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export class AutomaticTransferService {
|
|||||||
* Transfer author portion after article payment
|
* Transfer author portion after article payment
|
||||||
* Creates a Lightning invoice from the platform to the author
|
* Creates a Lightning invoice from the platform to the author
|
||||||
*/
|
*/
|
||||||
private logTransferRequired(type: 'article' | 'review', id: string, pubkey: string, amount: number, recipient: string, platformCommission: number) {
|
private logTransferRequired(type: 'article' | 'review', id: string, pubkey: string, amount: number, recipient: string, platformCommission: number): void {
|
||||||
const logData = {
|
const logData = {
|
||||||
[type === 'article' ? 'articleId' : 'reviewId']: id,
|
[type === 'article' ? 'articleId' : 'reviewId']: id,
|
||||||
[type === 'article' ? 'articlePubkey' : 'reviewerPubkey']: pubkey,
|
[type === 'article' ? 'articlePubkey' : 'reviewerPubkey']: pubkey,
|
||||||
|
|||||||
@ -15,7 +15,14 @@ export interface ContentDeliveryStatus {
|
|||||||
* Verify that private content was successfully delivered to recipient
|
* Verify that private content was successfully delivered to recipient
|
||||||
* Checks multiple aspects to ensure delivery certainty
|
* Checks multiple aspects to ensure delivery certainty
|
||||||
*/
|
*/
|
||||||
function createContentDeliveryFilters(authorPubkey: string, recipientPubkey: string, articleId: string, messageEventId: string) {
|
function createContentDeliveryFilters(authorPubkey: string, recipientPubkey: string, articleId: string, messageEventId: string): Array<{
|
||||||
|
kinds: number[]
|
||||||
|
ids?: string[]
|
||||||
|
authors: string[]
|
||||||
|
'#p': string[]
|
||||||
|
'#e': string[]
|
||||||
|
limit: number
|
||||||
|
}> {
|
||||||
const filters: Array<{
|
const filters: Array<{
|
||||||
kinds: number[]
|
kinds: number[]
|
||||||
ids?: string[]
|
ids?: string[]
|
||||||
@ -76,7 +83,7 @@ function createContentDeliverySubscription(
|
|||||||
recipientPubkey: string,
|
recipientPubkey: string,
|
||||||
articleId: string,
|
articleId: string,
|
||||||
messageEventId: string
|
messageEventId: string
|
||||||
) {
|
): import('@/types/nostr-tools-extended').Subscription {
|
||||||
const filters = createContentDeliveryFilters(authorPubkey, recipientPubkey, articleId, messageEventId)
|
const filters = createContentDeliveryFilters(authorPubkey, recipientPubkey, articleId, messageEventId)
|
||||||
const relayUrl = getPrimaryRelaySync()
|
const relayUrl = getPrimaryRelaySync()
|
||||||
return createSubscription(pool, [relayUrl], filters)
|
return createSubscription(pool, [relayUrl], filters)
|
||||||
@ -89,7 +96,7 @@ function createContentDeliveryPromise(
|
|||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
let resolved = false
|
let resolved = false
|
||||||
|
|
||||||
const finalize = (result: ContentDeliveryStatus) => {
|
const finalize = (result: ContentDeliveryStatus): void => {
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { TransactionVerificationResult } from './mempoolSpaceTypes'
|
|||||||
import { getTransaction } from './mempoolSpaceApi'
|
import { getTransaction } from './mempoolSpaceApi'
|
||||||
import { verifySponsoringTransaction } from './mempoolSpaceVerification'
|
import { verifySponsoringTransaction } from './mempoolSpaceVerification'
|
||||||
|
|
||||||
export function scheduleNextCheck(checkConfirmation: () => void, interval: number) {
|
export function scheduleNextCheck(checkConfirmation: () => void, interval: number): void {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
void checkConfirmation()
|
void checkConfirmation()
|
||||||
}, interval)
|
}, interval)
|
||||||
@ -53,7 +53,7 @@ export async function waitForConfirmation(
|
|||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const checkConfirmation = async () => {
|
const checkConfirmation = async (): Promise<void> => {
|
||||||
await checkTransactionStatus(txid, startTime, timeout, interval, resolve, checkConfirmation)
|
await checkTransactionStatus(txid, startTime, timeout, interval, resolve, checkConfirmation)
|
||||||
}
|
}
|
||||||
void checkConfirmation()
|
void checkConfirmation()
|
||||||
|
|||||||
@ -111,8 +111,8 @@ export function extractTagsFromEvent(event: { tags: string[][] }): {
|
|||||||
json?: string | undefined
|
json?: string | undefined
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
} {
|
} {
|
||||||
const findTag = (key: string) => event.tags.find((tag) => tag[0] === key)?.[1]
|
const findTag = (key: string): string | undefined => event.tags.find((tag) => tag[0] === key)?.[1]
|
||||||
const hasTag = (key: string) => event.tags.some((tag) => tag[0] === key || (tag.length === 1 && tag[0] === key))
|
const hasTag = (key: string): boolean => event.tags.some((tag) => tag[0] === key || (tag.length === 1 && tag[0] === key))
|
||||||
const typeCategory = extractTypeAndCategory(event)
|
const typeCategory = extractTypeAndCategory(event)
|
||||||
const commonTags = extractCommonTags(findTag, hasTag)
|
const commonTags = extractCommonTags(findTag, hasTag)
|
||||||
|
|
||||||
|
|||||||
@ -390,8 +390,8 @@ async function fetchAndCacheReviewTips(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SyncProgress {
|
export interface SyncProgress {
|
||||||
currentDay: number
|
currentStep: number
|
||||||
totalDays: number
|
totalSteps: number
|
||||||
completed: boolean
|
completed: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +399,7 @@ export interface SyncProgress {
|
|||||||
* Synchronize all user content to IndexedDB cache
|
* Synchronize all user content to IndexedDB cache
|
||||||
* Fetches profile, series, publications, purchases, sponsoring, and review tips and caches them
|
* Fetches profile, series, publications, purchases, sponsoring, and review tips and caches them
|
||||||
* @param userPubkey - The user's public key
|
* @param userPubkey - The user's public key
|
||||||
* @param onProgress - Optional callback to report progress (currentDay, totalDays, completed)
|
* @param onProgress - Optional callback to report progress (currentStep, totalSteps, completed)
|
||||||
*/
|
*/
|
||||||
export async function syncUserContentToCache(
|
export async function syncUserContentToCache(
|
||||||
userPubkey: string,
|
userPubkey: string,
|
||||||
@ -414,59 +414,59 @@ export async function syncUserContentToCache(
|
|||||||
|
|
||||||
const poolWithSub = pool as unknown as SimplePoolWithSub
|
const poolWithSub = pool as unknown as SimplePoolWithSub
|
||||||
|
|
||||||
// Get last sync date and calculate days
|
// Get current timestamp for last sync date
|
||||||
const { getLastSyncDate, setLastSyncDate, getCurrentTimestamp, calculateDaysBetween } = await import('./syncStorage')
|
const { setLastSyncDate, getCurrentTimestamp } = await import('./syncStorage')
|
||||||
const lastSyncDate = await getLastSyncDate()
|
|
||||||
const currentTimestamp = getCurrentTimestamp()
|
const currentTimestamp = getCurrentTimestamp()
|
||||||
const totalDays = calculateDaysBetween(lastSyncDate, currentTimestamp)
|
|
||||||
|
const TOTAL_STEPS = 6
|
||||||
|
|
||||||
// Report initial progress
|
// Report initial progress
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
onProgress({ currentDay: 0, totalDays, completed: false })
|
onProgress({ currentStep: 0, totalSteps: TOTAL_STEPS, completed: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentDay = 0
|
let currentStep = 0
|
||||||
|
|
||||||
// Fetch and cache author profile (already caches itself)
|
// Fetch and cache author profile (already caches itself)
|
||||||
await fetchAuthorPresentationFromPool(poolWithSub, userPubkey)
|
await fetchAuthorPresentationFromPool(poolWithSub, userPubkey)
|
||||||
currentDay++
|
currentStep++
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
onProgress({ currentDay, totalDays, completed: false })
|
onProgress({ currentStep, totalSteps: TOTAL_STEPS, completed: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and cache all series
|
// Fetch and cache all series
|
||||||
await fetchAndCacheSeries(poolWithSub, userPubkey)
|
await fetchAndCacheSeries(poolWithSub, userPubkey)
|
||||||
currentDay++
|
currentStep++
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
onProgress({ currentDay, totalDays, completed: false })
|
onProgress({ currentStep, totalSteps: TOTAL_STEPS, completed: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and cache all publications
|
// Fetch and cache all publications
|
||||||
await fetchAndCachePublications(poolWithSub, userPubkey)
|
await fetchAndCachePublications(poolWithSub, userPubkey)
|
||||||
currentDay++
|
currentStep++
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
onProgress({ currentDay, totalDays, completed: false })
|
onProgress({ currentStep, totalSteps: TOTAL_STEPS, completed: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and cache all purchases (as payer)
|
// Fetch and cache all purchases (as payer)
|
||||||
await fetchAndCachePurchases(poolWithSub, userPubkey)
|
await fetchAndCachePurchases(poolWithSub, userPubkey)
|
||||||
currentDay++
|
currentStep++
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
onProgress({ currentDay, totalDays, completed: false })
|
onProgress({ currentStep, totalSteps: TOTAL_STEPS, completed: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and cache all sponsoring (as author)
|
// Fetch and cache all sponsoring (as author)
|
||||||
await fetchAndCacheSponsoring(poolWithSub, userPubkey)
|
await fetchAndCacheSponsoring(poolWithSub, userPubkey)
|
||||||
currentDay++
|
currentStep++
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
onProgress({ currentDay, totalDays, completed: false })
|
onProgress({ currentStep, totalSteps: TOTAL_STEPS, completed: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and cache all review tips (as author)
|
// Fetch and cache all review tips (as author)
|
||||||
await fetchAndCacheReviewTips(poolWithSub, userPubkey)
|
await fetchAndCacheReviewTips(poolWithSub, userPubkey)
|
||||||
currentDay++
|
currentStep++
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
onProgress({ currentDay, totalDays, completed: true })
|
onProgress({ currentStep, totalSteps: TOTAL_STEPS, completed: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the current timestamp as last sync date
|
// Store the current timestamp as last sync date
|
||||||
|
|||||||
@ -12,7 +12,12 @@ interface ZapAggregationFilter {
|
|||||||
timeoutMs?: number
|
timeoutMs?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildFilters(params: ZapAggregationFilter) {
|
function buildFilters(params: ZapAggregationFilter): Array<{
|
||||||
|
kinds: number[]
|
||||||
|
since?: number
|
||||||
|
'#p'?: string[]
|
||||||
|
'#e'?: string[]
|
||||||
|
}> {
|
||||||
const filters = [
|
const filters = [
|
||||||
{
|
{
|
||||||
kinds: [9735],
|
kinds: [9735],
|
||||||
@ -59,7 +64,7 @@ function collectZap(
|
|||||||
let total = 0
|
let total = 0
|
||||||
let finished = false
|
let finished = false
|
||||||
|
|
||||||
const done = () => {
|
const done = (): void => {
|
||||||
if (finished) {
|
if (finished) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -68,7 +73,7 @@ function collectZap(
|
|||||||
resolve(total)
|
resolve(total)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onError = (err: unknown) => {
|
const onError = (err: unknown): void => {
|
||||||
if (finished) {
|
if (finished) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export async function getZapReceiptById(zapReceiptId: string): Promise<Event | n
|
|||||||
return new Promise<Event | null>((resolve) => {
|
return new Promise<Event | null>((resolve) => {
|
||||||
let resolved = false
|
let resolved = false
|
||||||
|
|
||||||
const finalize = (value: Event | null) => {
|
const finalize = (value: Event | null): void => {
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { useI18n } from '@/hooks/useI18n'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { platformSyncService } from '@/lib/platformSync'
|
import { platformSyncService } from '@/lib/platformSync'
|
||||||
|
|
||||||
function I18nProvider({ children }: { children: React.ReactNode }) {
|
function I18nProvider({ children }: { children: React.ReactNode }): React.ReactElement {
|
||||||
// Get saved locale from localStorage or default to French
|
// Get saved locale from localStorage or default to French
|
||||||
const getInitialLocale = (): 'fr' | 'en' => {
|
const getInitialLocale = (): 'fr' | 'en' => {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import path from 'path'
|
|||||||
|
|
||||||
const DOCS_DIR = path.join(process.cwd(), 'docs')
|
const DOCS_DIR = path.join(process.cwd(), 'docs')
|
||||||
|
|
||||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default function handler(req: NextApiRequest, res: NextApiResponse): void {
|
||||||
const { file } = req.query
|
const { file } = req.query
|
||||||
const locale = (req.query.locale as string) || 'fr' // Default to French
|
const locale = (req.query.locale as string) || 'fr' // Default to French
|
||||||
|
|
||||||
|
|||||||
@ -137,12 +137,12 @@ function useHomeController(): {
|
|||||||
const handleUnlock = useUnlockHandler(loadArticleContent, setUnlockedArticles)
|
const handleUnlock = useUnlockHandler(loadArticleContent, setUnlockedArticles)
|
||||||
|
|
||||||
// Get authors by category
|
// Get authors by category
|
||||||
const allAuthors = useMemo(() => {
|
const allAuthors = useMemo((): Article[] => {
|
||||||
const authorsArray = Array.from(presentationArticles.values())
|
const authorsArray = Array.from(presentationArticles.values())
|
||||||
return sortAuthors(authorsArray)
|
return sortAuthors(authorsArray)
|
||||||
}, [presentationArticles])
|
}, [presentationArticles])
|
||||||
|
|
||||||
const authors = useMemo(() => {
|
const authors = useMemo((): Article[] => {
|
||||||
return getAuthorsByCategory(presentationArticles, selectedCategory)
|
return getAuthorsByCategory(presentationArticles, selectedCategory)
|
||||||
}, [presentationArticles, selectedCategory])
|
}, [presentationArticles, selectedCategory])
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { useNostrAuth } from '@/hooks/useNostrAuth'
|
|||||||
import { getSeriesByAuthor } from '@/lib/seriesQueries'
|
import { getSeriesByAuthor } from '@/lib/seriesQueries'
|
||||||
import { t } from '@/lib/i18n'
|
import { t } from '@/lib/i18n'
|
||||||
|
|
||||||
function PublishHeader() {
|
function PublishHeader(): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<Head>
|
<Head>
|
||||||
<title>{t('publish.title')} - {t('home.title')}</title>
|
<title>{t('publish.title')} - {t('home.title')}</title>
|
||||||
|
|||||||
@ -239,7 +239,7 @@ settings.keyManagement.recovery.done=Done
|
|||||||
settings.sync.title=Notes Synchronization
|
settings.sync.title=Notes Synchronization
|
||||||
settings.sync.start=Start Synchronization
|
settings.sync.start=Start Synchronization
|
||||||
settings.sync.daysRange=From {{startDate}} to {{endDate}} ({{days}} days)
|
settings.sync.daysRange=From {{startDate}} to {{endDate}} ({{days}} days)
|
||||||
settings.sync.progress=Day {{current}} of {{total}}
|
settings.sync.progress=Step {{current}} of {{total}}
|
||||||
settings.sync.completed=Everything is synchronized
|
settings.sync.completed=Everything is synchronized
|
||||||
settings.nip95.title=NIP-95 Upload Endpoints
|
settings.nip95.title=NIP-95 Upload Endpoints
|
||||||
settings.nip95.loading=Loading...
|
settings.nip95.loading=Loading...
|
||||||
|
|||||||
@ -239,7 +239,7 @@ settings.keyManagement.recovery.done=Terminé
|
|||||||
settings.sync.title=Synchronisation des notes
|
settings.sync.title=Synchronisation des notes
|
||||||
settings.sync.start=Démarrer la synchronisation
|
settings.sync.start=Démarrer la synchronisation
|
||||||
settings.sync.daysRange=Du {{startDate}} au {{endDate}} ({{days}} jours)
|
settings.sync.daysRange=Du {{startDate}} au {{endDate}} ({{days}} jours)
|
||||||
settings.sync.progress=Jour {{current}} sur {{total}}
|
settings.sync.progress=Étape {{current}} sur {{total}}
|
||||||
settings.sync.completed=Tout est synchronisé
|
settings.sync.completed=Tout est synchronisé
|
||||||
settings.language.title=Langue de préférence
|
settings.language.title=Langue de préférence
|
||||||
settings.language.description=Choisissez votre langue préférée pour l'interface
|
settings.language.description=Choisissez votre langue préférée pour l'interface
|
||||||
|
|||||||
@ -157,13 +157,6 @@ export interface ReviewTip {
|
|||||||
kindType?: KindType
|
kindType?: KindType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ZapRequest {
|
|
||||||
event: Event
|
|
||||||
amount: number
|
|
||||||
targetPubkey: string
|
|
||||||
targetEventId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NostrConnectState {
|
export interface NostrConnectState {
|
||||||
connected: boolean
|
connected: boolean
|
||||||
pubkey: string | null
|
pubkey: string | null
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user