Free early-access launch: LAUNCH_MODE policy, CRO, pipeline hardening, AI-SEO, ops

- launch.ts policy: LAUNCH_MODE=free_early_access default; effective access =
  published && (free || early); intended tier (access) never rewritten
- Billing off server-side: /api/subscribe 503 honest copy; subscribe CTAs
  render disabled 'قريباً' from first paint; /subscription early-access panel
  + /api/notify (LaunchNotifications) with honest 503 when mail transport off
- Signup trust: verification email at signup (Users afterChange), no
  confirm-password, 429 message fidelity, typo suggestion, live password hint
- Agent pipeline: pickFields strips access/readingMinutes, sourceId dedup,
  secret-scan 422 (pattern names only), string-content rejected (was silently
  replaced), publish stays explicit opt-in
- lexical.ts: root unwrap fix — lexicalToText/countLexicalWords returned 0
  for real serialized trees (secret scan + readingMinutes were inert)
- llms-full.txt: fetchIndexDocs(true) — bodies were never selected
- Content honesty: contentType/authorName/sourceId fields + migration,
  readingMinutes computed at write (160 wpm) + backfill script, byline,
  'حُدّث' freshness
- SEO/AI-SEO: metadata rewrite (published-only, og:image 1200x630), Article +
  BreadcrumbList + FAQPage + Organization JSON-LD, isAccessibleForFree honest,
  table renderer, robots AI-crawler policy (10 named bots), llms.txt +
  llms-full.txt, /topics/* hubs + sitemap, noindex auth pages, www 301
- Ops: backup-db.sh (restore rehearsed on VPS), docs copies (launch
  readiness + marketing audit + owner-inputs for L-03), GLOSSARY.md entries

Verified: tsc --noEmit 0, npm run build 0, local smoke (ghost VIP read,
subscribe/notify/agent APIs, signup token, 429 on 6th, llms/topics/sitemap,
og:image/JSON-LD)
This commit is contained in:
AHMED
2026-10-10 18:03:45 +03:00
parent 82414e8a93
commit 0161924088
57 changed files with 4042 additions and 402 deletions
@@ -1,12 +1,52 @@
'use client'
import Link from 'next/link'
import { useState } from 'react'
import { useEffect, useState } from 'react'
/* Resend button + status — real /api/auth/resend-verification endpoint.
Without SMTP configured the API answers 503 with a clear message. */
export default function VerifyActions({ verified }: { verified: boolean }) {
Without SMTP configured the API answers 503 with a clear message.
«البريد غير صحيح؟» — collapsed change-email affordance: PATCH /api/users/:id
(self-update allowed in Users.ts access) then resend; 400 → duplicate-email
copy. Hidden without a session (no id → nothing to patch). */
export default function VerifyActions({ verified, email, userId }: { verified: boolean; email: string | null; userId: number | null }) {
const [status, setStatus] = useState('')
const [newEmail, setNewEmail] = useState('')
const [busy, setBusy] = useState(false)
useEffect(() => {
if (email) {
setNewEmail(email)
return
}
try {
const saved = sessionStorage.getItem('anwarpi:signup-email')
if (saved) setNewEmail(saved)
} catch { /* private mode */ }
}, [email])
async function changeEmail() {
if (busy || !userId) return
setBusy(true)
setStatus('جارٍ التحديث…')
try {
const patchRes = await fetch(`/api/users/${userId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: newEmail.trim() }),
})
if (!patchRes.ok) {
setStatus(patchRes.status === 400 ? 'هذا البريد مسجَّل لدينا.' : 'تعذّر تحديث البريد، حاول مرة أخرى.')
return
}
const resendRes = await fetch('/api/auth/resend-verification', { method: 'POST' })
const data = await resendRes.json().catch(() => null) as { error?: string; already?: boolean } | null
setStatus(resendRes.ok || data?.already ? 'أرسلنا رسالة تأكيد جديدة إلى بريدك المحدَّث.' : data?.error || 'تم تحديث البريد لكن تعذّر الإرسال الآن.')
} catch {
setStatus('تعذّر الاتصال، حاول مرة أخرى.')
} finally {
setBusy(false)
}
}
return (
<>
@@ -19,7 +59,7 @@ export default function VerifyActions({ verified }: { verified: boolean }) {
button.disabled = true
setStatus('جارٍ الإرسال…')
const res = await fetch('/api/auth/resend-verification', { method: 'POST' })
const data = await res.json().catch(() => null)
const data = await res.json().catch(() => null) as { error?: string; already?: boolean } | null
if (res.ok || data?.already) {
setStatus('أرسلنا الرسالة من جديد — افحص صندوق الوارد.')
} else {
@@ -33,6 +73,17 @@ export default function VerifyActions({ verified }: { verified: boolean }) {
)}
</div>
<p className="status" id="resendStatus" role="status" aria-live="polite">{status}</p>
{userId && !verified && (
<details className="change-email" data-od-id="change-email">
<summary>البريد غير صحيح؟</summary>
<div className="change-email-row">
<input className="input" type="email" inputMode="email" dir="ltr" value={newEmail} onChange={e => setNewEmail(e.target.value)} aria-label="البريد الإلكتروني الصحيح" />
<button className="btn btn-secondary" type="button" onClick={changeEmail} disabled={busy || newEmail.trim() === ''}>
{busy ? 'جارٍ التحديث…' : 'حدّث البريد وأعد الإرسال'}
</button>
</div>
</details>
)}
</>
)
}