31 Commits

Author SHA1 Message Date
pezkuwichain 070d682759 feat(p2p): rename Finance tile to P2P/Buy-Sell + add 'Buy with Visa' nav action
The Finance section tile label becomes 'P2P/Buy-Sell'. In the P2P dashboard's
top nav, next to the Messages icon, a Visa card (CreditCard) action 'Buy with
Visa' opens buy-sell.pezkiwi.app in a new tab. Adds p2pNav.buyVisa in all 6
locales.
2026-06-19 17:37:14 -07:00
pezkuwichain cd56ab8fb6 feat(social): replace Help tile with Loto (Newroz) → loto.pex.mom
In the Social section of both the desktop app grid and the mobile home, the Help
tile becomes a Loto tile with the Newroz flame logo (loto-icon.svg) that opens
loto.pex.mom in a new tab. Adds the mobile.app.loto i18n key in all 6 locales.
Help remains reachable from the landing page.
2026-06-19 17:32:57 -07:00
pezkuwichain b012fcaaac fix(security): patch ws (high DoS) and dompurify (XSS) via npm audit fix
Unblocks the deploy security gate — production deps only, no major bumps.
2026-06-15 18:07:37 -07:00
pezkuwichain 7a1d3e7917 feat(social): wire DKS Rojname → news.pex.mom and Events → kurdishtts.pezkiwi.app
- KurdMedia: DKS Rojname channel now links to the Dijital Kurdistan News site
- Social Events item opens the Kurdish TTS app (was coming-soon/locked)
- applies across mobile drawer, mobile home and desktop landing pallets
2026-06-15 17:54:19 -07:00
pezkuwichain 2ee3caac0d fix(ci): audit only production deps in the deploy gate (--omit=dev) (#18)
The security-audit gate ran 'npm audit --audit-level=high' over all deps,
so newly-published advisories on build-only tooling (esbuild, elliptic via
vite-plugin-node-polyfills, etc.) repeatedly blocked production deploys
even though that code ships to no user. Scope the gate to production
dependencies with --omit=dev. Verified: 'npm audit --audit-level=high
--omit=dev' → 0 vulnerabilities. TruffleHog secret scanning is unchanged.
2026-06-12 23:39:55 -07:00
pezkuwichain 78e93e9766 feat(web): PEZ-20 badge on PEZ & USDT balance cards (#17)
* fix(ci): unblock deploy pipeline (audit gate + orphan submodule)

The Quality Gate & Deploy pipeline was failing at security-audit
(npm audit --audit-level=high), which blocks telegram-gate and the
whole deploy chain — that is why production was serving a stale bundle.

- npm audit fix (no --force, lockfile only): clears the critical vitest
  advisory (GHSA-5xrq-8626-4rwp) and the high elliptic one; only low-
  severity items remain, so 'npm audit --audit-level=high' now exits 0.
- Remove the orphaned 'exchange' gitlink: it is an empty submodule
  pointer with no .gitmodules mapping, which made git print
  'fatal: no submodule mapping found' during checkout.

Verified: lint, test (32 passed), and vite build all pass; audit gate
is green. No package.json changes.

* feat(web): PEZ-20 badge on PEZ and USDT balance cards

Add a small reusable Pez20Badge pill next to the PEZ and USDT tokens in
the wallet balance view, linking to the Token Standards docs. These are
fungible assets on Asset Hub, i.e. the PEZ-20 standard — this gives users
the familiar ERC-20-style mental model at a glance.

Additive only: no labels removed, native HEZ is intentionally not badged
(it is the native/gas token, not a PEZ-20 asset).
2026-06-12 23:28:05 -07:00
pezkuwichain 83d66feacc fix(ci): unblock deploy pipeline (audit gate + orphan submodule) (#16)
The Quality Gate & Deploy pipeline was failing at security-audit
(npm audit --audit-level=high), which blocks telegram-gate and the
whole deploy chain — that is why production was serving a stale bundle.

- npm audit fix (no --force, lockfile only): clears the critical vitest
  advisory (GHSA-5xrq-8626-4rwp) and the high elliptic one; only low-
  severity items remain, so 'npm audit --audit-level=high' now exits 0.
- Remove the orphaned 'exchange' gitlink: it is an empty submodule
  pointer with no .gitmodules mapping, which made git print
  'fatal: no submodule mapping found' during checkout.

Verified: lint, test (32 passed), and vite build all pass; audit gate
is green. No package.json changes.
2026-06-11 18:42:45 -07:00
pezkuwichain d6ace14e70 fix(web): live collator/nominator counts after AHM + reliable B2B redirect (#15)
Staking migrated to Asset Hub (AHM), but the landing page still read
nominators from the relay (api.query.staking.counterForNominators),
which is now empty there — so the count showed '—'. Collators were read
from collatorSelection.candidates (empty; collators are invulnerables)
and only on Asset Hub, missing the People chain set.

- Nominators: query Asset Hub staking.counterForNominators (verified 30).
- Collators: count collatorSelection.invulnerables on both Asset Hub and
  People chain (2 + 2), tracked per-chain and summed.
- NetworkStats.tsx already used the correct sources; this aligns the
  landing page with it.

B2B button (/bereketli SSO interstitial): if there is no Supabase session
or the token exchange fails, redirect to https://bereketli.pezkiwi.app
instead of stranding the user on app.pezkuwichain.io/bereketli. (The
backend CORS allowlist was also missing app.pezkuwichain.io; fixed
server-side so the SSO exchange itself now succeeds.)
2026-06-11 16:41:14 -07:00
pezkuwichain 2cbfd21539 fix(cosign): explicit GHCR login before sign + verify
docker/login-action writes ~/.docker/config.json but cosign on self-
hosted runner does not always read it. Add 'cosign login ghcr.io'
before sign (build-image) and verify (deploy-app, deploy-pex) so the
registry blob upload/download authenticates correctly.

The previous run signed via Sigstore (Fulcio cert + Rekor tlog entry
created) but failed at the final 'push signature blob to GHCR' step
with UNAUTHORIZED. Explicit cosign login solves this.
2026-05-09 13:41:29 +03:00
pezkuwichain f7c070e45b fix(deps): drop invalid create-ecdh override (max version is 4.x not 5.x)
The earlier npm override 'create-ecdh: ^5.0.1' resolved to no version on
the registry. CI install failed with ETARGET. Removing the override —
elliptic override alone covers the high-severity transitive vulns.
Remaining 6 lows in vite-plugin-node-polyfills chain accepted.
2026-05-09 12:27:07 +03:00
pezkuwichain 06ed9734c6 ci(security): Faz 3 + ekstra — runner consolidation, auto-rollback, cosign, SRI, dep cleanup
* Faz 3.1 — All CI jobs moved to self-hosted pwap-runner (DEV VPS).
  No more dependency on GitHub-hosted runners — supply-chain attack
  surface from GHA runner image compromise eliminated.
* Faz 3.3 — Automatic rollback on health-check fail. Each deploy stamps
  /.deploy-sha into the artifact. On health-check failure, the deploy
  job reads the previous SHA from the live site, pulls that image, and
  redeploys. Telegram notification differentiates: rolled-back-OK,
  rollback-also-failed, no-prev-available, manual-rollback-needed.
* E.3 — cosign keyless image signing. build-image signs the GHCR
  manifest via Sigstore Fulcio (OIDC, no long-lived keys). deploy-app
  and deploy-pex verify the signature before extracting /dist —
  unsigned or tampered images cannot deploy. Identity-pinned to this
  workflow file.
* E.5 — Subresource Integrity (SRI). vite-plugin-subresource-integrity
  injects sha384 integrity= into <script>/<link> tags at build time.
  CDN/proxy compromise cannot inject tampered JS — browser blocks on
  hash mismatch.
* E.2 — Dependabot triage. 14 alerts: 7 high + 4 moderate cleared via
  npm audit fix + npm overrides (elliptic, create-ecdh). 6 low
  (transitive in vite-plugin-node-polyfills chain) accepted; the
  upstream fix proposes a semver-major DOWNGRADE which makes no sense.
* E.1 — Branch protection on main: CI Gate  required, 1 review
  required, force-push and deletion blocked.
2026-05-09 12:08:49 +03:00
pezkuwichain d93d4c6cd0 fix(docker): correct dist path after WORKDIR=/build/web
Stage 2 was looking for /build/dist but vite emits to /build/web/dist
(WORKDIR is /build/web in stage 1). Fix the COPY --from=builder path.
2026-05-08 21:39:07 +03:00
pezkuwichain faba2dee5d fix(docker): build context = pwap root so shared/ is reachable
Vite aliases @pezkuwi/utils → ../shared/utils, so the Docker build context
must include both web/ and shared/. Previous context: ./web missed shared/
which caused 'Could not load /shared/utils/formatting' at module resolution.

Changes:
- Dockerfile WORKDIR=/build/web; COPY web/* and shared/* explicitly
- Workflow context: ./ + file: ./web/Dockerfile
- Move .dockerignore from web/ to pwap root (matches new context)
2026-05-08 20:44:19 +03:00
pezkuwichain ca3976fe62 ci(security): Faz 1+2 — Telegram CEO gate, image-based deploy, hardened audits
Faz 1 — State-actor threat-model defenses:
* Telegram approval gate via PEXSEC_BOT — CEO must approve every deploy in Telegram (30-min timeout). Runs on new self-hosted pwap-runner on DEV VPS, shares /tmp/pexsec-gates/ with pexsec-bot.service.
* DEV VPS app-deploy user privilege drop — deploys no longer run as root. CI key restricted with no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-user-rc. Privilege drop verified (cannot read /etc/shadow, /root/, sudo blocked).
* Image-based deploy — Dockerfile (node 20 build → busybox:musl dist) pushed to GHCR with SHA tag. Deploys pull image, extract /dist, scp to VPS. Immutable artifacts, full provenance.
* Health check + Telegram failure alert post-deploy.
* Rollback path: workflow_dispatch with rollback_to=<sha> — skips build, redeploys old image. CEO gate still required.

Faz 2 — Higher-tier defenses:
* TruffleHog secret scan — PR diff (fast) + push full-repo (verified secrets only).
* CodeQL SAST workflow — javascript-typescript, security-extended + security-and-quality queries. PR + push + weekly cron.
* npm audit raised from --audit-level=critical to --audit-level=high (caught more CVEs).
* CI Gate  explicit merge-block job — fails if any required check is not success/skipped.
2026-05-08 20:32:48 +03:00
pezkuwichain 7fea37eb5d ci(deploy): allow workflow_dispatch to trigger deploy jobs
Enables manual re-deploy via 'gh workflow run quality-gate.yml' without
needing a code push. Useful for: redeploy after secret rotation, post-
incident recovery, deploy verification.
2026-05-08 15:06:19 +03:00
pezkuwichain 68379dcf3a ci(deploy): mirror web build to pex.mom for geo-redundancy
Split monolithic deploy job into bump-version + deploy-app + deploy-pex.
Both deploys run in parallel from same build artifact, independent
secrets per VPS. If one country blocks a domain, the other VPS keeps
serving the same version.

- bump-version: single source of version bump, runs before both deploys
- deploy-app: existing target /var/www/subdomains/app on DEV VPS
- deploy-pex: new target /var/www/pex.mom on VPS3 (217.77.6.126)

Requires secrets: VPS_PEX_HOST, VPS_PEX_USER, VPS_PEX_SSH_KEY, VPS_PEX_SSH_PORT
2026-05-08 14:07:35 +03:00
pezkuwichain 56f276af1b fix(wallet): add 20s timeout to web3Enable to prevent indefinite hang
- Wrap web3Enable() with Promise.race against a 20-second timeout
- On timeout: show descriptive error explaining the popup may be blocked
- Surface actual error messages (incl. timeout) instead of generic 'Failed to connect wallet'
- Both auto-restore and manual connect button now fail fast instead of hanging
2026-05-05 13:12:36 +03:00
pezkuwichain f024d21cf5 fix(wallet-modal): add loading state for extension connect, fix Play Store link
- Extension button now shows 'Approve in extension...' spinner while web3Enable waits
- Add generic error fallback for errors not matching 'authorize'/'not found' patterns
- Replace 'Coming soon on Play Store' with real Play Store download link (io.pezkuwichain.wallet)
- WalletConnectModal mobile hint now links directly to Play Store
- Updated in all 6 locales: en, tr, ar, fa, kmr, ckb
2026-05-05 08:28:52 +03:00
pezkuwichain 67bc28cff4 docs(readme): fix exchange URL to pex.network, add pex.mom as alt website 2026-05-04 00:36:26 +03:00
pezkuwichain d7fa9dd570 docs(readme): update URLs to app.pezkuwichain.io, pex.mom, docs.pezkuwichain.io 2026-05-04 00:34:22 +03:00
pezkuwichain 428b058cbc chore: add res/ to .gitignore (internal-only resources) 2026-05-04 00:28:44 +03:00
pezkuwichain 0b5e318538 fix(deps): npm audit fix — patch 14 high/moderate vulnerabilities in web/
Fixes: vite, rollup, dompurify, lodash, postcss, ajv, bn.js, defu,
flatted, h3, minimatch, picomatch, brace-expansion, qs
Remaining 7 (low/moderate): uuid + vite-plugin-node-polyfills require
--force (major breaking changes, deferred)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 00:16:27 +03:00
pezkuwichain 568507ab98 chore: remove leftover dev artifacts (screenshots, Zone.Identifier, PS1 script, PDFs)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 00:08:25 +03:00
pezkuwichain 198f53b96f fix(config): point production WS endpoint to rpc.pezkuwichain.io
- App.tsx fallback: localhost:9944 → wss://rpc.pezkuwichain.io
- All locales: remove hardcoded ws://127.0.0.1:9944 from error message
2026-05-03 02:00:40 +03:00
pezkuwichain 9babb94e07 fix(auth): add pexsecBot for Telegram login on app.pezkuwichain.io
- pex.mom uses @PexMomBOT (8690398980)
- app.pezkuwichain.io uses @pexsecBot (8754021997)
- Edge function selects token based on bot_id from request
2026-05-01 23:32:25 +03:00
pezkuwichain ef6a7b2583 feat(i18n): add landing page translations for Sorani, Arabic, and Farsi
All 187 landing.* keys were missing from ckb/ar/fa locales, causing fallback to English.
2026-05-01 19:32:29 +03:00
pezkuwichain d446d711ba fix(web): replace AppLayout footer with identical LandingPageDesktop footer
Footer now uses lp-footer CSS classes and identical markup to pre-login landing page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:49:12 +03:00
pezkuwichain d1af76f444 fix(web): remove ArrowRightLeft icon from trading button + match bottom tab bar to pre-login design
- Remove ArrowRightLeft icon from desktop nav Trading dropdown button
- Bottom tab bar: add max-w-md mx-auto (centered) and bump z-index to z-50 to match MobileHomeLayout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:16:09 +03:00
pezkuwichain 914d914b74 fix(lint): remove unused bodyOnly prop from LandingPageDesktop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 15:38:04 +03:00
pezkuwichain 8f57224700 feat(web): restore authenticated desktop home layout with modern section cards
- Add body content sections (HeroSection, NetworkStats, TrustScoreCalculator, ChainSpecs, RewardDistribution) after section grid
- Update section cards with distinct gradient header colors per category (Finance/green, Governance/purple, Social/blue, Education/orange)
- Fix bottom tab bar to be full-width (removed max-w-md mx-auto)
- Adjust role/score cards background to bg-gray-800/70 for contrast against main bg
- Add bodyOnly prop to LandingPageDesktop (non-breaking, unused)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 15:36:31 +03:00
pezkuwichain 1e047eba91 fix(ci): set SSH port 2222 for DEV VPS deploy 2026-05-01 14:09:27 +03:00
38 changed files with 1977 additions and 2469 deletions
+41
View File
@@ -0,0 +1,41 @@
# pwap/web Docker build context (root) — exclude everything not needed
# for `web/` build. Other monorepo subprojects stay out of the image.
# Other monorepo dirs (we only need web/ + shared/)
exchange/
mobile/
pwap-mobile/
docs/
res/
# All node_modules everywhere
**/node_modules/
**/dist/
**/build/
# Git, GitHub
.git/
.github/
# Env files (built-in vars are passed as build-args from CI)
**/.env
**/.env.*
!**/.env.example
# Editor / OS
.vscode/
.idea/
*.swp
*.swo
.DS_Store
# Logs
*.log
# Cache
**/.eslintcache
**/coverage/
# Already-built artifacts (we rebuild fresh inside container)
web/dist/
shared/**/dist/
+58
View File
@@ -0,0 +1,58 @@
name: CodeQL (SAST)
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
schedule:
# Every Sunday at 02:00 UTC — catches CVEs disclosed during the week
- cron: '0 2 * * 0'
permissions:
contents: read
security-events: write
actions: read
concurrency:
group: codeql-${{ github.ref }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze ${{ matrix.language }}
runs-on: pwap-runner
strategy:
fail-fast: false
matrix:
language: [javascript-typescript]
steps:
- uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# OWASP top-10 + injection + auth flaws + prototype pollution
queries: security-extended,security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform analysis
uses: github/codeql-action/analyze@v3
with:
category: /language:${{ matrix.language }}
# GitHub Advanced Security dashboard upload requires paid plan.
# SARIF saved as a downloadable artifact instead.
upload: false
output: /tmp/codeql-results
- name: Upload SARIF as artifact
uses: actions/upload-artifact@v4
continue-on-error: true
with:
name: codeql-sarif-${{ matrix.language }}
path: /tmp/codeql-results/*.sarif
retention-days: 7
+536 -19
View File
@@ -6,14 +6,25 @@ on:
pull_request: pull_request:
branches: [ main, develop ] branches: [ main, develop ]
workflow_dispatch: workflow_dispatch:
inputs:
rollback_to:
description: 'Rollback to git SHA (skips build, redeploys old image). Empty = normal deploy.'
required: false
default: ''
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
permissions:
contents: write # version bump commit
packages: write # GHCR push
env: env:
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }} VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }} VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
REGISTRY: ghcr.io
IMAGE_NAME: pezkuwichain/pwap-web
jobs: jobs:
# ======================================== # ========================================
@@ -21,7 +32,7 @@ jobs:
# ======================================== # ========================================
web: web:
name: Web App name: Web App
runs-on: ubuntu-latest runs-on: pwap-runner
steps: steps:
- name: Checkout code - name: Checkout code
@@ -75,13 +86,164 @@ jobs:
path: web/dist/ path: web/dist/
# ======================================== # ========================================
# DEPLOY WEB APP TO VPS # BUILD & PUSH DOCKER IMAGE TO GHCR
# Immutable artifact for audit + rollback (vs ephemeral GHA artifact).
# Tagged with git SHA so any commit can be redeployed by SHA.
# ======================================== # ========================================
deploy: build-image:
name: Deploy Web name: Build & Push Image
runs-on: ubuntu-latest runs-on: pwap-runner
needs: [web, telegram-gate]
if: |
github.ref == 'refs/heads/main' &&
(github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to == ''))
permissions:
contents: read
packages: write
id-token: write # cosign keyless signing via Sigstore OIDC
outputs:
image_sha: ${{ steps.meta.outputs.image_sha }}
image_digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Install cosign
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.4.1'
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract image metadata
id: meta
run: |
SHORT_SHA="${GITHUB_SHA:0:7}"
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
echo "image_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT
- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
context: ./
file: ./web/Dockerfile
push: true
tags: |
${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.short_sha }}
${{ steps.meta.outputs.image }}:latest
build-args: |
VITE_NETWORK=MAINNET
VITE_WS_ENDPOINT=wss://rpc.pezkuwichain.io
VITE_WS_ENDPOINT_FALLBACK_1=wss://mainnet.pezkuwichain.io
VITE_ASSET_HUB_ENDPOINT=wss://asset-hub-rpc.pezkuwichain.io
VITE_PEOPLE_CHAIN_ENDPOINT=wss://people-rpc.pezkuwichain.io
VITE_WALLETCONNECT_PROJECT_ID=8292a793b7640e8364c378e331e76d04
VITE_SUPABASE_URL=${{ secrets.VITE_SUPABASE_URL }}
VITE_SUPABASE_ANON_KEY=${{ secrets.VITE_SUPABASE_ANON_KEY }}
cache-from: type=registry,ref=${{ steps.meta.outputs.image }}:cache
cache-to: type=registry,ref=${{ steps.meta.outputs.image }}:cache,mode=max
provenance: false
- name: Sign image with cosign (keyless, Sigstore Fulcio)
env:
COSIGN_EXPERIMENTAL: '1'
run: |
IMAGE_DIGEST="${{ steps.meta.outputs.image }}@${{ steps.build.outputs.digest }}"
# cosign needs its own registry auth — docker/login-action only writes
# ~/.docker/config.json which cosign on self-hosted runner can't read
echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u "${{ github.actor }}" --password-stdin
echo "Signing $IMAGE_DIGEST"
cosign sign --yes "$IMAGE_DIGEST"
echo "✅ Image signed (transparency log: rekor.sigstore.dev)"
# ========================================
# TELEGRAM CEO APPROVAL GATE
# Runs on self-hosted pwap-runner (DEV VPS) where pexsec-bot.service
# writes the gate file to /tmp/pexsec-gates/<sha> when CEO clicks
# Approve/Cancel in Telegram. 30-minute timeout = deploy cancelled.
# ========================================
telegram-gate:
name: Telegram deploy approval
runs-on: pwap-runner
needs: [web, security-audit] needs: [web, security-audit]
if: github.ref == 'refs/heads/main' && github.event_name == 'push' if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
steps:
- name: Send approval request and wait
env:
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
SHA: ${{ github.sha }}
ACTOR: ${{ github.actor }}
MESSAGE: ${{ github.event.head_commit.message }}
run: |
SHORT="${SHA:0:7}"
GATE_DIR="/tmp/pexsec-gates"
mkdir -p "$GATE_DIR" 2>/dev/null || true
rm -f "$GATE_DIR/$SHORT" 2>/dev/null || true
# Strip Markdown special chars to prevent Telegram parse errors
SAFE_MSG=$(echo "${MESSAGE}" | head -1 | tr -d '_*`[]()#|{}!' | cut -c1-120)
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-H "Content-Type: application/json" \
-d "{
\"chat_id\": \"${CEO_CHAT_ID}\",
\"parse_mode\": \"Markdown\",
\"text\": \"🚀 *pwap/web Deploy Approval*\\n\\n\`${SHORT}\` — ${ACTOR}\\n\\n_${SAFE_MSG}_\\n\\nTargets: app.pezkuwichain.io + pex.mom\",
\"reply_markup\": {
\"inline_keyboard\": [[
{\"text\": \"✅ Approve\", \"callback_data\": \"deploy_approve:${SHORT}\"},
{\"text\": \"❌ Cancel\", \"callback_data\": \"deploy_cancel:${SHORT}\"}
]]
}
}"
echo "Waiting for Telegram approval (max 30 min)..."
TIMEOUT=1800
ELAPSED=0
while [ $ELAPSED -lt $TIMEOUT ]; do
if [ -f "$GATE_DIR/$SHORT" ]; then
DECISION=$(cat "$GATE_DIR/$SHORT")
rm -f "$GATE_DIR/$SHORT" 2>/dev/null || true
if [ "$DECISION" = "approved" ]; then
echo "Deploy approved."
exit 0
else
echo "Deploy cancelled."
exit 1
fi
fi
sleep 10
ELAPSED=$((ELAPSED + 10))
done
echo "No approval received within 30 minutes — deploy cancelled."
exit 1
# ========================================
# VERSION BUMP (RUNS BEFORE BOTH DEPLOYS)
# ========================================
bump-version:
name: Bump Version
runs-on: pwap-runner
needs: [web, security-audit, telegram-gate, build-image]
# Skip on rollback (workflow_dispatch with rollback_to set)
if: |
github.ref == 'refs/heads/main' &&
(github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to == ''))
outputs:
new_version: ${{ steps.bump.outputs.version }}
steps: steps:
- name: Checkout code - name: Checkout code
@@ -101,61 +263,416 @@ jobs:
git config user.email "github-actions[bot]@users.noreply.github.com" git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Bump version - name: Bump version
id: bump
working-directory: ./web working-directory: ./web
run: | run: |
npm version patch --no-git-tag-version npm version patch --no-git-tag-version
VERSION=$(node -p "require('./package.json').version") VERSION=$(node -p "require('./package.json').version")
echo "NEW_VERSION=$VERSION" >> $GITHUB_ENV echo "version=$VERSION" >> $GITHUB_OUTPUT
cd .. cd ..
git add web/package.json git add web/package.json
git commit -m "chore(web): bump version to $VERSION [skip ci]" || echo "No version change" git commit -m "chore(web): bump version to $VERSION [skip ci]" || echo "No version change"
git push || echo "Nothing to push" git push || echo "Nothing to push"
- name: Download build artifact # ========================================
uses: actions/download-artifact@v4 # DEPLOY TO app.pezkuwichain.io (DEV VPS)
with: # Pulls SHA-tagged image from GHCR, extracts /dist, scp to VPS.
name: web-dist # Health check + auto-rollback to .deploy-tag-prev on failure.
path: dist/ # ========================================
deploy-app:
name: Deploy app.pezkuwichain.io
runs-on: pwap-runner
needs: [telegram-gate, bump-version, build-image]
if: |
always() &&
needs.telegram-gate.result == 'success' &&
((github.event_name == 'push' && needs.build-image.result == 'success' && needs.bump-version.result == 'success') ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to != ''))
permissions:
contents: read
packages: read
env:
DOMAIN: app.pezkuwichain.io
TARGET_PATH: /var/www/subdomains/app
- name: Deploy to VPS steps:
- name: Determine image SHA
id: sha
run: |
if [ -n "${{ github.event.inputs.rollback_to }}" ]; then
echo "sha=${{ github.event.inputs.rollback_to }}" >> $GITHUB_OUTPUT
echo "Rolling back to: ${{ github.event.inputs.rollback_to }}"
else
echo "sha=${{ needs.build-image.outputs.image_sha }}" >> $GITHUB_OUTPUT
fi
- name: Capture currently-live SHA (for auto-rollback)
id: prev
run: |
# /.deploy-sha is written into every deploy; read what's live now
PREV=$(curl -sf --max-time 5 "https://${{ env.DOMAIN }}/.deploy-sha" | head -c 40 | tr -dc 'a-f0-9' || echo "")
echo "Previous live SHA: ${PREV:-unknown}"
echo "prev=$PREV" >> $GITHUB_OUTPUT
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install cosign (for verify)
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.4.1'
- name: Verify image signature (cosign keyless)
env:
COSIGN_EXPERIMENTAL: '1'
run: |
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u "${{ github.actor }}" --password-stdin
echo "Verifying signature for $IMAGE"
cosign verify "$IMAGE" \
--certificate-identity-regexp "^https://github.com/pezkuwichain/pwap/.github/workflows/quality-gate.yml@" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
> /dev/null
echo "✅ Signature valid — image was built by trusted CI"
- name: Extract /dist from image
run: |
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
docker pull "$IMAGE"
CID=$(docker create "$IMAGE")
mkdir -p dist
docker cp "$CID:/dist/." dist/
docker rm "$CID" >/dev/null
# Stamp this build's SHA into dist so future deploys can read PREV
echo "${{ steps.sha.outputs.sha }}" > dist/.deploy-sha
ls -la dist/ | head -10
- name: Deploy to DEV VPS
uses: appleboy/scp-action@v1.0.0 uses: appleboy/scp-action@v1.0.0
with: with:
host: ${{ secrets.VPS_HOST }} host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }} username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }} key: ${{ secrets.VPS_SSH_KEY }}
port: ${{ secrets.VPS_SSH_PORT || 2222 }}
source: 'dist/*' source: 'dist/*'
target: '/var/www/subdomains/app' target: '/var/www/subdomains/app'
strip_components: 1 strip_components: 1
- name: Post-deploy notification - name: Health check (60s window)
id: healthcheck
run: | run: |
echo "✅ Deployed web app v${{ env.NEW_VERSION }} to app.pezkuwichain.io" for i in 1 2 3 4 5 6; do
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
echo "✅ ${{ env.DOMAIN }} healthy"
exit 0
fi
echo "Attempt $i/6 failed, retrying in 10s..."
sleep 10
done
echo "❌ Health check failed for ${{ env.DOMAIN }}"
exit 1
# ── Automatic rollback: pull PREV SHA image, redeploy, recheck ──
- name: Auto-rollback to previous SHA
id: rollback
if: failure() && steps.healthcheck.conclusion == 'failure' && steps.prev.outputs.prev != ''
run: |
PREV="${{ steps.prev.outputs.prev }}"
echo "🔄 Rolling back ${{ env.DOMAIN }} to $PREV"
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$PREV"
docker pull "$IMAGE"
CID=$(docker create "$IMAGE")
rm -rf dist && mkdir dist
docker cp "$CID:/dist/." dist/
docker rm "$CID" >/dev/null
echo "$PREV" > dist/.deploy-sha
echo "rollback_sha=$PREV" >> $GITHUB_OUTPUT
- name: SCP rollback artifact
if: steps.rollback.outcome == 'success'
uses: appleboy/scp-action@v1.0.0
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
port: ${{ secrets.VPS_SSH_PORT || 2222 }}
source: 'dist/*'
target: '/var/www/subdomains/app'
strip_components: 1
- name: Re-health-check after rollback
if: steps.rollback.outcome == 'success'
id: healthcheck_rb
run: |
for i in 1 2 3 4 5 6; do
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
echo "✅ Rolled back successfully — ${{ env.DOMAIN }} healthy on ${{ steps.rollback.outputs.rollback_sha }}"
exit 0
fi
sleep 10
done
echo "❌ Rollback also failed!"
exit 1
- name: Post-deploy notification
if: success()
run: |
echo "✅ Deployed image ${{ steps.sha.outputs.sha }} to ${{ env.DOMAIN }}"
- name: Notify failure (Telegram)
if: failure()
env:
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
NEW_SHA: ${{ steps.sha.outputs.sha }}
PREV_SHA: ${{ steps.prev.outputs.prev }}
ROLLBACK_OUTCOME: ${{ steps.rollback.outcome }}
RECHECK_OUTCOME: ${{ steps.healthcheck_rb.outcome }}
run: |
if [ "$RECHECK_OUTCOME" = "success" ]; then
MSG="⚠️ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed health check, AUTO-ROLLED-BACK to $PREV_SHA. Site healthy."
elif [ "$ROLLBACK_OUTCOME" = "success" ]; then
MSG="🚨 pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed AND rollback to $PREV_SHA also failed. Manual intervention needed."
elif [ -z "$PREV_SHA" ]; then
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. No previous SHA available (first deploy?). Manual rollback: gh workflow run quality-gate.yml -f rollback_to=<sha>"
else
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. Auto-rollback was not attempted. Manual: gh workflow run quality-gate.yml -f rollback_to=$PREV_SHA"
fi
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-d "chat_id=${CEO_CHAT_ID}" --data-urlencode "text=$MSG"
# ========================================
# DEPLOY TO pex.mom (VPS3 — geo-redundant mirror)
# ========================================
deploy-pex:
name: Deploy pex.mom
runs-on: pwap-runner
needs: [telegram-gate, bump-version, build-image]
if: |
always() &&
needs.telegram-gate.result == 'success' &&
((github.event_name == 'push' && needs.build-image.result == 'success' && needs.bump-version.result == 'success') ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to != ''))
permissions:
contents: read
packages: read
env:
DOMAIN: pex.mom
TARGET_PATH: /var/www/pex.mom
steps:
- name: Determine image SHA
id: sha
run: |
if [ -n "${{ github.event.inputs.rollback_to }}" ]; then
echo "sha=${{ github.event.inputs.rollback_to }}" >> $GITHUB_OUTPUT
else
echo "sha=${{ needs.build-image.outputs.image_sha }}" >> $GITHUB_OUTPUT
fi
- name: Capture currently-live SHA (for auto-rollback)
id: prev
run: |
PREV=$(curl -sf --max-time 5 "https://${{ env.DOMAIN }}/.deploy-sha" | head -c 40 | tr -dc 'a-f0-9' || echo "")
echo "Previous live SHA: ${PREV:-unknown}"
echo "prev=$PREV" >> $GITHUB_OUTPUT
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install cosign (for verify)
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.4.1'
- name: Verify image signature (cosign keyless)
env:
COSIGN_EXPERIMENTAL: '1'
run: |
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
echo "Verifying signature for $IMAGE"
cosign verify "$IMAGE" \
--certificate-identity-regexp "^https://github.com/pezkuwichain/pwap/.github/workflows/quality-gate.yml@" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
> /dev/null
echo "✅ Signature valid — image was built by trusted CI"
- name: Extract /dist from image
run: |
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
docker pull "$IMAGE"
CID=$(docker create "$IMAGE")
mkdir -p dist
docker cp "$CID:/dist/." dist/
docker rm "$CID" >/dev/null
echo "${{ steps.sha.outputs.sha }}" > dist/.deploy-sha
- name: Deploy to VPS3
uses: appleboy/scp-action@v1.0.0
with:
host: ${{ secrets.VPS_PEX_HOST }}
username: ${{ secrets.VPS_PEX_USER }}
key: ${{ secrets.VPS_PEX_SSH_KEY }}
port: ${{ secrets.VPS_PEX_SSH_PORT || 22 }}
source: 'dist/*'
target: '/var/www/pex.mom'
strip_components: 1
- name: Health check (60s window)
id: healthcheck
run: |
for i in 1 2 3 4 5 6; do
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
echo "✅ ${{ env.DOMAIN }} healthy"
exit 0
fi
echo "Attempt $i/6 failed, retrying in 10s..."
sleep 10
done
echo "❌ Health check failed for ${{ env.DOMAIN }}"
exit 1
- name: Auto-rollback to previous SHA
id: rollback
if: failure() && steps.healthcheck.conclusion == 'failure' && steps.prev.outputs.prev != ''
run: |
PREV="${{ steps.prev.outputs.prev }}"
echo "🔄 Rolling back ${{ env.DOMAIN }} to $PREV"
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$PREV"
docker pull "$IMAGE"
CID=$(docker create "$IMAGE")
rm -rf dist && mkdir dist
docker cp "$CID:/dist/." dist/
docker rm "$CID" >/dev/null
echo "$PREV" > dist/.deploy-sha
echo "rollback_sha=$PREV" >> $GITHUB_OUTPUT
- name: SCP rollback artifact
if: steps.rollback.outcome == 'success'
uses: appleboy/scp-action@v1.0.0
with:
host: ${{ secrets.VPS_PEX_HOST }}
username: ${{ secrets.VPS_PEX_USER }}
key: ${{ secrets.VPS_PEX_SSH_KEY }}
port: ${{ secrets.VPS_PEX_SSH_PORT || 22 }}
source: 'dist/*'
target: '/var/www/pex.mom'
strip_components: 1
- name: Re-health-check after rollback
if: steps.rollback.outcome == 'success'
id: healthcheck_rb
run: |
for i in 1 2 3 4 5 6; do
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
echo "✅ Rolled back successfully — ${{ env.DOMAIN }} healthy on ${{ steps.rollback.outputs.rollback_sha }}"
exit 0
fi
sleep 10
done
echo "❌ Rollback also failed!"
exit 1
- name: Post-deploy notification
if: success()
run: |
echo "✅ Deployed image ${{ steps.sha.outputs.sha }} to ${{ env.DOMAIN }}"
- name: Notify failure (Telegram)
if: failure()
env:
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
NEW_SHA: ${{ steps.sha.outputs.sha }}
PREV_SHA: ${{ steps.prev.outputs.prev }}
ROLLBACK_OUTCOME: ${{ steps.rollback.outcome }}
RECHECK_OUTCOME: ${{ steps.healthcheck_rb.outcome }}
run: |
if [ "$RECHECK_OUTCOME" = "success" ]; then
MSG="⚠️ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed health check, AUTO-ROLLED-BACK to $PREV_SHA. Site healthy."
elif [ "$ROLLBACK_OUTCOME" = "success" ]; then
MSG="🚨 pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed AND rollback to $PREV_SHA also failed. Manual intervention needed."
elif [ -z "$PREV_SHA" ]; then
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. No previous SHA available (first deploy?). Manual rollback: gh workflow run quality-gate.yml -f rollback_to=<sha>"
else
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. Auto-rollback was not attempted. Manual: gh workflow run quality-gate.yml -f rollback_to=$PREV_SHA"
fi
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-d "chat_id=${CEO_CHAT_ID}" --data-urlencode "text=$MSG"
# ======================================== # ========================================
# SECURITY CHECKS (BLOCKING) # SECURITY CHECKS (BLOCKING)
# npm audit (high + critical) + TruffleHog secret scan
# ======================================== # ========================================
security-audit: security-audit:
name: Security Audit name: Security Audit
runs-on: ubuntu-latest runs-on: pwap-runner
needs: [web] needs: [web]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }}
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '20'
- name: Web - npm audit (critical only) - name: Web npm audit (high + critical, production deps only)
working-directory: ./web working-directory: ./web
run: | run: |
npm install npm install
npm audit --audit-level=critical # Audit only production dependencies. Build tooling (vite, esbuild,
# vite-plugin-node-polyfills → elliptic, etc.) ships to no user, and
# advisories on those dev deps kept blocking production deploys.
npm audit --audit-level=high --omit=dev
- name: TruffleHog Secret Scan - name: TruffleHog — PR diff (verified secrets only)
if: github.event_name == 'pull_request'
uses: trufflesecurity/trufflehog@main
with:
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.event.pull_request.head.sha }}
extra_args: --only-verified
- name: TruffleHog — full repo scan (verified secrets only)
if: github.event_name != 'pull_request'
uses: trufflesecurity/trufflehog@main uses: trufflesecurity/trufflehog@main
with: with:
path: ./ path: ./
extra_args: --only-verified extra_args: --only-verified
# ========================================
# CI GATE — explicit merge-block
# All required checks must succeed (or be skipped, e.g. for rollback path).
# Branch protection on main should require this job's success.
# ========================================
ci-gate:
name: CI Gate ✅
runs-on: pwap-runner
needs: [web, security-audit]
if: always()
steps:
- name: Verify all required jobs succeeded or were intentionally skipped
run: |
results='${{ toJSON(needs) }}'
echo "$results" | python3 -c "
import json, sys
needs = json.load(sys.stdin)
failed = [name for name, job in needs.items() if job['result'] not in ('success', 'skipped')]
if failed:
print('❌ Required jobs failed: ' + ', '.join(failed))
sys.exit(1)
print('✅ All required CI jobs passed or skipped')
"
+3
View File
@@ -1,3 +1,6 @@
# Internal resources (never commit)
res/
# Logs # Logs
logs logs
*.log *.log
-2002
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -26,7 +26,7 @@ pwap/
**Status:** ✅ Production Ready **Status:** ✅ Production Ready
The primary web interface for Pezkuwi blockchain at [pezkuwichain.app](https://pezkuwichain.app) The primary web interface for Pezkuwi blockchain at [app.pezkuwichain.io](https://app.pezkuwichain.io)
**Tech Stack:** **Tech Stack:**
- React 18 + TypeScript - React 18 + TypeScript
@@ -166,9 +166,10 @@ RTL support for CKB, AR, FA.
## Links ## Links
- **Website:** https://pezkuwichain.app - **Website:** https://app.pezkuwichain.io
- **SDK UI:** https://pezkuwichain.app/sdk - **Website (alt):** https://pex.mom
- **Documentation:** https://docs.pezkuwichain.app - **Exchange:** https://pex.network
- **Documentation:** https://docs.pezkuwichain.io
## License ## License
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 802 KiB

Submodule exchange deleted from bb3bc812ed
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 677 KiB

-31
View File
@@ -1,31 +0,0 @@
# PWAP WSL Dev Environment Setup
# Run in PowerShell as Administrator
Write-Host "=== PWAP Dev Setup ===" -ForegroundColor Cyan
# 1. Fix .wslconfig - enable mirrored networking
$wslconfig = @"
[wsl2]
memory=48GB
swap=16GB
networkingMode=mirrored
"@
Set-Content -Path "$env:USERPROFILE\.wslconfig" -Value $wslconfig
Write-Host "[OK] .wslconfig updated (mirrored networking)" -ForegroundColor Green
# 2. Restart ADB on default port
$adbPath = "C:\Users\satos\Desktop\platform-tools\adb.exe"
& $adbPath kill-server 2>$null
Start-Sleep -Seconds 1
& $adbPath start-server
Write-Host "[OK] ADB server restarted on port 5037" -ForegroundColor Green
& $adbPath devices
# 3. Shutdown WSL so new config takes effect
Write-Host "`nShutting down WSL..." -ForegroundColor Yellow
wsl --shutdown
Start-Sleep -Seconds 3
Write-Host "[OK] WSL shutdown complete" -ForegroundColor Green
Write-Host "`n=== Done! ===" -ForegroundColor Cyan
Write-Host "Now open WSL again and run: cd pwap && claude" -ForegroundColor White
BIN
View File
Binary file not shown.
+53
View File
@@ -0,0 +1,53 @@
# pwap/web — Static SPA build for distribution.
# Build context is the pwap repo ROOT (not web/) because vite aliases like
# @pezkuwi/utils, @shared/* resolve to ../shared/* — both web/ and shared/
# must be in the build context.
# Stage 1: build with Node. Stage 2: pure dist/ in busybox (smallest possible
# attacker surface — no shell, no package manager, no node runtime).
# Tag the resulting image with the git SHA in CI so rollback is just
# "pull pwap-web:<old-sha>".
# ─── Stage 1: Build ────────────────────────────────────────────
FROM node:20-alpine AS builder
WORKDIR /build/web
# Copy package files first to leverage Docker layer cache when only src changes
COPY web/package.json web/package-lock.json ./
RUN npm ci
# Copy shared/ first (less frequently changed), then web/ source
COPY shared/ /build/shared/
COPY web/ /build/web/
# Build args for environment-specific values (passed from CI)
ARG VITE_NETWORK=MAINNET
ARG VITE_WS_ENDPOINT=wss://rpc.pezkuwichain.io
ARG VITE_WS_ENDPOINT_FALLBACK_1=wss://mainnet.pezkuwichain.io
ARG VITE_ASSET_HUB_ENDPOINT=wss://asset-hub-rpc.pezkuwichain.io
ARG VITE_PEOPLE_CHAIN_ENDPOINT=wss://people-rpc.pezkuwichain.io
ARG VITE_WALLETCONNECT_PROJECT_ID=8292a793b7640e8364c378e331e76d04
ARG VITE_SUPABASE_URL
ARG VITE_SUPABASE_ANON_KEY
ENV VITE_NETWORK=$VITE_NETWORK
ENV VITE_WS_ENDPOINT=$VITE_WS_ENDPOINT
ENV VITE_WS_ENDPOINT_FALLBACK_1=$VITE_WS_ENDPOINT_FALLBACK_1
ENV VITE_ASSET_HUB_ENDPOINT=$VITE_ASSET_HUB_ENDPOINT
ENV VITE_PEOPLE_CHAIN_ENDPOINT=$VITE_PEOPLE_CHAIN_ENDPOINT
ENV VITE_WALLETCONNECT_PROJECT_ID=$VITE_WALLETCONNECT_PROJECT_ID
ENV VITE_SUPABASE_URL=$VITE_SUPABASE_URL
ENV VITE_SUPABASE_ANON_KEY=$VITE_SUPABASE_ANON_KEY
RUN npm run build
# ─── Stage 2: Distribution image ───────────────────────────────
# busybox:musl gives us a tiny base (~1.5MB) with a shell for `cp` operations
# during deploy extraction, but no npm/curl/wget/ssh — minimal attack surface
# if the image were ever exposed.
FROM busybox:musl
WORKDIR /dist
COPY --from=builder /build/web/dist /dist
LABEL org.opencontainers.image.source="https://github.com/pezkuwichain/pwap"
LABEL org.opencontainers.image.description="pwap/web static SPA — Pezkuwi wallet/exchange frontend"
LABEL org.opencontainers.image.licenses="proprietary"
CMD ["sh", "-c", "echo 'pwap-web image — extract /dist via: docker create + docker cp'; sleep infinity"]
+300 -271
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -120,7 +120,8 @@
"@pezkuwi/x-textdecoder": "^14.0.25", "@pezkuwi/x-textdecoder": "^14.0.25",
"@pezkuwi/x-textencoder": "^14.0.25", "@pezkuwi/x-textencoder": "^14.0.25",
"@pezkuwi/x-ws": "^14.0.25", "@pezkuwi/x-ws": "^14.0.25",
"@pezkuwi/networks": "^14.0.25" "@pezkuwi/networks": "^14.0.25",
"elliptic": "^6.6.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.9.0", "@eslint/js": "^9.9.0",
@@ -147,6 +148,7 @@
"typescript-eslint": "^8.0.1", "typescript-eslint": "^8.0.1",
"vite": "^7.3.1", "vite": "^7.3.1",
"vite-plugin-node-polyfills": "^0.25.0", "vite-plugin-node-polyfills": "^0.25.0",
"vite-plugin-subresource-integrity": "^0.0.12",
"vitest": "^4.0.10" "vitest": "^4.0.10"
} }
} }
+72
View File
@@ -0,0 +1,72 @@
<?xml version="1.0"?>
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="fireGradient" x1="0%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" style="stop-color:#1B5E20;stop-opacity:1"></stop>
<stop offset="30%" style="stop-color:#FF6F00;stop-opacity:1"></stop>
<stop offset="60%" style="stop-color:#FFD600;stop-opacity:1"></stop>
<stop offset="100%" style="stop-color:#D32F2F;stop-opacity:1"></stop>
</linearGradient>
<linearGradient id="innerFlame" x1="0%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" style="stop-color:#FFD600;stop-opacity:1"></stop>
<stop offset="50%" style="stop-color:#FF8F00;stop-opacity:1"></stop>
<stop offset="100%" style="stop-color:#FF5722;stop-opacity:1"></stop>
</linearGradient>
<filter id="glow">
<feGaussianBlur stdDeviation="8" result="coloredBlur"></feGaussianBlur>
<feMerge>
<feMergeNode in="coloredBlur"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<radialGradient id="sunGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" style="stop-color:#FFD600;stop-opacity:0.6"></stop>
<stop offset="100%" style="stop-color:#FFD600;stop-opacity:0"></stop>
</radialGradient>
</defs>
<circle cx="256" cy="256" r="240" fill="#1a1a2e" opacity="0.9"></circle>
<circle cx="256" cy="280" r="180" fill="url(#sunGlow)" filter="url(#glow)"></circle>
<path d="M256 60&#xA; C280 120 340 160 360 220&#xA; C380 280 360 340 340 380&#xA; C320 420 280 460 256 470&#xA; C232 460 192 420 172 380&#xA; C152 340 132 280 152 220&#xA; C172 160 232 120 256 60Z" fill="url(#fireGradient)" filter="url(#glow)">
</path>
<path d="M256 140&#xA; C270 180 310 210 320 260&#xA; C330 310 320 350 300 380&#xA; C280 410 268 430 256 440&#xA; C244 430 232 410 212 380&#xA; C192 350 182 310 192 260&#xA; C202 210 242 180 256 140Z" fill="url(#innerFlame)" opacity="0.95">
</path>
<ellipse cx="256" cy="320" rx="50" ry="80" fill="#FFFDE7" opacity="0.8">
</ellipse>
<circle cx="200" cy="180" r="8" fill="#4CAF50" opacity="0.8">
</circle>
<circle cx="312" cy="200" r="6" fill="#4CAF50" opacity="0.7">
</circle>
<circle cx="230" cy="150" r="5" fill="#81C784" opacity="0.6">
</circle>
<circle cx="280" cy="165" r="7" fill="#66BB6A" opacity="0.7">
</circle>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+1 -1
View File
@@ -113,7 +113,7 @@ function ReferralHandler() {
} }
function App() { function App() {
const endpoint = import.meta.env.VITE_WS_ENDPOINT || 'ws://127.0.0.1:9944'; const endpoint = import.meta.env.VITE_WS_ENDPOINT || 'wss://rpc.pezkuwichain.io';
return ( return (
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme"> <ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
+3
View File
@@ -5,6 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Wallet, TrendingUp, RefreshCw, Award, Plus, Coins, Send, Shield, Users, Fuel, Lock } from 'lucide-react'; import { Wallet, TrendingUp, RefreshCw, Award, Plus, Coins, Send, Shield, Users, Fuel, Lock } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { ASSET_IDS, getAssetSymbol } from '@pezkuwi/lib/wallet'; import { ASSET_IDS, getAssetSymbol } from '@pezkuwi/lib/wallet';
import { Pez20Badge } from './Pez20Badge';
import { AddTokenModal } from './AddTokenModal'; import { AddTokenModal } from './AddTokenModal';
import { TransferModal } from './TransferModal'; import { TransferModal } from './TransferModal';
import { XCMTeleportModal } from './XCMTeleportModal'; import { XCMTeleportModal } from './XCMTeleportModal';
@@ -811,6 +812,7 @@ export const AccountBalance: React.FC = () => {
<CardTitle className="text-lg font-medium text-gray-300 whitespace-nowrap"> <CardTitle className="text-lg font-medium text-gray-300 whitespace-nowrap">
{t('balance.pezBalance')} {t('balance.pezBalance')}
</CardTitle> </CardTitle>
<Pez20Badge className="flex-shrink-0" />
</div> </div>
<Button <Button
size="sm" size="sm"
@@ -853,6 +855,7 @@ export const AccountBalance: React.FC = () => {
<CardTitle className="text-lg font-medium text-gray-300"> <CardTitle className="text-lg font-medium text-gray-300">
{t('balance.usdtBalance')} {t('balance.usdtBalance')}
</CardTitle> </CardTitle>
<Pez20Badge className="flex-shrink-0" />
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
+104 -52
View File
@@ -8,6 +8,12 @@ import { fetchUserTikis, getPrimaryRole, getTikiDisplayName, getTikiEmoji, getCi
import { getAllScores, type UserScores } from '@pezkuwi/lib/scores'; import { getAllScores, type UserScores } from '@pezkuwi/lib/scores';
import { getKycStatus } from '@pezkuwi/lib/kyc'; import { getKycStatus } from '@pezkuwi/lib/kyc';
import LandingPageDesktop from './landing/LandingPageDesktop'; import LandingPageDesktop from './landing/LandingPageDesktop';
import './landing/landing.css';
import HeroSection from './HeroSection';
import { NetworkStats } from './NetworkStats';
import TrustScoreCalculator from './TrustScoreCalculator';
import ChainSpecs from './ChainSpecs';
import RewardDistribution from './RewardDistribution';
import { LanguageSwitcher } from './LanguageSwitcher'; import { LanguageSwitcher } from './LanguageSwitcher';
import NotificationBell from './notifications/NotificationBell'; import NotificationBell from './notifications/NotificationBell';
import ProposalWizard from './proposals/ProposalWizard'; import ProposalWizard from './proposals/ProposalWizard';
@@ -22,7 +28,7 @@ import {
ExternalLink, FileEdit, Users2, MessageSquare, Wifi, WifiOff, ExternalLink, FileEdit, Users2, MessageSquare, Wifi, WifiOff,
Wallet, DollarSign, PiggyBank, History, Key, TrendingUp, Wallet, DollarSign, PiggyBank, History, Key, TrendingUp,
ArrowRightLeft, Lock, LogIn, LayoutDashboard, Settings, Users, ArrowRightLeft, Lock, LogIn, LayoutDashboard, Settings, Users,
Droplet, Mail, Coins, Menu, X, ChevronDown, Droplet, Coins, Menu, X, ChevronDown,
} from 'lucide-react'; } from 'lucide-react';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { useWebSocket } from '@/contexts/WebSocketContext'; import { useWebSocket } from '@/contexts/WebSocketContext';
@@ -56,11 +62,18 @@ interface AppItem {
title: string; icon: string; imgIcon?: string; title: string; icon: string; imgIcon?: string;
route: string; href?: string; comingSoon?: boolean; requiresAuth?: boolean; route: string; href?: string; comingSoon?: boolean; requiresAuth?: boolean;
} }
interface AppSection { titleKey: string; emoji: string; borderColor: string; apps: AppItem[]; } interface AppSection {
titleKey: string; emoji: string;
headerBg: string; cardBg: string; iconBg: string;
apps: AppItem[];
}
const APP_SECTIONS: AppSection[] = [ const APP_SECTIONS: AppSection[] = [
{ {
titleKey: 'mobile.section.finance', emoji: '💰', borderColor: 'border-l-green-500', titleKey: 'mobile.section.finance', emoji: '💰',
headerBg: 'bg-gradient-to-r from-green-900/80 to-green-800/60',
cardBg: 'bg-green-950/40 border border-green-700/30',
iconBg: 'hover:bg-green-900/40',
apps: [ apps: [
{ title: 'mobile.app.wallet', icon: '👛', route: '/wallet' }, { title: 'mobile.app.wallet', icon: '👛', route: '/wallet' },
{ title: 'mobile.app.bank', icon: '🏦', route: '/finance/bank' }, { title: 'mobile.app.bank', icon: '🏦', route: '/finance/bank' },
@@ -73,7 +86,10 @@ const APP_SECTIONS: AppSection[] = [
], ],
}, },
{ {
titleKey: 'mobile.section.governance', emoji: '🏛️', borderColor: 'border-l-red-500', titleKey: 'mobile.section.governance', emoji: '🏛️',
headerBg: 'bg-gradient-to-r from-red-900/80 to-red-800/60',
cardBg: 'bg-red-950/40 border border-red-700/30',
iconBg: 'hover:bg-red-900/40',
apps: [ apps: [
{ title: 'mobile.app.president', icon: '👑', route: '/elections', requiresAuth: true }, { title: 'mobile.app.president', icon: '👑', route: '/elections', requiresAuth: true },
{ title: 'mobile.app.assembly', icon: '🏛️', route: '/governance/assembly' }, { title: 'mobile.app.assembly', icon: '🏛️', route: '/governance/assembly' },
@@ -86,20 +102,26 @@ const APP_SECTIONS: AppSection[] = [
], ],
}, },
{ {
titleKey: 'mobile.section.social', emoji: '💬', borderColor: 'border-l-blue-500', titleKey: 'mobile.section.social', emoji: '💬',
headerBg: 'bg-gradient-to-r from-blue-900/80 to-blue-800/60',
cardBg: 'bg-blue-950/40 border border-blue-700/30',
iconBg: 'hover:bg-blue-900/40',
apps: [ apps: [
{ title: 'mobile.app.whatsKurd', icon: '💬', route: '/social/whatskurd' }, { title: 'mobile.app.whatsKurd', icon: '💬', route: '/social/whatskurd' },
{ title: 'mobile.app.forum', icon: '📰', route: '/forum' }, { title: 'mobile.app.forum', icon: '📰', route: '/forum' },
{ title: 'mobile.app.kurdMedia', icon: '📺', route: '/social/kurdmedia' }, { title: 'mobile.app.kurdMedia', icon: '📺', route: '/social/kurdmedia' },
{ title: 'mobile.app.events', icon: '📅', route: '/forum', comingSoon: true }, { title: 'mobile.app.events', icon: '📅', route: '/forum', href: 'https://kurdishtts.pezkiwi.app' },
{ title: 'mobile.app.help', icon: '❓', route: '/help' }, { title: 'mobile.app.loto', icon: '🔥', imgIcon: '/loto-icon.svg', route: '/forum', href: 'https://loto.pex.mom' },
{ title: 'mobile.app.music', icon: '🎵', route: '/forum', comingSoon: true }, { title: 'mobile.app.music', icon: '🎵', route: '/forum', comingSoon: true },
{ title: 'mobile.app.rewshenbir',icon: '📡', imgIcon: '/rewshenbir-icon.png', route: '/rewshenbir', href: 'https://rewshenbir.pezkuwi.app' }, { title: 'mobile.app.rewshenbir',icon: '📡', imgIcon: '/rewshenbir-icon.png', route: '/rewshenbir', href: 'https://rewshenbir.pezkuwi.app' },
{ title: 'mobile.app.referral', icon: '👥', route: '/dashboard', requiresAuth: true }, { title: 'mobile.app.referral', icon: '👥', route: '/dashboard', requiresAuth: true },
], ],
}, },
{ {
titleKey: 'mobile.section.education', emoji: '📚', borderColor: 'border-l-yellow-500', titleKey: 'mobile.section.education', emoji: '📚',
headerBg: 'bg-gradient-to-r from-amber-900/80 to-amber-800/60',
cardBg: 'bg-amber-950/40 border border-amber-700/30',
iconBg: 'hover:bg-amber-900/40',
apps: [ apps: [
{ title: 'mobile.app.university', icon: '🎓', route: '/education/university' }, { title: 'mobile.app.university', icon: '🎓', route: '/education/university' },
{ title: 'mobile.app.perwerde', icon: '📖', route: '/education', requiresAuth: true }, { title: 'mobile.app.perwerde', icon: '📖', route: '/education', requiresAuth: true },
@@ -278,7 +300,6 @@ const AppLayout: React.FC = () => {
onClick={(e) => { e.stopPropagation(); setOpenMenu(openMenu === 'hdr-trading' ? null : 'hdr-trading'); }} onClick={(e) => { e.stopPropagation(); setOpenMenu(openMenu === 'hdr-trading' ? null : 'hdr-trading'); }}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-gray-900/70 border border-red-500/30 text-sm text-gray-300 hover:text-white transition-colors whitespace-nowrap" className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-gray-900/70 border border-red-500/30 text-sm text-gray-300 hover:text-white transition-colors whitespace-nowrap"
> >
<ArrowRightLeft className="w-3.5 h-3.5 text-red-400" />
{t('nav.trading')} {t('nav.trading')}
<ChevronDown className="w-3 h-3" /> <ChevronDown className="w-3 h-3" />
</button> </button>
@@ -477,7 +498,8 @@ const AppLayout: React.FC = () => {
<div className="pt-20 min-h-screen bg-gray-950"><P2PDashboard /></div> <div className="pt-20 min-h-screen bg-gray-950"><P2PDashboard /></div>
) : ( ) : (
/* ── LOGGED-IN DESKTOP HOME ── */ /* ── LOGGED-IN DESKTOP HOME ── */
<div className="pt-20 pb-20 min-h-screen bg-gray-950"> <>
<div className="pt-20 bg-gray-950">
<div className="max-w-7xl mx-auto px-4 py-6"> <div className="max-w-7xl mx-auto px-4 py-6">
{/* Greeting row */} {/* Greeting row */}
@@ -534,12 +556,17 @@ const AppLayout: React.FC = () => {
{/* Section cards — 2-column grid */} {/* Section cards — 2-column grid */}
<div className="grid grid-cols-1 xl:grid-cols-2 gap-5"> <div className="grid grid-cols-1 xl:grid-cols-2 gap-5">
{APP_SECTIONS.map((section, sectionIdx) => ( {APP_SECTIONS.map((section, sectionIdx) => (
<div key={section.titleKey} className="bg-gray-900/60 rounded-2xl border border-gray-800/60 overflow-hidden"> <div key={section.titleKey} className={`${section.cardBg} rounded-2xl overflow-hidden shadow-lg`}>
<div className={`flex items-center px-5 py-3 border-l-4 ${section.borderColor} bg-gray-900/40`}> {/* Colored header */}
<h3 className="text-sm font-bold text-white tracking-wide"> <div className={`flex items-center justify-between px-5 py-3.5 ${section.headerBg}`}>
{t(section.titleKey)} {section.emoji} <h3 className="text-sm font-bold text-white tracking-wide drop-shadow">
{section.emoji} {t(section.titleKey)}
</h3> </h3>
<span className="text-[11px] text-white/60 font-medium">
{section.apps.length + (sectionIdx === 1 ? govExtras.length : 0)} apps
</span>
</div> </div>
{/* App icon grid */}
<div className="grid grid-cols-4 gap-1 px-3 py-3"> <div className="grid grid-cols-4 gap-1 px-3 py-3">
{section.apps.map((app) => { {section.apps.map((app) => {
const needsLogin = app.requiresAuth && !user; const needsLogin = app.requiresAuth && !user;
@@ -548,7 +575,7 @@ const AppLayout: React.FC = () => {
key={app.title} key={app.title}
onClick={getAppClickHandler(app)} onClick={getAppClickHandler(app)}
className={`flex flex-col items-center gap-1.5 py-3 px-2 rounded-xl transition-all active:scale-95 className={`flex flex-col items-center gap-1.5 py-3 px-2 rounded-xl transition-all active:scale-95
${app.comingSoon ? 'opacity-50' : 'hover:bg-gray-800/60'}`} ${app.comingSoon ? 'opacity-40 cursor-default' : section.iconBg}`}
> >
<div className="relative"> <div className="relative">
{app.imgIcon ? ( {app.imgIcon ? (
@@ -559,7 +586,7 @@ const AppLayout: React.FC = () => {
{app.comingSoon && <span className="absolute -top-1 -right-2 text-[10px]">🔒</span>} {app.comingSoon && <span className="absolute -top-1 -right-2 text-[10px]">🔒</span>}
{needsLogin && !app.comingSoon && <span className="absolute -top-1 -right-2 text-[10px]">🔑</span>} {needsLogin && !app.comingSoon && <span className="absolute -top-1 -right-2 text-[10px]">🔑</span>}
</div> </div>
<span className="text-[11px] text-gray-300 font-medium text-center leading-tight"> <span className="text-[11px] text-gray-200 font-medium text-center leading-tight">
{t(app.title)} {t(app.title)}
</span> </span>
</button> </button>
@@ -570,10 +597,10 @@ const AppLayout: React.FC = () => {
<button <button
key={ex.title} key={ex.title}
onClick={ex.onAction} onClick={ex.onAction}
className="flex flex-col items-center gap-1.5 py-3 px-2 rounded-xl transition-all active:scale-95 hover:bg-gray-800/60" className={`flex flex-col items-center gap-1.5 py-3 px-2 rounded-xl transition-all active:scale-95 ${section.iconBg}`}
> >
<span className="text-3xl">{ex.icon}</span> <span className="text-3xl">{ex.icon}</span>
<span className="text-[11px] text-gray-300 font-medium text-center leading-tight"> <span className="text-[11px] text-gray-200 font-medium text-center leading-tight">
{t(ex.title)} {t(ex.title)}
</span> </span>
</button> </button>
@@ -584,6 +611,12 @@ const AppLayout: React.FC = () => {
</div> </div>
</div> </div>
</div> </div>
<HeroSection />
<NetworkStats key="network-stats-live" />
<div id="trust-calculator"><TrustScoreCalculator /></div>
<div id="chain-specs"><ChainSpecs /></div>
<div id="rewards"><RewardDistribution /></div>
</>
)} )}
{/* Back-to-home button */} {/* Back-to-home button */}
@@ -599,9 +632,9 @@ const AppLayout: React.FC = () => {
)} )}
</main> </main>
{/* ── BOTTOM TAB BAR (mirrors MobileHomeLayout) ── */} {/* ── BOTTOM TAB BAR ── */}
<div className="fixed bottom-0 left-0 right-0 z-40 bg-gray-950/95 backdrop-blur-md border-t border-gray-800"> <div className="fixed bottom-0 left-0 right-0 z-50 bg-gray-950/95 backdrop-blur-md border-t border-gray-800">
<div className="flex items-center justify-around h-16 max-w-md mx-auto"> <div className="flex items-center justify-around h-16">
<BottomTabBtn icon="🏠" label={t('mobile.home', 'Home')} active={currentTab === 'home'} onClick={() => navigate('/')} /> <BottomTabBtn icon="🏠" label={t('mobile.home', 'Home')} active={currentTab === 'home'} onClick={() => navigate('/')} />
<BottomTabBtn icon="🏛️" label={t('mobile.citizen', 'Citizen')} active={currentTab === 'citizen'} onClick={() => navigate('/be-citizen')} accent /> <BottomTabBtn icon="🏛️" label={t('mobile.citizen', 'Citizen')} active={currentTab === 'citizen'} onClick={() => navigate('/be-citizen')} accent />
<BottomTabBtn icon="👥" label={t('mobile.referral', 'Referral')} active={currentTab === 'referral'} onClick={() => navigate('/dashboard')} /> <BottomTabBtn icon="👥" label={t('mobile.referral', 'Referral')} active={currentTab === 'referral'} onClick={() => navigate('/dashboard')} />
@@ -609,44 +642,63 @@ const AppLayout: React.FC = () => {
</div> </div>
{/* ── FOOTER ── */} {/* ── FOOTER ── */}
<footer className="bg-gray-950 border-t border-gray-800 py-12 pb-28"> <footer className="lp-footer pb-20">
<div className="max-w-full mx-auto px-4"> <div className="lp-container">
<div className="mb-8 space-y-2 text-sm text-gray-400 text-center"> <div className="lp-foot-grid">
<p className="flex items-center justify-center gap-2"><Mail className="w-4 h-4" />info@pezkuwichain.io</p> <div className="lp-foot-brand">
<p className="flex items-center justify-center gap-2"><Mail className="w-4 h-4" />info@pezkuwichain.app</p> <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
</div> <div className="lp-logo-mark" style={{ width: 28, height: 28 }} />
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 text-left"> <h4>PezkuwiChain</h4>
<div> </div>
<h3 className="text-lg font-semibold mb-4 bg-gradient-to-r from-green-500 to-yellow-400 bg-clip-text text-transparent">PezkuwiChain</h3> <p>{t('landing.footer.desc')}</p>
<p className="text-gray-400 text-sm">{t('footer.platform')}</p> <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
<span className="lp-status-dot" />
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--fg-2)' }}>{t('landing.footer.mainnet')}</span>
</div>
</div> </div>
<div> <div className="lp-foot-col">
<h4 className="text-white font-semibold mb-4">{t('footer.about')}</h4> <h5>{t('landing.footer.network')}</h5>
<ul className="space-y-2"> <ul>
<li><a href="https://raw.githubusercontent.com/pezkuwichain/DKSweb/main/public/Whitepaper.pdf" download="Pezkuwi_Whitepaper.pdf" className="text-gray-400 hover:text-white text-sm inline-flex items-center">{t('footer.whitepaper')}<ExternalLink className="w-3 h-3 ml-1" /></a></li> <li><a href="/network">{t('landing.footer.explorer')}</a></li>
<li><a href="https://github.com/pezkuwichain" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-white text-sm inline-flex items-center">{t('footer.github')}<ExternalLink className="w-3 h-3 ml-1" /></a></li> <li><a href="/telemetry">{t('landing.footer.telemetry')}</a></li>
<li><a href="/network">{t('landing.footer.validators')}</a></li>
<li><a href="/faucet">{t('landing.footer.faucet')}</a></li>
</ul> </ul>
</div> </div>
<div> <div className="lp-foot-col">
<h4 className="text-white font-semibold mb-4">{t('footer.developers')}</h4> <h5>{t('landing.footer.use')}</h5>
<ul className="space-y-2"> <ul>
<li><a href="/api" className="text-gray-400 hover:text-white text-sm inline-flex items-center">{t('footer.api')}<ExternalLink className="w-3 h-3 ml-1" /></a></li> <li><a href="/wallet">{t('landing.footer.wallet')}</a></li>
<li><a href="/developers" className="text-gray-400 hover:text-white text-sm inline-flex items-center">{t('footer.sdk')}<ExternalLink className="w-3 h-3 ml-1" /></a></li> <li><a href="/p2p">{t('landing.footer.trade')}</a></li>
<li><a href="/">{t('landing.footer.vote')}</a></li>
<li><a href="/grants">{t('landing.footer.grants')}</a></li>
</ul> </ul>
</div> </div>
<div> <div className="lp-foot-col">
<h4 className="text-white font-semibold mb-4">{t('footer.community')}</h4> <h5>{t('landing.footer.build')}</h5>
<ul className="space-y-2"> <ul>
<li><a href="https://discord.gg/pezkuwichain" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-white text-sm inline-flex items-center">{t('footer.discord')}<ExternalLink className="w-3 h-3 ml-1" /></a></li> <li><a href="/docs">{t('landing.footer.docs')}</a></li>
<li><a href="https://x.com/PezkuwiChain" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-white text-sm inline-flex items-center">{t('footer.twitter')}<ExternalLink className="w-3 h-3 ml-1" /></a></li> <li><a href="/api">{t('landing.footer.api')}</a></li>
<li><a href="https://t.me/PezkuwiApp" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-white text-sm inline-flex items-center">{t('footer.telegram')}<ExternalLink className="w-3 h-3 ml-1" /></a></li> <li><a href="/developers">{t('landing.footer.sdk')}</a></li>
<li><a href="https://www.youtube.com/@SatoshiQazi" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-white text-sm inline-flex items-center">{t('footer.youtube')}<ExternalLink className="w-3 h-3 ml-1" /></a></li> <li><a href="https://github.com/pezkuwichain" target="_blank" rel="noopener noreferrer">{t('landing.footer.github')}</a></li>
<li><a href="https://facebook.com/profile.php?id=61582484611719" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-white text-sm inline-flex items-center">{t('footer.facebook')}<ExternalLink className="w-3 h-3 ml-1" /></a></li> </ul>
</div>
<div className="lp-foot-col">
<h5>{t('landing.footer.community')}</h5>
<ul>
<li><a href="/forum">{t('landing.footer.forum')}</a></li>
<li><a href="https://discord.gg/pezkuwichain" target="_blank" rel="noopener noreferrer">{t('landing.footer.discord')}</a></li>
<li><a href="https://t.me/PezkuwiApp" target="_blank" rel="noopener noreferrer">{t('landing.footer.telegram')}</a></li>
<li><a href="https://x.com/PezkuwiChain" target="_blank" rel="noopener noreferrer">{t('landing.footer.twitter')}</a></li>
</ul> </ul>
</div> </div>
</div> </div>
<div className="mt-8 pt-8 border-t border-gray-800 text-center"> <div className="lp-foot-bottom">
<p className="text-gray-400 text-sm">{t('footer.copyright')}</p> <span>{t('landing.footer.copyright')}</span>
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
<span className="lp-foot-flag" title="Kurdish flag" />
<span className="lp-foot-kurdish-text">{t('landing.footer.builtBy')}</span>
</div>
</div> </div>
</div> </div>
</footer> </footer>
@@ -685,7 +737,7 @@ function DesktopScoreCard({ icon, label, value, sub, color, onClick, actionLabel
}) { }) {
return ( return (
<div <div
className={`flex-shrink-0 w-36 bg-gray-900/80 rounded-xl border border-gray-800/60 border-l-4 ${color} p-3 space-y-1 className={`flex-shrink-0 w-36 bg-gray-800/70 rounded-xl border border-gray-700/50 border-l-4 ${color} p-3 space-y-1
${onClick ? 'cursor-pointer hover:bg-gray-800/60 transition-colors' : ''}`} ${onClick ? 'cursor-pointer hover:bg-gray-800/60 transition-colors' : ''}`}
onClick={onClick} onClick={onClick}
> >
+2 -2
View File
@@ -87,8 +87,8 @@ const APP_SECTIONS: AppSection[] = [
{ title: 'mobile.app.whatsKurd', icon: '💬', route: '/social/whatskurd' }, { title: 'mobile.app.whatsKurd', icon: '💬', route: '/social/whatskurd' },
{ title: 'mobile.app.forum', icon: '📰', route: '/forum' }, { title: 'mobile.app.forum', icon: '📰', route: '/forum' },
{ title: 'mobile.app.kurdMedia', icon: '📺', route: '/social/kurdmedia' }, { title: 'mobile.app.kurdMedia', icon: '📺', route: '/social/kurdmedia' },
{ title: 'mobile.app.events', icon: '📅', route: '/forum', comingSoon: true }, { title: 'mobile.app.events', icon: '📅', route: '/forum', href: 'https://kurdishtts.pezkiwi.app' },
{ title: 'mobile.app.help', icon: '❓', route: '/help' }, { title: 'mobile.app.loto', icon: '🔥', imgIcon: '/loto-icon.svg', route: '/forum', href: 'https://loto.pex.mom' },
{ title: 'mobile.app.music', icon: '🎵', route: '/forum', comingSoon: true }, { title: 'mobile.app.music', icon: '🎵', route: '/forum', comingSoon: true },
{ title: 'mobile.app.rewshenbir', icon: '📡', imgIcon: '/rewshenbir-icon.png', route: '/rewshenbir', href: 'https://rewshenbir.pezkuwi.app' }, { title: 'mobile.app.rewshenbir', icon: '📡', imgIcon: '/rewshenbir-icon.png', route: '/rewshenbir', href: 'https://rewshenbir.pezkuwi.app' },
{ title: 'mobile.app.referral', icon: '👥', route: '/dashboard', requiresAuth: true }, { title: 'mobile.app.referral', icon: '👥', route: '/dashboard', requiresAuth: true },
+28
View File
@@ -0,0 +1,28 @@
import React from 'react';
/**
* Small pill marking a token as a Pezkuwi token-standard asset.
* PEZ-20 = fungible standard (pallet-assets on Asset Hub), PEZ-721 = NFT standard.
* See docs.pezkuwichain.io → Token Standards.
*/
export const Pez20Badge: React.FC<{ standard?: 'PEZ-20' | 'PEZ-721'; className?: string }> = ({
standard = 'PEZ-20',
className = '',
}) => (
<a
href="https://docs.pezkuwichain.io/token-standards"
target="_blank"
rel="noopener noreferrer"
title={`${standard} token standard on Pezkuwi Asset Hub`}
className={
'inline-flex items-center rounded-full border border-blue-500/40 bg-blue-500/10 ' +
'px-2 py-0.5 text-[10px] font-semibold tracking-wide text-blue-300 ' +
'hover:bg-blue-500/20 transition-colors no-underline ' +
className
}
>
{standard}
</a>
);
export default Pez20Badge;
@@ -15,6 +15,8 @@ interface ChainStats {
validators: number; validators: number;
nominators: number; nominators: number;
collators: number; collators: number;
collatorsAH: number;
collatorsPeople: number;
activeProposals: number; activeProposals: number;
totalVoters: number; totalVoters: number;
citizenCount: number; citizenCount: number;
@@ -325,6 +327,7 @@ const LandingPageDesktop: React.FC = () => {
const [stats, setStats] = useState<ChainStats>({ const [stats, setStats] = useState<ChainStats>({
latestBlock: 0, finalizedBlock: 0, blockHash: '', latestBlock: 0, finalizedBlock: 0, blockHash: '',
peers: 0, validators: 0, nominators: 0, collators: 0, peers: 0, validators: 0, nominators: 0, collators: 0,
collatorsAH: 0, collatorsPeople: 0,
activeProposals: 0, totalVoters: 0, citizenCount: 0, activeProposals: 0, totalVoters: 0, citizenCount: 0,
tokensStakedPct: '—', tokensStakedPct: '—',
}); });
@@ -417,12 +420,7 @@ const LandingPageDesktop: React.FC = () => {
const validators = sessionVals.length; const validators = sessionVals.length;
setStats(prev => ({ ...prev, activeProposals, totalVoters, validators })); setStats(prev => ({ ...prev, activeProposals, totalVoters, validators }));
} catch {} } catch {}
// Nominators/staking migrated to Asset Hub — counted in the Asset Hub effect below.
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const nomCount = await (api.query.staking as any).counterForNominators?.();
if (nomCount != null) setStats(prev => ({ ...prev, nominators: nomCount.toNumber() }));
} catch {}
})(); })();
}, [api, isApiReady]); }, [api, isApiReady]);
@@ -448,10 +446,18 @@ const LandingPageDesktop: React.FC = () => {
} }
} catch {} } catch {}
// Nominators live on Asset Hub after the staking migration (AHM).
try { try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const collCount = await (assetHubApi.query.collatorSelection as any)?.candidates?.(); const nomCount = await (assetHubApi.query.staking as any)?.counterForNominators?.();
if (collCount != null) setStats(prev => ({ ...prev, collators: collCount.length })); if (nomCount != null) setStats(prev => ({ ...prev, nominators: nomCount.toNumber() }));
} catch {}
// Collators are the invulnerable set (not staking candidates, which are empty).
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const inv = await (assetHubApi.query.collatorSelection as any)?.invulnerables?.();
if (inv != null) setStats(prev => ({ ...prev, collatorsAH: inv.length, collators: inv.length + prev.collatorsPeople }));
} catch {} } catch {}
})(); })();
}, [assetHubApi, isAssetHubReady]); }, [assetHubApi, isAssetHubReady]);
@@ -465,6 +471,13 @@ const LandingPageDesktop: React.FC = () => {
const entries = await (peopleApi.query as any).tiki?.citizenNft?.entries?.(); const entries = await (peopleApi.query as any).tiki?.citizenNft?.entries?.();
if (entries) setStats(prev => ({ ...prev, citizenCount: entries.length })); if (entries) setStats(prev => ({ ...prev, citizenCount: entries.length }));
} catch {} } catch {}
// People Chain also runs invulnerable collators — add them to the total.
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const inv = await (peopleApi.query.collatorSelection as any)?.invulnerables?.();
if (inv != null) setStats(prev => ({ ...prev, collatorsPeople: inv.length, collators: prev.collatorsAH + inv.length }));
} catch {}
})(); })();
}, [peopleApi, isPeopleReady]); }, [peopleApi, isPeopleReady]);
@@ -1086,7 +1099,7 @@ const LandingPageDesktop: React.FC = () => {
<PalletItem icon="lp-i-chat" label={t('landing.pallets.whatskurd')} to="/social/whatskurd" requiresLogin /> <PalletItem icon="lp-i-chat" label={t('landing.pallets.whatskurd')} to="/social/whatskurd" requiresLogin />
<PalletItem icon="lp-i-forum" label={t('landing.pallets.forum')} to="/forum" /> <PalletItem icon="lp-i-forum" label={t('landing.pallets.forum')} to="/forum" />
<PalletItem icon="lp-i-media" label={t('landing.pallets.kurdmedia')} to="/social/kurdmedia" requiresLogin /> <PalletItem icon="lp-i-media" label={t('landing.pallets.kurdmedia')} to="/social/kurdmedia" requiresLogin />
<PalletItem icon="lp-i-cal" label={t('landing.pallets.events')} locked /> <PalletItem icon="lp-i-cal" label={t('landing.pallets.events')} external="https://kurdishtts.pezkiwi.app" />
<PalletItem icon="lp-i-help" label={t('landing.pallets.help')} to="/help" /> <PalletItem icon="lp-i-help" label={t('landing.pallets.help')} to="/help" />
<PalletItem icon="lp-i-music" label={t('landing.pallets.music')} locked /> <PalletItem icon="lp-i-music" label={t('landing.pallets.music')} locked />
<PalletItem imgSrc="/rewshenbir-icon.png" label={t('landing.pallets.rewshenbir')} external="https://rewshenbir.pezkuwi.app" /> <PalletItem imgSrc="/rewshenbir-icon.png" label={t('landing.pallets.rewshenbir')} external="https://rewshenbir.pezkuwi.app" />
+10 -1
View File
@@ -5,7 +5,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { PlusCircle, Home, ClipboardList, TrendingUp, CheckCircle2, Clock, Store, Zap, Blocks, MessageSquare } from 'lucide-react'; import { PlusCircle, Home, ClipboardList, TrendingUp, CheckCircle2, Clock, Store, Zap, Blocks, MessageSquare, CreditCard } from 'lucide-react';
import { AdList } from './AdList'; import { AdList } from './AdList';
import { CreateAd } from './CreateAd'; import { CreateAd } from './CreateAd';
import { NotificationBell } from './NotificationBell'; import { NotificationBell } from './NotificationBell';
@@ -191,6 +191,15 @@ export function P2PDashboard() {
</Badge> </Badge>
)} )}
</button> </button>
<a
href="https://buy-sell.pezkiwi.app"
target="_blank"
rel="noopener noreferrer"
className="relative flex flex-col items-center gap-0.5 px-3 py-1.5 rounded-lg hover:bg-amber-900/30 transition-colors"
>
<CreditCard className="w-5 h-5 text-amber-400" />
<span className="text-[10px] text-amber-300">{t('p2pNav.buyVisa')}</span>
</a>
</div> </div>
</div> </div>
@@ -165,9 +165,14 @@ export const WalletConnectModal: React.FC<WalletConnectModalProps> = ({ isOpen,
<ExternalLink className="mr-2 h-4 w-4" /> <ExternalLink className="mr-2 h-4 w-4" />
{t('walletModal.wcOpenApp', 'Open pezWallet')} {t('walletModal.wcOpenApp', 'Open pezWallet')}
</Button> </Button>
<p className="text-xs text-gray-500 text-center"> <a
{t('walletModal.wcInstallHint', "Don't have pezWallet? It will be available on Play Store soon.")} href="https://play.google.com/store/apps/details?id=io.pezkuwichain.wallet"
</p> target="_blank"
rel="noopener noreferrer"
className="text-xs text-gray-500 text-center hover:text-purple-400 transition-colors"
>
{t('walletModal.wcInstallHint', "Don't have pezWallet? Download it on Play Store.")}
</a>
</div> </div>
) : ( ) : (
// Desktop: QR code // Desktop: QR code
+48 -5
View File
@@ -40,6 +40,7 @@ export const WalletModal: React.FC<WalletModalProps> = ({ isOpen, onClose }) =>
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const [showWCModal, setShowWCModal] = useState(false); const [showWCModal, setShowWCModal] = useState(false);
const [isConnecting, setIsConnecting] = useState(false);
const [scores, setScores] = useState<UserScores>({ const [scores, setScores] = useState<UserScores>({
trustScore: 0, trustScore: 0,
referralScore: 0, referralScore: 0,
@@ -58,7 +59,12 @@ export const WalletModal: React.FC<WalletModalProps> = ({ isOpen, onClose }) =>
}; };
const handleConnect = async () => { const handleConnect = async () => {
await connectWallet(); setIsConnecting(true);
try {
await connectWallet();
} finally {
setIsConnecting(false);
}
}; };
const handleSelectAccount = (account: typeof accounts[0]) => { const handleSelectAccount = (account: typeof accounts[0]) => {
@@ -169,6 +175,32 @@ export const WalletModal: React.FC<WalletModalProps> = ({ isOpen, onClose }) =>
</div> </div>
)} )}
{/* Generic Error - any error not caught above */}
{error && !error.includes('authorize') && !error.includes('not found') && (
<div className="space-y-4">
<div className="p-4 bg-red-500/10 border border-red-500/30 rounded-lg">
<p className="text-sm text-red-300">{error}</p>
</div>
<Button
onClick={handleConnect}
className="w-full bg-gradient-to-r from-purple-600 to-cyan-400 hover:from-purple-700 hover:to-cyan-500"
disabled={isConnecting}
>
{isConnecting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
{t('walletModal.connectingExtension', 'Approve in extension...')}
</>
) : (
<>
<Wallet className="mr-2 h-4 w-4" />
{t('walletModal.tryAgain')}
</>
)}
</Button>
</div>
)}
{/* Connected State */} {/* Connected State */}
{selectedAccount && !error && ( {selectedAccount && !error && (
<div className="space-y-4"> <div className="space-y-4">
@@ -351,13 +383,18 @@ export const WalletModal: React.FC<WalletModalProps> = ({ isOpen, onClose }) =>
onClick={handleConnect} onClick={handleConnect}
className="w-full bg-gradient-to-r from-purple-600 to-cyan-400 hover:from-purple-700 hover:to-cyan-500" className="w-full bg-gradient-to-r from-purple-600 to-cyan-400 hover:from-purple-700 hover:to-cyan-500"
size="sm" size="sm"
disabled={isApiInitializing} disabled={isApiInitializing || isConnecting}
> >
{isApiInitializing ? ( {isApiInitializing ? (
<> <>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-2 h-4 w-4 animate-spin" />
{t('walletModal.connectingBlockchain', 'Connecting to blockchain...')} {t('walletModal.connectingBlockchain', 'Connecting to blockchain...')}
</> </>
) : isConnecting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
{t('walletModal.connectingExtension', 'Approve in extension...')}
</>
) : ( ) : (
<> <>
<Wallet className="mr-2 h-4 w-4" /> <Wallet className="mr-2 h-4 w-4" />
@@ -405,9 +442,15 @@ export const WalletModal: React.FC<WalletModalProps> = ({ isOpen, onClose }) =>
</> </>
)} )}
</Button> </Button>
<div className="flex items-center justify-center gap-1 text-xs text-gray-400"> <a
{t('walletModal.mobileComingSoon')} href="https://play.google.com/store/apps/details?id=io.pezkuwichain.wallet"
</div> target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-1 text-xs text-gray-400 hover:text-purple-400 transition-colors"
>
<ExternalLink className="h-3 w-3" />
{t('walletModal.mobilePlayStore', 'Download on Play Store')}
</a>
</div> </div>
</div> </div>
)} )}
+11 -3
View File
@@ -22,6 +22,14 @@ if (typeof window !== 'undefined' && !import.meta.env.DEV) {
import React, { createContext, useContext, useEffect, useState, useRef, ReactNode } from 'react'; import React, { createContext, useContext, useEffect, useState, useRef, ReactNode } from 'react';
import { ApiPromise, WsProvider } from '@pezkuwi/api'; import { ApiPromise, WsProvider } from '@pezkuwi/api';
import { web3Accounts, web3Enable } from '@pezkuwi/extension-dapp'; import { web3Accounts, web3Enable } from '@pezkuwi/extension-dapp';
const web3EnableWithTimeout = (origin: string, timeoutMs = 20_000): Promise<Awaited<ReturnType<typeof web3Enable>>> =>
Promise.race([
web3Enable(origin),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('Extension connection timed out. Please check if the extension popup is blocked by your browser, then click Authorize and try again.')), timeoutMs)
)
]);
import type { InjectedAccountWithMeta } from '@pezkuwi/extension-inject/types'; import type { InjectedAccountWithMeta } from '@pezkuwi/extension-inject/types';
import { DEFAULT_ENDPOINT } from '../../../shared/blockchain/pezkuwi'; import { DEFAULT_ENDPOINT } from '../../../shared/blockchain/pezkuwi';
import { getCurrentNetworkConfig } from '../../../shared/blockchain/endpoints'; import { getCurrentNetworkConfig } from '../../../shared/blockchain/endpoints';
@@ -371,7 +379,7 @@ export const PezkuwiProvider: React.FC<PezkuwiProviderProps> = ({
try { try {
// Enable extension (works for both desktop extension and pezWallet DApps browser) // Enable extension (works for both desktop extension and pezWallet DApps browser)
const extensions = await web3Enable('PezkuwiChain'); const extensions = await web3EnableWithTimeout('PezkuwiChain');
if (extensions.length === 0) return; if (extensions.length === 0) return;
// Get accounts // Get accounts
@@ -449,7 +457,7 @@ export const PezkuwiProvider: React.FC<PezkuwiProviderProps> = ({
// Desktop / pezWallet DApps browser: Try extension (injected provider) // Desktop / pezWallet DApps browser: Try extension (injected provider)
const hasExtension = !!(window as unknown as { injectedWeb3?: Record<string, unknown> }).injectedWeb3; const hasExtension = !!(window as unknown as { injectedWeb3?: Record<string, unknown> }).injectedWeb3;
const extensions = await web3Enable('PezkuwiChain'); const extensions = await web3EnableWithTimeout('PezkuwiChain');
if (extensions.length === 0) { if (extensions.length === 0) {
if (hasExtension) { if (hasExtension) {
@@ -490,7 +498,7 @@ export const PezkuwiProvider: React.FC<PezkuwiProviderProps> = ({
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.error('❌ Wallet connection failed:', err); console.error('❌ Wallet connection failed:', err);
} }
setError('Failed to connect wallet'); setError(err instanceof Error ? err.message : 'Failed to connect wallet');
} }
}; };
+197 -4
View File
@@ -1561,6 +1561,7 @@ export default {
'p2pNav.orders': 'الطلبات', 'p2pNav.orders': 'الطلبات',
'p2pNav.ads': 'الإعلانات', 'p2pNav.ads': 'الإعلانات',
'p2pNav.messages': 'الرسائل', 'p2pNav.messages': 'الرسائل',
'p2pNav.buyVisa': 'اشترِ بفيزا',
// P2P Messages Inbox // P2P Messages Inbox
'p2pMessages.title': 'الرسائل', 'p2pMessages.title': 'الرسائل',
@@ -1840,14 +1841,16 @@ export default {
'walletModal.mobileTitle': 'محفظة الجوال', 'walletModal.mobileTitle': 'محفظة الجوال',
'walletModal.mobileDesc': 'للجوال — امسح رمز QR بتطبيق pezWallet', 'walletModal.mobileDesc': 'للجوال — امسح رمز QR بتطبيق pezWallet',
'walletModal.mobileConnect': 'اتصل بـ pezWallet', 'walletModal.mobileConnect': 'اتصل بـ pezWallet',
'walletModal.mobileComingSoon': 'قريباً على Play Store', 'walletModal.mobileComingSoon': 'تنزيل من Play Store',
'walletModal.mobilePlayStore': 'تنزيل من Play Store',
'walletModal.connectingExtension': 'وافق في الإضافة...',
'walletModal.or': 'أو', 'walletModal.or': 'أو',
'walletModal.connectWC': 'اتصل عبر pezWallet (الجوال)', 'walletModal.connectWC': 'اتصل عبر pezWallet (الجوال)',
'walletModal.wcScanQR': 'امسح بـ pezWallet للاتصال', 'walletModal.wcScanQR': 'امسح بـ pezWallet للاتصال',
'walletModal.wcOpenWallet': 'اتصل بتطبيق pezWallet', 'walletModal.wcOpenWallet': 'اتصل بتطبيق pezWallet',
'walletModal.wcWaitingMobile': 'وافق على الاتصال في pezWallet', 'walletModal.wcWaitingMobile': 'وافق على الاتصال في pezWallet',
'walletModal.wcOpenApp': 'افتح pezWallet', 'walletModal.wcOpenApp': 'افتح pezWallet',
'walletModal.wcInstallHint': 'ليس لديك pezWallet؟ سيكون متاحاً قريباً على Play Store.', 'walletModal.wcInstallHint': 'ليس لديك pezWallet؟ حمّله من Play Store.',
'walletModal.wcGenerating': 'جاري إنشاء رمز QR...', 'walletModal.wcGenerating': 'جاري إنشاء رمز QR...',
'walletModal.wcWaiting': 'في انتظار اتصال المحفظة...', 'walletModal.wcWaiting': 'في انتظار اتصال المحفظة...',
'walletModal.wcConnected': 'تم الاتصال!', 'walletModal.wcConnected': 'تم الاتصال!',
@@ -2892,7 +2895,7 @@ export default {
// Network Stats // Network Stats
'networkStats.disconnected': 'الشبكة غير متصلة', 'networkStats.disconnected': 'الشبكة غير متصلة',
'networkStats.disconnectedDesc': أكد من تشغيل عقدة المدقق على ws://127.0.0.1:9944', 'networkStats.disconnectedDesc': عذر الاتصال بالشبكة. يرجى المحاولة لاحقاً.',
'networkStats.connecting': 'جاري الاتصال بالشبكة...', 'networkStats.connecting': 'جاري الاتصال بالشبكة...',
'networkStats.title': 'حالة الشبكة', 'networkStats.title': 'حالة الشبكة',
'networkStats.connected': 'متصل', 'networkStats.connected': 'متصل',
@@ -3785,7 +3788,7 @@ export default {
'mobile.app.bank': 'البنك', 'mobile.app.bank': 'البنك',
'mobile.app.exchange': 'البورصة', 'mobile.app.exchange': 'البورصة',
'mobile.app.dex': 'Pez-DEX', 'mobile.app.dex': 'Pez-DEX',
'mobile.app.p2p': 'P2P', 'mobile.app.p2p': 'P2P/Buy-Sell',
'mobile.app.b2b': 'B2B', 'mobile.app.b2b': 'B2B',
'mobile.app.bacZekat': 'الضريبة/الزكاة', 'mobile.app.bacZekat': 'الضريبة/الزكاة',
'mobile.app.launchpad': 'منصة الإطلاق', 'mobile.app.launchpad': 'منصة الإطلاق',
@@ -3802,6 +3805,7 @@ export default {
'mobile.app.kurdMedia': 'كورد ميديا', 'mobile.app.kurdMedia': 'كورد ميديا',
'mobile.app.events': 'الفعاليات', 'mobile.app.events': 'الفعاليات',
'mobile.app.help': 'المساعدة', 'mobile.app.help': 'المساعدة',
'mobile.app.loto': 'لوتو',
'mobile.app.music': 'الموسيقى', 'mobile.app.music': 'الموسيقى',
'mobile.app.vpn': 'VPN', 'mobile.app.vpn': 'VPN',
'mobile.app.referral': 'الإحالة', 'mobile.app.referral': 'الإحالة',
@@ -3938,4 +3942,193 @@ export default {
'help.feature.community': 'التواصل مع المجتمع', 'help.feature.community': 'التواصل مع المجتمع',
'help.whatskurd.title': 'مراسلة WhatsKURD', 'help.whatskurd.title': 'مراسلة WhatsKURD',
'help.whatskurd.desc': 'تواصل معنا عبر نظام المراسلة على البلوك تشين', 'help.whatskurd.desc': 'تواصل معنا عبر نظام المراسلة على البلوك تشين',
// Landing page
'landing.nav.network': 'الشبكة',
'landing.nav.governance': 'الحوكمة',
'landing.nav.trading': 'التداول',
'landing.nav.citizens': 'المواطنون',
'landing.nav.docs': 'التوثيق',
'landing.nav.mainnet': 'الشبكة الرئيسية',
'landing.nav.connectWallet': 'ربط المحفظة',
'landing.nav.login': 'تسجيل الدخول',
'landing.nav.peers': 'الأعضاء',
'landing.hero.badge': 'الدولة الرقمية الكردية · الآن على الشبكة الرئيسية',
'landing.hero.badgeVersion': 'v1.0',
'landing.hero.h1part1': 'سلسلة كتل سيادية',
'landing.hero.h1part2': 'لأمة',
'landing.hero.h1accent': 'بلا حدود',
'landing.hero.h1part3': '.',
'landing.hero.sub': 'PezkuwiChain بلوك تشين عام ومفتوح — بُني أولاً للأمة الكردية، ولكل الشعوب عديمة الجنسية والأمم الثقافية حول العالم.',
'landing.hero.cta.explore': 'استكشف الشبكة ←',
'landing.hero.cta.whitepaper': 'اقرأ الورقة البيضاء',
'landing.hero.sun.name': 'روج · شمس كردستان',
'landing.hero.sun.rays': '٢١ شعاعاً · ↻ منذ ١٩٣٢',
'landing.statbar.network': 'الشبكة',
'landing.statbar.connected': 'متصل',
'landing.statbar.latestBlock': 'آخر كتلة',
'landing.statbar.finalized': 'مكتمل',
'landing.statbar.finalizedMeta': '~كتلتان خلف',
'landing.statbar.validators': 'المحققون',
'landing.statbar.validatorsMeta': 'يتحققون من الكتل',
'landing.statbar.collators': 'المجمّعون',
'landing.statbar.collatorsMeta': 'ينتجون الكتل',
'landing.statbar.nominators': 'المرشِّحون',
'landing.statbar.nominatorsMeta': 'يراهنون على المحققين',
'landing.ticker.block': 'كتلة',
'landing.ticker.validators': 'محققون',
'landing.ticker.nominators': 'مرشِّحون',
'landing.ticker.citizens': 'مواطنون',
'landing.ticker.staked': 'مراهَن به',
'landing.ticker.proposals': 'مقترحات',
'landing.ticker.peers': 'أعضاء',
'landing.network.eyebrow': '// ٠١ · الشبكة الحية',
'landing.network.h2': 'أرقام',
'landing.network.h2em': 'تحكم نفسها.',
'landing.network.p': 'كل مقياس أدناه مُجلَب مباشرة من سلسلة التتابع — ما يراه المحققون، تراه أنت.',
'landing.network.activeProposals': 'المقترحات النشطة',
'landing.network.proposalsMeta': '↗ الحوكمة حية',
'landing.network.totalVoters': 'إجمالي الناخبين',
'landing.network.votersMeta': 'التصويت بالاقتناع',
'landing.network.tokensStaked': 'الرموز المراهَن بها',
'landing.network.stakedMeta': 'من إجمالي العرض',
'landing.network.citizens': 'مواطنون حول العالم',
'landing.network.citizensMeta': 'في ١٨٧ دولة',
'landing.features.eyebrow': '// ٠٢ · ما يمكنك فعله',
'landing.features.h2': 'سلسلة واحدة، كل',
'landing.features.h2em': 'أداة مدنية.',
'landing.features.p': 'طبقة Layer 1 مستقلة — مشتقة أصلاً من Polkadot، والآن قاعدة كود سيادية كاملة.',
'landing.features.01.eyebrow': '٠١ · الحوكمة',
'landing.features.01.h3': 'صوّت على القواعد التي تحكم الشبكة والأمة.',
'landing.features.01.p': 'استفتاءات بأسلوب OpenGov مع التصويت بالاقتناع، والتفويض، وفترة قرار ٧ أيام.',
'landing.features.01.link': 'افتح لوحة الحوكمة ←',
'landing.features.02.eyebrow': '٠٢ · المواطنة',
'landing.features.02.h3': 'هويتك، موقّعة من الدولة.',
'landing.features.02.p': 'NFT روحي على سلسلة الشعب. درجة الثقة، السمات الموثقة، وزن التصويت.',
'landing.features.02.link': 'كن مواطناً ←',
'landing.features.03.eyebrow': '٠٣ · الاقتصاد',
'landing.features.03.h3': 'تداول وأقرض وموّل الأمة بـ HEZ.',
'landing.features.03.p': 'مجمعات DEX على Asset Hub، وP2P مدعوم بالضمانة، وخزينة تمول المنح.',
'landing.features.03.link': 'افتح الاقتصاد ←',
'landing.features.04.eyebrow': '٠٤ · المحققون',
'landing.features.04.p': 'Nominated Proof-of-Stake يؤمّن كل كتلة. راهن بـ HEZ لدعم محقق.',
'landing.features.04.h3suffix': 'عقدة. ١٢ أمة. صفر توقف منذ التكوين.',
'landing.features.04.link': 'شغّل عقدة ←',
'landing.arch.eyebrow': '// ٠٣ · الهندسة المعمارية',
'landing.arch.h2': 'ثلاث سلاسل،',
'landing.arch.h2em': 'أمة واحدة.',
'landing.arch.p': 'ثلاث سلاسل مستقلة، مملوكة بالكامل من قبل الشبكة.',
'landing.arch.rc.tag': 'Pezkuwi RC · سلسلة التتابع',
'landing.arch.rc.h4': 'Pezkuwi Relay',
'landing.arch.rc.p': 'الحوكمة، واختيار المحقق، والتنسيق عبر السلاسل.',
'landing.arch.ah.tag': 'Pezkuwi AH · Asset Hub',
'landing.arch.ah.h4': 'HezDex على Asset Hub',
'landing.arch.ah.p': 'إصدار HEZ وPEZ، مجمعات السيولة، المقايضات على السلسلة، الرهان.',
'landing.arch.people.tag': 'Pezkuwi People · سلسلة الشعب',
'landing.arch.people.h4': 'هوية Tîkî',
'landing.arch.people.p': 'NFT المواطنة، KYC، درجة الثقة، الرسم الاجتماعي.',
'landing.arch.stats.block': 'كتلة',
'landing.arch.stats.time': 'الوقت',
'landing.arch.stats.validators': 'محققون',
'landing.arch.stats.staked': 'مراهَن به',
'landing.arch.stats.collators': 'مجمّعون',
'landing.arch.stats.nominators': 'مرشِّحون',
'landing.arch.stats.citizens': 'مواطنون',
'landing.arch.stats.countries': 'دول',
'landing.tok.eyebrow': '// ٠٤ · الاقتصاد الرمزي',
'landing.tok.h2': 'رمزان.',
'landing.tok.h2em': 'دوران.',
'landing.tok.p': 'HEZ وحدة المنفعة المستقرة للشبكة. PEZ رمز مكافأة المواطن.',
'landing.tok.tabHez': 'HEZ · المنفعة',
'landing.tok.tabPez': 'PEZ · المواطن',
'landing.tok.total': 'الإجمالي',
'landing.ref.eyebrow': '// كن مرجعاً',
'landing.ref.h2': 'لا تدعو فقط —',
'landing.ref.h2em': 'تُرشِّح.',
'landing.ref.p': 'كل صديق توافق عليه يصبح مواطناً في PezkuwiChain ويمنحك +١٠ درجات ثقة.',
'landing.ref.step1.label': 'الخطوة ١',
'landing.ref.step1.title': 'شارك رابطك',
'landing.ref.step1.desc': 'أرسل رابط إحالتك الشخصي من محفظة Pezkuwi إلى شخص تثق به.',
'landing.ref.step2.label': 'الخطوة ٢',
'landing.ref.step2.title': 'يتقدمون بطلب',
'landing.ref.step2.desc': 'يقدم صديقك طلب مواطنة على PezkuwiChain وينتظر موافقتك.',
'landing.ref.step3.label': 'الخطوة ٣',
'landing.ref.step3.title': 'توافق — ثم يوقّعون',
'landing.ref.step3.desc': 'كن مرجعهم، ثم يوقّعون معاملة المواطنة. تنمو درجة ثقتك بـ +١٠.',
'landing.cta.eyebrow': '// انضم إلى الشبكة',
'landing.cta.h2': 'المواطنة',
'landing.cta.h2em': 'على بُعد توقيع واحد.',
'landing.cta.p': 'اربط محفظتك، واسكُب NFT مواطنتك، وصوّت على المقترحات.',
'landing.cta.become': 'كن مواطناً ←',
'landing.cta.validator': 'شغّل محققاً',
'landing.cta.services': 'الخدمات المتاحة',
'landing.pallets.eyebrow': '// ٠٢ب · منظومة المحفظة',
'landing.pallets.h2': 'كل أداة،',
'landing.pallets.h2em': 'محفظة واحدة.',
'landing.pallets.p': 'ستة عشر طبقاً منظمة في أربعة أعمدة — المالية، الحوكمة، الاجتماعي، والتعليم.',
'landing.pallets.finance': 'المالية',
'landing.pallets.financeCount': '٨ وحدات',
'landing.pallets.governance': 'الحوكمة',
'landing.pallets.governanceCount': '٨ وحدات',
'landing.pallets.social': 'الاجتماعي',
'landing.pallets.socialCount': '٥ مباشرة · ٣ قريباً',
'landing.pallets.education': 'التعليم',
'landing.pallets.educationCount': '٤ مباشرة · ٤ قريباً',
'landing.pallets.comingSoon': 'قريباً',
'landing.pallets.wallet': 'المحفظة',
'landing.pallets.bank': 'البنك',
'landing.pallets.exchange': 'البورصة',
'landing.pallets.dex': 'Pez-DEX',
'landing.pallets.p2p': 'P2P',
'landing.pallets.b2b': 'B2B',
'landing.pallets.zekat': 'الضريبة/الزكاة',
'landing.pallets.launchpad': 'منصة الإطلاق',
'landing.pallets.president': 'الرئيس',
'landing.pallets.assembly': 'البرلمان',
'landing.pallets.vote': 'التصويت',
'landing.pallets.validators': 'المحققون',
'landing.pallets.justice': 'العدالة',
'landing.pallets.proposals': 'المقترحات',
'landing.pallets.polls': 'الاستطلاعات',
'landing.pallets.identity': 'الهوية',
'landing.pallets.whatskurd': 'whatsKURD',
'landing.pallets.forum': 'المنتدى',
'landing.pallets.kurdmedia': 'KurdMedia',
'landing.pallets.events': 'الفعاليات',
'landing.pallets.help': 'المساعدة',
'landing.pallets.music': 'الموسيقى',
'landing.pallets.rewshenbir': 'روشنبير',
'landing.pallets.referral': 'الإحالة',
'landing.pallets.university': 'الجامعة',
'landing.pallets.perwerde': 'پەروەردە',
'landing.pallets.certificates': 'الشهادات',
'landing.pallets.research': 'البحث',
'landing.pallets.library': 'المكتبة',
'landing.pallets.tutor': 'المعلم',
'landing.pallets.labs': 'المختبرات',
'landing.pallets.languages': 'اللغات',
'landing.footer.desc': 'طبقة Layer 1 عامة ومفتوحة لسيادة رقمية للأمم عديمة الجنسية والأمم الثقافية — الكرد أولاً، كل الشعوب تالياً.',
'landing.footer.mainnet': 'الشبكة الرئيسية · كتلة كل ٦ ثواني',
'landing.footer.network': 'الشبكة',
'landing.footer.use': 'الاستخدام',
'landing.footer.build': 'البناء',
'landing.footer.community': 'المجتمع',
'landing.footer.explorer': 'المستكشف',
'landing.footer.telemetry': 'القياس عن بُعد',
'landing.footer.validators': 'المحققون',
'landing.footer.faucet': 'الصنبور',
'landing.footer.wallet': 'المحفظة',
'landing.footer.trade': 'التداول',
'landing.footer.vote': 'التصويت',
'landing.footer.grants': 'المنح',
'landing.footer.docs': 'التوثيق',
'landing.footer.api': 'مرجع API',
'landing.footer.sdk': 'SDK',
'landing.footer.github': 'GitHub',
'landing.footer.forum': 'المنتدى',
'landing.footer.discord': 'Discord',
'landing.footer.telegram': 'Telegram',
'landing.footer.twitter': 'X / Twitter',
'landing.footer.copyright': '© ٢٠٢٦ PezkuwiChain · مفتوح المصدر تحت MIT',
'landing.footer.builtBy': 'بناه الشعب الرقمي الكردي لكل أمة وشعب عديم الجنسية',
}; };
+197 -4
View File
@@ -1551,6 +1551,7 @@ export default {
'p2pNav.orders': 'داواکاریەکان', 'p2pNav.orders': 'داواکاریەکان',
'p2pNav.ads': 'ڕیکلامەکان', 'p2pNav.ads': 'ڕیکلامەکان',
'p2pNav.messages': 'پەیامەکان', 'p2pNav.messages': 'پەیامەکان',
'p2pNav.buyVisa': 'بە ڤیزا بکڕە',
// P2P Messages Inbox // P2P Messages Inbox
'p2pMessages.title': 'پەیامەکان', 'p2pMessages.title': 'پەیامەکان',
@@ -1830,14 +1831,16 @@ export default {
'walletModal.mobileTitle': 'جزدانی مۆبایل', 'walletModal.mobileTitle': 'جزدانی مۆبایل',
'walletModal.mobileDesc': 'بۆ مۆبایل — بە pezWallet QR کۆد بخوێنەوە', 'walletModal.mobileDesc': 'بۆ مۆبایل — بە pezWallet QR کۆد بخوێنەوە',
'walletModal.mobileConnect': 'بە pezWallet پەیوەندی بکە', 'walletModal.mobileConnect': 'بە pezWallet پەیوەندی بکە',
'walletModal.mobileComingSoon': 'بەم زووانە لە Play Store', 'walletModal.mobileComingSoon': 'دابەزاندن لە Play Store',
'walletModal.mobilePlayStore': 'دابەزاندن لە Play Store',
'walletModal.connectingExtension': 'لە پێوەکراوەکەدا پەسەندی بکە...',
'walletModal.or': 'یان', 'walletModal.or': 'یان',
'walletModal.connectWC': 'بە pezWallet پەیوەندی بکە (مۆبایل)', 'walletModal.connectWC': 'بە pezWallet پەیوەندی بکە (مۆبایل)',
'walletModal.wcScanQR': 'بۆ پەیوەندیکردن بە pezWallet سکان بکە', 'walletModal.wcScanQR': 'بۆ پەیوەندیکردن بە pezWallet سکان بکە',
'walletModal.wcOpenWallet': 'بە pezWallet پەیوەندی بکە', 'walletModal.wcOpenWallet': 'بە pezWallet پەیوەندی بکە',
'walletModal.wcWaitingMobile': 'لە pezWallet پەیوەندییەکە پشتڕاست بکەوە', 'walletModal.wcWaitingMobile': 'لە pezWallet پەیوەندییەکە پشتڕاست بکەوە',
'walletModal.wcOpenApp': 'pezWallet بکەوە', 'walletModal.wcOpenApp': 'pezWallet بکەوە',
'walletModal.wcInstallHint': 'pezWallet نییە؟ بەم زووانە لە Play Store بەردەست دەبێت.', 'walletModal.wcInstallHint': 'pezWallet نییە؟ لە Play Store دابەزێنە.',
'walletModal.wcGenerating': 'QR کۆد دروستدەکرێت...', 'walletModal.wcGenerating': 'QR کۆد دروستدەکرێت...',
'walletModal.wcWaiting': 'چاوەڕوانی پەیوەندیکردنی جزدان...', 'walletModal.wcWaiting': 'چاوەڕوانی پەیوەندیکردنی جزدان...',
'walletModal.wcConnected': 'پەیوەندی کرا!', 'walletModal.wcConnected': 'پەیوەندی کرا!',
@@ -2882,7 +2885,7 @@ export default {
// Network Stats // Network Stats
'networkStats.disconnected': 'تۆڕ پچڕاوە', 'networkStats.disconnected': 'تۆڕ پچڕاوە',
'networkStats.disconnectedDesc': 'دڵنیابەرەوە کە گرێی پشتڕاستکەرەوەکەت لە ws://127.0.0.1:9944 کاردەکات', 'networkStats.disconnectedDesc': 'پەیوەندی بە تۆڕەکە نەکرا. تکایە دواتر هەوڵبدەرەوە.',
'networkStats.connecting': 'پەیوەستبوون بە تۆڕ...', 'networkStats.connecting': 'پەیوەستبوون بە تۆڕ...',
'networkStats.title': 'بارودۆخی تۆڕ', 'networkStats.title': 'بارودۆخی تۆڕ',
'networkStats.connected': 'پەیوەستە', 'networkStats.connected': 'پەیوەستە',
@@ -3775,7 +3778,7 @@ export default {
'mobile.app.bank': 'بانک', 'mobile.app.bank': 'بانک',
'mobile.app.exchange': 'ئاڵوگۆڕ', 'mobile.app.exchange': 'ئاڵوگۆڕ',
'mobile.app.dex': 'Pez-DEX', 'mobile.app.dex': 'Pez-DEX',
'mobile.app.p2p': 'P2P', 'mobile.app.p2p': 'P2P/Buy-Sell',
'mobile.app.b2b': 'B2B', 'mobile.app.b2b': 'B2B',
'mobile.app.bacZekat': 'باج/زەکات', 'mobile.app.bacZekat': 'باج/زەکات',
'mobile.app.launchpad': 'دەستپێکردن', 'mobile.app.launchpad': 'دەستپێکردن',
@@ -3792,6 +3795,7 @@ export default {
'mobile.app.kurdMedia': 'کوردمیدیا', 'mobile.app.kurdMedia': 'کوردمیدیا',
'mobile.app.events': 'چالاکی', 'mobile.app.events': 'چالاکی',
'mobile.app.help': 'یارمەتی', 'mobile.app.help': 'یارمەتی',
'mobile.app.loto': 'لۆتۆ',
'mobile.app.music': 'مۆسیقا', 'mobile.app.music': 'مۆسیقا',
'mobile.app.vpn': 'VPN', 'mobile.app.vpn': 'VPN',
'mobile.app.referral': 'ئاماژە', 'mobile.app.referral': 'ئاماژە',
@@ -3928,4 +3932,193 @@ export default {
'help.feature.community': 'پەیوەندی کۆمەڵگە', 'help.feature.community': 'پەیوەندی کۆمەڵگە',
'help.whatskurd.title': 'WhatsKURD پەیامگێڕ', 'help.whatskurd.title': 'WhatsKURD پەیامگێڕ',
'help.whatskurd.desc': 'لەڕێگەی سیستەمی پەیامگێڕی بلۆکچێیندا پەیوەندیمان پێوە بکە', 'help.whatskurd.desc': 'لەڕێگەی سیستەمی پەیامگێڕی بلۆکچێیندا پەیوەندیمان پێوە بکە',
// Landing page
'landing.nav.network': 'تۆڕ',
'landing.nav.governance': 'حوکمڕانی',
'landing.nav.trading': 'بازرگانی',
'landing.nav.citizens': 'هاووڵاتیان',
'landing.nav.docs': 'بەڵگەنامەکان',
'landing.nav.mainnet': 'تۆڕی سەرەکی',
'landing.nav.connectWallet': 'پووچەڵکە پەیوەندبکە',
'landing.nav.login': 'چوونەژوورەوە',
'landing.nav.peers': 'ئەندام',
'landing.hero.badge': 'دەوڵەتی دیجیتاڵی کوردستان · ئێستا لە تۆڕی سەرەکیدا',
'landing.hero.badgeVersion': 'v1.0',
'landing.hero.h1part1': 'زنجیرەیەکی سەربەخۆ',
'landing.hero.h1part2': 'بۆ نەتەوەیەکی',
'landing.hero.h1accent': 'بێسنوور',
'landing.hero.h1part3': '.',
'landing.hero.sub': 'PezkuwiChain بلۆکچێینێکی گشتی و بێ-مۆڵەتەیی — یەکەم جار بۆ نەتەوەی کوردی دروستکراوە، و بۆ هەر خەڵکی بێ-دەوڵەت و نەتەوەی فەرهەنگی لەسەرتاسەری جیهان.',
'landing.hero.cta.explore': 'تۆڕەکە بگەڕێ ←',
'landing.hero.cta.whitepaper': 'کاغەزی سپی بخوێنەرەوە',
'landing.hero.sun.name': 'ڕۆژ · خۆری کوردستان',
'landing.hero.sun.rays': '٢١ تیژ · ↻ لە ١٩٣٢ەوە',
'landing.statbar.network': 'تۆڕ',
'landing.statbar.connected': 'پەیوەندیکراو',
'landing.statbar.latestBlock': 'دوایین بلۆک',
'landing.statbar.finalized': 'کۆتایی پێهاتوو',
'landing.statbar.finalizedMeta': '~٢ بلۆک دواکەوتوو',
'landing.statbar.validators': 'دڵنیاکەرەوەکان',
'landing.statbar.validatorsMeta': 'بلۆکەکان دڵنیا دەکەنەوە',
'landing.statbar.collators': 'کۆکەرەوەکان',
'landing.statbar.collatorsMeta': 'بلۆکەکان بەرهەم دەهێنن',
'landing.statbar.nominators': 'نامزدکەرەوەکان',
'landing.statbar.nominatorsMeta': 'بۆ دڵنیاکەرەوەکان ستاک دەکەن',
'landing.ticker.block': 'بلۆک',
'landing.ticker.validators': 'دڵنیاکەرەوەکان',
'landing.ticker.nominators': 'نامزدکەرەوەکان',
'landing.ticker.citizens': 'هاووڵاتیان',
'landing.ticker.staked': 'ستاک کراو',
'landing.ticker.proposals': 'پێشنیارەکان',
'landing.ticker.peers': 'ئەندامان',
'landing.network.eyebrow': '// ٠١ · تۆڕی زیندوو',
'landing.network.h2': 'ژمارەیەک کە',
'landing.network.h2em': 'خۆی حوکمی دەکات.',
'landing.network.p': 'هەر ئامارێک لەژێرەوە ڕاستەوخۆ لە زنجیرەی پەیوەندەوە وەردەگیرێت.',
'landing.network.activeProposals': 'پێشنیارە چالاکەکان',
'landing.network.proposalsMeta': '↗ حوکمڕانی زیندووە',
'landing.network.totalVoters': 'کۆی دەنگدەران',
'landing.network.votersMeta': 'دەنگدانی باوەڕ',
'landing.network.tokensStaked': 'تۆکنی ستاک کراو',
'landing.network.stakedMeta': 'لە کۆی دابینکراو',
'landing.network.citizens': 'هاووڵاتیان لە جیهاندا',
'landing.network.citizensMeta': 'لە ١٨٧ وڵاتدا',
'landing.features.eyebrow': '// ٠٢ · ئەوەی دەتوانی بکەیت',
'landing.features.h2': 'یەک زنجیرە، هەموو',
'landing.features.h2em': 'ئامرازی شارستانی.',
'landing.features.p': 'Layer 1ـێکی سەربەخۆ — لە سەرەتاوە فۆرکی Polkadot بوو، ئێستا کۆدبەیسێکی تەواو سەربەخۆیە.',
'landing.features.01.eyebrow': '٠١ · حوکمڕانی',
'landing.features.01.h3': 'لەسەر ئەو یاسانە دەنگ بدە کە تۆڕ و نەتەوەت بەڕێدەبات.',
'landing.features.01.p': 'ڕێفراندۆمی شێوازی OpenGov بە دەنگدانی باوەڕ، نوێنەری، و ماوەی بڕیار ٧ ڕۆژ.',
'landing.features.01.link': 'داشبۆردی حوکمڕانی بکەرەوە ←',
'landing.features.02.eyebrow': '٠٢ · هاووڵاتیبوون',
'landing.features.02.h3': 'ناسنامەت، واژووکراوی دەوڵەت.',
'landing.features.02.p': 'NFT سۆڵبەندی لە زنجیرەی خەڵکدا. پوانەی باوەڕ، تایبەتمەندییە دڵنیاکراوەکان، ئیقرار دەنگدان.',
'landing.features.02.link': 'بە هاووڵاتی بە ←',
'landing.features.03.eyebrow': '٠٣ · ئابووری',
'landing.features.03.h3': 'بازرگانی، قەرز، و فینانسکردنی نەتەوە بە HEZ.',
'landing.features.03.p': 'گۆڵی DEX لە Asset Hub، P2P پاراستووی بە ئەمانەت، و خەزینە.',
'landing.features.03.link': 'ئابووری بکەرەوە ←',
'landing.features.04.eyebrow': '٠٤ · دڵنیاکەرەوەکان',
'landing.features.04.p': 'Nominated Proof-of-Stake هەموو بلۆکێک دڵنیا دەکاتەوە. HEZ ستاک بکە بۆ پشتگیری دڵنیاکەرەوەیەک.',
'landing.features.04.h3suffix': 'گرێ. ١٢ نەتەوە. هیچ وەستانێک لە دواوە نیە.',
'landing.features.04.link': 'گرێ بخستەکارەوە ←',
'landing.arch.eyebrow': '// ٠٣ · ئەندازیاری',
'landing.arch.h2': 'سێ زنجیرە،',
'landing.arch.h2em': 'یەک نەتەوە.',
'landing.arch.p': 'سێ زنجیرەی سەربەخۆ، بە تەواوی خاوەنداری تۆڕەکەیە.',
'landing.arch.rc.tag': 'Pezkuwi RC · زنجیرەی پەیوەند',
'landing.arch.rc.h4': 'Pezkuwi Relay',
'landing.arch.rc.p': 'حوکمڕانی، دیاریکردنی دڵنیاکەرەوە، و هاوئاهەنگیی نێوان زنجیرەکان.',
'landing.arch.ah.tag': 'Pezkuwi AH · Asset Hub',
'landing.arch.ah.h4': 'HezDex لە Asset Hub',
'landing.arch.ah.p': 'دەرکردنی HEZ و PEZ، گۆڵی ئاوی، گۆڕینی لەسەر زنجیرە، ستاک.',
'landing.arch.people.tag': 'Pezkuwi People · زنجیرەی خەڵک',
'landing.arch.people.h4': 'ناسنامەی Tîkî',
'landing.arch.people.p': 'NFT هاووڵاتیبوون، KYC، پوانەی باوەڕ، گرافی کۆمەڵایەتی.',
'landing.arch.stats.block': 'بلۆک',
'landing.arch.stats.time': 'کات',
'landing.arch.stats.validators': 'دڵنیاکەرەوەکان',
'landing.arch.stats.staked': 'ستاک کراو',
'landing.arch.stats.collators': 'کۆکەرەوەکان',
'landing.arch.stats.nominators': 'نامزدکەرەوەکان',
'landing.arch.stats.citizens': 'هاووڵاتیان',
'landing.arch.stats.countries': 'وڵاتان',
'landing.tok.eyebrow': '// ٠٤ · تۆکنۆمیک',
'landing.tok.h2': 'دوو تۆکن.',
'landing.tok.h2em': 'دوو ڕۆڵ.',
'landing.tok.p': 'HEZ یەکەی بەکارهێنانی ئیستەبلی تۆڕەکەیە. PEZ تۆکنی خەڵاتی هاووڵاتیانە.',
'landing.tok.tabHez': 'HEZ · بەکارهێنان',
'landing.tok.tabPez': 'PEZ · هاووڵاتی',
'landing.tok.total': 'کۆی',
'landing.ref.eyebrow': '// بە ئامادەکاری بە',
'landing.ref.h2': 'تەنها بانگهێشت ناکەی —',
'landing.ref.h2em': 'ئامادەکاری دەکەی.',
'landing.ref.p': 'هەر هاوڕێیەک کە ئەمڕۆیی دەکەیت بە هاووڵاتی PezkuwiChain دەبێت و +١٠ پوانەی باوەڕت بۆ دەبات.',
'landing.ref.step1.label': 'هەنگاوی ١',
'landing.ref.step1.title': 'بەستەرەکەت هاوبەش بکە',
'landing.ref.step1.desc': 'بەستەری ئامادەکارییەکەت لە جزدانی Pezkuwi بنێرە بۆ کەسێکی متمانەپێکراو.',
'landing.ref.step2.label': 'هەنگاوی ٢',
'landing.ref.step2.title': 'ئەوان داواکاری دەکەن',
'landing.ref.step2.desc': 'هاوڕێکەت داواکاری هاووڵاتیبوون لە PezkuwiChain دەکات.',
'landing.ref.step3.label': 'هەنگاوی ٣',
'landing.ref.step3.title': 'تۆ ئەمڕۆیی دەکەیت — ئەوانیش واژوو دەکەن',
'landing.ref.step3.desc': 'بە ئامادەکارییان بە، پاشان مامەڵەکەی هاووڵاتیبوون واژوو دەکەن. پوانەی باوەڕت +١٠ دەبێت.',
'landing.cta.eyebrow': '// بەشدارییکردن لە تۆڕ',
'landing.cta.h2': 'هاووڵاتیبوون',
'landing.cta.h2em': 'یەک واژوو دووریە.',
'landing.cta.p': 'جزدانەکەت پەیوەند بکە، NFT هاووڵاتیبوونت بخچە، و لەسەر پێشنیارەکانی دەوڵەتی دیجیتاڵ دەنگ بدە.',
'landing.cta.become': 'بە هاووڵاتی بە ←',
'landing.cta.validator': 'دڵنیاکەرەوە بخستەکار',
'landing.cta.services': 'خزمەتگوزارییە بەردەستەکان',
'landing.pallets.eyebrow': '// ٠٢ب · ئیکۆسیستەمی جزدان',
'landing.pallets.h2': 'هەموو ئامرازێک،',
'landing.pallets.h2em': 'یەک جزدان.',
'landing.pallets.p': 'شازدە پالێت لە چوار ستوون ڕێکخراون — دارایی، حوکمڕانی، کۆمەڵایەتی، و پەروەردە.',
'landing.pallets.finance': 'دارایی',
'landing.pallets.financeCount': '٨ مۆدیوول',
'landing.pallets.governance': 'حوکمڕانی',
'landing.pallets.governanceCount': '٨ مۆدیوول',
'landing.pallets.social': 'کۆمەڵایەتی',
'landing.pallets.socialCount': '٥ زیندوو · ٣ بەزووی',
'landing.pallets.education': 'پەروەردە',
'landing.pallets.educationCount': '٤ زیندوو · ٤ بەزووی',
'landing.pallets.comingSoon': 'بەزووی دێت',
'landing.pallets.wallet': 'جزدان',
'landing.pallets.bank': 'بانک',
'landing.pallets.exchange': 'گۆڕینگە',
'landing.pallets.dex': 'Pez-DEX',
'landing.pallets.p2p': 'P2P',
'landing.pallets.b2b': 'B2B',
'landing.pallets.zekat': 'باج/زەکات',
'landing.pallets.launchpad': 'لۆنچپاد',
'landing.pallets.president': 'سەرۆک',
'landing.pallets.assembly': 'پەرلەمان',
'landing.pallets.vote': 'دەنگدان',
'landing.pallets.validators': 'دڵنیاکەرەوەکان',
'landing.pallets.justice': 'دادگەری',
'landing.pallets.proposals': 'پێشنیارەکان',
'landing.pallets.polls': 'دەنگپرسییەکان',
'landing.pallets.identity': 'ناسنامە',
'landing.pallets.whatskurd': 'whatsKURD',
'landing.pallets.forum': 'فۆرۆم',
'landing.pallets.kurdmedia': 'KurdMedia',
'landing.pallets.events': 'ڕووداوەکان',
'landing.pallets.help': 'یارمەتی',
'landing.pallets.music': 'میوزیک',
'landing.pallets.rewshenbir': 'ڕووشەنبیر',
'landing.pallets.referral': 'ئامادەکاری',
'landing.pallets.university': 'زانکۆ',
'landing.pallets.perwerde': 'پەروەردە',
'landing.pallets.certificates': 'بڕوانامەکان',
'landing.pallets.research': 'توێژینەوە',
'landing.pallets.library': 'پەرتووکخانە',
'landing.pallets.tutor': 'مامۆستا',
'landing.pallets.labs': 'تاقیگە',
'landing.pallets.languages': 'زمانەکان',
'landing.footer.desc': 'Layer 1ـێکی گشتی و بێ-مۆڵەتەیی بۆ سەروەری دیجیتاڵی نەتەوە و خەڵکی بێ-دەوڵەت — کورد یەکەم، هەموو خەڵکەکانیش.',
'landing.footer.mainnet': 'تۆڕی سەرەکی · ٦ چرکەی بلۆک',
'landing.footer.network': 'تۆڕ',
'landing.footer.use': 'بەکارهێنان',
'landing.footer.build': 'بنیادنان',
'landing.footer.community': 'کۆمەڵگە',
'landing.footer.explorer': 'گەڕاندن',
'landing.footer.telemetry': 'تێلێمیتری',
'landing.footer.validators': 'دڵنیاکەرەوەکان',
'landing.footer.faucet': 'فاوسیت',
'landing.footer.wallet': 'جزدان',
'landing.footer.trade': 'بازرگانی',
'landing.footer.vote': 'دەنگدان',
'landing.footer.grants': 'گرانتەکان',
'landing.footer.docs': 'بەڵگەنامەکان',
'landing.footer.api': 'ئامرازی API',
'landing.footer.sdk': 'SDK',
'landing.footer.github': 'GitHub',
'landing.footer.forum': 'فۆرۆم',
'landing.footer.discord': 'Discord',
'landing.footer.telegram': 'Telegram',
'landing.footer.twitter': 'X / Twitter',
'landing.footer.copyright': '© ٢٠٢٦ PezkuwiChain · کۆدی سەرچاوەی کراوە لە ژێر MIT',
'landing.footer.builtBy': 'دروستکراوی خەڵکی دیجیتاڵی PezkuwiChain بۆ هەموو نەتەوە و خەڵکی بێ-دەوڵەت',
}; };
+8 -4
View File
@@ -1915,6 +1915,7 @@ export default {
'p2pNav.orders': 'Orders', 'p2pNav.orders': 'Orders',
'p2pNav.ads': 'Ads', 'p2pNav.ads': 'Ads',
'p2pNav.messages': 'Messages', 'p2pNav.messages': 'Messages',
'p2pNav.buyVisa': 'Buy with Visa',
// P2P Messages Inbox // P2P Messages Inbox
'p2pMessages.title': 'Messages', 'p2pMessages.title': 'Messages',
@@ -2199,7 +2200,9 @@ export default {
'walletModal.mobileTitle': 'Mobile Wallet', 'walletModal.mobileTitle': 'Mobile Wallet',
'walletModal.mobileDesc': 'For mobile — scan QR code with pezWallet app', 'walletModal.mobileDesc': 'For mobile — scan QR code with pezWallet app',
'walletModal.mobileConnect': 'Connect with pezWallet', 'walletModal.mobileConnect': 'Connect with pezWallet',
'walletModal.mobileComingSoon': 'Coming soon on Play Store', 'walletModal.mobileComingSoon': 'Download on Play Store',
'walletModal.mobilePlayStore': 'Download on Play Store',
'walletModal.connectingExtension': 'Approve in extension...',
'walletModal.or': 'or', 'walletModal.or': 'or',
'walletModal.connectWC': 'Connect with pezWallet (Mobile)', 'walletModal.connectWC': 'Connect with pezWallet (Mobile)',
'walletModal.wcScanQR': 'Scan with pezWallet to connect', 'walletModal.wcScanQR': 'Scan with pezWallet to connect',
@@ -2208,7 +2211,7 @@ export default {
'walletModal.wcWaiting': 'Waiting for wallet to connect...', 'walletModal.wcWaiting': 'Waiting for wallet to connect...',
'walletModal.wcWaitingMobile': 'Approve the connection in pezWallet', 'walletModal.wcWaitingMobile': 'Approve the connection in pezWallet',
'walletModal.wcOpenApp': 'Open pezWallet', 'walletModal.wcOpenApp': 'Open pezWallet',
'walletModal.wcInstallHint': "Don't have pezWallet? It will be available on Play Store soon.", 'walletModal.wcInstallHint': "Don't have pezWallet? Download it on Play Store.",
'walletModal.wcConnected': 'Connected!', 'walletModal.wcConnected': 'Connected!',
'walletModal.wcInstructions': 'Open pezWallet app → Settings → WalletConnect → Scan QR code', 'walletModal.wcInstructions': 'Open pezWallet app → Settings → WalletConnect → Scan QR code',
'walletModal.wcRetry': 'Try Again', 'walletModal.wcRetry': 'Try Again',
@@ -3223,7 +3226,7 @@ export default {
// Network Stats // Network Stats
'networkStats.disconnected': 'Network Disconnected', 'networkStats.disconnected': 'Network Disconnected',
'networkStats.disconnectedDesc': 'Make sure your validator node is running at ws://127.0.0.1:9944', 'networkStats.disconnectedDesc': 'Unable to connect to the network. Please try again later.',
'networkStats.connecting': 'Connecting to Network...', 'networkStats.connecting': 'Connecting to Network...',
'networkStats.title': 'Network Status', 'networkStats.title': 'Network Status',
'networkStats.connected': 'Connected', 'networkStats.connected': 'Connected',
@@ -3837,7 +3840,7 @@ export default {
'mobile.app.bank': 'Bank', 'mobile.app.bank': 'Bank',
'mobile.app.exchange': 'Exchange', 'mobile.app.exchange': 'Exchange',
'mobile.app.dex': 'Pez-DEX', 'mobile.app.dex': 'Pez-DEX',
'mobile.app.p2p': 'P2P', 'mobile.app.p2p': 'P2P/Buy-Sell',
'mobile.app.b2b': 'B2B', 'mobile.app.b2b': 'B2B',
'mobile.app.bacZekat': 'Bac/Zekat', 'mobile.app.bacZekat': 'Bac/Zekat',
'mobile.app.launchpad': 'Launchpad', 'mobile.app.launchpad': 'Launchpad',
@@ -3854,6 +3857,7 @@ export default {
'mobile.app.kurdMedia': 'KurdMedia', 'mobile.app.kurdMedia': 'KurdMedia',
'mobile.app.events': 'Events', 'mobile.app.events': 'Events',
'mobile.app.help': 'Help', 'mobile.app.help': 'Help',
'mobile.app.loto': 'Loto',
'mobile.app.music': 'Music', 'mobile.app.music': 'Music',
'mobile.app.vpn': 'VPN', 'mobile.app.vpn': 'VPN',
'mobile.app.rewshenbir': 'Rewshenbir', 'mobile.app.rewshenbir': 'Rewshenbir',
+197 -4
View File
@@ -1585,6 +1585,7 @@ export default {
'p2pNav.orders': 'سفارشات', 'p2pNav.orders': 'سفارشات',
'p2pNav.ads': 'آگهی‌ها', 'p2pNav.ads': 'آگهی‌ها',
'p2pNav.messages': 'پیام‌ها', 'p2pNav.messages': 'پیام‌ها',
'p2pNav.buyVisa': 'خرید با ویزا',
// P2P Messages Inbox // P2P Messages Inbox
'p2pMessages.title': 'پیام‌ها', 'p2pMessages.title': 'پیام‌ها',
@@ -1800,14 +1801,16 @@ export default {
'walletModal.mobileTitle': 'کیف پول موبایل', 'walletModal.mobileTitle': 'کیف پول موبایل',
'walletModal.mobileDesc': 'برای موبایل — کد QR را با اپلیکیشن pezWallet اسکن کنید', 'walletModal.mobileDesc': 'برای موبایل — کد QR را با اپلیکیشن pezWallet اسکن کنید',
'walletModal.mobileConnect': 'اتصال با pezWallet', 'walletModal.mobileConnect': 'اتصال با pezWallet',
'walletModal.mobileComingSoon': 'به زودی در Play Store', 'walletModal.mobileComingSoon': 'دانلود از Play Store',
'walletModal.mobilePlayStore': 'دانلود از Play Store',
'walletModal.connectingExtension': 'در افزونه تأیید کنید...',
'walletModal.or': 'یا', 'walletModal.or': 'یا',
'walletModal.connectWC': 'اتصال با pezWallet (موبایل)', 'walletModal.connectWC': 'اتصال با pezWallet (موبایل)',
'walletModal.wcScanQR': 'برای اتصال با pezWallet اسکن کنید', 'walletModal.wcScanQR': 'برای اتصال با pezWallet اسکن کنید',
'walletModal.wcOpenWallet': 'اتصال با اپلیکیشن pezWallet', 'walletModal.wcOpenWallet': 'اتصال با اپلیکیشن pezWallet',
'walletModal.wcWaitingMobile': 'اتصال را در pezWallet تأیید کنید', 'walletModal.wcWaitingMobile': 'اتصال را در pezWallet تأیید کنید',
'walletModal.wcOpenApp': 'باز کردن pezWallet', 'walletModal.wcOpenApp': 'باز کردن pezWallet',
'walletModal.wcInstallHint': 'pezWallet ندارید؟ به زودی در Play Store در دسترس خواهد بود.', 'walletModal.wcInstallHint': 'pezWallet ندارید؟ از Play Store دانلود کنید.',
'walletModal.wcGenerating': 'در حال ایجاد کد QR...', 'walletModal.wcGenerating': 'در حال ایجاد کد QR...',
'walletModal.wcWaiting': 'در انتظار اتصال کیف پول...', 'walletModal.wcWaiting': 'در انتظار اتصال کیف پول...',
'walletModal.wcConnected': 'متصل شد!', 'walletModal.wcConnected': 'متصل شد!',
@@ -2926,7 +2929,7 @@ export default {
// Network Stats // Network Stats
'networkStats.disconnected': 'شبکه قطع شده', 'networkStats.disconnected': 'شبکه قطع شده',
'networkStats.disconnectedDesc': 'مطمئن شوید که نود اعتبارسنج شما در ws://127.0.0.1:9944 در حال اجرا است', 'networkStats.disconnectedDesc': 'اتصال به شبکه برقرار نشد. لطفاً بعداً دوباره امتحان کنید.',
'networkStats.connecting': 'در حال اتصال به شبکه...', 'networkStats.connecting': 'در حال اتصال به شبکه...',
'networkStats.title': 'وضعیت شبکه', 'networkStats.title': 'وضعیت شبکه',
'networkStats.connected': 'متصل', 'networkStats.connected': 'متصل',
@@ -3819,7 +3822,7 @@ export default {
'mobile.app.bank': 'بانک', 'mobile.app.bank': 'بانک',
'mobile.app.exchange': 'صرافی', 'mobile.app.exchange': 'صرافی',
'mobile.app.dex': 'Pez-DEX', 'mobile.app.dex': 'Pez-DEX',
'mobile.app.p2p': 'P2P', 'mobile.app.p2p': 'P2P/Buy-Sell',
'mobile.app.b2b': 'B2B', 'mobile.app.b2b': 'B2B',
'mobile.app.bacZekat': 'مالیات/زکات', 'mobile.app.bacZekat': 'مالیات/زکات',
'mobile.app.launchpad': 'سکوی پرتاب', 'mobile.app.launchpad': 'سکوی پرتاب',
@@ -3836,6 +3839,7 @@ export default {
'mobile.app.kurdMedia': 'کوردمدیا', 'mobile.app.kurdMedia': 'کوردمدیا',
'mobile.app.events': 'رویدادها', 'mobile.app.events': 'رویدادها',
'mobile.app.help': 'کمک', 'mobile.app.help': 'کمک',
'mobile.app.loto': 'لاتاری',
'mobile.app.music': 'موسیقی', 'mobile.app.music': 'موسیقی',
'mobile.app.vpn': 'VPN', 'mobile.app.vpn': 'VPN',
'mobile.app.referral': 'ارجاع', 'mobile.app.referral': 'ارجاع',
@@ -3972,4 +3976,193 @@ export default {
'help.feature.community': 'ارتباط با جامعه', 'help.feature.community': 'ارتباط با جامعه',
'help.whatskurd.title': 'پیام‌رسانی WhatsKURD', 'help.whatskurd.title': 'پیام‌رسانی WhatsKURD',
'help.whatskurd.desc': 'از طریق سیستم پیام‌رسانی بلاک‌چین با ما تماس بگیرید', 'help.whatskurd.desc': 'از طریق سیستم پیام‌رسانی بلاک‌چین با ما تماس بگیرید',
// Landing page
'landing.nav.network': 'شبکه',
'landing.nav.governance': 'حکمرانی',
'landing.nav.trading': 'معامله',
'landing.nav.citizens': 'شهروندان',
'landing.nav.docs': 'مستندات',
'landing.nav.mainnet': 'شبکه اصلی',
'landing.nav.connectWallet': 'اتصال کیف پول',
'landing.nav.login': 'ورود',
'landing.nav.peers': 'اعضا',
'landing.hero.badge': 'دولت دیجیتال کردستان · اکنون در شبکه اصلی',
'landing.hero.badgeVersion': 'v1.0',
'landing.hero.h1part1': 'زنجیره‌ای مستقل',
'landing.hero.h1part2': 'برای ملتی',
'landing.hero.h1accent': 'بدون مرز',
'landing.hero.h1part3': '.',
'landing.hero.sub': 'PezkuwiChain یک بلاک‌چین عمومی و بدون مجوز است — ابتدا برای ملت کرد ساخته شده، و برای هر ملت بی‌دولت و فرهنگی در سراسر جهان.',
'landing.hero.cta.explore': 'کاوش در شبکه ←',
'landing.hero.cta.whitepaper': 'مطالعه وایت‌پیپر',
'landing.hero.sun.name': 'روژ · خورشید کردستان',
'landing.hero.sun.rays': '۲۱ شعاع · ↻ از ۱۹۳۲',
'landing.statbar.network': 'شبکه',
'landing.statbar.connected': 'متصل',
'landing.statbar.latestBlock': 'آخرین بلوک',
'landing.statbar.finalized': 'نهایی‌شده',
'landing.statbar.finalizedMeta': '~۲ بلوک عقب',
'landing.statbar.validators': 'اعتبارسنجان',
'landing.statbar.validatorsMeta': 'بلوک‌ها را تأیید می‌کنند',
'landing.statbar.collators': 'جمع‌آورندگان',
'landing.statbar.collatorsMeta': 'بلوک‌ها را تولید می‌کنند',
'landing.statbar.nominators': 'نامزدکنندگان',
'landing.statbar.nominatorsMeta': 'برای اعتبارسنجان سهام می‌گذارند',
'landing.ticker.block': 'بلوک',
'landing.ticker.validators': 'اعتبارسنج',
'landing.ticker.nominators': 'نامزدکننده',
'landing.ticker.citizens': 'شهروند',
'landing.ticker.staked': 'سهام‌گذاری‌شده',
'landing.ticker.proposals': 'پیشنهادها',
'landing.ticker.peers': 'عضو',
'landing.network.eyebrow': '// ۰۱ · شبکه زنده',
'landing.network.h2': 'اعدادی که',
'landing.network.h2em': 'خودشان را اداره می‌کنند.',
'landing.network.p': 'هر معیار زیر مستقیماً از زنجیره رله دریافت می‌شود — آنچه اعتبارسنجان می‌بینند، شما می‌بینید.',
'landing.network.activeProposals': 'پیشنهادهای فعال',
'landing.network.proposalsMeta': '↗ حکمرانی زنده است',
'landing.network.totalVoters': 'مجموع رأی‌دهندگان',
'landing.network.votersMeta': 'رأی‌گیری با اقناع',
'landing.network.tokensStaked': 'توکن‌های سهام‌گذاری‌شده',
'landing.network.stakedMeta': 'از کل عرضه',
'landing.network.citizens': 'شهروندان در جهان',
'landing.network.citizensMeta': 'در ۱۸۷ کشور',
'landing.features.eyebrow': '// ۰۲ · آنچه می‌توانید انجام دهید',
'landing.features.h2': 'یک زنجیره، هر',
'landing.features.h2em': 'ابزار مدنی.',
'landing.features.p': 'یک Layer 1 مستقل — در ابتدا از Polkadot فورک شده، اکنون یک پایگاه کد کاملاً مستقل.',
'landing.features.01.eyebrow': '۰۱ · حکمرانی',
'landing.features.01.h3': 'درباره قوانین حاکم بر شبکه و ملت رأی بدهید.',
'landing.features.01.p': 'رفراندوم‌های OpenGov با رأی‌گیری اقناعی، تفویض اختیار، و دوره تصمیم‌گیری ۷ روزه.',
'landing.features.01.link': 'باز کردن داشبورد حکمرانی ←',
'landing.features.02.eyebrow': '۰۲ · شهروندی',
'landing.features.02.h3': 'هویت شما، امضاشده توسط دولت.',
'landing.features.02.p': 'NFT روح‌پیوند در زنجیره مردم. امتیاز اعتماد، ویژگی‌های تأییدشده، وزن رأی.',
'landing.features.02.link': 'شهروند شوید ←',
'landing.features.03.eyebrow': '۰۳ · اقتصاد',
'landing.features.03.h3': 'با HEZ معامله، وام و تأمین مالی ملت کنید.',
'landing.features.03.p': 'استخرهای DEX در Asset Hub، P2P با پشتوانه امانت، و خزانه‌ای که کمک‌هزینه‌ها را تأمین می‌کند.',
'landing.features.03.link': 'باز کردن اقتصاد ←',
'landing.features.04.eyebrow': '۰۴ · اعتبارسنجان',
'landing.features.04.p': 'Nominated Proof-of-Stake هر بلوک را ایمن می‌کند. HEZ سهام‌گذاری کنید تا از اعتبارسنج حمایت کنید.',
'landing.features.04.h3suffix': 'گره. ۱۲ ملت. صفر توقف از لحظه تأسیس.',
'landing.features.04.link': 'راه‌اندازی گره ←',
'landing.arch.eyebrow': '// ۰۳ · معماری',
'landing.arch.h2': 'سه زنجیره،',
'landing.arch.h2em': 'یک ملت.',
'landing.arch.p': 'سه زنجیره مستقل، کاملاً متعلق به شبکه.',
'landing.arch.rc.tag': 'Pezkuwi RC · زنجیره رله',
'landing.arch.rc.h4': 'Pezkuwi Relay',
'landing.arch.rc.p': 'حکمرانی، انتخاب اعتبارسنج، و هماهنگی بین زنجیره‌ها.',
'landing.arch.ah.tag': 'Pezkuwi AH · Asset Hub',
'landing.arch.ah.h4': 'HezDex در Asset Hub',
'landing.arch.ah.p': 'انتشار HEZ و PEZ، استخرهای نقدینگی، سوآپ‌های درون‌زنجیره، سهام‌گذاری.',
'landing.arch.people.tag': 'Pezkuwi People · زنجیره مردم',
'landing.arch.people.h4': 'هویت Tîkî',
'landing.arch.people.p': 'NFT شهروندی، KYC، امتیاز اعتماد، گراف اجتماعی.',
'landing.arch.stats.block': 'بلوک',
'landing.arch.stats.time': 'زمان',
'landing.arch.stats.validators': 'اعتبارسنج',
'landing.arch.stats.staked': 'سهام‌گذاری‌شده',
'landing.arch.stats.collators': 'جمع‌آورنده',
'landing.arch.stats.nominators': 'نامزدکننده',
'landing.arch.stats.citizens': 'شهروند',
'landing.arch.stats.countries': 'کشور',
'landing.tok.eyebrow': '// ۰۴ · توکنومیکس',
'landing.tok.h2': 'دو توکن.',
'landing.tok.h2em': 'دو نقش.',
'landing.tok.p': 'HEZ واحد کاربردی پایدار شبکه است. PEZ توکن پاداش شهروندان است.',
'landing.tok.tabHez': 'HEZ · کاربردی',
'landing.tok.tabPez': 'PEZ · شهروند',
'landing.tok.total': 'مجموع',
'landing.ref.eyebrow': '// مرجع باشید',
'landing.ref.h2': 'فقط دعوت نمی‌کنید —',
'landing.ref.h2em': 'معرفی می‌کنید.',
'landing.ref.p': 'هر دوستی که تأییدش کنید شهروند PezkuwiChain می‌شود و به شما +۱۰ امتیاز اعتماد می‌دهد.',
'landing.ref.step1.label': 'مرحله ۱',
'landing.ref.step1.title': 'لینک خود را به اشتراک بگذارید',
'landing.ref.step1.desc': 'لینک ارجاع شخصی‌تان را از کیف پول Pezkuwi به کسی که به او اعتماد دارید ارسال کنید.',
'landing.ref.step2.label': 'مرحله ۲',
'landing.ref.step2.title': 'آن‌ها درخواست می‌دهند',
'landing.ref.step2.desc': 'دوست شما درخواست شهروندی در PezkuwiChain را ثبت می‌کند و منتظر تأیید شما می‌ماند.',
'landing.ref.step3.label': 'مرحله ۳',
'landing.ref.step3.title': 'شما تأیید می‌کنید — آن‌ها امضا می‌کنند',
'landing.ref.step3.desc': 'مرجع آن‌ها باشید، سپس تراکنش شهروندی را امضا می‌کنند. امتیاز اعتماد شما +۱۰ می‌شود.',
'landing.cta.eyebrow': '// به شبکه بپیوندید',
'landing.cta.h2': 'شهروندی',
'landing.cta.h2em': 'یک امضا فاصله است.',
'landing.cta.p': 'کیف پولتان را وصل کنید، NFT شهروندی‌تان را ضرب کنید، و درباره پیشنهادها رأی بدهید.',
'landing.cta.become': 'شهروند شوید ←',
'landing.cta.validator': 'اجرای اعتبارسنج',
'landing.cta.services': 'خدمات موجود',
'landing.pallets.eyebrow': '// ۰۲ب · اکوسیستم کیف پول',
'landing.pallets.h2': 'هر ابزاری،',
'landing.pallets.h2em': 'یک کیف پول.',
'landing.pallets.p': 'شانزده پالت در چهار ستون سازمان‌یافته — مالی، حکمرانی، اجتماعی، و آموزشی.',
'landing.pallets.finance': 'مالی',
'landing.pallets.financeCount': '۸ ماژول',
'landing.pallets.governance': 'حکمرانی',
'landing.pallets.governanceCount': '۸ ماژول',
'landing.pallets.social': 'اجتماعی',
'landing.pallets.socialCount': '۵ زنده · ۳ به‌زودی',
'landing.pallets.education': 'آموزش',
'landing.pallets.educationCount': '۴ زنده · ۴ به‌زودی',
'landing.pallets.comingSoon': 'به‌زودی',
'landing.pallets.wallet': 'کیف پول',
'landing.pallets.bank': 'بانک',
'landing.pallets.exchange': 'صرافی',
'landing.pallets.dex': 'Pez-DEX',
'landing.pallets.p2p': 'P2P',
'landing.pallets.b2b': 'B2B',
'landing.pallets.zekat': 'مالیات/زکات',
'landing.pallets.launchpad': 'لانچ‌پد',
'landing.pallets.president': 'رئیس‌جمهور',
'landing.pallets.assembly': 'پارلمان',
'landing.pallets.vote': 'رأی‌گیری',
'landing.pallets.validators': 'اعتبارسنجان',
'landing.pallets.justice': 'عدالت',
'landing.pallets.proposals': 'پیشنهادها',
'landing.pallets.polls': 'نظرسنجی‌ها',
'landing.pallets.identity': 'هویت',
'landing.pallets.whatskurd': 'whatsKURD',
'landing.pallets.forum': 'انجمن',
'landing.pallets.kurdmedia': 'KurdMedia',
'landing.pallets.events': 'رویدادها',
'landing.pallets.help': 'کمک',
'landing.pallets.music': 'موسیقی',
'landing.pallets.rewshenbir': 'روشنبیر',
'landing.pallets.referral': 'ارجاع',
'landing.pallets.university': 'دانشگاه',
'landing.pallets.perwerde': 'پەروەردە',
'landing.pallets.certificates': 'گواهینامه‌ها',
'landing.pallets.research': 'پژوهش',
'landing.pallets.library': 'کتابخانه',
'landing.pallets.tutor': 'معلم خصوصی',
'landing.pallets.labs': 'آزمایشگاه‌ها',
'landing.pallets.languages': 'زبان‌ها',
'landing.footer.desc': 'یک Layer 1 عمومی و بدون مجوز برای حاکمیت دیجیتال ملت‌های بی‌دولت و فرهنگی — کردها اول، همه مردم بعد.',
'landing.footer.mainnet': 'شبکه اصلی · بلوک هر ۶ ثانیه',
'landing.footer.network': 'شبکه',
'landing.footer.use': 'استفاده',
'landing.footer.build': 'ساخت',
'landing.footer.community': 'جامعه',
'landing.footer.explorer': 'کاوشگر',
'landing.footer.telemetry': 'تله‌متری',
'landing.footer.validators': 'اعتبارسنجان',
'landing.footer.faucet': 'شیر آب',
'landing.footer.wallet': 'کیف پول',
'landing.footer.trade': 'معامله',
'landing.footer.vote': 'رأی‌گیری',
'landing.footer.grants': 'کمک‌هزینه‌ها',
'landing.footer.docs': 'مستندات',
'landing.footer.api': 'مرجع API',
'landing.footer.sdk': 'SDK',
'landing.footer.github': 'GitHub',
'landing.footer.forum': 'انجمن',
'landing.footer.discord': 'Discord',
'landing.footer.telegram': 'Telegram',
'landing.footer.twitter': 'X / Twitter',
'landing.footer.copyright': '© ۲۰۲۶ PezkuwiChain · متن‌باز تحت مجوز MIT',
'landing.footer.builtBy': 'ساخته‌شده توسط مردم دیجیتال کرد برای هر ملت و مردم بی‌دولت',
}; };
+8 -4
View File
@@ -1573,6 +1573,7 @@ export default {
'p2pNav.orders': 'Ferman', 'p2pNav.orders': 'Ferman',
'p2pNav.ads': 'Reklam', 'p2pNav.ads': 'Reklam',
'p2pNav.messages': 'Peyam', 'p2pNav.messages': 'Peyam',
'p2pNav.buyVisa': 'Bi Visa bikire',
// P2P Messages Inbox // P2P Messages Inbox
'p2pMessages.title': 'Peyam', 'p2pMessages.title': 'Peyam',
@@ -1857,14 +1858,16 @@ export default {
'walletModal.mobileTitle': 'Berîka Mobîl', 'walletModal.mobileTitle': 'Berîka Mobîl',
'walletModal.mobileDesc': 'Ji bo mobîl — bi pezWallet QR kodê bişopîne', 'walletModal.mobileDesc': 'Ji bo mobîl — bi pezWallet QR kodê bişopîne',
'walletModal.mobileConnect': 'Bi pezWallet Ve Girêbide', 'walletModal.mobileConnect': 'Bi pezWallet Ve Girêbide',
'walletModal.mobileComingSoon': 'Di nêzîk de li Play Store', 'walletModal.mobileComingSoon': 'Ji Play Store dakêşin',
'walletModal.mobilePlayStore': 'Ji Play Store dakêşin',
'walletModal.connectingExtension': 'Di pêvekê de erê bikin...',
'walletModal.or': 'an jî', 'walletModal.or': 'an jî',
'walletModal.connectWC': 'Bi pezWallet ve girêbide (Mobîl)', 'walletModal.connectWC': 'Bi pezWallet ve girêbide (Mobîl)',
'walletModal.wcScanQR': 'Ji bo girêdanê bi pezWallet re bişopîne', 'walletModal.wcScanQR': 'Ji bo girêdanê bi pezWallet re bişopîne',
'walletModal.wcOpenWallet': 'Bi pezWallet ve girêbide', 'walletModal.wcOpenWallet': 'Bi pezWallet ve girêbide',
'walletModal.wcWaitingMobile': 'Di pezWallet de girêdanê bipejirîne', 'walletModal.wcWaitingMobile': 'Di pezWallet de girêdanê bipejirîne',
'walletModal.wcOpenApp': 'pezWallet Veke', 'walletModal.wcOpenApp': 'pezWallet Veke',
'walletModal.wcInstallHint': 'pezWallet tune? Di demek nêzîk de li Play Store dê hebe.', 'walletModal.wcInstallHint': 'pezWallet tune? Ji Play Store dakêşin.',
'walletModal.wcGenerating': 'QR kod tê çêkirin...', 'walletModal.wcGenerating': 'QR kod tê çêkirin...',
'walletModal.wcWaiting': 'Li benda girêdana berîkê...', 'walletModal.wcWaiting': 'Li benda girêdana berîkê...',
'walletModal.wcConnected': 'Girêdayî!', 'walletModal.wcConnected': 'Girêdayî!',
@@ -2909,7 +2912,7 @@ export default {
// Network Stats // Network Stats
'networkStats.disconnected': 'Girêdana Torê Qut Bû', 'networkStats.disconnected': 'Girêdana Torê Qut Bû',
'networkStats.disconnectedDesc': 'Piştrast bikin ku girêka piştrastkirina we li ws://127.0.0.1:9944 dixebite', 'networkStats.disconnectedDesc': 'Girêdana bi torê re çênebû. Ji kerema xwe paşê dîsa biceribîne.',
'networkStats.connecting': 'Bi Torê ve Tê Girêdan...', 'networkStats.connecting': 'Bi Torê ve Tê Girêdan...',
'networkStats.title': 'Rewşa Torê', 'networkStats.title': 'Rewşa Torê',
'networkStats.connected': 'Girêdayî', 'networkStats.connected': 'Girêdayî',
@@ -3802,7 +3805,7 @@ export default {
'mobile.app.bank': 'Bank', 'mobile.app.bank': 'Bank',
'mobile.app.exchange': 'Danûstandin', 'mobile.app.exchange': 'Danûstandin',
'mobile.app.dex': 'Pez-DEX', 'mobile.app.dex': 'Pez-DEX',
'mobile.app.p2p': 'P2P', 'mobile.app.p2p': 'P2P/Buy-Sell',
'mobile.app.b2b': 'B2B', 'mobile.app.b2b': 'B2B',
'mobile.app.bacZekat': 'Bac/Zekat', 'mobile.app.bacZekat': 'Bac/Zekat',
'mobile.app.launchpad': 'Destpêk', 'mobile.app.launchpad': 'Destpêk',
@@ -3819,6 +3822,7 @@ export default {
'mobile.app.kurdMedia': 'KurdMedya', 'mobile.app.kurdMedia': 'KurdMedya',
'mobile.app.events': 'Çalakî', 'mobile.app.events': 'Çalakî',
'mobile.app.help': 'Alîkarî', 'mobile.app.help': 'Alîkarî',
'mobile.app.loto': 'Loto',
'mobile.app.music': 'Muzîk', 'mobile.app.music': 'Muzîk',
'mobile.app.vpn': 'VPN', 'mobile.app.vpn': 'VPN',
'mobile.app.rewshenbir': 'Rewşenbir', 'mobile.app.rewshenbir': 'Rewşenbir',
+8 -4
View File
@@ -1567,6 +1567,7 @@ export default {
'p2pNav.orders': 'Siparişler', 'p2pNav.orders': 'Siparişler',
'p2pNav.ads': 'İlanlar', 'p2pNav.ads': 'İlanlar',
'p2pNav.messages': 'Mesajlar', 'p2pNav.messages': 'Mesajlar',
'p2pNav.buyVisa': 'Visa ile Al',
// P2P Messages Inbox // P2P Messages Inbox
'p2pMessages.title': 'Mesajlar', 'p2pMessages.title': 'Mesajlar',
@@ -1851,14 +1852,16 @@ export default {
'walletModal.mobileTitle': 'Mobil Cüzdan', 'walletModal.mobileTitle': 'Mobil Cüzdan',
'walletModal.mobileDesc': 'Mobil için — pezWallet uygulamasıyla QR kodu tarayın', 'walletModal.mobileDesc': 'Mobil için — pezWallet uygulamasıyla QR kodu tarayın',
'walletModal.mobileConnect': 'pezWallet ile Bağlan', 'walletModal.mobileConnect': 'pezWallet ile Bağlan',
'walletModal.mobileComingSoon': 'Yakında Play Store\'da', 'walletModal.mobileComingSoon': 'Play Store\'dan İndir',
'walletModal.mobilePlayStore': 'Play Store\'dan İndir',
'walletModal.connectingExtension': 'Uzantıda onaylayın...',
'walletModal.or': 'veya', 'walletModal.or': 'veya',
'walletModal.connectWC': 'pezWallet ile Bağlan (Mobil)', 'walletModal.connectWC': 'pezWallet ile Bağlan (Mobil)',
'walletModal.wcScanQR': 'Bağlanmak için pezWallet ile tarayın', 'walletModal.wcScanQR': 'Bağlanmak için pezWallet ile tarayın',
'walletModal.wcOpenWallet': 'pezWallet uygulamasıyla bağlan', 'walletModal.wcOpenWallet': 'pezWallet uygulamasıyla bağlan',
'walletModal.wcWaitingMobile': 'pezWallet\'ta bağlantıyı onaylayın', 'walletModal.wcWaitingMobile': 'pezWallet\'ta bağlantıyı onaylayın',
'walletModal.wcOpenApp': 'pezWallet\'ı Aç', 'walletModal.wcOpenApp': 'pezWallet\'ı Aç',
'walletModal.wcInstallHint': 'pezWallet yok mu? Yakında Play Store\'da olacak.', 'walletModal.wcInstallHint': 'pezWallet yok mu? Play Store\'dan indirin.',
'walletModal.wcGenerating': 'QR kod oluşturuluyor...', 'walletModal.wcGenerating': 'QR kod oluşturuluyor...',
'walletModal.wcWaiting': 'Cüzdan bağlantısı bekleniyor...', 'walletModal.wcWaiting': 'Cüzdan bağlantısı bekleniyor...',
'walletModal.wcConnected': 'Bağlandı!', 'walletModal.wcConnected': 'Bağlandı!',
@@ -2912,7 +2915,7 @@ export default {
// Network Stats // Network Stats
'networkStats.disconnected': 'Ağ Bağlantısı Kesildi', 'networkStats.disconnected': 'Ağ Bağlantısı Kesildi',
'networkStats.disconnectedDesc': 'Doğrulayıcı düğümünüzün ws://127.0.0.1:9944 adresinde çalıştığından emin olun', 'networkStats.disconnectedDesc': 'Ağa bağlanılamadı. Lütfen daha sonra tekrar deneyin.',
'networkStats.connecting': 'Ağa Bağlanılıyor...', 'networkStats.connecting': 'Ağa Bağlanılıyor...',
'networkStats.title': 'Ağ Durumu', 'networkStats.title': 'Ağ Durumu',
'networkStats.connected': 'Bağlandı', 'networkStats.connected': 'Bağlandı',
@@ -3805,7 +3808,7 @@ export default {
'mobile.app.bank': 'Banka', 'mobile.app.bank': 'Banka',
'mobile.app.exchange': 'Borsa', 'mobile.app.exchange': 'Borsa',
'mobile.app.dex': 'Pez-DEX', 'mobile.app.dex': 'Pez-DEX',
'mobile.app.p2p': 'P2P', 'mobile.app.p2p': 'P2P/Buy-Sell',
'mobile.app.b2b': 'B2B', 'mobile.app.b2b': 'B2B',
'mobile.app.bacZekat': 'Vergi/Zekat', 'mobile.app.bacZekat': 'Vergi/Zekat',
'mobile.app.launchpad': 'Launchpad', 'mobile.app.launchpad': 'Launchpad',
@@ -3822,6 +3825,7 @@ export default {
'mobile.app.kurdMedia': 'KurdMedya', 'mobile.app.kurdMedia': 'KurdMedya',
'mobile.app.events': 'Etkinlikler', 'mobile.app.events': 'Etkinlikler',
'mobile.app.help': 'Yardım', 'mobile.app.help': 'Yardım',
'mobile.app.loto': 'Loto',
'mobile.app.music': 'Müzik', 'mobile.app.music': 'Müzik',
'mobile.app.vpn': 'VPN', 'mobile.app.vpn': 'VPN',
'mobile.app.rewshenbir': 'Rewshenbir', 'mobile.app.rewshenbir': 'Rewshenbir',
+8 -20
View File
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'; import { useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import { Loader2 } from 'lucide-react'; import { Loader2 } from 'lucide-react';
@@ -12,7 +12,6 @@ const BEREKETLI_API = `${BEREKETLI_URL}/v1`;
*/ */
export default function Bereketli() { export default function Bereketli() {
const { t } = useTranslation(); const { t } = useTranslation();
const [error, setError] = useState('');
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@@ -20,8 +19,10 @@ export default function Bereketli() {
const { const {
data: { session }, data: { session },
} = await supabase.auth.getSession(); } = await supabase.auth.getSession();
// Not signed in: skip SSO and send the user to the Bereketli site,
// which handles its own login. Never dead-end on this interstitial.
if (!session?.access_token) { if (!session?.access_token) {
setError(t('bereketli.noSession', 'Lütfen önce giriş yapın')); window.location.href = BEREKETLI_URL;
return; return;
} }
@@ -46,27 +47,14 @@ export default function Bereketli() {
}); });
window.location.href = `${BEREKETLI_URL}/app?auth=${btoa(params.toString())}`; window.location.href = `${BEREKETLI_URL}/app?auth=${btoa(params.toString())}`;
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : 'Bağlantı hatası'); // SSO failed (expired token, network, etc.) — fall back to the public
// Bereketli site instead of stranding the user on app.pezkuwichain.io.
if (import.meta.env.DEV) console.warn('Bereketli SSO failed, falling back:', err);
window.location.href = BEREKETLI_URL;
} }
})(); })();
}, [t]); }, [t]);
if (error) {
return (
<div className="min-h-screen bg-gray-950 flex items-center justify-center px-6">
<div className="text-center space-y-4">
<p className="text-red-400 text-sm">{error}</p>
<a
href="/"
className="inline-block px-4 py-2 bg-green-600 text-white rounded-lg text-sm"
>
{t('common.backToHome', 'Ana Sayfaya Dön')}
</a>
</div>
</div>
);
}
return ( return (
<div className="min-h-screen bg-gray-950 flex items-center justify-center"> <div className="min-h-screen bg-gray-950 flex items-center justify-center">
<div className="text-center space-y-3"> <div className="text-center space-y-3">
+2 -2
View File
@@ -163,7 +163,7 @@ const Login: React.FC = () => {
setError(''); setError('');
setLoading(true); setLoading(true);
const BOT_ID = '8690398980'; const BOT_ID = window.location.hostname === 'pex.mom' ? '8690398980' : '8754021997';
const origin = window.location.origin; const origin = window.location.origin;
const popup = window.open( const popup = window.open(
`https://oauth.telegram.org/auth?bot_id=${BOT_ID}&origin=${encodeURIComponent(origin)}&embed=1&request_access=write`, `https://oauth.telegram.org/auth?bot_id=${BOT_ID}&origin=${encodeURIComponent(origin)}&embed=1&request_access=write`,
@@ -196,7 +196,7 @@ const Login: React.FC = () => {
'apikey': supabaseKey, 'apikey': supabaseKey,
'Authorization': `Bearer ${supabaseKey}`, 'Authorization': `Bearer ${supabaseKey}`,
}, },
body: JSON.stringify(tgData), body: JSON.stringify({ ...tgData, bot_id: BOT_ID }),
}); });
const json = await res.json(); const json = await res.json();
+33 -14
View File
@@ -9,6 +9,7 @@ interface MediaChannel {
descriptionKu: string; descriptionKu: string;
description: string; description: string;
color: string; color: string;
url?: string;
} }
interface SocialPlatform { interface SocialPlatform {
@@ -21,7 +22,7 @@ interface SocialPlatform {
const MEDIA_CHANNELS: MediaChannel[] = [ const MEDIA_CHANNELS: MediaChannel[] = [
{ id: 'dkstv', nameKu: 'DKS TV', name: 'DKS TV', icon: '📺', descriptionKu: 'Televizyona Dewleta Dijîtal a Kurdistanê', description: 'Digital Kurdistan State Television', color: '#E53935' }, { id: 'dkstv', nameKu: 'DKS TV', name: 'DKS TV', icon: '📺', descriptionKu: 'Televizyona Dewleta Dijîtal a Kurdistanê', description: 'Digital Kurdistan State Television', color: '#E53935' },
{ id: 'dksgzt', nameKu: 'DKS Rojname', name: 'DKS Gazette', icon: '📰', descriptionKu: 'Nûçe û Daxuyaniyên Fermî', description: 'Official News & Announcements', color: '#1E88E5' }, { id: 'dksgzt', nameKu: 'DKS Rojname', name: 'DKS Gazette', icon: '📰', descriptionKu: 'Nûçe û Daxuyaniyên Fermî', description: 'Official News & Announcements', color: '#1E88E5', url: 'https://news.pex.mom' },
{ id: 'dksradio', nameKu: 'DKS Radyo', name: 'DKS Radio', icon: '📻', descriptionKu: 'Radyoya Dewleta Dijîtal a Kurdistanê', description: 'Digital Kurdistan State Radio', color: '#7B1FA2' }, { id: 'dksradio', nameKu: 'DKS Radyo', name: 'DKS Radio', icon: '📻', descriptionKu: 'Radyoya Dewleta Dijîtal a Kurdistanê', description: 'Digital Kurdistan State Radio', color: '#7B1FA2' },
{ id: 'dksmusic', nameKu: 'DKS Muzîk', name: 'DKS Music', icon: '🎵', descriptionKu: 'Weşana Muzîka Kurdî', description: 'Kurdish Music Streaming', color: '#00897B' }, { id: 'dksmusic', nameKu: 'DKS Muzîk', name: 'DKS Music', icon: '🎵', descriptionKu: 'Weşana Muzîka Kurdî', description: 'Kurdish Music Streaming', color: '#00897B' },
{ id: 'dkspodcast',nameKu: 'DKS Podcast', name: 'DKS Podcast', icon: '🎙️', descriptionKu: 'Podcast û Gotûbêjên Kurdî', description: 'Kurdish Podcasts & Talks', color: '#F4511E' }, { id: 'dkspodcast',nameKu: 'DKS Podcast', name: 'DKS Podcast', icon: '🎙️', descriptionKu: 'Podcast û Gotûbêjên Kurdî', description: 'Kurdish Podcasts & Talks', color: '#F4511E' },
@@ -71,20 +72,38 @@ export default function KurdMediaPage() {
<p className="text-sm text-gray-300 mb-1">{t('kurdMedia.channels.desc', 'Weşanên fermî yên Dewleta Dijîtal a Kurdistanê.')}</p> <p className="text-sm text-gray-300 mb-1">{t('kurdMedia.channels.desc', 'Weşanên fermî yên Dewleta Dijîtal a Kurdistanê.')}</p>
<p className="text-xs text-gray-500 mb-4">{t('kurdMedia.channels.descEn', 'Official broadcasts of Digital Kurdistan State. TV, radio, news and more.')}</p> <p className="text-xs text-gray-500 mb-4">{t('kurdMedia.channels.descEn', 'Official broadcasts of Digital Kurdistan State. TV, radio, news and more.')}</p>
<div className="space-y-3"> <div className="space-y-3">
{MEDIA_CHANNELS.map(ch => ( {MEDIA_CHANNELS.map(ch => {
<div key={ch.id} className="flex items-center gap-3 bg-gray-800 rounded-xl p-3"> const inner = (
<div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl flex-shrink-0" style={{ backgroundColor: ch.color }}> <>
{ch.icon} <div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl flex-shrink-0" style={{ backgroundColor: ch.color }}>
{ch.icon}
</div>
<div className="flex-1 min-w-0">
<p className="font-semibold text-white text-sm">{ch.nameKu}</p>
<p className="text-xs text-gray-400 truncate">{ch.descriptionKu}</p>
</div>
{ch.url ? (
<span className="text-[10px] font-bold text-green-400 bg-green-400/10 px-2 py-1 rounded-full flex-shrink-0">
{t('kurdMedia.open', 'Open')}
</span>
) : (
<span className="text-[10px] font-bold text-yellow-400 bg-yellow-400/10 px-2 py-1 rounded-full flex-shrink-0">
{t('kurdMedia.soon', 'Soon')}
</span>
)}
</>
);
return ch.url ? (
<a key={ch.id} href={ch.url} target="_blank" rel="noopener noreferrer"
className="flex items-center gap-3 bg-gray-800 rounded-xl p-3 hover:bg-gray-700 transition-colors">
{inner}
</a>
) : (
<div key={ch.id} className="flex items-center gap-3 bg-gray-800 rounded-xl p-3">
{inner}
</div> </div>
<div className="flex-1 min-w-0"> );
<p className="font-semibold text-white text-sm">{ch.nameKu}</p> })}
<p className="text-xs text-gray-400 truncate">{ch.descriptionKu}</p>
</div>
<span className="text-[10px] font-bold text-yellow-400 bg-yellow-400/10 px-2 py-1 rounded-full flex-shrink-0">
{t('kurdMedia.soon', 'Soon')}
</span>
</div>
))}
</div> </div>
</div> </div>
</div> </div>
@@ -42,11 +42,13 @@ Deno.serve(async (req) => {
} }
try { try {
const botToken = Deno.env.get('TELEGRAM_BOT_TOKEN_PEZMOM')
if (!botToken) throw new Error('TELEGRAM_BOT_TOKEN_PEZMOM not configured')
const body = await req.json() const body = await req.json()
const { id, first_name, last_name, username, photo_url, auth_date, hash } = body const { id, first_name, last_name, username, photo_url, auth_date, hash, bot_id } = body
const botToken = bot_id === '8690398980'
? Deno.env.get('TELEGRAM_BOT_TOKEN_PEZMOM')
: Deno.env.get('TELEGRAM_BOT_TOKEN_PEXSEC')
if (!botToken) throw new Error('Bot token not configured for bot_id: ' + bot_id)
if (!id || !hash || !auth_date) { if (!id || !hash || !auth_date) {
return new Response(JSON.stringify({ error: 'Missing required fields' }), { return new Response(JSON.stringify({ error: 'Missing required fields' }), {
+5
View File
@@ -3,6 +3,7 @@ import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react-swc"; import react from "@vitejs/plugin-react-swc";
import path from "path"; import path from "path";
import { nodePolyfills } from 'vite-plugin-node-polyfills'; import { nodePolyfills } from 'vite-plugin-node-polyfills';
import subresourceIntegrity from 'vite-plugin-subresource-integrity';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(({ command }) => ({ export default defineConfig(({ command }) => ({
@@ -38,6 +39,10 @@ export default defineConfig(({ command }) => ({
}, },
protocolImports: true, protocolImports: true,
}), }),
// SRI: production build sırasında <script>/<link> tag'lerine
// sha384 integrity hash ekle. CDN/proxy compromise olsa bile
// tampered asset browser tarafından load edilmez.
command === 'build' ? subresourceIntegrity({ algorithm: 'sha384' }) : null,
].filter(Boolean), ].filter(Boolean),
resolve: { resolve: {
mainFields: ['browser', 'module', 'main', 'exports'], mainFields: ['browser', 'module', 'main', 'exports'],