12 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
22 changed files with 524 additions and 162 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ concurrency:
jobs:
analyze:
name: Analyze ${{ matrix.language }}
runs-on: ubuntu-latest
runs-on: pwap-runner
strategy:
fail-fast: false
matrix:
+197 -14
View File
@@ -32,7 +32,7 @@ jobs:
# ========================================
web:
name: Web App
runs-on: ubuntu-latest
runs-on: pwap-runner
steps:
- name: Checkout code
@@ -92,7 +92,7 @@ jobs:
# ========================================
build-image:
name: Build & Push Image
runs-on: ubuntu-latest
runs-on: pwap-runner
needs: [web, telegram-gate]
if: |
github.ref == 'refs/heads/main' &&
@@ -101,14 +101,21 @@ jobs:
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:
@@ -125,6 +132,7 @@ jobs:
echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT
- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
context: ./
@@ -146,6 +154,18 @@ jobs:
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
@@ -215,7 +235,7 @@ jobs:
# ========================================
bump-version:
name: Bump Version
runs-on: ubuntu-latest
runs-on: pwap-runner
needs: [web, security-audit, telegram-gate, build-image]
# Skip on rollback (workflow_dispatch with rollback_to set)
if: |
@@ -261,7 +281,7 @@ jobs:
# ========================================
deploy-app:
name: Deploy app.pezkuwichain.io
runs-on: ubuntu-latest
runs-on: pwap-runner
needs: [telegram-gate, bump-version, build-image]
if: |
always() &&
@@ -286,6 +306,14 @@ jobs:
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:
@@ -293,6 +321,24 @@ jobs:
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 }}"
@@ -301,7 +347,8 @@ jobs:
mkdir -p dist
docker cp "$CID:/dist/." dist/
docker rm "$CID" >/dev/null
echo "Extracted dist/ contents:"
# 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
@@ -329,6 +376,48 @@ jobs:
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: |
@@ -339,17 +428,29 @@ jobs:
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}" \
-d "text=❌ pwap/web deploy FAILED: ${{ env.DOMAIN }} (sha ${{ steps.sha.outputs.sha }}). Health check did not pass after deploy. Manual rollback needed: gh workflow run quality-gate.yml -f rollback_to=<previous-sha>"
-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: ubuntu-latest
runs-on: pwap-runner
needs: [telegram-gate, bump-version, build-image]
if: |
always() &&
@@ -373,6 +474,13 @@ jobs:
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:
@@ -380,6 +488,23 @@ jobs:
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 }}"
@@ -388,6 +513,7 @@ jobs:
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
@@ -401,6 +527,7 @@ jobs:
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
@@ -413,6 +540,47 @@ jobs:
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: |
@@ -423,10 +591,22 @@ jobs:
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}" \
-d "text=❌ pwap/web deploy FAILED: ${{ env.DOMAIN }} (sha ${{ steps.sha.outputs.sha }}). Health check did not pass. Rollback: gh workflow run quality-gate.yml -f rollback_to=<previous-sha>"
-d "chat_id=${CEO_CHAT_ID}" --data-urlencode "text=$MSG"
# ========================================
# SECURITY CHECKS (BLOCKING)
@@ -434,7 +614,7 @@ jobs:
# ========================================
security-audit:
name: Security Audit
runs-on: ubuntu-latest
runs-on: pwap-runner
needs: [web]
steps:
@@ -448,11 +628,14 @@ jobs:
with:
node-version: '20'
- name: Web — npm audit (high + critical)
- name: Web — npm audit (high + critical, production deps only)
working-directory: ./web
run: |
npm install
npm audit --audit-level=high
# 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 — PR diff (verified secrets only)
if: github.event_name == 'pull_request'
@@ -476,7 +659,7 @@ jobs:
# ========================================
ci-gate:
name: CI Gate ✅
runs-on: ubuntu-latest
runs-on: pwap-runner
needs: [web, security-audit]
if: always()
Submodule exchange deleted from bb3bc812ed
+1 -1
View File
@@ -46,7 +46,7 @@ RUN npm run build
# if the image were ever exposed.
FROM busybox:musl
WORKDIR /dist
COPY --from=builder /build/dist /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"
+119 -90
View File
@@ -109,6 +109,7 @@
"typescript-eslint": "^8.0.1",
"vite": "^7.3.1",
"vite-plugin-node-polyfills": "^0.25.0",
"vite-plugin-subresource-integrity": "^0.0.12",
"vitest": "^4.0.10"
}
},
@@ -3507,9 +3508,9 @@
"license": "MIT"
},
"node_modules/@remix-run/router": {
"version": "1.23.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
"integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
"version": "1.23.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz",
"integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
@@ -5070,31 +5071,31 @@
}
},
"node_modules/@vitest/expect": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz",
"integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz",
"integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@standard-schema/spec": "^1.1.0",
"@types/chai": "^5.2.2",
"@vitest/spy": "4.0.18",
"@vitest/utils": "4.0.18",
"chai": "^6.2.1",
"tinyrainbow": "^3.0.3"
"@vitest/spy": "4.1.8",
"@vitest/utils": "4.1.8",
"chai": "^6.2.2",
"tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/mocker": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz",
"integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz",
"integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "4.0.18",
"@vitest/spy": "4.1.8",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@@ -5103,7 +5104,7 @@
},
"peerDependencies": {
"msw": "^2.4.9",
"vite": "^6.0.0 || ^7.0.0-0"
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"msw": {
@@ -5125,26 +5126,26 @@
}
},
"node_modules/@vitest/pretty-format": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz",
"integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz",
"integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==",
"dev": true,
"license": "MIT",
"dependencies": {
"tinyrainbow": "^3.0.3"
"tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/runner": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz",
"integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz",
"integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "4.0.18",
"@vitest/utils": "4.1.8",
"pathe": "^2.0.3"
},
"funding": {
@@ -5152,13 +5153,14 @@
}
},
"node_modules/@vitest/snapshot": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz",
"integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz",
"integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.0.18",
"@vitest/pretty-format": "4.1.8",
"@vitest/utils": "4.1.8",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -5167,9 +5169,9 @@
}
},
"node_modules/@vitest/spy": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz",
"integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz",
"integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==",
"dev": true,
"license": "MIT",
"funding": {
@@ -5177,14 +5179,15 @@
}
},
"node_modules/@vitest/utils": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz",
"integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz",
"integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.0.18",
"tinyrainbow": "^3.0.3"
"@vitest/pretty-format": "4.1.8",
"convert-source-map": "^2.0.0",
"tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
@@ -5454,9 +5457,9 @@
}
},
"node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/ws": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"version": "7.5.11",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz",
"integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==",
"license": "MIT",
"engines": {
"node": ">=8.3.0"
@@ -6567,13 +6570,13 @@
}
},
"node_modules/browserify-sign": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.5.tgz",
"integrity": "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==",
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.6.tgz",
"integrity": "sha512-sd+Q65fjlWCYWtZKXiKfrUc8d+4jtp/8f0W2NkwzLtoW4bI6UDnWusLWIurHnmurW0XShIRxpwiOX4EoPtXUAg==",
"dev": true,
"license": "ISC",
"dependencies": {
"bn.js": "^5.2.2",
"bn.js": "^5.2.3",
"browserify-rsa": "^4.1.1",
"create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
@@ -7028,6 +7031,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
"license": "MIT"
},
"node_modules/cookie-es": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz",
@@ -7641,9 +7651,9 @@
}
},
"node_modules/dompurify": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz",
"integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==",
"version": "3.4.10",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.10.tgz",
"integrity": "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -7859,9 +7869,9 @@
}
},
"node_modules/es-module-lexer": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
"integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
"dev": true,
"license": "MIT"
},
@@ -11147,9 +11157,9 @@
}
},
"node_modules/qs": {
"version": "6.15.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
"integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
"version": "6.15.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
"integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -11367,12 +11377,12 @@
}
},
"node_modules/react-router": {
"version": "6.30.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
"integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
"version": "6.30.4",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz",
"integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.2"
"@remix-run/router": "1.23.3"
},
"engines": {
"node": ">=14.0.0"
@@ -11382,13 +11392,13 @@
}
},
"node_modules/react-router-dom": {
"version": "6.30.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
"integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
"version": "6.30.4",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz",
"integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.2",
"react-router": "6.30.3"
"@remix-run/router": "1.23.3",
"react-router": "6.30.4"
},
"engines": {
"node": ">=14.0.0"
@@ -12237,9 +12247,9 @@
"license": "MIT"
},
"node_modules/std-env": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
"integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
"dev": true,
"license": "MIT"
},
@@ -12696,9 +12706,9 @@
}
},
"node_modules/tinyrainbow": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
"integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
"integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -13259,6 +13269,13 @@
"vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
"node_modules/vite-plugin-subresource-integrity": {
"version": "0.0.12",
"resolved": "https://registry.npmjs.org/vite-plugin-subresource-integrity/-/vite-plugin-subresource-integrity-0.0.12.tgz",
"integrity": "sha512-geKEo1KgGA56G8CciaoKA3Yf7ckpR23zSuSW802xrisW6vnH+dAYjKXZygEcmFKfsOe0+r3uG7Oz9eFEwhdjxg==",
"dev": true,
"license": "MIT"
},
"node_modules/vite/node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -13291,31 +13308,31 @@
}
},
"node_modules/vitest": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz",
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz",
"integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/expect": "4.0.18",
"@vitest/mocker": "4.0.18",
"@vitest/pretty-format": "4.0.18",
"@vitest/runner": "4.0.18",
"@vitest/snapshot": "4.0.18",
"@vitest/spy": "4.0.18",
"@vitest/utils": "4.0.18",
"es-module-lexer": "^1.7.0",
"expect-type": "^1.2.2",
"@vitest/expect": "4.1.8",
"@vitest/mocker": "4.1.8",
"@vitest/pretty-format": "4.1.8",
"@vitest/runner": "4.1.8",
"@vitest/snapshot": "4.1.8",
"@vitest/spy": "4.1.8",
"@vitest/utils": "4.1.8",
"es-module-lexer": "^2.0.0",
"expect-type": "^1.3.0",
"magic-string": "^0.30.21",
"obug": "^2.1.1",
"pathe": "^2.0.3",
"picomatch": "^4.0.3",
"std-env": "^3.10.0",
"std-env": "^4.0.0-rc.1",
"tinybench": "^2.9.0",
"tinyexec": "^1.0.2",
"tinyglobby": "^0.2.15",
"tinyrainbow": "^3.0.3",
"vite": "^6.0.0 || ^7.0.0",
"tinyrainbow": "^3.1.0",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
"why-is-node-running": "^2.3.0"
},
"bin": {
@@ -13331,12 +13348,15 @@
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
"@vitest/browser-playwright": "4.0.18",
"@vitest/browser-preview": "4.0.18",
"@vitest/browser-webdriverio": "4.0.18",
"@vitest/ui": "4.0.18",
"@vitest/browser-playwright": "4.1.8",
"@vitest/browser-preview": "4.1.8",
"@vitest/browser-webdriverio": "4.1.8",
"@vitest/coverage-istanbul": "4.1.8",
"@vitest/coverage-v8": "4.1.8",
"@vitest/ui": "4.1.8",
"happy-dom": "*",
"jsdom": "*"
"jsdom": "*",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"@edge-runtime/vm": {
@@ -13357,6 +13377,12 @@
"@vitest/browser-webdriverio": {
"optional": true
},
"@vitest/coverage-istanbul": {
"optional": true
},
"@vitest/coverage-v8": {
"optional": true
},
"@vitest/ui": {
"optional": true
},
@@ -13365,6 +13391,9 @@
},
"jsdom": {
"optional": true
},
"vite": {
"optional": false
}
}
},
@@ -13606,9 +13635,9 @@
}
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"version": "8.21.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
+3 -1
View File
@@ -120,7 +120,8 @@
"@pezkuwi/x-textdecoder": "^14.0.25",
"@pezkuwi/x-textencoder": "^14.0.25",
"@pezkuwi/x-ws": "^14.0.25",
"@pezkuwi/networks": "^14.0.25"
"@pezkuwi/networks": "^14.0.25",
"elliptic": "^6.6.1"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
@@ -147,6 +148,7 @@
"typescript-eslint": "^8.0.1",
"vite": "^7.3.1",
"vite-plugin-node-polyfills": "^0.25.0",
"vite-plugin-subresource-integrity": "^0.0.12",
"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

+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 { Button } from '@/components/ui/button';
import { ASSET_IDS, getAssetSymbol } from '@pezkuwi/lib/wallet';
import { Pez20Badge } from './Pez20Badge';
import { AddTokenModal } from './AddTokenModal';
import { TransferModal } from './TransferModal';
import { XCMTeleportModal } from './XCMTeleportModal';
@@ -811,6 +812,7 @@ export const AccountBalance: React.FC = () => {
<CardTitle className="text-lg font-medium text-gray-300 whitespace-nowrap">
{t('balance.pezBalance')}
</CardTitle>
<Pez20Badge className="flex-shrink-0" />
</div>
<Button
size="sm"
@@ -853,6 +855,7 @@ export const AccountBalance: React.FC = () => {
<CardTitle className="text-lg font-medium text-gray-300">
{t('balance.usdtBalance')}
</CardTitle>
<Pez20Badge className="flex-shrink-0" />
</div>
</CardHeader>
<CardContent>
+2 -2
View File
@@ -110,8 +110,8 @@ const APP_SECTIONS: AppSection[] = [
{ title: 'mobile.app.whatsKurd', icon: '💬', route: '/social/whatskurd' },
{ title: 'mobile.app.forum', icon: '📰', route: '/forum' },
{ title: 'mobile.app.kurdMedia', icon: '📺', route: '/social/kurdmedia' },
{ title: 'mobile.app.events', icon: '📅', route: '/forum', comingSoon: true },
{ title: 'mobile.app.help', icon: '❓', route: '/help' },
{ title: 'mobile.app.events', icon: '📅', route: '/forum', href: 'https://kurdishtts.pezkiwi.app' },
{ 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.rewshenbir',icon: '📡', imgIcon: '/rewshenbir-icon.png', route: '/rewshenbir', href: 'https://rewshenbir.pezkuwi.app' },
{ title: 'mobile.app.referral', icon: '👥', route: '/dashboard', requiresAuth: true },
+2 -2
View File
@@ -87,8 +87,8 @@ const APP_SECTIONS: AppSection[] = [
{ title: 'mobile.app.whatsKurd', icon: '💬', route: '/social/whatskurd' },
{ title: 'mobile.app.forum', icon: '📰', route: '/forum' },
{ title: 'mobile.app.kurdMedia', icon: '📺', route: '/social/kurdmedia' },
{ title: 'mobile.app.events', icon: '📅', route: '/forum', comingSoon: true },
{ title: 'mobile.app.help', icon: '❓', route: '/help' },
{ title: 'mobile.app.events', icon: '📅', route: '/forum', href: 'https://kurdishtts.pezkiwi.app' },
{ 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.rewshenbir', icon: '📡', imgIcon: '/rewshenbir-icon.png', route: '/rewshenbir', href: 'https://rewshenbir.pezkuwi.app' },
{ 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;
nominators: number;
collators: number;
collatorsAH: number;
collatorsPeople: number;
activeProposals: number;
totalVoters: number;
citizenCount: number;
@@ -325,6 +327,7 @@ const LandingPageDesktop: React.FC = () => {
const [stats, setStats] = useState<ChainStats>({
latestBlock: 0, finalizedBlock: 0, blockHash: '',
peers: 0, validators: 0, nominators: 0, collators: 0,
collatorsAH: 0, collatorsPeople: 0,
activeProposals: 0, totalVoters: 0, citizenCount: 0,
tokensStakedPct: '—',
});
@@ -417,12 +420,7 @@ const LandingPageDesktop: React.FC = () => {
const validators = sessionVals.length;
setStats(prev => ({ ...prev, activeProposals, totalVoters, validators }));
} catch {}
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 {}
// Nominators/staking migrated to Asset Hub — counted in the Asset Hub effect below.
})();
}, [api, isApiReady]);
@@ -448,10 +446,18 @@ const LandingPageDesktop: React.FC = () => {
}
} catch {}
// Nominators live on Asset Hub after the staking migration (AHM).
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const collCount = await (assetHubApi.query.collatorSelection as any)?.candidates?.();
if (collCount != null) setStats(prev => ({ ...prev, collators: collCount.length }));
const nomCount = await (assetHubApi.query.staking as any)?.counterForNominators?.();
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 {}
})();
}, [assetHubApi, isAssetHubReady]);
@@ -465,6 +471,13 @@ const LandingPageDesktop: React.FC = () => {
const entries = await (peopleApi.query as any).tiki?.citizenNft?.entries?.();
if (entries) setStats(prev => ({ ...prev, citizenCount: entries.length }));
} 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]);
@@ -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-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-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-music" label={t('landing.pallets.music')} locked />
<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 { Badge } from '@/components/ui/badge';
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 { CreateAd } from './CreateAd';
import { NotificationBell } from './NotificationBell';
@@ -191,6 +191,15 @@ export function P2PDashboard() {
</Badge>
)}
</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>
+3 -1
View File
@@ -1561,6 +1561,7 @@ export default {
'p2pNav.orders': 'الطلبات',
'p2pNav.ads': 'الإعلانات',
'p2pNav.messages': 'الرسائل',
'p2pNav.buyVisa': 'اشترِ بفيزا',
// P2P Messages Inbox
'p2pMessages.title': 'الرسائل',
@@ -3787,7 +3788,7 @@ export default {
'mobile.app.bank': 'البنك',
'mobile.app.exchange': 'البورصة',
'mobile.app.dex': 'Pez-DEX',
'mobile.app.p2p': 'P2P',
'mobile.app.p2p': 'P2P/Buy-Sell',
'mobile.app.b2b': 'B2B',
'mobile.app.bacZekat': 'الضريبة/الزكاة',
'mobile.app.launchpad': 'منصة الإطلاق',
@@ -3804,6 +3805,7 @@ export default {
'mobile.app.kurdMedia': 'كورد ميديا',
'mobile.app.events': 'الفعاليات',
'mobile.app.help': 'المساعدة',
'mobile.app.loto': 'لوتو',
'mobile.app.music': 'الموسيقى',
'mobile.app.vpn': 'VPN',
'mobile.app.referral': 'الإحالة',
+3 -1
View File
@@ -1551,6 +1551,7 @@ export default {
'p2pNav.orders': 'داواکاریەکان',
'p2pNav.ads': 'ڕیکلامەکان',
'p2pNav.messages': 'پەیامەکان',
'p2pNav.buyVisa': 'بە ڤیزا بکڕە',
// P2P Messages Inbox
'p2pMessages.title': 'پەیامەکان',
@@ -3777,7 +3778,7 @@ export default {
'mobile.app.bank': 'بانک',
'mobile.app.exchange': 'ئاڵوگۆڕ',
'mobile.app.dex': 'Pez-DEX',
'mobile.app.p2p': 'P2P',
'mobile.app.p2p': 'P2P/Buy-Sell',
'mobile.app.b2b': 'B2B',
'mobile.app.bacZekat': 'باج/زەکات',
'mobile.app.launchpad': 'دەستپێکردن',
@@ -3794,6 +3795,7 @@ export default {
'mobile.app.kurdMedia': 'کوردمیدیا',
'mobile.app.events': 'چالاکی',
'mobile.app.help': 'یارمەتی',
'mobile.app.loto': 'لۆتۆ',
'mobile.app.music': 'مۆسیقا',
'mobile.app.vpn': 'VPN',
'mobile.app.referral': 'ئاماژە',
+3 -1
View File
@@ -1915,6 +1915,7 @@ export default {
'p2pNav.orders': 'Orders',
'p2pNav.ads': 'Ads',
'p2pNav.messages': 'Messages',
'p2pNav.buyVisa': 'Buy with Visa',
// P2P Messages Inbox
'p2pMessages.title': 'Messages',
@@ -3839,7 +3840,7 @@ export default {
'mobile.app.bank': 'Bank',
'mobile.app.exchange': 'Exchange',
'mobile.app.dex': 'Pez-DEX',
'mobile.app.p2p': 'P2P',
'mobile.app.p2p': 'P2P/Buy-Sell',
'mobile.app.b2b': 'B2B',
'mobile.app.bacZekat': 'Bac/Zekat',
'mobile.app.launchpad': 'Launchpad',
@@ -3856,6 +3857,7 @@ export default {
'mobile.app.kurdMedia': 'KurdMedia',
'mobile.app.events': 'Events',
'mobile.app.help': 'Help',
'mobile.app.loto': 'Loto',
'mobile.app.music': 'Music',
'mobile.app.vpn': 'VPN',
'mobile.app.rewshenbir': 'Rewshenbir',
+3 -1
View File
@@ -1585,6 +1585,7 @@ export default {
'p2pNav.orders': 'سفارشات',
'p2pNav.ads': 'آگهی‌ها',
'p2pNav.messages': 'پیام‌ها',
'p2pNav.buyVisa': 'خرید با ویزا',
// P2P Messages Inbox
'p2pMessages.title': 'پیام‌ها',
@@ -3821,7 +3822,7 @@ export default {
'mobile.app.bank': 'بانک',
'mobile.app.exchange': 'صرافی',
'mobile.app.dex': 'Pez-DEX',
'mobile.app.p2p': 'P2P',
'mobile.app.p2p': 'P2P/Buy-Sell',
'mobile.app.b2b': 'B2B',
'mobile.app.bacZekat': 'مالیات/زکات',
'mobile.app.launchpad': 'سکوی پرتاب',
@@ -3838,6 +3839,7 @@ export default {
'mobile.app.kurdMedia': 'کوردمدیا',
'mobile.app.events': 'رویدادها',
'mobile.app.help': 'کمک',
'mobile.app.loto': 'لاتاری',
'mobile.app.music': 'موسیقی',
'mobile.app.vpn': 'VPN',
'mobile.app.referral': 'ارجاع',
+3 -1
View File
@@ -1573,6 +1573,7 @@ export default {
'p2pNav.orders': 'Ferman',
'p2pNav.ads': 'Reklam',
'p2pNav.messages': 'Peyam',
'p2pNav.buyVisa': 'Bi Visa bikire',
// P2P Messages Inbox
'p2pMessages.title': 'Peyam',
@@ -3804,7 +3805,7 @@ export default {
'mobile.app.bank': 'Bank',
'mobile.app.exchange': 'Danûstandin',
'mobile.app.dex': 'Pez-DEX',
'mobile.app.p2p': 'P2P',
'mobile.app.p2p': 'P2P/Buy-Sell',
'mobile.app.b2b': 'B2B',
'mobile.app.bacZekat': 'Bac/Zekat',
'mobile.app.launchpad': 'Destpêk',
@@ -3821,6 +3822,7 @@ export default {
'mobile.app.kurdMedia': 'KurdMedya',
'mobile.app.events': 'Çalakî',
'mobile.app.help': 'Alîkarî',
'mobile.app.loto': 'Loto',
'mobile.app.music': 'Muzîk',
'mobile.app.vpn': 'VPN',
'mobile.app.rewshenbir': 'Rewşenbir',
+3 -1
View File
@@ -1567,6 +1567,7 @@ export default {
'p2pNav.orders': 'Siparişler',
'p2pNav.ads': 'İlanlar',
'p2pNav.messages': 'Mesajlar',
'p2pNav.buyVisa': 'Visa ile Al',
// P2P Messages Inbox
'p2pMessages.title': 'Mesajlar',
@@ -3807,7 +3808,7 @@ export default {
'mobile.app.bank': 'Banka',
'mobile.app.exchange': 'Borsa',
'mobile.app.dex': 'Pez-DEX',
'mobile.app.p2p': 'P2P',
'mobile.app.p2p': 'P2P/Buy-Sell',
'mobile.app.b2b': 'B2B',
'mobile.app.bacZekat': 'Vergi/Zekat',
'mobile.app.launchpad': 'Launchpad',
@@ -3824,6 +3825,7 @@ export default {
'mobile.app.kurdMedia': 'KurdMedya',
'mobile.app.events': 'Etkinlikler',
'mobile.app.help': 'Yardım',
'mobile.app.loto': 'Loto',
'mobile.app.music': 'Müzik',
'mobile.app.vpn': 'VPN',
'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 { supabase } from '@/lib/supabase';
import { Loader2 } from 'lucide-react';
@@ -12,7 +12,6 @@ const BEREKETLI_API = `${BEREKETLI_URL}/v1`;
*/
export default function Bereketli() {
const { t } = useTranslation();
const [error, setError] = useState('');
useEffect(() => {
(async () => {
@@ -20,8 +19,10 @@ export default function Bereketli() {
const {
data: { session },
} = 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) {
setError(t('bereketli.noSession', 'Lütfen önce giriş yapın'));
window.location.href = BEREKETLI_URL;
return;
}
@@ -46,27 +47,14 @@ export default function Bereketli() {
});
window.location.href = `${BEREKETLI_URL}/app?auth=${btoa(params.toString())}`;
} 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]);
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 (
<div className="min-h-screen bg-gray-950 flex items-center justify-center">
<div className="text-center space-y-3">
+33 -14
View File
@@ -9,6 +9,7 @@ interface MediaChannel {
descriptionKu: string;
description: string;
color: string;
url?: string;
}
interface SocialPlatform {
@@ -21,7 +22,7 @@ interface SocialPlatform {
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: '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: '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' },
@@ -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-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">
{MEDIA_CHANNELS.map(ch => (
<div key={ch.id} className="flex items-center gap-3 bg-gray-800 rounded-xl p-3">
<div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl flex-shrink-0" style={{ backgroundColor: ch.color }}>
{ch.icon}
{MEDIA_CHANNELS.map(ch => {
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>
<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 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>
+5
View File
@@ -3,6 +3,7 @@ import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react-swc";
import path from "path";
import { nodePolyfills } from 'vite-plugin-node-polyfills';
import subresourceIntegrity from 'vite-plugin-subresource-integrity';
// https://vitejs.dev/config/
export default defineConfig(({ command }) => ({
@@ -38,6 +39,10 @@ export default defineConfig(({ command }) => ({
},
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),
resolve: {
mainFields: ['browser', 'module', 'main', 'exports'],