Spaces:
Running
Running
Upload 36 files
#351
by
almortamoh
- opened
This view is limited to 50 files because it contains too many changes.
See the raw diff here.
- .env.example +0 -4
- .github/workflows/deploy-prod.yml +0 -77
- .gitignore +140 -35
- ANDROID_STUDIO_GUIDE.md +200 -0
- APK_BUILD_GUIDE.md +206 -0
- APK_README.md +200 -0
- BUILD_APK_SIMPLE.bat +84 -0
- BUILD_FROM_GITHUB.bat +128 -0
- BUILD_INSTRUCTIONS.md +265 -0
- CLOUD_BUILD_QUICK.md +123 -0
- Dockerfile +6 -9
- GITHUB_SETUP_GUIDE.md +209 -0
- INSTALL_ANDROID_SDK.bat +84 -0
- PROJECT_SUMMARY.md +219 -0
- QUICK_APK_BUILD.bat +79 -0
- QUICK_START.md +107 -0
- README.md +260 -21
- README_GITHUB.md +124 -0
- STEP_BY_STEP_APK.md +185 -0
- Windows +1 -0
- actions/mentions.ts +0 -31
- actions/projects.ts +0 -175
- angular.json +147 -0
- app.js +780 -0
- app/(public)/layout.tsx +4 -3
- app/(public)/page.tsx +37 -18
- app/(public)/projects/page.tsx +13 -0
- app/(public)/signin/page.tsx +0 -21
- app/[owner]/[repoId]/page.tsx +0 -35
- app/actions/auth.ts +18 -0
- app/actions/projects.ts +63 -0
- app/api/ask-ai/route.ts +419 -0
- app/api/ask/route.ts +0 -183
- app/api/auth/[...nextauth]/route.ts +0 -6
- app/api/auth/route.ts +86 -0
- app/api/healthcheck/route.ts +0 -5
- app/api/me/projects/[namespace]/[repoId]/route.ts +237 -0
- app/api/me/projects/route.ts +126 -0
- app/api/me/route.ts +25 -0
- app/api/projects/[repoId]/[commitId]/route.ts +0 -49
- app/api/projects/[repoId]/download/route.ts +0 -76
- app/api/projects/[repoId]/medias/route.ts +0 -87
- app/api/projects/[repoId]/rename/route.ts +0 -92
- app/api/projects/[repoId]/route.ts +0 -104
- app/api/projects/route.ts +0 -145
- app/api/re-design/route.ts +39 -0
- app/api/redesign/route.ts +0 -73
- app/auth/callback/page.tsx +72 -0
- app/auth/page.tsx +28 -0
- app/layout.tsx +65 -65
.env.example
DELETED
|
@@ -1,4 +0,0 @@
|
|
| 1 |
-
AUTH_HUGGINGFACE_ID=
|
| 2 |
-
AUTH_HUGGINGFACE_SECRET=
|
| 3 |
-
NEXTAUTH_URL=http://localhost:3001
|
| 4 |
-
AUTH_SECRET=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.github/workflows/deploy-prod.yml
DELETED
|
@@ -1,77 +0,0 @@
|
|
| 1 |
-
name: Deploy to k8s
|
| 2 |
-
on:
|
| 3 |
-
# run this workflow manually from the Actions tab
|
| 4 |
-
workflow_dispatch:
|
| 5 |
-
|
| 6 |
-
jobs:
|
| 7 |
-
build-and-publish:
|
| 8 |
-
runs-on:
|
| 9 |
-
group: cpu-high
|
| 10 |
-
steps:
|
| 11 |
-
- name: Checkout
|
| 12 |
-
uses: actions/checkout@v4
|
| 13 |
-
|
| 14 |
-
- name: Login to Registry
|
| 15 |
-
uses: docker/login-action@v3
|
| 16 |
-
with:
|
| 17 |
-
registry: registry.internal.huggingface.tech
|
| 18 |
-
username: ${{ secrets.DOCKER_INTERNAL_USERNAME }}
|
| 19 |
-
password: ${{ secrets.DOCKER_INTERNAL_PASSWORD }}
|
| 20 |
-
|
| 21 |
-
- name: Docker metadata
|
| 22 |
-
id: meta
|
| 23 |
-
uses: docker/metadata-action@v5
|
| 24 |
-
with:
|
| 25 |
-
images: |
|
| 26 |
-
registry.internal.huggingface.tech/deepsite/deepsite
|
| 27 |
-
tags: |
|
| 28 |
-
type=raw,value=latest,enable={{is_default_branch}}
|
| 29 |
-
type=sha,enable=true,prefix=sha-,format=short,sha-len=8
|
| 30 |
-
|
| 31 |
-
- name: Set up Docker Buildx
|
| 32 |
-
uses: docker/setup-buildx-action@v3
|
| 33 |
-
|
| 34 |
-
- name: Inject slug/short variables
|
| 35 |
-
uses: rlespinasse/github-slug-action@v4
|
| 36 |
-
|
| 37 |
-
- name: Build and Publish image
|
| 38 |
-
uses: docker/build-push-action@v5
|
| 39 |
-
with:
|
| 40 |
-
context: .
|
| 41 |
-
file: Dockerfile
|
| 42 |
-
push: ${{ github.event_name != 'pull_request' }}
|
| 43 |
-
tags: ${{ steps.meta.outputs.tags }}
|
| 44 |
-
labels: ${{ steps.meta.outputs.labels }}
|
| 45 |
-
platforms: linux/amd64
|
| 46 |
-
cache-to: type=gha,mode=max,scope=amd64
|
| 47 |
-
cache-from: type=gha,scope=amd64
|
| 48 |
-
provenance: false
|
| 49 |
-
|
| 50 |
-
deploy:
|
| 51 |
-
name: Deploy on prod
|
| 52 |
-
runs-on: ubuntu-latest
|
| 53 |
-
needs: ["build-and-publish"]
|
| 54 |
-
steps:
|
| 55 |
-
- name: Inject slug/short variables
|
| 56 |
-
uses: rlespinasse/github-slug-action@v4
|
| 57 |
-
|
| 58 |
-
- name: Gen values
|
| 59 |
-
run: |
|
| 60 |
-
VALUES=$(cat <<-END
|
| 61 |
-
image:
|
| 62 |
-
tag: "sha-${{ env.GITHUB_SHA_SHORT }}"
|
| 63 |
-
END
|
| 64 |
-
)
|
| 65 |
-
echo "VALUES=$(echo "$VALUES" | yq -o=json | jq tostring)" >> $GITHUB_ENV
|
| 66 |
-
|
| 67 |
-
- name: Deploy on infra-deployments
|
| 68 |
-
uses: the-actions-org/workflow-dispatch@v2
|
| 69 |
-
with:
|
| 70 |
-
workflow: Update application single value
|
| 71 |
-
repo: huggingface/infra-deployments
|
| 72 |
-
wait-for-completion: true
|
| 73 |
-
wait-for-completion-interval: 10s
|
| 74 |
-
display-workflow-run-url-interval: 10s
|
| 75 |
-
ref: refs/heads/main
|
| 76 |
-
token: ${{ secrets.GIT_TOKEN_INFRA_DEPLOYMENT }}
|
| 77 |
-
inputs: '{"path": "hub/deepsite/deepsite.yaml", "value": ${{ env.VALUES }}, "url": "${{ github.event.head_commit.url }}"}'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
CHANGED
|
@@ -1,47 +1,152 @@
|
|
| 1 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
#
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
.
|
| 8 |
-
!.yarn/patches
|
| 9 |
-
!.yarn/plugins
|
| 10 |
-
!.yarn/releases
|
| 11 |
-
!.yarn/versions
|
| 12 |
|
| 13 |
-
#
|
| 14 |
-
|
| 15 |
|
| 16 |
-
#
|
| 17 |
-
|
| 18 |
-
|
| 19 |
|
| 20 |
-
#
|
| 21 |
-
|
| 22 |
|
| 23 |
-
#
|
| 24 |
-
.
|
| 25 |
-
*.pem
|
| 26 |
|
| 27 |
-
#
|
| 28 |
-
|
| 29 |
-
yarn-debug.log*
|
| 30 |
-
yarn-error.log*
|
| 31 |
-
.pnpm-debug.log*
|
| 32 |
|
| 33 |
-
#
|
| 34 |
-
.
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
-
#
|
| 37 |
-
|
|
|
|
| 38 |
|
| 39 |
-
#
|
|
|
|
|
|
|
|
|
|
| 40 |
*.tsbuildinfo
|
| 41 |
-
next-env.d.ts
|
| 42 |
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
-
#
|
| 46 |
-
|
| 47 |
-
|
|
|
|
| 1 |
+
# Logs
|
| 2 |
+
logs
|
| 3 |
+
*.log
|
| 4 |
+
npm-debug.log*
|
| 5 |
+
yarn-debug.log*
|
| 6 |
+
yarn-error.log*
|
| 7 |
+
lerna-debug.log*
|
| 8 |
|
| 9 |
+
# Runtime data
|
| 10 |
+
pids
|
| 11 |
+
*.pid
|
| 12 |
+
*.seed
|
| 13 |
+
*.pid.lock
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
| 16 |
+
lib-cov
|
| 17 |
|
| 18 |
+
# Coverage directory used by tools like istanbul
|
| 19 |
+
coverage
|
| 20 |
+
*.lcov
|
| 21 |
|
| 22 |
+
# nyc test coverage
|
| 23 |
+
.nyc_output
|
| 24 |
|
| 25 |
+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
| 26 |
+
.grunt
|
|
|
|
| 27 |
|
| 28 |
+
# Bower dependency directory (https://bower.io/)
|
| 29 |
+
bower_components
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
# node-waf configuration
|
| 32 |
+
.lock-wscript
|
| 33 |
+
|
| 34 |
+
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
| 35 |
+
build/Release
|
| 36 |
|
| 37 |
+
# Dependency directories
|
| 38 |
+
node_modules/
|
| 39 |
+
jspm_packages/
|
| 40 |
|
| 41 |
+
# TypeScript v1 declaration files
|
| 42 |
+
typings/
|
| 43 |
+
|
| 44 |
+
# TypeScript cache
|
| 45 |
*.tsbuildinfo
|
|
|
|
| 46 |
|
| 47 |
+
# Optional npm cache directory
|
| 48 |
+
.npm
|
| 49 |
+
|
| 50 |
+
# Optional eslint cache
|
| 51 |
+
.eslintcache
|
| 52 |
+
|
| 53 |
+
# Microbundle cache
|
| 54 |
+
.rpt2_cache/
|
| 55 |
+
.rts2_cache_cjs/
|
| 56 |
+
.rts2_cache_es/
|
| 57 |
+
.rts2_cache_umd/
|
| 58 |
+
|
| 59 |
+
# Optional REPL history
|
| 60 |
+
.node_repl_history
|
| 61 |
+
|
| 62 |
+
# Output of 'npm pack'
|
| 63 |
+
*.tgz
|
| 64 |
+
|
| 65 |
+
# Yarn Integrity file
|
| 66 |
+
.yarn-integrity
|
| 67 |
+
|
| 68 |
+
# dotenv environment variables file
|
| 69 |
+
.env
|
| 70 |
+
.env.test
|
| 71 |
+
|
| 72 |
+
# parcel-bundler cache (https://parceljs.org/)
|
| 73 |
+
.cache
|
| 74 |
+
.parcel-cache
|
| 75 |
+
|
| 76 |
+
# Next.js build output
|
| 77 |
+
.next
|
| 78 |
+
|
| 79 |
+
# Nuxt.js build / generate output
|
| 80 |
+
.nuxt
|
| 81 |
+
dist
|
| 82 |
+
|
| 83 |
+
# Gatsby files
|
| 84 |
+
.cache/
|
| 85 |
+
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
| 86 |
+
# https://nextjs.org/blog/next-9-1#public-directory-support
|
| 87 |
+
# public
|
| 88 |
+
|
| 89 |
+
# vuepress build output
|
| 90 |
+
.vuepress/dist
|
| 91 |
+
|
| 92 |
+
# Serverless directories
|
| 93 |
+
.serverless/
|
| 94 |
+
|
| 95 |
+
# FuseBox cache
|
| 96 |
+
.fusebox/
|
| 97 |
+
|
| 98 |
+
# DynamoDB Local files
|
| 99 |
+
.dynamodb/
|
| 100 |
+
|
| 101 |
+
# TernJS port file
|
| 102 |
+
.tern-port
|
| 103 |
+
|
| 104 |
+
# Capacitor
|
| 105 |
+
.capacitor/
|
| 106 |
+
capacitor.config.json
|
| 107 |
+
|
| 108 |
+
# Android
|
| 109 |
+
android/app/build/
|
| 110 |
+
android/build/
|
| 111 |
+
android/.gradle/
|
| 112 |
+
android/local.properties
|
| 113 |
+
android/app/release/
|
| 114 |
+
android/app/debug/
|
| 115 |
+
android/gradle/
|
| 116 |
+
android/gradlew
|
| 117 |
+
android/gradlew.bat
|
| 118 |
+
|
| 119 |
+
# iOS
|
| 120 |
+
ios/build/
|
| 121 |
+
ios/App/Pods/
|
| 122 |
+
ios/App/App.xcworkspace/xcuserdata/
|
| 123 |
+
ios/App/App.xcodeproj/xcuserdata/
|
| 124 |
+
ios/App/App.xcodeproj/project.xcworkspace/xcuserdata/
|
| 125 |
+
|
| 126 |
+
# Editor directories and files
|
| 127 |
+
.vscode/
|
| 128 |
+
.idea/
|
| 129 |
+
*.swp
|
| 130 |
+
*.swo
|
| 131 |
+
*~
|
| 132 |
+
|
| 133 |
+
# OS generated files
|
| 134 |
+
.DS_Store
|
| 135 |
+
.DS_Store?
|
| 136 |
+
._*
|
| 137 |
+
.Spotlight-V100
|
| 138 |
+
.Trashes
|
| 139 |
+
ehthumbs.db
|
| 140 |
+
Thumbs.db
|
| 141 |
+
|
| 142 |
+
# Local development
|
| 143 |
+
*.local
|
| 144 |
+
|
| 145 |
+
# Build outputs
|
| 146 |
+
build/
|
| 147 |
+
dist/
|
| 148 |
+
www/build/
|
| 149 |
|
| 150 |
+
# Temporary files
|
| 151 |
+
tmp/
|
| 152 |
+
temp/
|
ANDROID_STUDIO_GUIDE.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 📱 دليل بناء APK باستخدام Android Studio
|
| 2 |
+
|
| 3 |
+
## 🎯 **الهدف:** تحويل المشروع إلى ملف APK قابل للتثبيت
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 📋 **الخطوات المفصلة:**
|
| 8 |
+
|
| 9 |
+
### **الخطوة 1: فتح المشروع** ⏱️ (2-3 دقائق)
|
| 10 |
+
|
| 11 |
+
1. **افتح Android Studio**
|
| 12 |
+
2. **اختر "Open an Existing Project"** أو **"Open"**
|
| 13 |
+
3. **انتقل إلى مجلد المشروع:**
|
| 14 |
+
```
|
| 15 |
+
E:\almada\android
|
| 16 |
+
```
|
| 17 |
+
4. **اختر مجلد `android`** (وليس المجلد الرئيسي)
|
| 18 |
+
5. **اضغط "OK"**
|
| 19 |
+
|
| 20 |
+
### **الخطوة 2: انتظار المزامنة** ⏱️ (5-10 دقائق)
|
| 21 |
+
|
| 22 |
+
عند فتح المشروع لأول مرة، سيقوم Android Studio بـ:
|
| 23 |
+
- 📥 **تحميل Gradle** (إذا لم يكن مثبت)
|
| 24 |
+
- 📦 **تحميل التبعيات** (Dependencies)
|
| 25 |
+
- 🔄 **مزامنة المشروع** (Sync)
|
| 26 |
+
|
| 27 |
+
**انتظر حتى تكتمل العملية!** ستظهر رسالة في الأسفل:
|
| 28 |
+
```
|
| 29 |
+
✅ Gradle sync finished
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
### **الخطوة 3: حل المشاكل المحتملة** ⏱️ (2-5 دقائق)
|
| 33 |
+
|
| 34 |
+
إذا ظهرت أي رسائل خطأ:
|
| 35 |
+
|
| 36 |
+
#### **مشكلة SDK:**
|
| 37 |
+
```
|
| 38 |
+
SDK location not found
|
| 39 |
+
```
|
| 40 |
+
**الحل:**
|
| 41 |
+
- اذهب إلى **File > Project Structure**
|
| 42 |
+
- اختر **SDK Location**
|
| 43 |
+
- تأكد من مسار Android SDK
|
| 44 |
+
|
| 45 |
+
#### **مشكلة Gradle:**
|
| 46 |
+
```
|
| 47 |
+
Gradle version not supported
|
| 48 |
+
```
|
| 49 |
+
**الحل:**
|
| 50 |
+
- اضغط على **"Update Gradle"** في الرسالة
|
| 51 |
+
- أو اذهب إلى **File > Project Structure > Project**
|
| 52 |
+
|
| 53 |
+
### **الخطوة 4: بناء APK** ⏱️ (3-5 دقائق)
|
| 54 |
+
|
| 55 |
+
1. **في شريط القوائم، اختر:**
|
| 56 |
+
```
|
| 57 |
+
Build > Build Bundle(s) / APK(s) > Build APK(s)
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
2. **انتظر اكتمال البناء** - ستظهر رسالة في الأسفل:
|
| 61 |
+
```
|
| 62 |
+
⏳ Building APK...
|
| 63 |
+
✅ APK(s) generated successfully
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
3. **عند اكتمال البناء، ستظهر نافذة:**
|
| 67 |
+
```
|
| 68 |
+
APK(s) generated successfully.
|
| 69 |
+
|
| 70 |
+
[locate] [analyze]
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
4. **اضغط "locate"** للذهاب إلى مجلد APK
|
| 74 |
+
|
| 75 |
+
### **الخطوة 5: العثور على ملف APK** ⏱️ (1 دقيقة)
|
| 76 |
+
|
| 77 |
+
ملف APK سيكون في:
|
| 78 |
+
```
|
| 79 |
+
E:\almada\android\app\build\outputs\apk\debug\app-debug.apk
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
**معلومات الملف:**
|
| 83 |
+
- **الاسم:** `app-debug.apk`
|
| 84 |
+
- **الحجم:** ~15-20 MB
|
| 85 |
+
- **النوع:** Debug APK (للاختبار)
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
|
| 89 |
+
## 🔧 **استكشاف الأخطاء:**
|
| 90 |
+
|
| 91 |
+
### **خطأ: "SDK not found"**
|
| 92 |
+
```bash
|
| 93 |
+
# الحل:
|
| 94 |
+
1. اذهب إلى File > Settings
|
| 95 |
+
2. اختر Appearance & Behavior > System Settings > Android SDK
|
| 96 |
+
3. تأكد من تثبيت Android SDK
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
### **خطأ: "Gradle sync failed"**
|
| 100 |
+
```bash
|
| 101 |
+
# الحل:
|
| 102 |
+
1. اضغط "Try Again"
|
| 103 |
+
2. أو اذهب إلى File > Sync Project with Gradle Files
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
### **خطأ: "Build failed"**
|
| 107 |
+
```bash
|
| 108 |
+
# الحل:
|
| 109 |
+
1. اذهب إلى Build > Clean Project
|
| 110 |
+
2. ثم Build > Rebuild Project
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
---
|
| 114 |
+
|
| 115 |
+
## 📱 **اختبار التطبيق:**
|
| 116 |
+
|
| 117 |
+
### **الطريقة 1: على الكمبيوتر (محاكي)**
|
| 118 |
+
1. **إنشاء محاكي:**
|
| 119 |
+
- اذهب إلى **Tools > AVD Manager**
|
| 120 |
+
- اضغط **"Create Virtual Device"**
|
| 121 |
+
- اختر جهاز (مثل Pixel 4)
|
| 122 |
+
- اختر نظام Android (API 30+)
|
| 123 |
+
|
| 124 |
+
2. **تشغيل التطبيق:**
|
| 125 |
+
- اضغط **Run** (الزر الأخضر)
|
| 126 |
+
- اختر المحاكي
|
| 127 |
+
- انتظر تشغيل التطبيق
|
| 128 |
+
|
| 129 |
+
### **الطريقة 2: على الهاتف الحقيقي**
|
| 130 |
+
1. **تفعيل Developer Options:**
|
| 131 |
+
- اذهب إلى **Settings > About Phone**
|
| 132 |
+
- اضغط على **Build Number** 7 مرات
|
| 133 |
+
- ارجع إلى Settings وادخل **Developer Options**
|
| 134 |
+
- فعل **USB Debugging**
|
| 135 |
+
|
| 136 |
+
2. **توصيل الهاتف:**
|
| 137 |
+
- وصل الهاتف بـ USB
|
| 138 |
+
- اختر **File Transfer** في الهاتف
|
| 139 |
+
- في Android Studio، اختر جهازك من القائمة
|
| 140 |
+
- اضغط **Run**
|
| 141 |
+
|
| 142 |
+
### **الطريقة 3: تثبيت APK يدوياً**
|
| 143 |
+
1. **نسخ APK إلى الهاتف**
|
| 144 |
+
2. **في الهاتف:**
|
| 145 |
+
- اذهب إلى **Settings > Security**
|
| 146 |
+
- فعل **"Install from Unknown Sources"**
|
| 147 |
+
- افتح ملف APK واضغط **Install**
|
| 148 |
+
|
| 149 |
+
---
|
| 150 |
+
|
| 151 |
+
## 🎯 **بيانات التجربة:**
|
| 152 |
+
|
| 153 |
+
بعد تثبيت التطبيق، استخدم:
|
| 154 |
+
- **رقم الهاتف:** `777123456`
|
| 155 |
+
- **رمز PIN:** `1234`
|
| 156 |
+
|
| 157 |
+
---
|
| 158 |
+
|
| 159 |
+
## 📊 **معلومات التطبيق:**
|
| 160 |
+
|
| 161 |
+
| المعلومة | القيمة |
|
| 162 |
+
|---------|--------|
|
| 163 |
+
| **اسم التطبيق** | محفظتي الموحدة |
|
| 164 |
+
| **Package Name** | com.almada.unifiedwallet |
|
| 165 |
+
| **الإصدار** | 1.0.0 |
|
| 166 |
+
| **حجم APK** | ~15-20 MB |
|
| 167 |
+
| **الحد الأدنى** | Android 7.0 (API 24) |
|
| 168 |
+
|
| 169 |
+
---
|
| 170 |
+
|
| 171 |
+
## 🎉 **النجاح!**
|
| 172 |
+
|
| 173 |
+
عند اكتمال جميع الخطوات، ستحصل على:
|
| 174 |
+
- ✅ **ملف APK** جاهز للتثبيت
|
| 175 |
+
- ✅ **تطبيق يعمل** على الأندرويد
|
| 176 |
+
- ✅ **واجهة عربية** كاملة
|
| 177 |
+
- ✅ **جميع الميزات** متاحة
|
| 178 |
+
|
| 179 |
+
---
|
| 180 |
+
|
| 181 |
+
## 💡 **نصائح مهمة:**
|
| 182 |
+
|
| 183 |
+
### **ل��بناء الناجح:**
|
| 184 |
+
- تأكد من **اتصال الإنترنت** أثناء أول مزامنة
|
| 185 |
+
- **لا تغلق** Android Studio أثناء التحميل
|
| 186 |
+
- **انتظر** اكتمال جميع العمليات
|
| 187 |
+
|
| 188 |
+
### **للاختبار:**
|
| 189 |
+
- اختبر على **أجهزة مختلفة** إن أمكن
|
| 190 |
+
- تأكد من **جميع الميزات** تعمل
|
| 191 |
+
- اختبر **تسجيل الدخول** والتنقل
|
| 192 |
+
|
| 193 |
+
### **للمشاكل:**
|
| 194 |
+
- راجع **Build Output** في الأسفل
|
| 195 |
+
- استخدم **"Clean Project"** عند المشاكل
|
| 196 |
+
- أعد تشغيل **Android Studio** إذا لزم الأمر
|
| 197 |
+
|
| 198 |
+
---
|
| 199 |
+
|
| 200 |
+
**🚀 مبروك! تطبيقك جاهز للعالم!**
|
APK_BUILD_GUIDE.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# دليل بناء APK - محفظتي الموحدة
|
| 2 |
+
|
| 3 |
+
## 🎯 **الوضع الحالي**
|
| 4 |
+
|
| 5 |
+
✅ **تم إنجازه:**
|
| 6 |
+
- ✅ إعداد مشروع Capacitor كامل
|
| 7 |
+
- ✅ تثبيت Java JDK 11
|
| 8 |
+
- ✅ إضافة منصة الأندرويد
|
| 9 |
+
- ✅ نسخ ملفات التطبيق إلى مجلد www
|
| 10 |
+
- ✅ إضافة الأذونات المطلوبة
|
| 11 |
+
- ✅ إعداد ملفات التكوين
|
| 12 |
+
|
| 13 |
+
❌ **المطلوب لإكمال البناء:**
|
| 14 |
+
- ❌ تثبيت Android SDK
|
| 15 |
+
- ❌ إعداد متغيرات البيئة
|
| 16 |
+
- ❌ بناء APK
|
| 17 |
+
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
## 📋 **خطوات إكمال بناء APK**
|
| 21 |
+
|
| 22 |
+
### **الطريقة 1: استخدام Android Studio (الأسهل)**
|
| 23 |
+
|
| 24 |
+
#### 1. تحميل وتثبيت Android Studio:
|
| 25 |
+
```
|
| 26 |
+
https://developer.android.com/studio
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
#### 2. فتح المشروع:
|
| 30 |
+
```bash
|
| 31 |
+
cd E:\almada\android
|
| 32 |
+
# ثم فتح المجلد في Android Studio
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
#### 3. بناء APK:
|
| 36 |
+
- في Android Studio: **Build > Build Bundle(s) / APK(s) > Build APK(s)**
|
| 37 |
+
- انتظار اكتمال البناء
|
| 38 |
+
- ستجد APK في: `android/app/build/outputs/apk/debug/`
|
| 39 |
+
|
| 40 |
+
---
|
| 41 |
+
|
| 42 |
+
### **الطريقة 2: سطر الأوامر (متقدم)**
|
| 43 |
+
|
| 44 |
+
#### 1. تثبيت Android SDK:
|
| 45 |
+
```bash
|
| 46 |
+
# تحميل Command Line Tools من:
|
| 47 |
+
# https://developer.android.com/studio#command-tools
|
| 48 |
+
|
| 49 |
+
# استخراج إلى مجلد مثل:
|
| 50 |
+
# C:\Android\cmdline-tools\latest\
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
#### 2. إعداد متغيرات البيئة:
|
| 54 |
+
```bash
|
| 55 |
+
# إضافة إلى متغيرات البيئة:
|
| 56 |
+
ANDROID_HOME=C:\Android
|
| 57 |
+
ANDROID_SDK_ROOT=C:\Android
|
| 58 |
+
PATH=%PATH%;%ANDROID_HOME%\cmdline-tools\latest\bin
|
| 59 |
+
PATH=%PATH%;%ANDROID_HOME%\platform-tools
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
#### 3. تثبيت SDK Components:
|
| 63 |
+
```bash
|
| 64 |
+
sdkmanager "platform-tools" "platforms;android-33" "build-tools;33.0.0"
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
#### 4. بناء APK:
|
| 68 |
+
```bash
|
| 69 |
+
cd E:\almada\android
|
| 70 |
+
.\gradlew assembleDebug
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
---
|
| 74 |
+
|
| 75 |
+
### **الطريقة 3: استخدام Ionic CLI (الأبسط)**
|
| 76 |
+
|
| 77 |
+
#### 1. تثبيت Android Studio أولاً (للحصول على SDK)
|
| 78 |
+
|
| 79 |
+
#### 2. استخدام Ionic:
|
| 80 |
+
```bash
|
| 81 |
+
cd E:\almada
|
| 82 |
+
ionic cap build android
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
---
|
| 86 |
+
|
| 87 |
+
## 📱 **ملفات APK المتوقعة**
|
| 88 |
+
|
| 89 |
+
بعد البناء الناجح ستجد:
|
| 90 |
+
|
| 91 |
+
### **APK للتطوير:**
|
| 92 |
+
```
|
| 93 |
+
android/app/build/outputs/apk/debug/app-debug.apk
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
### **APK للإنتاج:**
|
| 97 |
+
```
|
| 98 |
+
android/app/build/outputs/apk/release/app-release.apk
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
---
|
| 102 |
+
|
| 103 |
+
## 🔧 **إعدادات إضافية للتطبيق**
|
| 104 |
+
|
| 105 |
+
### **الأذونات المضافة:**
|
| 106 |
+
```xml
|
| 107 |
+
<!-- الأذونات الأساسية -->
|
| 108 |
+
<uses-permission android:name="android.permission.INTERNET" />
|
| 109 |
+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
| 110 |
+
<uses-permission android:name="android.permission.CAMERA" />
|
| 111 |
+
<uses-permission android:name="android.permission.VIBRATE" />
|
| 112 |
+
|
| 113 |
+
<!-- أذونات SMS (للمستقبل) -->
|
| 114 |
+
<uses-permission android:name="android.permission.READ_SMS" />
|
| 115 |
+
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
| 116 |
+
|
| 117 |
+
<!-- أذونات البصمة -->
|
| 118 |
+
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
| 119 |
+
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
### **معلومات التطبيق:**
|
| 123 |
+
- **اسم التطبيق:** محفظتي الموحدة
|
| 124 |
+
- **Package ID:** com.almada.unifiedwallet
|
| 125 |
+
- **الإصدار:** 1.0.0
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
## 🚀 **اختبار التطبيق**
|
| 130 |
+
|
| 131 |
+
### **تثبيت APK على الهاتف:**
|
| 132 |
+
```bash
|
| 133 |
+
# تفعيل Developer Options و USB Debugging
|
| 134 |
+
# ثم:
|
| 135 |
+
adb install app-debug.apk
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
### **أو نسخ APK إلى الهاتف وتثبيته يدوياً**
|
| 139 |
+
|
| 140 |
+
---
|
| 141 |
+
|
| 142 |
+
## 📊 **الميزات المتاحة في التطبيق**
|
| 143 |
+
|
| 144 |
+
### **الصفحة الرئيسية:**
|
| 145 |
+
- تسجيل دخول برقم هاتف + PIN
|
| 146 |
+
- عرض المحافظ الـ6 (جوالي، ONE Cash، إلخ)
|
| 147 |
+
- عرض الأرصدة الموحدة
|
| 148 |
+
|
| 149 |
+
### **الميزات المتقدمة:**
|
| 150 |
+
- واجهة عربية كاملة (RTL)
|
| 151 |
+
- تصميم متجاوب
|
| 152 |
+
- رسوم متحركة سلسة
|
| 153 |
+
- دعم الوضع الليلي
|
| 154 |
+
|
| 155 |
+
### **الأمان:**
|
| 156 |
+
- تشفير البيانات محلياً
|
| 157 |
+
- حماية PIN
|
| 158 |
+
- جلسات آمنة
|
| 159 |
+
|
| 160 |
+
---
|
| 161 |
+
|
| 162 |
+
## 🎯 **الخطوات التالية بعد البناء**
|
| 163 |
+
|
| 164 |
+
### **للاختبار:**
|
| 165 |
+
1. تثبيت APK على الهاتف
|
| 166 |
+
2. اختبار تسجيل الدخول (777123456 / 1234)
|
| 167 |
+
3. اختبار جميع الميزات
|
| 168 |
+
4. التأكد من الأداء
|
| 169 |
+
|
| 170 |
+
### **للتطوير:**
|
| 171 |
+
1. إضافة ميزات قراءة SMS
|
| 172 |
+
2. تطوير نظام الإشعارات
|
| 173 |
+
3. إضافة المزيد من المحافظ
|
| 174 |
+
4. تحسين الأمان
|
| 175 |
+
|
| 176 |
+
### **للنشر:**
|
| 177 |
+
1. إنشاء حساب Google Play Developer
|
| 178 |
+
2. إعداد التوقيع للإنتاج
|
| 179 |
+
3. رفع التطبيق للمراجعة
|
| 180 |
+
4. التسويق والترويج
|
| 181 |
+
|
| 182 |
+
---
|
| 183 |
+
|
| 184 |
+
## 💡 **نصائح مهمة**
|
| 185 |
+
|
| 186 |
+
### **للبناء الناجح:**
|
| 187 |
+
- تأكد من تثبيت Java JDK 11+ ✅
|
| 188 |
+
- تأكد من تثبيت Android SDK
|
| 189 |
+
- تأكد من إعداد متغيرات البيئة
|
| 190 |
+
- استخدم Android Studio للسهولة
|
| 191 |
+
|
| 192 |
+
### **للاختبار:**
|
| 193 |
+
- اختبر على أجهزة مختلفة
|
| 194 |
+
- اختبر جميع الميزات
|
| 195 |
+
- تأكد من الأداء والاستقرار
|
| 196 |
+
|
| 197 |
+
### **للأمان:**
|
| 198 |
+
- لا تشارك ملفات التوقيع
|
| 199 |
+
- استخدم ProGuard للحماية
|
| 200 |
+
- اختبر الأمان بعناية
|
| 201 |
+
|
| 202 |
+
---
|
| 203 |
+
|
| 204 |
+
**🎉 مبروك! تطبيقك جاهز للبناء والاختبار!**
|
| 205 |
+
|
| 206 |
+
للمساعدة في أي خطوة، راجع الوثائق أو تواصل مع فريق التطوير.
|
APK_README.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 📱 محفظتي الموحدة - تطبيق APK جاهز!
|
| 2 |
+
|
| 3 |
+
## 🎉 **تهانينا! تطبيقك جاهز للبناء**
|
| 4 |
+
|
| 5 |
+
تم إعداد مشروع **محفظتي الموحدة** بنجاح كتطبيق أندرويد باستخدام تقنية **Capacitor**.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## 🚀 **طرق بناء APK**
|
| 10 |
+
|
| 11 |
+
### **الطريقة الأسهل: Android Studio**
|
| 12 |
+
1. حمل وثبت [Android Studio](https://developer.android.com/studio)
|
| 13 |
+
2. افتح مجلد `android` في Android Studio
|
| 14 |
+
3. اختر **Build > Build APK**
|
| 15 |
+
4. انتظر اكتمال البناء
|
| 16 |
+
5. ستجد APK في: `android/app/build/outputs/apk/debug/`
|
| 17 |
+
|
| 18 |
+
### **الطريقة السريعة: ملف BAT**
|
| 19 |
+
1. شغل `INSTALL_ANDROID_SDK.bat` (مرة واحدة فقط)
|
| 20 |
+
2. اتبع التعليمات لتثبيت Android SDK
|
| 21 |
+
3. شغل `QUICK_APK_BUILD.bat`
|
| 22 |
+
4. انتظر اكتمال البناء
|
| 23 |
+
|
| 24 |
+
### **الطريقة اليدوية: سطر الأوامر**
|
| 25 |
+
```bash
|
| 26 |
+
# تأكد من تثبيت Java و Android SDK
|
| 27 |
+
java -version
|
| 28 |
+
sdkmanager --version
|
| 29 |
+
|
| 30 |
+
# بناء APK
|
| 31 |
+
cd android
|
| 32 |
+
gradlew assembleDebug
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
---
|
| 36 |
+
|
| 37 |
+
## 📋 **متطلبات البناء**
|
| 38 |
+
|
| 39 |
+
### **مثبت بالفعل:**
|
| 40 |
+
✅ **Node.js** و **npm**
|
| 41 |
+
✅ **Capacitor** و **التبعيات**
|
| 42 |
+
✅ **Java JDK 11**
|
| 43 |
+
✅ **مشروع Android** جاهز
|
| 44 |
+
|
| 45 |
+
### **مطلوب تثبيته:**
|
| 46 |
+
❌ **Android SDK** (عبر Android Studio أو Command Line Tools)
|
| 47 |
+
|
| 48 |
+
---
|
| 49 |
+
|
| 50 |
+
## 📱 **معلومات التطبيق**
|
| 51 |
+
|
| 52 |
+
| المعلومة | القيمة |
|
| 53 |
+
|---------|--------|
|
| 54 |
+
| **اسم التطبيق** | محفظتي الموحدة |
|
| 55 |
+
| **Package ID** | com.almada.unifiedwallet |
|
| 56 |
+
| **الإصدار** | 1.0.0 |
|
| 57 |
+
| **الحد الأدنى للأندرويد** | API 24 (Android 7.0) |
|
| 58 |
+
|
| 59 |
+
---
|
| 60 |
+
|
| 61 |
+
## 🔑 **بيانات التجربة**
|
| 62 |
+
|
| 63 |
+
للدخول إلى التطبيق بعد التثبيت:
|
| 64 |
+
- **رقم الهاتف:** `777123456`
|
| 65 |
+
- **رمز PIN:** `1234`
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
## 🎯 **الميزات المتاحة**
|
| 70 |
+
|
| 71 |
+
### **الأساسية:**
|
| 72 |
+
- ✅ تسجيل دخول آمن برقم الهاتف + PIN
|
| 73 |
+
- ✅ عرض 6 محافظ يمنية (جوالي، ONE Cash، إلخ)
|
| 74 |
+
- ✅ واجهة عربية كاملة (RTL)
|
| 75 |
+
- ✅ تصميم متجاوب وجذاب
|
| 76 |
+
|
| 77 |
+
### **المتقدمة:**
|
| 78 |
+
- ✅ رسوم متحركة سلسة
|
| 79 |
+
- ✅ دعم الوضع الليلي
|
| 80 |
+
- ✅ حفظ البيانات محلياً
|
| 81 |
+
- ✅ أمان متعدد الطبقات
|
| 82 |
+
|
| 83 |
+
### **المستقبلية (جاهزة للتطوير):**
|
| 84 |
+
- 🔄 قراءة رسائل SMS تلقائياً
|
| 85 |
+
- 🔄 مصادقة بيومترية (بصمة/وجه)
|
| 86 |
+
- 🔄 إشعارات ذكية
|
| 87 |
+
- 🔄 تحليل الإنفاق
|
| 88 |
+
|
| 89 |
+
---
|
| 90 |
+
|
| 91 |
+
## 📂 **هيكل الملفات**
|
| 92 |
+
|
| 93 |
+
```
|
| 94 |
+
almada/
|
| 95 |
+
├── 📱 android/ # مشروع الأندرويد
|
| 96 |
+
├── 🌐 www/ # ملفات التطبيق
|
| 97 |
+
├── 📄 capacitor.config.ts # إعدادات Capacitor
|
| 98 |
+
├── 📦 package.json # تبعيات المشروع
|
| 99 |
+
├── 🔨 QUICK_APK_BUILD.bat # بناء سريع
|
| 100 |
+
├── ⚙️ INSTALL_ANDROID_SDK.bat # تثبيت SDK
|
| 101 |
+
└── 📖 APK_BUILD_GUIDE.md # دليل مفصل
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
---
|
| 105 |
+
|
| 106 |
+
## 🛠️ **استكشاف الأخطاء**
|
| 107 |
+
|
| 108 |
+
### **خطأ: Java غير موجود**
|
| 109 |
+
```bash
|
| 110 |
+
# تحقق من تثبيت Java
|
| 111 |
+
java -version
|
| 112 |
+
|
| 113 |
+
# إذا لم يكن مثبت، حمل من:
|
| 114 |
+
# https://adoptium.net/
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
### **خطأ: Android SDK غير موجود**
|
| 118 |
+
```bash
|
| 119 |
+
# شغل ملف التثبيت
|
| 120 |
+
INSTALL_ANDROID_SDK.bat
|
| 121 |
+
|
| 122 |
+
# أو ثبت Android Studio
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
### **خطأ: Gradle Build فشل**
|
| 126 |
+
```bash
|
| 127 |
+
# نظف المشروع
|
| 128 |
+
cd android
|
| 129 |
+
gradlew clean
|
| 130 |
+
|
| 131 |
+
# أعد البناء
|
| 132 |
+
gradlew assembleDebug
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
---
|
| 136 |
+
|
| 137 |
+
## 📲 **تثبيت APK على الهاتف**
|
| 138 |
+
|
| 139 |
+
### **الطريقة 1: USB**
|
| 140 |
+
```bash
|
| 141 |
+
# فعل USB Debugging في الهاتف
|
| 142 |
+
# ثم:
|
| 143 |
+
adb install app-debug.apk
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
### **الطريقة 2: يدوياً**
|
| 147 |
+
1. انسخ ملف APK إلى الهاتف
|
| 148 |
+
2. فعل "مصادر غير معروفة" في الإعدادات
|
| 149 |
+
3. اضغط على ملف APK لتثبيته
|
| 150 |
+
|
| 151 |
+
---
|
| 152 |
+
|
| 153 |
+
## 🎯 **الخطوات التالية**
|
| 154 |
+
|
| 155 |
+
### **للاختبار:**
|
| 156 |
+
1. 📱 ثبت APK على الهاتف
|
| 157 |
+
2. 🔐 جرب تسجيل الدخول
|
| 158 |
+
3. 💳 استكشف المحافظ
|
| 159 |
+
4. 🎨 جرب الميزات المختلفة
|
| 160 |
+
|
| 161 |
+
### **للتطوير:**
|
| 162 |
+
1. 📨 أضف قراءة SMS
|
| 163 |
+
2. 🔔 طور الإشعارات
|
| 164 |
+
3. 📊 أضف تحليل البيانات
|
| 165 |
+
4. 🛡️ حسن الأمان
|
| 166 |
+
|
| 167 |
+
### **للنشر:**
|
| 168 |
+
1. 🏪 أنشئ حساب Google Play
|
| 169 |
+
2. 🔏 أعد التوقيع للإنتاج
|
| 170 |
+
3. 📤 ارفع للمراجعة
|
| 171 |
+
4. 📢 سوق التطبيق
|
| 172 |
+
|
| 173 |
+
---
|
| 174 |
+
|
| 175 |
+
## 💡 **نصائح مهمة**
|
| 176 |
+
|
| 177 |
+
### **للبناء الناجح:**
|
| 178 |
+
- استخدم **Android Studio** للسهولة
|
| 179 |
+
- تأكد من **اتصال الإنترنت** أثناء البناء
|
| 180 |
+
- **أعد تشغيل** Command Prompt بعد تثبيت SDK
|
| 181 |
+
|
| 182 |
+
### **للاختبار:**
|
| 183 |
+
- اختبر على **أجهزة مختلفة**
|
| 184 |
+
- تأكد من **جميع الميزات**
|
| 185 |
+
- راقب **الأداء والاستقرار**
|
| 186 |
+
|
| 187 |
+
---
|
| 188 |
+
|
| 189 |
+
## 📞 **الدعم والمساعدة**
|
| 190 |
+
|
| 191 |
+
للحصول على المساعدة:
|
| 192 |
+
1. راجع `APK_BUILD_GUIDE.md` للتفاصيل
|
| 193 |
+
2. تحقق من `BUILD_INSTRUCTIONS.md` للتعليمات الكاملة
|
| 194 |
+
3. تواصل مع فريق التطوير
|
| 195 |
+
|
| 196 |
+
---
|
| 197 |
+
|
| 198 |
+
**🎊 مبروك! تطبيقك جاهز للعالم!**
|
| 199 |
+
|
| 200 |
+
*تطبيق محفظتي الموحدة - المدى للخدمات البرمجية التسويقية والإعلانية*
|
BUILD_APK_SIMPLE.bat
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
echo ========================================
|
| 3 |
+
echo محفظتي الموحدة - بناء APK مبسط
|
| 4 |
+
echo ========================================
|
| 5 |
+
echo.
|
| 6 |
+
|
| 7 |
+
echo 🔍 التحقق من المتطلبات...
|
| 8 |
+
|
| 9 |
+
:: التحقق من Java
|
| 10 |
+
java -version >nul 2>&1
|
| 11 |
+
if %errorlevel% neq 0 (
|
| 12 |
+
echo ❌ Java غير مثبت!
|
| 13 |
+
echo يرجى تثبيت Java JDK من: https://adoptium.net/
|
| 14 |
+
pause
|
| 15 |
+
exit /b 1
|
| 16 |
+
)
|
| 17 |
+
echo ✅ Java متوفر
|
| 18 |
+
|
| 19 |
+
:: التحقق من Node.js
|
| 20 |
+
node --version >nul 2>&1
|
| 21 |
+
if %errorlevel% neq 0 (
|
| 22 |
+
echo ❌ Node.js غير مثبت!
|
| 23 |
+
echo يرجى تثبيت Node.js من: https://nodejs.org/
|
| 24 |
+
pause
|
| 25 |
+
exit /b 1
|
| 26 |
+
)
|
| 27 |
+
echo ✅ Node.js متوفر
|
| 28 |
+
|
| 29 |
+
echo.
|
| 30 |
+
echo 📦 تثبيت أدوات البناء...
|
| 31 |
+
call npm install -g @ionic/cli @capacitor/cli
|
| 32 |
+
|
| 33 |
+
echo.
|
| 34 |
+
echo 🔄 مزامنة المشروع...
|
| 35 |
+
call cap sync android
|
| 36 |
+
|
| 37 |
+
echo.
|
| 38 |
+
echo 🏗️ بناء APK...
|
| 39 |
+
echo هذه العملية قد تستغرق 5-10 دقائق...
|
| 40 |
+
echo يرجى الانتظار...
|
| 41 |
+
|
| 42 |
+
cd android
|
| 43 |
+
call gradlew assembleDebug
|
| 44 |
+
|
| 45 |
+
if %errorlevel% equ 0 (
|
| 46 |
+
echo.
|
| 47 |
+
echo ========================================
|
| 48 |
+
echo 🎉 تم بناء APK بنجاح!
|
| 49 |
+
echo ========================================
|
| 50 |
+
echo.
|
| 51 |
+
echo 📱 ملف APK متوفر في:
|
| 52 |
+
echo android\app\build\outputs\apk\debug\app-debug.apk
|
| 53 |
+
echo.
|
| 54 |
+
echo 📋 معلومات التطبيق:
|
| 55 |
+
echo - الاسم: محفظتي الموحدة
|
| 56 |
+
echo - الحجم: ~15-20 MB
|
| 57 |
+
echo - النوع: Debug APK
|
| 58 |
+
echo.
|
| 59 |
+
echo 🔑 بيانات التجربة:
|
| 60 |
+
echo - رقم الهاتف: 777123456
|
| 61 |
+
echo - رمز PIN: 1234
|
| 62 |
+
echo.
|
| 63 |
+
echo 📲 لتثبيت التطبيق:
|
| 64 |
+
echo 1. انسخ ملف APK إلى هاتفك
|
| 65 |
+
echo 2. فعل "مصادر غير معروفة" في إعدادات الأمان
|
| 66 |
+
echo 3. اضغط على ملف APK لتثبيته
|
| 67 |
+
echo.
|
| 68 |
+
|
| 69 |
+
:: فتح مجلد APK
|
| 70 |
+
explorer "app\build\outputs\apk\debug\"
|
| 71 |
+
|
| 72 |
+
) else (
|
| 73 |
+
echo.
|
| 74 |
+
echo ❌ فشل في بناء APK!
|
| 75 |
+
echo.
|
| 76 |
+
echo 🔧 الحلول المقترحة:
|
| 77 |
+
echo 1. تأكد من تثبيت Android SDK
|
| 78 |
+
echo 2. استخدم Android Studio للبناء
|
| 79 |
+
echo 3. راجع رسائل الخطأ أعلاه
|
| 80 |
+
echo.
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
echo.
|
| 84 |
+
pause
|
BUILD_FROM_GITHUB.bat
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
echo ========================================
|
| 3 |
+
echo بناء APK من GitHub - محفظتي الموحدة
|
| 4 |
+
echo ========================================
|
| 5 |
+
echo.
|
| 6 |
+
|
| 7 |
+
echo [1/6] التحقق من الأدوات المطلوبة...
|
| 8 |
+
|
| 9 |
+
:: التحقق من Git
|
| 10 |
+
git --version >nul 2>&1
|
| 11 |
+
if %errorlevel% neq 0 (
|
| 12 |
+
echo ❌ Git غير مثبت!
|
| 13 |
+
echo يرجى تثبيت Git من: https://git-scm.com/
|
| 14 |
+
pause
|
| 15 |
+
exit /b 1
|
| 16 |
+
)
|
| 17 |
+
echo ✅ Git متوفر
|
| 18 |
+
|
| 19 |
+
:: التحقق من Node.js
|
| 20 |
+
node --version >nul 2>&1
|
| 21 |
+
if %errorlevel% neq 0 (
|
| 22 |
+
echo ❌ Node.js غير مثبت!
|
| 23 |
+
echo يرجى تثبيت Node.js من: https://nodejs.org/
|
| 24 |
+
pause
|
| 25 |
+
exit /b 1
|
| 26 |
+
)
|
| 27 |
+
echo ✅ Node.js متوفر
|
| 28 |
+
|
| 29 |
+
:: التحقق من Java
|
| 30 |
+
java -version >nul 2>&1
|
| 31 |
+
if %errorlevel% neq 0 (
|
| 32 |
+
echo ❌ Java غير مثبت!
|
| 33 |
+
echo يرجى تثبيت Java JDK من: https://adoptium.net/
|
| 34 |
+
pause
|
| 35 |
+
exit /b 1
|
| 36 |
+
)
|
| 37 |
+
echo ✅ Java متوفر
|
| 38 |
+
|
| 39 |
+
echo.
|
| 40 |
+
echo [2/6] استنساخ المشروع من GitHub...
|
| 41 |
+
if exist "almada-unified-wallet" (
|
| 42 |
+
echo مجلد المشروع موجود، سيتم حذفه وإعادة الاستنساخ...
|
| 43 |
+
rmdir /s /q "almada-unified-wallet"
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
git clone https://github.com/moh77544/---.git almada-unified-wallet
|
| 47 |
+
if %errorlevel% neq 0 (
|
| 48 |
+
echo ❌ فشل في استنساخ المشروع!
|
| 49 |
+
pause
|
| 50 |
+
exit /b 1
|
| 51 |
+
)
|
| 52 |
+
echo ✅ تم استنساخ المشروع
|
| 53 |
+
|
| 54 |
+
echo.
|
| 55 |
+
echo [3/6] الانتقال إلى مجلد المشروع...
|
| 56 |
+
cd almada-unified-wallet
|
| 57 |
+
|
| 58 |
+
echo.
|
| 59 |
+
echo [4/6] تثبيت التبعيات...
|
| 60 |
+
call npm install
|
| 61 |
+
if %errorlevel% neq 0 (
|
| 62 |
+
echo ❌ فشل في تثبيت التبعيات!
|
| 63 |
+
pause
|
| 64 |
+
exit /b 1
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
call npm install -g @ionic/cli @capacitor/cli
|
| 68 |
+
echo ✅ تم تثبيت التبعيات
|
| 69 |
+
|
| 70 |
+
echo.
|
| 71 |
+
echo [5/6] إعداد ملفات الويب...
|
| 72 |
+
if not exist "www" mkdir www
|
| 73 |
+
copy index.html www\ >nul 2>&1
|
| 74 |
+
copy styles.css www\ >nul 2>&1
|
| 75 |
+
copy app.js www\ >nul 2>&1
|
| 76 |
+
copy auth.js www\ >nul 2>&1
|
| 77 |
+
copy wallets.js www\ >nul 2>&1
|
| 78 |
+
copy notifications.js www\ >nul 2>&1
|
| 79 |
+
copy demo.html www\ >nul 2>&1
|
| 80 |
+
copy src\manifest.json www\ >nul 2>&1
|
| 81 |
+
echo ✅ تم إعداد ملفات الويب
|
| 82 |
+
|
| 83 |
+
echo.
|
| 84 |
+
echo [6/6] مزامنة وبناء APK...
|
| 85 |
+
call npx cap sync android
|
| 86 |
+
if %errorlevel% neq 0 (
|
| 87 |
+
echo ❌ فشل في مزامنة Capacitor!
|
| 88 |
+
pause
|
| 89 |
+
exit /b 1
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
cd android
|
| 93 |
+
call gradlew assembleDebug
|
| 94 |
+
if %errorlevel% neq 0 (
|
| 95 |
+
echo ❌ فشل في بناء APK!
|
| 96 |
+
echo تأكد من تثبيت Android SDK
|
| 97 |
+
pause
|
| 98 |
+
exit /b 1
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
echo.
|
| 102 |
+
echo ========================================
|
| 103 |
+
echo 🎉 تم بناء APK بنجاح!
|
| 104 |
+
echo ========================================
|
| 105 |
+
echo.
|
| 106 |
+
echo 📱 ملف APK متوفر في:
|
| 107 |
+
echo %cd%\app\build\outputs\apk\debug\app-debug.apk
|
| 108 |
+
echo.
|
| 109 |
+
echo 📋 معلومات التطبيق:
|
| 110 |
+
echo - الاسم: محفظتي الموحدة
|
| 111 |
+
echo - الحجم: ~15-20 MB
|
| 112 |
+
echo - النوع: Debug APK
|
| 113 |
+
echo.
|
| 114 |
+
echo 🔑 بيانات التجربة:
|
| 115 |
+
echo - رقم الهاتف: 777123456
|
| 116 |
+
echo - رمز PIN: 1234
|
| 117 |
+
echo.
|
| 118 |
+
echo 📲 لتثبيت التطبيق:
|
| 119 |
+
echo 1. انسخ ملف APK إلى هاتفك
|
| 120 |
+
echo 2. فعل "مصادر غير معروفة" في إعدادات الأمان
|
| 121 |
+
echo 3. اضغط على ملف APK لتثبيته
|
| 122 |
+
echo.
|
| 123 |
+
|
| 124 |
+
:: فتح مجلد APK
|
| 125 |
+
explorer "app\build\outputs\apk\debug\"
|
| 126 |
+
|
| 127 |
+
echo.
|
| 128 |
+
pause
|
BUILD_INSTRUCTIONS.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# تعليمات بناء تطبيق محفظتي الموحدة
|
| 2 |
+
|
| 3 |
+
## متطلبات النظام
|
| 4 |
+
|
| 5 |
+
### الأدوات المطلوبة:
|
| 6 |
+
- **Node.js** (الإصدار 18 أو أحدث)
|
| 7 |
+
- **npm** أو **yarn**
|
| 8 |
+
- **Ionic CLI** (الإصدار 7 أو أحدث)
|
| 9 |
+
- **Angular CLI** (الإصدار 17 أو أحدث)
|
| 10 |
+
- **Capacitor CLI** (الإصدار 5 أو أحدث)
|
| 11 |
+
|
| 12 |
+
### للأندرويد:
|
| 13 |
+
- **Android Studio** (أحدث إصدار)
|
| 14 |
+
- **Android SDK** (API Level 24 أو أحدث)
|
| 15 |
+
- **Java JDK** (الإصدار 11 أو أحدث)
|
| 16 |
+
|
| 17 |
+
### لـ iOS:
|
| 18 |
+
- **Xcode** (أحدث إصدار)
|
| 19 |
+
- **iOS SDK** (iOS 13 أو أحدث)
|
| 20 |
+
- **macOS** (مطلوب لبناء تطبيقات iOS)
|
| 21 |
+
|
| 22 |
+
## خطوات التثبيت
|
| 23 |
+
|
| 24 |
+
### 1. تثبيت الأدوات العامة
|
| 25 |
+
```bash
|
| 26 |
+
# تثبيت Node.js من https://nodejs.org
|
| 27 |
+
|
| 28 |
+
# تثبيت Ionic CLI
|
| 29 |
+
npm install -g @ionic/cli
|
| 30 |
+
|
| 31 |
+
# تثبيت Angular CLI
|
| 32 |
+
npm install -g @angular/cli
|
| 33 |
+
|
| 34 |
+
# تثبيت Capacitor CLI
|
| 35 |
+
npm install -g @capacitor/cli
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
### 2. إعداد المشروع
|
| 39 |
+
```bash
|
| 40 |
+
# الانتقال إلى مجلد المشروع
|
| 41 |
+
cd almada
|
| 42 |
+
|
| 43 |
+
# تثبيت التبعيات
|
| 44 |
+
npm install
|
| 45 |
+
|
| 46 |
+
# أو باستخدام yarn
|
| 47 |
+
yarn install
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
### 3. إعداد Capacitor
|
| 51 |
+
```bash
|
| 52 |
+
# تهيئة Capacitor
|
| 53 |
+
npx cap init "محفظتي الموحدة" "com.almada.unifiedwallet"
|
| 54 |
+
|
| 55 |
+
# إضافة منصات
|
| 56 |
+
npx cap add android
|
| 57 |
+
npx cap add ios
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
## بناء التطبيق
|
| 61 |
+
|
| 62 |
+
### 1. بناء تطبيق الويب
|
| 63 |
+
```bash
|
| 64 |
+
# بناء للتطوير
|
| 65 |
+
ionic build
|
| 66 |
+
|
| 67 |
+
# بناء للإنتاج
|
| 68 |
+
ionic build --prod
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
### 2. بناء تطبيق الأندرويد
|
| 72 |
+
|
| 73 |
+
#### أ. إعداد Android Studio
|
| 74 |
+
```bash
|
| 75 |
+
# نسخ الملفات إلى مجلد الأندرويد
|
| 76 |
+
npx cap copy android
|
| 77 |
+
|
| 78 |
+
# مزامنة المشروع
|
| 79 |
+
npx cap sync android
|
| 80 |
+
|
| 81 |
+
# فتح في Android Studio
|
| 82 |
+
npx cap open android
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
#### ب. بناء APK من سطر الأوامر
|
| 86 |
+
```bash
|
| 87 |
+
# بناء APK للتطوير
|
| 88 |
+
cd android
|
| 89 |
+
./gradlew assembleDebug
|
| 90 |
+
|
| 91 |
+
# بناء APK للإنتاج
|
| 92 |
+
./gradlew assembleRelease
|
| 93 |
+
|
| 94 |
+
# بناء AAB للنشر في Google Play
|
| 95 |
+
./gradlew bundleRelease
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
#### ج. بناء APK من Android Studio
|
| 99 |
+
1. افتح Android Studio
|
| 100 |
+
2. اختر **Build > Build Bundle(s) / APK(s) > Build APK(s)**
|
| 101 |
+
3. انتظر حتى اكتمال البناء
|
| 102 |
+
4. ستجد ملف APK في: `android/app/build/outputs/apk/`
|
| 103 |
+
|
| 104 |
+
### 3. بناء تطبيق iOS
|
| 105 |
+
|
| 106 |
+
#### أ. إعداد Xcode
|
| 107 |
+
```bash
|
| 108 |
+
# نسخ الملفات إلى مجلد iOS
|
| 109 |
+
npx cap copy ios
|
| 110 |
+
|
| 111 |
+
# مزامنة المشروع
|
| 112 |
+
npx cap sync ios
|
| 113 |
+
|
| 114 |
+
# فتح في Xcode
|
| 115 |
+
npx cap open ios
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
#### ب. بناء IPA من Xcode
|
| 119 |
+
1. افتح Xcode
|
| 120 |
+
2. اختر جهاز أو محاكي
|
| 121 |
+
3. اختر **Product > Archive**
|
| 122 |
+
4. بعد اكتمال الأرشفة، اختر **Distribute App**
|
| 123 |
+
5. اتبع التعليمات لإنشاء ملف IPA
|
| 124 |
+
|
| 125 |
+
## تشغيل التطبيق للتطوير
|
| 126 |
+
|
| 127 |
+
### 1. تشغيل في المتصفح
|
| 128 |
+
```bash
|
| 129 |
+
# تشغيل خادم التطوير
|
| 130 |
+
ionic serve
|
| 131 |
+
|
| 132 |
+
# تشغيل مع إعادة التحميل التلقائي
|
| 133 |
+
ionic serve --lab
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
### 2. تشغيل على الأندرويد
|
| 137 |
+
```bash
|
| 138 |
+
# تشغيل على جهاز أو محاكي
|
| 139 |
+
ionic cap run android
|
| 140 |
+
|
| 141 |
+
# تشغيل مع إعادة التحميل المباشر
|
| 142 |
+
ionic cap run android --livereload --external
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
### 3. تشغيل على iOS
|
| 146 |
+
```bash
|
| 147 |
+
# تشغيل على جهاز أو محاكي
|
| 148 |
+
ionic cap run ios
|
| 149 |
+
|
| 150 |
+
# تشغيل مع إعادة التحميل المباشر
|
| 151 |
+
ionic cap run ios --livereload --external
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
## إعدادات الإنتاج
|
| 155 |
+
|
| 156 |
+
### 1. تحديث المتغيرات
|
| 157 |
+
قم بتحديث ملف `src/environments/environment.prod.ts`:
|
| 158 |
+
```typescript
|
| 159 |
+
export const environment = {
|
| 160 |
+
production: true,
|
| 161 |
+
apiUrl: 'https://api.almada.com',
|
| 162 |
+
// ... باقي الإعدادات
|
| 163 |
+
};
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
### 2. إعداد الأيقونات والشاشات
|
| 167 |
+
```bash
|
| 168 |
+
# إنشاء الأيقونات والشاشات تلقائياً
|
| 169 |
+
npm install -g cordova-res
|
| 170 |
+
cordova-res android --skip-config --copy
|
| 171 |
+
cordova-res ios --skip-config --copy
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
### 3. توقيع التطبيق للأندرويد
|
| 175 |
+
```bash
|
| 176 |
+
# إنشاء مفتاح التوقيع
|
| 177 |
+
keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
|
| 178 |
+
|
| 179 |
+
# إضافة إعدادات التوقيع في android/app/build.gradle
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
### 4. إعداد التوقيع لـ iOS
|
| 183 |
+
1. افتح Xcode
|
| 184 |
+
2. اذهب إلى **Signing & Capabilities**
|
| 185 |
+
3. اختر **Team** و **Bundle Identifier**
|
| 186 |
+
4. تأكد من إعداد **Provisioning Profile**
|
| 187 |
+
|
| 188 |
+
## اختبار التطبيق
|
| 189 |
+
|
| 190 |
+
### 1. اختبار الوحدة
|
| 191 |
+
```bash
|
| 192 |
+
# تشغيل اختبارات الوحدة
|
| 193 |
+
npm test
|
| 194 |
+
|
| 195 |
+
# تشغيل مع المراقبة
|
| 196 |
+
npm test -- --watch
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
### 2. اختبار النهاية إلى النهاية
|
| 200 |
+
```bash
|
| 201 |
+
# تشغيل اختبارات e2e
|
| 202 |
+
npm run e2e
|
| 203 |
+
```
|
| 204 |
+
|
| 205 |
+
### 3. اختبار على الأجهزة
|
| 206 |
+
```bash
|
| 207 |
+
# تثبيت على جهاز أندرويد
|
| 208 |
+
adb install android/app/build/outputs/apk/debug/app-debug.apk
|
| 209 |
+
|
| 210 |
+
# عرض سجلات الأندرويد
|
| 211 |
+
adb logcat
|
| 212 |
+
```
|
| 213 |
+
|
| 214 |
+
## نشر التطبيق
|
| 215 |
+
|
| 216 |
+
### 1. Google Play Store
|
| 217 |
+
1. إنشاء حساب مطور في Google Play Console
|
| 218 |
+
2. رفع ملف AAB
|
| 219 |
+
3. ملء معلومات التطبيق
|
| 220 |
+
4. إرسال للمراجعة
|
| 221 |
+
|
| 222 |
+
### 2. Apple App Store
|
| 223 |
+
1. إنشاء حساب مطور في App Store Connect
|
| 224 |
+
2. رفع التطبيق عبر Xcode أو Application Loader
|
| 225 |
+
3. ملء معلومات التطبيق
|
| 226 |
+
4. إرسال للمراجعة
|
| 227 |
+
|
| 228 |
+
## استكشاف الأخطاء
|
| 229 |
+
|
| 230 |
+
### مشاكل شائعة:
|
| 231 |
+
|
| 232 |
+
#### 1. خطأ في بناء الأندرويد
|
| 233 |
+
```bash
|
| 234 |
+
# تنظيف المشروع
|
| 235 |
+
cd android
|
| 236 |
+
./gradlew clean
|
| 237 |
+
|
| 238 |
+
# إعادة بناء
|
| 239 |
+
./gradlew build
|
| 240 |
+
```
|
| 241 |
+
|
| 242 |
+
#### 2. مشاكل Capacitor
|
| 243 |
+
```bash
|
| 244 |
+
# إعادة مزامنة
|
| 245 |
+
npx cap sync
|
| 246 |
+
|
| 247 |
+
# تحديث Capacitor
|
| 248 |
+
npm update @capacitor/core @capacitor/cli
|
| 249 |
+
```
|
| 250 |
+
|
| 251 |
+
#### 3. مشاكل الأذونات
|
| 252 |
+
تأكد من إضافة الأذونات المطلوبة في:
|
| 253 |
+
- `android/app/src/main/AndroidManifest.xml` للأندرويد
|
| 254 |
+
- `ios/App/App/Info.plist` لـ iOS
|
| 255 |
+
|
| 256 |
+
## الدعم والمساعدة
|
| 257 |
+
|
| 258 |
+
للحصول على المساعدة:
|
| 259 |
+
1. راجع [وثائق Ionic](https://ionicframework.com/docs)
|
| 260 |
+
2. راجع [وثائق Capacitor](https://capacitorjs.com/docs)
|
| 261 |
+
3. تواصل مع فريق التطوير
|
| 262 |
+
|
| 263 |
+
---
|
| 264 |
+
|
| 265 |
+
**ملاحظة**: تأكد من تحديث جميع التبعيات بانتظام للحصول على أحدث الميزات وإصلاحات الأمان.
|
CLOUD_BUILD_QUICK.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ☁️ البناء السحابي السريع - 15 دقيقة فقط!
|
| 2 |
+
|
| 3 |
+
## 🚀 **الهدف:** APK جاهز من السحابة في 15 دقيقة!
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## ⚡ **الخطوات السريعة:**
|
| 8 |
+
|
| 9 |
+
### **1️⃣ إنشاء حساب GitHub** (2 دقيقة)
|
| 10 |
+
```
|
| 11 |
+
🌐 اذهب إلى: https://github.com
|
| 12 |
+
📝 اضغط "Sign up" وأنشئ حساب
|
| 13 |
+
✅ تأكد من البريد الإلكتروني
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
### **2️⃣ إنشاء Repository** (1 دقيقة)
|
| 17 |
+
```
|
| 18 |
+
➕ اضغط "New repository"
|
| 19 |
+
📝 الاسم: almada-unified-wallet
|
| 20 |
+
📄 الوصف: محفظتي الموحدة
|
| 21 |
+
🌍 اختر "Public"
|
| 22 |
+
✅ اضغط "Create repository"
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
### **3️⃣ رفع الملفات** (5 دقائق)
|
| 26 |
+
```
|
| 27 |
+
📁 اضغط "uploading an existing file"
|
| 28 |
+
📤 اسحب وأفلت هذه الملفات:
|
| 29 |
+
✅ .github/workflows/build-apk.yml
|
| 30 |
+
✅ index.html
|
| 31 |
+
✅ styles.css
|
| 32 |
+
✅ app.js
|
| 33 |
+
✅ auth.js
|
| 34 |
+
✅ wallets.js
|
| 35 |
+
✅ notifications.js
|
| 36 |
+
✅ package.json
|
| 37 |
+
✅ capacitor.config.ts
|
| 38 |
+
✅ مجلد android كامل
|
| 39 |
+
💬 رسالة: "Initial commit"
|
| 40 |
+
✅ اضغط "Commit changes"
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
### **4️⃣ تشغيل البناء** (10 دقائق)
|
| 44 |
+
```
|
| 45 |
+
🔧 اذهب إلى تبويب "Actions"
|
| 46 |
+
🚀 اضغط "Run workflow"
|
| 47 |
+
⏰ انتظر 10-15 دقيقة
|
| 48 |
+
✅ ابحث عن علامة ✅ خضراء
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
### **5️⃣ تحميل APK** (1 دقيقة)
|
| 52 |
+
```
|
| 53 |
+
📱 اضغط على Build الناجح
|
| 54 |
+
📦 في الأسفل: "Artifacts"
|
| 55 |
+
⬇️ حمل: almada-unified-wallet-apk
|
| 56 |
+
📱 استخرج APK وثبته!
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
---
|
| 60 |
+
|
| 61 |
+
## 🎯 **النتيجة:**
|
| 62 |
+
|
| 63 |
+
✅ **ملف APK** جاهز للتثبيت
|
| 64 |
+
✅ **بناء تلقائي** عند كل تحديث
|
| 65 |
+
✅ **مجاني تماماً** مع GitHub
|
| 66 |
+
✅ **رابط تحميل** مباشر
|
| 67 |
+
|
| 68 |
+
---
|
| 69 |
+
|
| 70 |
+
## 📱 **معلومات APK:**
|
| 71 |
+
|
| 72 |
+
| المعلومة | القيمة |
|
| 73 |
+
|---------|--------|
|
| 74 |
+
| **الاسم** | app-debug.apk |
|
| 75 |
+
| **الحجم** | ~15-20 MB |
|
| 76 |
+
| **بيانات التجربة** | 777123456 / 1234 |
|
| 77 |
+
| **متوافق مع** | Android 7.0+ |
|
| 78 |
+
|
| 79 |
+
---
|
| 80 |
+
|
| 81 |
+
## 🔄 **للتحديثات المستقبلية:**
|
| 82 |
+
|
| 83 |
+
```
|
| 84 |
+
1. عدل أي ملف في GitHub
|
| 85 |
+
2. Commit التغييرات
|
| 86 |
+
3. APK جديد سيُبنى تلقائياً!
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
---
|
| 90 |
+
|
| 91 |
+
## 🆘 **مشاكل شائعة:**
|
| 92 |
+
|
| 93 |
+
### **❌ Build فشل:**
|
| 94 |
+
```
|
| 95 |
+
✅ تحقق من رفع جميع الملفات
|
| 96 |
+
✅ راجع logs في Actions
|
| 97 |
+
✅ تأكد من package.json صحيح
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
### **❌ لا يوجد APK:**
|
| 101 |
+
```
|
| 102 |
+
✅ انتظر اكتمال Build (علامة ✅)
|
| 103 |
+
✅ ابحث في "Artifacts"
|
| 104 |
+
✅ حدث الصفحة
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
---
|
| 108 |
+
|
| 109 |
+
## 🎊 **مبروك!**
|
| 110 |
+
|
| 111 |
+
عند اكتمال هذه الخطوات:
|
| 112 |
+
- 📱 **APK جاهز** للتثبيت
|
| 113 |
+
- ☁️ **بناء سحابي** مجاني
|
| 114 |
+
- 🔄 **تحديثات تلقائية**
|
| 115 |
+
- 🌍 **متاح للعالم**
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
## 📞 **تحتاج مساعدة؟**
|
| 120 |
+
|
| 121 |
+
راجع الدليل المفصل: `GITHUB_SETUP_GUIDE.md`
|
| 122 |
+
|
| 123 |
+
**🚀 تطبيقك على بُعد 15 دقيقة من الواقع!**
|
Dockerfile
CHANGED
|
@@ -1,22 +1,19 @@
|
|
| 1 |
FROM node:20-alpine
|
| 2 |
USER root
|
| 3 |
|
| 4 |
-
# Install pnpm
|
| 5 |
-
RUN corepack enable && corepack prepare pnpm@latest --activate
|
| 6 |
-
|
| 7 |
USER 1000
|
| 8 |
WORKDIR /usr/src/app
|
| 9 |
-
# Copy package.json and
|
| 10 |
-
COPY --chown=1000 package.json
|
| 11 |
|
| 12 |
# Copy the rest of the application files to the container
|
| 13 |
COPY --chown=1000 . .
|
| 14 |
|
| 15 |
-
RUN
|
| 16 |
-
RUN
|
| 17 |
|
| 18 |
# Expose the application port (assuming your app runs on port 3000)
|
| 19 |
-
EXPOSE
|
| 20 |
|
| 21 |
# Start the application
|
| 22 |
-
CMD ["
|
|
|
|
| 1 |
FROM node:20-alpine
|
| 2 |
USER root
|
| 3 |
|
|
|
|
|
|
|
|
|
|
| 4 |
USER 1000
|
| 5 |
WORKDIR /usr/src/app
|
| 6 |
+
# Copy package.json and package-lock.json to the container
|
| 7 |
+
COPY --chown=1000 package.json package-lock.json ./
|
| 8 |
|
| 9 |
# Copy the rest of the application files to the container
|
| 10 |
COPY --chown=1000 . .
|
| 11 |
|
| 12 |
+
RUN npm install
|
| 13 |
+
RUN npm run build
|
| 14 |
|
| 15 |
# Expose the application port (assuming your app runs on port 3000)
|
| 16 |
+
EXPOSE 3000
|
| 17 |
|
| 18 |
# Start the application
|
| 19 |
+
CMD ["npm", "start"]
|
GITHUB_SETUP_GUIDE.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ☁️ دليل إعداد البناء السحابي - GitHub Actions
|
| 2 |
+
|
| 3 |
+
## 🎯 **الهدف:** بناء APK تلقائياً في السحابة مجاناً!
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 📋 **الخطوات المطلوبة:**
|
| 8 |
+
|
| 9 |
+
### **الخطوة 1: إنشاء حساب GitHub** ⏱️ (2 دقيقة)
|
| 10 |
+
|
| 11 |
+
1. **اذهب إلى:** https://github.com
|
| 12 |
+
2. **اضغط "Sign up"** وأنشئ حساب جديد
|
| 13 |
+
3. **تأكد من البريد الإلكتروني**
|
| 14 |
+
|
| 15 |
+
### **الخطوة 2: إنشاء Repository جديد** ⏱️ (1 دقيقة)
|
| 16 |
+
|
| 17 |
+
1. **اضغط "New repository"** (الزر الأخضر)
|
| 18 |
+
2. **اسم المستودع:** `almada-unified-wallet`
|
| 19 |
+
3. **الوصف:** `محفظتي الموحدة - تطبيق المحافظ الإلكترونية اليمنية`
|
| 20 |
+
4. **اختر "Public"** (مجاني)
|
| 21 |
+
5. **فعل "Add a README file"**
|
| 22 |
+
6. **اضغط "Create repository"**
|
| 23 |
+
|
| 24 |
+
### **الخطوة 3: رفع الملفات** ⏱️ (5 دقائق)
|
| 25 |
+
|
| 26 |
+
#### **الطريقة الأسهل: عبر الموقع**
|
| 27 |
+
|
| 28 |
+
1. **في صفحة Repository، اضغط "uploading an existing file"**
|
| 29 |
+
2. **اسحب وأفلت الملفات التالية:**
|
| 30 |
+
```
|
| 31 |
+
📁 .github/workflows/build-apk.yml
|
| 32 |
+
📄 index.html
|
| 33 |
+
📄 styles.css
|
| 34 |
+
📄 app.js
|
| 35 |
+
📄 auth.js
|
| 36 |
+
📄 wallets.js
|
| 37 |
+
📄 notifications.js
|
| 38 |
+
📄 demo.html
|
| 39 |
+
📄 package.json
|
| 40 |
+
📄 capacitor.config.ts
|
| 41 |
+
📄 .gitignore
|
| 42 |
+
📁 src/manifest.json
|
| 43 |
+
📁 android/ (كامل)
|
| 44 |
+
```
|
| 45 |
+
3. **اكتب رسالة:** `Initial commit - محفظتي الموحدة`
|
| 46 |
+
4. **اضغط "Commit changes"**
|
| 47 |
+
|
| 48 |
+
#### **الطريقة المتقدمة: Git Command Line**
|
| 49 |
+
|
| 50 |
+
```bash
|
| 51 |
+
# في مجلد المشروع
|
| 52 |
+
git init
|
| 53 |
+
git add .
|
| 54 |
+
git commit -m "Initial commit - محفظتي الموحدة"
|
| 55 |
+
git branch -M main
|
| 56 |
+
git remote add origin https://github.com/USERNAME/almada-unified-wallet.git
|
| 57 |
+
git push -u origin main
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
### **الخطوة 4: تشغيل البناء التلقائي** ⏱️ (10-15 دقيقة)
|
| 61 |
+
|
| 62 |
+
1. **اذهب إلى تبويب "Actions"** في Repository
|
| 63 |
+
2. **ستجد workflow اسمه:** `🚀 Build APK - محفظتي الموحدة`
|
| 64 |
+
3. **اضغط "Run workflow"** إذا لم يبدأ تلقائياً
|
| 65 |
+
4. **انتظر اكتمال البناء** (10-15 دقيقة)
|
| 66 |
+
|
| 67 |
+
### **الخطوة 5: تحميل APK** ⏱️ (1 دقيقة)
|
| 68 |
+
|
| 69 |
+
عند اكتمال البناء:
|
| 70 |
+
|
| 71 |
+
1. **اضغط على Build الناجح** (علامة ✅ خضراء)
|
| 72 |
+
2. **في الأسفل، ستجد "Artifacts"**
|
| 73 |
+
3. **اضغط على:** `almada-unified-wallet-apk`
|
| 74 |
+
4. **حمل ملف ZIP واستخرج APK منه**
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
|
| 78 |
+
## 🎯 **البدائل السحابية الأخرى:**
|
| 79 |
+
|
| 80 |
+
### **Ionic Appflow (مجاني للمشاريع الصغيرة):**
|
| 81 |
+
|
| 82 |
+
```bash
|
| 83 |
+
# تثبيت Ionic CLI
|
| 84 |
+
npm install -g @ionic/cli
|
| 85 |
+
|
| 86 |
+
# تسجيل الدخول
|
| 87 |
+
ionic login
|
| 88 |
+
|
| 89 |
+
# ربط المشروع
|
| 90 |
+
ionic link
|
| 91 |
+
|
| 92 |
+
# بناء في السحابة
|
| 93 |
+
ionic capacitor build android --prod
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
### **CodeMagic (مجاني 500 دقيقة/شهر):**
|
| 97 |
+
|
| 98 |
+
1. اذهب إلى: https://codemagic.io
|
| 99 |
+
2. ربط حساب GitHub
|
| 100 |
+
3. اختر Repository
|
| 101 |
+
4. إعداد workflow للأندرويد
|
| 102 |
+
5. بناء تلقائي
|
| 103 |
+
|
| 104 |
+
---
|
| 105 |
+
|
| 106 |
+
## 📱 **ما ستحصل عليه:**
|
| 107 |
+
|
| 108 |
+
### **من GitHub Actions:**
|
| 109 |
+
- ✅ **بناء تلقائي** عند كل تحديث
|
| 110 |
+
- ✅ **APK مجاني** بدون حدود
|
| 111 |
+
- ✅ **تاريخ الإصدارات** كامل
|
| 112 |
+
- ✅ **رابط تحميل** مباشر
|
| 113 |
+
|
| 114 |
+
### **معلومات APK:**
|
| 115 |
+
- 📱 **الاسم:** `app-debug.apk`
|
| 116 |
+
- 💾 **الحجم:** ~15-20 MB
|
| 117 |
+
- 🔧 **النوع:** Debug APK
|
| 118 |
+
- 📲 **جاهز للتثبيت** على أي هاتف أندرويد
|
| 119 |
+
|
| 120 |
+
---
|
| 121 |
+
|
| 122 |
+
## 🔄 **التحديثات المستقبلية:**
|
| 123 |
+
|
| 124 |
+
### **لإضافة ميزات جديدة:**
|
| 125 |
+
1. **عدل الملفات** في Repository
|
| 126 |
+
2. **Commit التغييرات**
|
| 127 |
+
3. **APK جديد** سيُبنى تلقائياً!
|
| 128 |
+
|
| 129 |
+
### **لإنشاء Release:**
|
| 130 |
+
```bash
|
| 131 |
+
# إنشاء tag جديد
|
| 132 |
+
git tag v1.0.1
|
| 133 |
+
git push origin v1.0.1
|
| 134 |
+
|
| 135 |
+
# سيُنشئ Release تلقائياً مع APK
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
---
|
| 139 |
+
|
| 140 |
+
## 🆘 **حل المشاكل:**
|
| 141 |
+
|
| 142 |
+
### **مشكلة: Build فشل**
|
| 143 |
+
```
|
| 144 |
+
الحل:
|
| 145 |
+
1. تحقق من logs في Actions
|
| 146 |
+
2. تأكد من رفع جميع الملفات
|
| 147 |
+
3. تحقق من package.json
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
### **مشكلة: لا يوجد Artifacts**
|
| 151 |
+
```
|
| 152 |
+
الحل:
|
| 153 |
+
1. تأكد من نجاح Build (علامة ✅)
|
| 154 |
+
2. انتظر اكتمال جميع الخطوات
|
| 155 |
+
3. حدث الصفحة
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
### **مشكلة: APK لا يعمل**
|
| 159 |
+
```
|
| 160 |
+
الحل:
|
| 161 |
+
1. تأكد من تفعيل "مصادر غير معروفة"
|
| 162 |
+
2. تحقق من توافق إصدار الأندرويد
|
| 163 |
+
3. أعد تحميل APK
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
---
|
| 167 |
+
|
| 168 |
+
## 💡 **نصائح مهمة:**
|
| 169 |
+
|
| 170 |
+
### **للنجاح:**
|
| 171 |
+
- 📁 **ارفع جميع الملفات** المطلوبة
|
| 172 |
+
- 🌐 **تأكد من اتصال الإنترنت** أثناء البناء
|
| 173 |
+
- ⏰ **انتظر اكتمال** جميع الخطوات
|
| 174 |
+
|
| 175 |
+
### **للأمان:**
|
| 176 |
+
- 🔒 **لا تشارك** معلومات حساسة في Repository العام
|
| 177 |
+
- 🔑 **استخدم Secrets** للمعلومات الحساسة
|
| 178 |
+
- 🛡️ **راجع الأذونات** بانتظام
|
| 179 |
+
|
| 180 |
+
### **للتطوير:**
|
| 181 |
+
- 📝 **اكتب وصف واضح** للـ commits
|
| 182 |
+
- 🏷️ **استخدم tags** للإصدارات
|
| 183 |
+
- 📚 **حدث README** بانتظام
|
| 184 |
+
|
| 185 |
+
---
|
| 186 |
+
|
| 187 |
+
## 🎉 **النتيجة النهائية:**
|
| 188 |
+
|
| 189 |
+
عند اكتمال هذه الخطوات، ستحصل على:
|
| 190 |
+
|
| 191 |
+
- ☁️ **نظام بناء سحابي** مجاني
|
| 192 |
+
- 🔄 **APK تلقائي** عند كل تحديث
|
| 193 |
+
- 📱 **رابط تحميل** مباشر
|
| 194 |
+
- 🌍 **متاح للعالم** عبر GitHub
|
| 195 |
+
|
| 196 |
+
---
|
| 197 |
+
|
| 198 |
+
## 📞 **تحتاج مساعدة؟**
|
| 199 |
+
|
| 200 |
+
إذا واجهت أي مشكلة:
|
| 201 |
+
1. **راجع logs** في GitHub Actions
|
| 202 |
+
2. **تحقق من الملفات** المرفوعة
|
| 203 |
+
3. **تواصل للمساعدة**
|
| 204 |
+
|
| 205 |
+
**🚀 مبروك! تطبيقك سيُبنى في السحابة!**
|
| 206 |
+
|
| 207 |
+
---
|
| 208 |
+
|
| 209 |
+
**💡 ملاحظة:** GitHub Actions مجاني للمشاريع العامة مع 2000 دقيقة/شهر للمشاريع الخاصة.
|
INSTALL_ANDROID_SDK.bat
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
echo ========================================
|
| 3 |
+
echo تثبيت Android SDK - محفظتي الموحدة
|
| 4 |
+
echo ========================================
|
| 5 |
+
echo.
|
| 6 |
+
|
| 7 |
+
echo هذا الملف سيساعدك في تثبيت Android SDK بدون Android Studio
|
| 8 |
+
echo.
|
| 9 |
+
|
| 10 |
+
echo [الخطوة 1] إنشاء مجلد Android SDK...
|
| 11 |
+
if not exist "C:\Android" mkdir "C:\Android"
|
| 12 |
+
if not exist "C:\Android\cmdline-tools" mkdir "C:\Android\cmdline-tools"
|
| 13 |
+
echo ✅ تم إنشاء المجلدات
|
| 14 |
+
|
| 15 |
+
echo.
|
| 16 |
+
echo [الخطوة 2] تحميل Command Line Tools...
|
| 17 |
+
echo يرجى تحميل Command Line Tools من:
|
| 18 |
+
echo https://developer.android.com/studio#command-tools
|
| 19 |
+
echo.
|
| 20 |
+
echo اختر: "Command line tools only" > Windows
|
| 21 |
+
echo.
|
| 22 |
+
echo بعد التحميل:
|
| 23 |
+
echo 1. استخرج الملف المضغوط
|
| 24 |
+
echo 2. انسخ محتويات مجلد cmdline-tools إلى:
|
| 25 |
+
echo C:\Android\cmdline-tools\latest\
|
| 26 |
+
echo.
|
| 27 |
+
pause
|
| 28 |
+
|
| 29 |
+
echo.
|
| 30 |
+
echo [الخطوة 3] إعداد متغيرات البيئة...
|
| 31 |
+
echo سيتم إضافة متغيرات البيئة التالية:
|
| 32 |
+
echo ANDROID_HOME=C:\Android
|
| 33 |
+
echo ANDROID_SDK_ROOT=C:\Android
|
| 34 |
+
echo.
|
| 35 |
+
|
| 36 |
+
setx ANDROID_HOME "C:\Android" /M >nul 2>&1
|
| 37 |
+
setx ANDROID_SDK_ROOT "C:\Android" /M >nul 2>&1
|
| 38 |
+
|
| 39 |
+
echo ✅ تم إعداد متغيرات البيئة
|
| 40 |
+
|
| 41 |
+
echo.
|
| 42 |
+
echo [الخطوة 4] إضافة إلى PATH...
|
| 43 |
+
set "newPath=C:\Android\cmdline-tools\latest\bin;C:\Android\platform-tools"
|
| 44 |
+
|
| 45 |
+
for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v PATH 2^>nul') do set "currentPath=%%b"
|
| 46 |
+
|
| 47 |
+
if not defined currentPath set "currentPath="
|
| 48 |
+
|
| 49 |
+
echo %currentPath% | find /i "%newPath%" >nul
|
| 50 |
+
if %errorlevel% neq 0 (
|
| 51 |
+
setx PATH "%currentPath%;%newPath%" /M >nul 2>&1
|
| 52 |
+
echo ✅ تم إضافة Android SDK إلى PATH
|
| 53 |
+
) else (
|
| 54 |
+
echo ✅ Android SDK موجود بالفعل في PATH
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
echo.
|
| 58 |
+
echo [الخطوة 5] إعادة تشغيل Command Prompt...
|
| 59 |
+
echo يرجى إغلاق هذه النافذة وفتح Command Prompt جديد
|
| 60 |
+
echo ثم تشغيل الأوامر التالية:
|
| 61 |
+
echo.
|
| 62 |
+
echo sdkmanager --version
|
| 63 |
+
echo sdkmanager "platform-tools"
|
| 64 |
+
echo sdkmanager "platforms;android-33"
|
| 65 |
+
echo sdkmanager "build-tools;33.0.0"
|
| 66 |
+
echo.
|
| 67 |
+
|
| 68 |
+
echo ========================================
|
| 69 |
+
echo 📋 ملخص ما تم:
|
| 70 |
+
echo ========================================
|
| 71 |
+
echo ✅ إنشاء مجلد C:\Android
|
| 72 |
+
echo ✅ إعداد ANDROID_HOME
|
| 73 |
+
echo ✅ إعداد ANDROID_SDK_ROOT
|
| 74 |
+
echo ✅ إضافة إلى PATH
|
| 75 |
+
echo.
|
| 76 |
+
echo 📝 المطلوب منك:
|
| 77 |
+
echo 1. تحميل Command Line Tools
|
| 78 |
+
echo 2. استخراج إلى C:\Android\cmdline-tools\latest\
|
| 79 |
+
echo 3. إعادة تشغيل Command Prompt
|
| 80 |
+
echo 4. تشغيل أوامر sdkmanager
|
| 81 |
+
echo.
|
| 82 |
+
echo بعد ذلك يمكنك تشغيل QUICK_APK_BUILD.bat
|
| 83 |
+
echo ========================================
|
| 84 |
+
pause
|
PROJECT_SUMMARY.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 📊 ملخص مشروع محفظتي الموحدة - التحويل إلى APK
|
| 2 |
+
|
| 3 |
+
## 🎯 **الهدف المحقق**
|
| 4 |
+
تم تحويل تطبيق **محفظتي الموحدة** من تطبيق ويب إلى **تطبيق أندرويد APK** جاهز للتثبيت والاستخدام.
|
| 5 |
+
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
## ✅ **ما تم إنجازه بنجاح**
|
| 9 |
+
|
| 10 |
+
### **1. إعداد البيئة التقنية**
|
| 11 |
+
- ✅ تحويل المشروع إلى **Capacitor** (تقنية التطبيقات الهجينة)
|
| 12 |
+
- ✅ إعداد **package.json** مع جميع التبعيات المطلوبة
|
| 13 |
+
- ✅ تثبيت **Java JDK 11** للبناء
|
| 14 |
+
- ✅ إعداد **capacitor.config.ts** للتكوين
|
| 15 |
+
|
| 16 |
+
### **2. إعداد مشروع الأندرويد**
|
| 17 |
+
- ✅ إضافة منصة الأندرويد: `cap add android`
|
| 18 |
+
- ✅ إعداد **AndroidManifest.xml** مع الأذونات المطلوبة
|
| 19 |
+
- ✅ تكوين **strings.xml** باللغة العربية
|
| 20 |
+
- ✅ نسخ ملفات التطبيق إلى مجلد `www`
|
| 21 |
+
|
| 22 |
+
### **3. إضافة الأذونات المطلوبة**
|
| 23 |
+
```xml
|
| 24 |
+
<!-- الأذونات الأساسية -->
|
| 25 |
+
<uses-permission android:name="android.permission.INTERNET" />
|
| 26 |
+
<uses-permission android:name="android.permission.CAMERA" />
|
| 27 |
+
<uses-permission android:name="android.permission.VIBRATE" />
|
| 28 |
+
|
| 29 |
+
<!-- أذونات SMS (للمستقبل) -->
|
| 30 |
+
<uses-permission android:name="android.permission.READ_SMS" />
|
| 31 |
+
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
| 32 |
+
|
| 33 |
+
<!-- أذونات البصمة -->
|
| 34 |
+
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
| 35 |
+
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
### **4. تحسين التطبيق للجوال**
|
| 39 |
+
- ✅ إضافة **Capacitor Core** للتفاعل مع ميزات الجهاز
|
| 40 |
+
- ✅ إعداد **SplashScreen** (شاشة البداية)
|
| 41 |
+
- ✅ تكوين **StatusBar** (شريط الحالة)
|
| 42 |
+
- ✅ إعداد **LocalNotifications** (الإشعارات المحلية)
|
| 43 |
+
|
| 44 |
+
### **5. إنشاء ملفات التعليمات**
|
| 45 |
+
- ✅ **APK_BUILD_GUIDE.md** - دليل بناء مفصل
|
| 46 |
+
- ✅ **QUICK_APK_BUILD.bat** - ملف بناء سريع
|
| 47 |
+
- ✅ **INSTALL_ANDROID_SDK.bat** - تثبيت Android SDK
|
| 48 |
+
- ✅ **APK_README.md** - دليل المستخدم النهائي
|
| 49 |
+
|
| 50 |
+
---
|
| 51 |
+
|
| 52 |
+
## 📱 **معلومات التطبيق النهائي**
|
| 53 |
+
|
| 54 |
+
| المعلومة | القيمة |
|
| 55 |
+
|---------|--------|
|
| 56 |
+
| **اسم التطبيق** | محفظتي الموحدة |
|
| 57 |
+
| **Package ID** | com.almada.unifiedwallet |
|
| 58 |
+
| **الإصدار** | 1.0.0 |
|
| 59 |
+
| **التقنية** | Capacitor + HTML/CSS/JS |
|
| 60 |
+
| **الحد الأدنى للأندرويد** | API 24 (Android 7.0) |
|
| 61 |
+
| **حجم APK المتوقع** | ~15-20 MB |
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
## 🔧 **الحالة الحالية**
|
| 66 |
+
|
| 67 |
+
### **جاهز للبناء:**
|
| 68 |
+
- ✅ جميع الملفات معدة
|
| 69 |
+
- ✅ التبعيات مثبتة
|
| 70 |
+
- ✅ Java JDK متوفر
|
| 71 |
+
- ✅ مشروع الأندرويد جاهز
|
| 72 |
+
|
| 73 |
+
### **المطلوب لإكمال البناء:**
|
| 74 |
+
- ❌ **Android SDK** (يحتاج تثبيت)
|
| 75 |
+
- ❌ تشغيل أمر البناء: `gradlew assembleDebug`
|
| 76 |
+
|
| 77 |
+
---
|
| 78 |
+
|
| 79 |
+
## 🚀 **طرق إكمال البناء**
|
| 80 |
+
|
| 81 |
+
### **الطريقة الأسهل:**
|
| 82 |
+
1. تثبيت [Android Studio](https://developer.android.com/studio)
|
| 83 |
+
2. فتح مجلد `android` في Android Studio
|
| 84 |
+
3. **Build > Build APK**
|
| 85 |
+
|
| 86 |
+
### **الطريقة السريعة:**
|
| 87 |
+
1. تشغيل `INSTALL_ANDROID_SDK.bat`
|
| 88 |
+
2. تشغيل `QUICK_APK_BUILD.bat`
|
| 89 |
+
|
| 90 |
+
### **الطريقة اليدوية:**
|
| 91 |
+
```bash
|
| 92 |
+
# تثبيت Android SDK
|
| 93 |
+
# ثم:
|
| 94 |
+
cd android
|
| 95 |
+
gradlew assembleDebug
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
---
|
| 99 |
+
|
| 100 |
+
## 📂 **هيكل المشروع النهائي**
|
| 101 |
+
|
| 102 |
+
```
|
| 103 |
+
almada/
|
| 104 |
+
├── 📱 android/ # مشروع الأندرويد الكامل
|
| 105 |
+
│ ├── app/ # تطبيق الأندرويد
|
| 106 |
+
│ ├── gradle/ # إعدادات Gradle
|
| 107 |
+
│ └── build.gradle # ملف البناء
|
| 108 |
+
├── 🌐 www/ # ملفات التطبيق
|
| 109 |
+
│ ├── index.html # الصفحة الرئيسية
|
| 110 |
+
│ ├── styles.css # التصميمات
|
| 111 |
+
│ ├── app.js # المنطق الرئيسي
|
| 112 |
+
│ ├── auth.js # نظام المصادقة
|
| 113 |
+
│ ├── wallets.js # إدارة المحافظ
|
| 114 |
+
│ └── notifications.js # الإشعارات
|
| 115 |
+
├── 📄 capacitor.config.ts # إعدادات Capacitor
|
| 116 |
+
├── 📦 package.json # تبعيات المشروع
|
| 117 |
+
├── 🔨 QUICK_APK_BUILD.bat # بناء سريع
|
| 118 |
+
├── ⚙️ INSTALL_ANDROID_SDK.bat # تثبيت SDK
|
| 119 |
+
├── 📖 APK_BUILD_GUIDE.md # دليل البناء المفصل
|
| 120 |
+
├── 📋 APK_README.md # دليل المستخدم
|
| 121 |
+
└── 📊 PROJECT_SUMMARY.md # هذا الملف
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
---
|
| 125 |
+
|
| 126 |
+
## 🎯 **الميزات المتاحة في التطبيق**
|
| 127 |
+
|
| 128 |
+
### **الأساسية:**
|
| 129 |
+
- 🔐 تس��يل دخول آمن (رقم هاتف + PIN)
|
| 130 |
+
- 💳 عرض 6 محافظ يمنية
|
| 131 |
+
- 🏠 واجهة رئيسية موحدة
|
| 132 |
+
- 🌙 دعم الوضع الليلي
|
| 133 |
+
|
| 134 |
+
### **التقنية:**
|
| 135 |
+
- 📱 واجهة أصلية للأندرويد
|
| 136 |
+
- 🔄 عمل بدون إنترنت
|
| 137 |
+
- 💾 حفظ البيانات محلياً
|
| 138 |
+
- 🎨 رسوم متحركة سلسة
|
| 139 |
+
|
| 140 |
+
### **الأمان:**
|
| 141 |
+
- 🛡️ تشفير البيانات
|
| 142 |
+
- 🔒 حماية PIN
|
| 143 |
+
- ⏰ انتهاء الجلسات
|
| 144 |
+
- 🚫 حماية من المحاولات المتكررة
|
| 145 |
+
|
| 146 |
+
---
|
| 147 |
+
|
| 148 |
+
## 🔮 **الميزات المستقبلية (جاهزة للتطوير)**
|
| 149 |
+
|
| 150 |
+
### **قراءة SMS:**
|
| 151 |
+
- 📨 استخراج أرصدة المحافظ من الرسائل
|
| 152 |
+
- 🔄 تحديث الأرصدة تلقائياً
|
| 153 |
+
- 📊 تحليل أنماط الإنفاق
|
| 154 |
+
|
| 155 |
+
### **المصادقة البيومترية:**
|
| 156 |
+
- 👆 بصمة الإصبع
|
| 157 |
+
- 👁️ التعرف على الوجه
|
| 158 |
+
- 🗣️ التعرف على الصوت
|
| 159 |
+
|
| 160 |
+
### **الإشعارات الذكية:**
|
| 161 |
+
- 💰 تنبيهات الرصيد المنخفض
|
| 162 |
+
- 💸 تنبيهات الإنفاق الزائد
|
| 163 |
+
- 📅 تذكير دفع الفواتير
|
| 164 |
+
|
| 165 |
+
---
|
| 166 |
+
|
| 167 |
+
## 💰 **نموذج الربح المقترح**
|
| 168 |
+
|
| 169 |
+
### **الإعلانات:**
|
| 170 |
+
- 📺 إعلانات مستهدفة بناءً على أنماط الإنفاق
|
| 171 |
+
- 🏪 شراكات مع المتاجر والخدمات
|
| 172 |
+
- 💳 عروض خاصة للمحافظ
|
| 173 |
+
|
| 174 |
+
### **الاشتراكات:**
|
| 175 |
+
- 🆓 **مجاني:** الميزات الأساسية
|
| 176 |
+
- 💎 **مميز:** تحليل متقدم + بدون إعلانات
|
| 177 |
+
- 🏢 **تجاري:** ميزات للشركات
|
| 178 |
+
|
| 179 |
+
### **العمولات:**
|
| 180 |
+
- 🤝 شراكات مع مقدمي الخدمات
|
| 181 |
+
- 🎁 كاش باك من المشتريات
|
| 182 |
+
- 💰 عمولات التحويلات
|
| 183 |
+
|
| 184 |
+
---
|
| 185 |
+
|
| 186 |
+
## 📈 **التوقعات**
|
| 187 |
+
|
| 188 |
+
### **السنة الأولى:**
|
| 189 |
+
- 👥 **10,000 مستخدم** نشط
|
| 190 |
+
- 💰 **400,000 ر.ي** دخل متوقع
|
| 191 |
+
- 📱 **50,000 تحميل** من Google Play
|
| 192 |
+
|
| 193 |
+
### **خطة النمو:**
|
| 194 |
+
1. **الشهر 1-3:** إطلاق وتسويق أولي
|
| 195 |
+
2. **الشهر 4-6:** إضافة ميزات SMS
|
| 196 |
+
3. **الشهر 7-9:** تطوير الذكاء الاصطناعي
|
| 197 |
+
4. **الشهر 10-12:** توسع إقليمي
|
| 198 |
+
|
| 199 |
+
---
|
| 200 |
+
|
| 201 |
+
## 🎊 **الخلاصة**
|
| 202 |
+
|
| 203 |
+
### **تم إنجازه:**
|
| 204 |
+
✅ **تطبيق ويب** محول إلى **تطبيق أندرويد**
|
| 205 |
+
✅ **جميع الملفات** جاهزة للبناء
|
| 206 |
+
✅ **التعليمات** مفصلة وواضحة
|
| 207 |
+
✅ **الأذونات** معدة للميزات المستقبلية
|
| 208 |
+
|
| 209 |
+
### **الخطوة التالية:**
|
| 210 |
+
🔨 **بناء APK** باستخدام إحدى الطرق المذكورة
|
| 211 |
+
|
| 212 |
+
### **النتيجة المتوقعة:**
|
| 213 |
+
📱 **تطبيق أندرويد** جاهز للتثبيت والاستخدام
|
| 214 |
+
|
| 215 |
+
---
|
| 216 |
+
|
| 217 |
+
**🎉 مبروك! مشروعك جاهز للانطلاق إلى العالم!**
|
| 218 |
+
|
| 219 |
+
*تطبيق محفظتي الموحدة - المدى للخدمات البرمجية التسويقية والإعلانية*
|
QUICK_APK_BUILD.bat
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
echo ========================================
|
| 3 |
+
echo محفظتي الموحدة - بناء APK سريع
|
| 4 |
+
echo ========================================
|
| 5 |
+
echo.
|
| 6 |
+
|
| 7 |
+
echo [1/5] التحقق من Java...
|
| 8 |
+
java -version
|
| 9 |
+
if %errorlevel% neq 0 (
|
| 10 |
+
echo ❌ Java غير مثبت! يرجى تثبيت Java JDK 11+
|
| 11 |
+
pause
|
| 12 |
+
exit /b 1
|
| 13 |
+
)
|
| 14 |
+
echo ✅ Java متوفر
|
| 15 |
+
|
| 16 |
+
echo.
|
| 17 |
+
echo [2/5] التحقق من Android SDK...
|
| 18 |
+
if not exist "%ANDROID_HOME%" (
|
| 19 |
+
echo ❌ Android SDK غير مثبت!
|
| 20 |
+
echo يرجى تثبيت Android Studio أو SDK Tools
|
| 21 |
+
echo أو تعيين متغير ANDROID_HOME
|
| 22 |
+
pause
|
| 23 |
+
exit /b 1
|
| 24 |
+
)
|
| 25 |
+
echo ✅ Android SDK متوفر
|
| 26 |
+
|
| 27 |
+
echo.
|
| 28 |
+
echo [3/5] نسخ ملفات التطبيق...
|
| 29 |
+
if not exist "www" mkdir www
|
| 30 |
+
copy index.html www\ >nul 2>&1
|
| 31 |
+
copy styles.css www\ >nul 2>&1
|
| 32 |
+
copy app.js www\ >nul 2>&1
|
| 33 |
+
copy auth.js www\ >nul 2>&1
|
| 34 |
+
copy wallets.js www\ >nul 2>&1
|
| 35 |
+
copy notifications.js www\ >nul 2>&1
|
| 36 |
+
copy demo.html www\ >nul 2>&1
|
| 37 |
+
copy src\manifest.json www\ >nul 2>&1
|
| 38 |
+
echo ✅ تم نسخ الملفات
|
| 39 |
+
|
| 40 |
+
echo.
|
| 41 |
+
echo [4/5] مزامنة Capacitor...
|
| 42 |
+
call cap sync android
|
| 43 |
+
if %errorlevel% neq 0 (
|
| 44 |
+
echo ❌ فشل في مزامنة Capacitor
|
| 45 |
+
pause
|
| 46 |
+
exit /b 1
|
| 47 |
+
)
|
| 48 |
+
echo ✅ تم مزامنة Capacitor
|
| 49 |
+
|
| 50 |
+
echo.
|
| 51 |
+
echo [5/5] بناء APK...
|
| 52 |
+
cd android
|
| 53 |
+
call gradlew assembleDebug
|
| 54 |
+
if %errorlevel% neq 0 (
|
| 55 |
+
echo ❌ فشل في بناء APK
|
| 56 |
+
echo يرجى مراجعة الأخطاء أعلاه
|
| 57 |
+
pause
|
| 58 |
+
exit /b 1
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
echo.
|
| 62 |
+
echo ========================================
|
| 63 |
+
echo 🎉 تم بناء APK بنجاح!
|
| 64 |
+
echo ========================================
|
| 65 |
+
echo.
|
| 66 |
+
echo 📱 ملف APK متوفر في:
|
| 67 |
+
echo android\app\build\outputs\apk\debug\app-debug.apk
|
| 68 |
+
echo.
|
| 69 |
+
echo 📋 الخطوات التالية:
|
| 70 |
+
echo 1. نسخ APK إلى الهاتف
|
| 71 |
+
echo 2. تفعيل "مصادر غير معروفة" في الإعدادات
|
| 72 |
+
echo 3. تثبيت APK
|
| 73 |
+
echo 4. اختبار التطبيق
|
| 74 |
+
echo.
|
| 75 |
+
echo 🔑 بيانات التجربة:
|
| 76 |
+
echo رقم الهاتف: 777123456
|
| 77 |
+
echo رمز PIN: 1234
|
| 78 |
+
echo.
|
| 79 |
+
pause
|
QUICK_START.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# البدء السريع - محفظتي الموحدة
|
| 2 |
+
|
| 3 |
+
## 🚀 تشغيل التطبيق في 5 دقائق
|
| 4 |
+
|
| 5 |
+
### المتطلبات الأساسية
|
| 6 |
+
- Node.js (الإصدار 18+)
|
| 7 |
+
- npm أو yarn
|
| 8 |
+
|
| 9 |
+
### خطوات سريعة
|
| 10 |
+
|
| 11 |
+
#### 1. تثبيت الأدوات
|
| 12 |
+
```bash
|
| 13 |
+
npm install -g @ionic/cli @angular/cli @capacitor/cli
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
#### 2. إعداد المشروع
|
| 17 |
+
```bash
|
| 18 |
+
cd almada
|
| 19 |
+
npm install
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
#### 3. تشغيل التطبيق
|
| 23 |
+
```bash
|
| 24 |
+
ionic serve
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
#### 4. فتح المتصفح
|
| 28 |
+
انتقل إلى: http://localhost:8100
|
| 29 |
+
|
| 30 |
+
### 📱 بناء تطبيق الجوال
|
| 31 |
+
|
| 32 |
+
#### للأندرويد:
|
| 33 |
+
```bash
|
| 34 |
+
ionic build --prod
|
| 35 |
+
ionic cap add android
|
| 36 |
+
ionic cap open android
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
#### لـ iOS:
|
| 40 |
+
```bash
|
| 41 |
+
ionic build --prod
|
| 42 |
+
ionic cap add ios
|
| 43 |
+
ionic cap open ios
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
## 🔑 بيانات التجربة
|
| 47 |
+
|
| 48 |
+
- **رقم الهاتف**: 777123456
|
| 49 |
+
- **رمز PIN**: 1234
|
| 50 |
+
|
| 51 |
+
## 📋 الميزات المتاحة
|
| 52 |
+
|
| 53 |
+
✅ تسجيل دخول آمن
|
| 54 |
+
✅ عرض المحافظ والأرصدة
|
| 55 |
+
✅ تحويل الأموال
|
| 56 |
+
✅ إشعارات فورية
|
| 57 |
+
✅ مصادقة بيومترية
|
| 58 |
+
✅ واجهة عربية كاملة
|
| 59 |
+
|
| 60 |
+
## 🛠️ استكشاف الأخطاء
|
| 61 |
+
|
| 62 |
+
### مشكلة: خطأ في npm install
|
| 63 |
+
```bash
|
| 64 |
+
npm cache clean --force
|
| 65 |
+
rm -rf node_modules
|
| 66 |
+
npm install
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
### مشكلة: خطأ في ionic serve
|
| 70 |
+
```bash
|
| 71 |
+
ionic repair
|
| 72 |
+
ionic serve --verbose
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
### مشكلة: خطأ في Capacitor
|
| 76 |
+
```bash
|
| 77 |
+
npx cap sync
|
| 78 |
+
npx cap doctor
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
## 📚 روابط مفيدة
|
| 82 |
+
|
| 83 |
+
- [تعليمات البناء الكاملة](BUILD_INSTRUCTIONS.md)
|
| 84 |
+
- [وثائق Ionic](https://ionicframework.com/docs)
|
| 85 |
+
- [وثائق Angular](https://angular.io/docs)
|
| 86 |
+
- [وثائق Capacitor](https://capacitorjs.com/docs)
|
| 87 |
+
|
| 88 |
+
## 💡 نصائح
|
| 89 |
+
|
| 90 |
+
1. **للتطوير السريع**: استخدم `ionic serve --lab`
|
| 91 |
+
2. **لاختبار الجوال**: استخدم `ionic cap run android --livereload`
|
| 92 |
+
3. **للتصحيح**: افتح Developer Tools في المتصفح
|
| 93 |
+
4. **للأداء**: استخدم `ionic build --prod` للإنتاج
|
| 94 |
+
|
| 95 |
+
## 🎯 الخطوات التالية
|
| 96 |
+
|
| 97 |
+
1. جرب تسجيل الدخول
|
| 98 |
+
2. استكشف المحافظ المختلفة
|
| 99 |
+
3. جرب التحويلات
|
| 100 |
+
4. اختبر الإشعارات
|
| 101 |
+
5. جرب المصادقة البيومترية (في التطبيق الأصلي)
|
| 102 |
+
|
| 103 |
+
---
|
| 104 |
+
|
| 105 |
+
**🎉 مبروك! تطبيقك جاهز للاستخدام**
|
| 106 |
+
|
| 107 |
+
للمساعدة أو الاستفسارات، راجع الوثائق الكاملة أو تواصل مع فريق التطوير.
|
README.md
CHANGED
|
@@ -1,23 +1,262 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
-
|
|
|
|
| 1 |
+
# محفظتي الموحدة - تطبيق المحافظ الإلكترونية اليمنية الموحد
|
| 2 |
+
|
| 3 |
+
## نظرة عامة
|
| 4 |
+
|
| 5 |
+
**محفظتي الموحدة** هو تطبيق جوال متطور مبني بتقنية Ionic/Angular يجمع جميع المحافظ الإلكترونية اليمنية في واجهة موحدة، مما يتيح للمستخدمين إدارة جميع محافظهم من مكان واحد باستخدام رقم هاتف موحد.
|
| 6 |
+
|
| 7 |
+
## 📱 **التطبيق متاح الآن كـ:**
|
| 8 |
+
- **تطبيق أندرويد** (APK)
|
| 9 |
+
- **تطبيق iOS** (IPA)
|
| 10 |
+
- **تطبيق ويب** (PWA)
|
| 11 |
+
- **تطبيق سطح المكتب** (عبر Electron)
|
| 12 |
+
|
| 13 |
+
## المحافظ المدعومة
|
| 14 |
+
|
| 15 |
+
التطبيق يدعم المحافظ الإلكترونية اليمنية التالية:
|
| 16 |
+
|
| 17 |
+
1. **جوالي (Jawali)** - من WeCash YE
|
| 18 |
+
2. **ONE Cash** - المحفظة الرقمية الأولى في اليمن
|
| 19 |
+
3. **Cash** - من Tamkeen Financial
|
| 20 |
+
4. **Jaib Digital Wallet** - من AHD Financial
|
| 21 |
+
5. **mFloos** - من Alkuraimi Islamic Microfinance Bank
|
| 22 |
+
6. **Mobile Money Wallet** - من CAC Bank
|
| 23 |
+
|
| 24 |
+
## الميزات الرئيسية
|
| 25 |
+
|
| 26 |
+
### 🔐 نظام أمان متقدم
|
| 27 |
+
- تسجيل دخول برقم الهاتف ورمز PIN
|
| 28 |
+
- مصادقة بيومترية (بصمة الإصبع)
|
| 29 |
+
- حماية من المحاولات المتكررة
|
| 30 |
+
- جلسات آمنة مع انتهاء صلاحية تلقائي
|
| 31 |
+
|
| 32 |
+
### 💸 إدارة المحافظ
|
| 33 |
+
- عرض جميع المحافظ في واجهة موحدة
|
| 34 |
+
- عرض الأرصدة الإجمالية والفردية
|
| 35 |
+
- تحديث الأرصدة في الوقت الفعلي
|
| 36 |
+
- إخفاء/إظهار الأرصدة للخصوصية
|
| 37 |
+
|
| 38 |
+
### 🔄 التحويلات والمدفوعات
|
| 39 |
+
- تحويل الأموال بين المحافظ المختلفة
|
| 40 |
+
- دفع الفواتير (كهرباء، مياه، إنترنت)
|
| 41 |
+
- شحن أرصدة الهواتف
|
| 42 |
+
- مسح رموز QR للدفع
|
| 43 |
+
|
| 44 |
+
### 📱 واجهة مستخدم عصرية
|
| 45 |
+
- تصميم متجاوب يعمل على جميع الأجهزة
|
| 46 |
+
- واجهة باللغة العربية مع دعم RTL
|
| 47 |
+
- رسوم متحركة سلسة
|
| 48 |
+
- تجربة مستخدم بديهية
|
| 49 |
+
|
| 50 |
+
### 🔔 نظام إشعارات متطور
|
| 51 |
+
- إشعارات فورية للمعاملات
|
| 52 |
+
- تنبيهات أمنية
|
| 53 |
+
- إشعارات النظام
|
| 54 |
+
- إدارة الإشعارات المقروءة وغير المقروءة
|
| 55 |
+
|
| 56 |
+
## التقنيات المستخدمة
|
| 57 |
+
|
| 58 |
+
### Frontend Framework
|
| 59 |
+
- **Ionic 7** - إطار عمل التطبيقات الهجينة
|
| 60 |
+
- **Angular 17** - إطار عمل الواجهة الأمامية
|
| 61 |
+
- **TypeScript** - لغة البرمجة الأساسية
|
| 62 |
+
- **SCSS** - معالج CSS المتقدم
|
| 63 |
+
|
| 64 |
+
### Mobile Development
|
| 65 |
+
- **Capacitor 5** - منصة التطبيقات الأصلية
|
| 66 |
+
- **Cordova Plugins** - الوصول لميزات الجهاز
|
| 67 |
+
- **PWA** - تطبيق ويب تقدمي
|
| 68 |
+
|
| 69 |
+
### Backend & Storage
|
| 70 |
+
- **Ionic Storage** - تخزين البيانات المحلية
|
| 71 |
+
- **RxJS** - إدارة البيانات التفاعلية
|
| 72 |
+
- **HTTP Client** - التواصل مع APIs
|
| 73 |
+
|
| 74 |
+
### UI/UX
|
| 75 |
+
- **Ionic Components** - مكونات واجهة المستخدم
|
| 76 |
+
- **Ionicons** - مكتبة الأيقونات
|
| 77 |
+
- **Google Fonts** - خط Tajawal العربي
|
| 78 |
+
- **CSS Animations** - الرسوم المتحركة
|
| 79 |
+
|
| 80 |
+
### Development Tools
|
| 81 |
+
- **Angular CLI** - أدوات التطوير
|
| 82 |
+
- **Capacitor CLI** - أدوات البناء للجوال
|
| 83 |
+
- **ESLint** - فحص جودة الكود
|
| 84 |
+
- **Prettier** - تنسيق الكود
|
| 85 |
+
|
| 86 |
+
## هيكل المشروع
|
| 87 |
+
|
| 88 |
+
```
|
| 89 |
+
almada/
|
| 90 |
+
├── src/ # مجلد المصدر الرئيسي
|
| 91 |
+
│ ├── app/ # تطبيق Angular
|
| 92 |
+
│ │ ├── pages/ # صفحات التطبيق
|
| 93 |
+
│ │ │ ├── login/ # صفحة تسجيل الدخول
|
| 94 |
+
│ │ │ ├── home/ # الصفحة الرئيسية
|
| 95 |
+
│ │ │ ├── wallets/ # صفحة المحافظ
|
| 96 |
+
│ │ │ ├── transfer/ # صفحة التحويلات
|
| 97 |
+
│ │ │ └── ... # باقي الصفحات
|
| 98 |
+
│ │ ├── services/ # الخدمات
|
| 99 |
+
│ │ │ ├── auth.service.ts # خدمة المصادقة
|
| 100 |
+
│ │ │ ├── wallet.service.ts # خدمة المحافظ
|
| 101 |
+
│ │ │ └── ... # باقي الخدمات
|
| 102 |
+
│ │ ├── guards/ # حراس الحماية
|
| 103 |
+
│ │ └── components/ # المكو��ات المشتركة
|
| 104 |
+
│ ├── assets/ # الملفات الثابتة
|
| 105 |
+
│ ├── theme/ # ملفات الثيم
|
| 106 |
+
│ └── environments/ # إعدادات البيئة
|
| 107 |
+
├── android/ # مشروع الأندرويد
|
| 108 |
+
├── ios/ # مشروع iOS
|
| 109 |
+
├── capacitor.config.ts # إعدادات Capacitor
|
| 110 |
+
├── ionic.config.json # إعدادات Ionic
|
| 111 |
+
├── angular.json # إعدادات Angular
|
| 112 |
+
├── package.json # تبعيات المشروع
|
| 113 |
+
├── BUILD_INSTRUCTIONS.md # تعليمات البناء
|
| 114 |
+
└── README.md # هذا الملف
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
## كيفية التشغيل
|
| 118 |
+
|
| 119 |
+
### 1. تشغيل للتطوير
|
| 120 |
+
|
| 121 |
+
```bash
|
| 122 |
+
# استنساخ المشروع
|
| 123 |
+
git clone [repository-url]
|
| 124 |
+
cd almada
|
| 125 |
+
|
| 126 |
+
# تثبيت التبعيات
|
| 127 |
+
npm install
|
| 128 |
+
|
| 129 |
+
# تشغيل خادم التطوير
|
| 130 |
+
ionic serve
|
| 131 |
+
|
| 132 |
+
# فتح المتصفح على
|
| 133 |
+
http://localhost:8100
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
### 2. بناء التطبيق للجوال
|
| 137 |
+
|
| 138 |
+
```bash
|
| 139 |
+
# بناء المشروع
|
| 140 |
+
ionic build --prod
|
| 141 |
+
|
| 142 |
+
# إضافة منصة الأندرويد
|
| 143 |
+
ionic cap add android
|
| 144 |
+
|
| 145 |
+
# إضافة منصة iOS
|
| 146 |
+
ionic cap add ios
|
| 147 |
+
|
| 148 |
+
# بناء APK للأندرويد
|
| 149 |
+
ionic cap build android
|
| 150 |
|
| 151 |
+
# بناء IPA لـ iOS
|
| 152 |
+
ionic cap build ios
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
### 3. تشغيل على الأجهزة
|
| 156 |
+
|
| 157 |
+
```bash
|
| 158 |
+
# تشغيل على الأندرويد
|
| 159 |
+
ionic cap run android
|
| 160 |
+
|
| 161 |
+
# تشغيل على iOS
|
| 162 |
+
ionic cap run ios
|
| 163 |
+
|
| 164 |
+
# تشغيل في المتصفح مع إعادة التحميل
|
| 165 |
+
ionic serve --lab
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
راجع ملف [BUILD_INSTRUCTIONS.md](BUILD_INSTRUCTIONS.md) للتفاصيل الكاملة.
|
| 169 |
+
|
| 170 |
+
## بيانات التجربة
|
| 171 |
+
|
| 172 |
+
للاختبار، يمكن استخدام البيانات التالية:
|
| 173 |
+
|
| 174 |
+
- **رقم الهاتف**: أي رقم يمني صحيح (9 أرقام)
|
| 175 |
+
- **رمز PIN**: أي رمز من 4-6 أرقام
|
| 176 |
+
- **مثال**: 777123456 / 1234
|
| 177 |
+
|
| 178 |
+
## الاستخدام
|
| 179 |
+
|
| 180 |
+
### تسجيل الدخول
|
| 181 |
+
1. أدخل رقم الهاتف (9 أرقام)
|
| 182 |
+
2. أدخل رمز PIN (4-6 أرقام)
|
| 183 |
+
3. أو استخدم المصادقة البيومترية
|
| 184 |
+
|
| 185 |
+
### إدارة المحافظ
|
| 186 |
+
- عرض جميع المحافظ والأرصدة
|
| 187 |
+
- تحديث الأرصدة
|
| 188 |
+
- إخفاء/إظهار الأرصدة
|
| 189 |
+
|
| 190 |
+
### التحويلات
|
| 191 |
+
1. اختر المحفظة المرسلة
|
| 192 |
+
2. أدخل تفاصيل التحويل
|
| 193 |
+
3. أكد بـ PIN
|
| 194 |
+
|
| 195 |
+
### الإشعارات
|
| 196 |
+
- عرض الإشعارات من الأيقونة في الأعلى
|
| 197 |
+
- وضع علامة مقروء
|
| 198 |
+
- حذف الإشعارات
|
| 199 |
+
|
| 200 |
+
## الأمان
|
| 201 |
+
|
| 202 |
+
التطبيق يتضمن عدة طبقات أمان:
|
| 203 |
+
|
| 204 |
+
- **تشفير البيانات**: جميع البيانات الحساسة مشفرة
|
| 205 |
+
- **جلسات آمنة**: انتهاء صلاحية تلقائي للجلسات
|
| 206 |
+
- **حماية من الهجمات**: حماية من المحاولات المتكررة
|
| 207 |
+
- **مصادقة متعددة**: PIN + بصمة
|
| 208 |
+
- **تخزين آمن**: استخدام Local Storage بشكل آمن
|
| 209 |
+
|
| 210 |
+
## ملاحظات مهمة
|
| 211 |
+
|
| 212 |
+
⚠️ **هذا تطبيق تجريبي لأغراض العرض فقط**
|
| 213 |
+
|
| 214 |
+
- جميع البيانات والمعاملات محاكاة
|
| 215 |
+
- لا يؤثر على الحسابات الحقيقية
|
| 216 |
+
- البيانات محفوظة محلياً في المتصفح
|
| 217 |
+
- يتطلب متصفح حديث للمصادقة البيومترية
|
| 218 |
+
|
| 219 |
+
## التطوير المستقبلي
|
| 220 |
+
|
| 221 |
+
### الميزات المخطط لها
|
| 222 |
+
- [ ] دعم المزيد من المحافظ
|
| 223 |
+
- [ ] تطبيق جوال أصلي
|
| 224 |
+
- [ ] تكامل مع APIs الحقيقية
|
| 225 |
+
- [ ] نظام إحصائيات متقدم
|
| 226 |
+
- [ ] دعم العملات المتعددة
|
| 227 |
+
- [ ] نظام النسخ الاحتياطي
|
| 228 |
+
|
| 229 |
+
### التحسينات التقنية
|
| 230 |
+
- [ ] PWA (Progressive Web App)
|
| 231 |
+
- [ ] وضع عدم الاتصال
|
| 232 |
+
- [ ] تحسين الأداء
|
| 233 |
+
- [ ] اختبارات تلقائية
|
| 234 |
+
- [ ] CI/CD Pipeline
|
| 235 |
+
|
| 236 |
+
## المساهمة
|
| 237 |
+
|
| 238 |
+
نرحب بالمساهمات! يرجى:
|
| 239 |
+
|
| 240 |
+
1. Fork المشروع
|
| 241 |
+
2. إنشاء branch للميزة الجديدة
|
| 242 |
+
3. Commit التغييرات
|
| 243 |
+
4. Push إلى Branch
|
| 244 |
+
5. فتح Pull Request
|
| 245 |
+
|
| 246 |
+
## الترخيص
|
| 247 |
+
|
| 248 |
+
هذا المشروع مرخص تحت رخصة MIT - انظر ملف [LICENSE](LICENSE) للتفاصيل.
|
| 249 |
+
|
| 250 |
+
## التواصل
|
| 251 |
+
|
| 252 |
+
**المدى للخدمات البرمجية التسويقية والإعلانية**
|
| 253 |
+
- المدير العام: المهندس/ محمد المرتضى
|
| 254 |
+
- © 2025 جميع الحقوق محفوظة
|
| 255 |
+
|
| 256 |
+
## الدعم
|
| 257 |
+
|
| 258 |
+
للدعم التقني أو الاستفسارات، يرجى فتح issue في المستودع أو التواصل مع فريق التطوير.
|
| 259 |
+
|
| 260 |
+
---
|
| 261 |
|
| 262 |
+
**شكراً لاستخدام محفظتي الموحدة! 🚀**
|
README_GITHUB.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 📱 محفظتي الموحدة - تطبيق المحافظ الإلكترونية اليمنية
|
| 2 |
+
|
| 3 |
+
[](https://github.com/USERNAME/almada-unified-wallet/actions/workflows/build-apk.yml)
|
| 4 |
+
[](https://github.com/USERNAME/almada-unified-wallet/releases/latest)
|
| 5 |
+
|
| 6 |
+
## 🎯 **نظرة عامة**
|
| 7 |
+
|
| 8 |
+
**محفظتي الموحدة** هو تطبيق أندرويد متطور يجمع جميع المحافظ الإلكترونية اليمنية في واجهة موحدة، مما يتيح للمستخدمين إدارة جميع محافظهم من مكان واحد.
|
| 9 |
+
|
| 10 |
+
## 📱 **تحميل التطبيق**
|
| 11 |
+
|
| 12 |
+
### 🚀 **أحدث إصدار:**
|
| 13 |
+
[](https://github.com/USERNAME/almada-unified-wallet/releases/latest/download/app-debug.apk)
|
| 14 |
+
|
| 15 |
+
### 🔄 **البناء التلقائي:**
|
| 16 |
+
- يتم بناء APK تلقائياً عند كل تحديث
|
| 17 |
+
- متوفر في قسم [Actions](https://github.com/USERNAME/almada-unified-wallet/actions)
|
| 18 |
+
- تحميل مباشر من [Releases](https://github.com/USERNAME/almada-unified-wallet/releases)
|
| 19 |
+
|
| 20 |
+
## 🎯 **الميزات**
|
| 21 |
+
|
| 22 |
+
### ✅ **المتاحة حالياً:**
|
| 23 |
+
- 🔐 تسجيل دخول آمن برقم الهاتف + PIN
|
| 24 |
+
- 💳 عرض 6 محافظ يمنية رئيسية:
|
| 25 |
+
- جوالي (Jawali)
|
| 26 |
+
- ONE Cash
|
| 27 |
+
- Cash
|
| 28 |
+
- Aman
|
| 29 |
+
- Tadawul
|
| 30 |
+
- Al-Kuraimi
|
| 31 |
+
- 🏠 واجهة موحدة لجميع المحافظ
|
| 32 |
+
- 🌙 دعم الوضع الليلي
|
| 33 |
+
- 🔄 واجهة عربية كاملة (RTL)
|
| 34 |
+
- 📱 تصميم متجاوب لجميع أحجام الشاشات
|
| 35 |
+
|
| 36 |
+
### 🔄 **قيد التطوير:**
|
| 37 |
+
- 📨 قراءة رسائل SMS لاستخراج الأرصدة
|
| 38 |
+
- 👆 مصادقة بيومترية (بصمة/وجه)
|
| 39 |
+
- 🔔 إشعارات ذكية ومخصصة
|
| 40 |
+
- 📊 تحليل أنماط الإنفاق والتوفير
|
| 41 |
+
- 💰 اقتراحات التوفير الذكية
|
| 42 |
+
|
| 43 |
+
## 🔑 **بيانات التجربة**
|
| 44 |
+
|
| 45 |
+
للدخول إلى التطبيق:
|
| 46 |
+
- **رقم الهاتف:** `777123456`
|
| 47 |
+
- **رمز PIN:** `1234`
|
| 48 |
+
|
| 49 |
+
## 🛠️ **التقنيات المستخدمة**
|
| 50 |
+
|
| 51 |
+
- **Frontend:** HTML5, CSS3, JavaScript (ES6+)
|
| 52 |
+
- **Mobile Framework:** Capacitor 5
|
| 53 |
+
- **Build System:** Gradle
|
| 54 |
+
- **CI/CD:** GitHub Actions
|
| 55 |
+
- **Platform:** Android (API 24+)
|
| 56 |
+
|
| 57 |
+
## 📲 **التثبيت**
|
| 58 |
+
|
| 59 |
+
### **من GitHub Releases:**
|
| 60 |
+
1. اذهب إلى [Releases](https://github.com/USERNAME/almada-unified-wallet/releases)
|
| 61 |
+
2. حمل أحدث ملف APK
|
| 62 |
+
3. في الهاتف: فعل "مصادر غير معروفة"
|
| 63 |
+
4. ثبت التطبيق
|
| 64 |
+
|
| 65 |
+
### **من GitHub Actions:**
|
| 66 |
+
1. اذهب إلى [Actions](https://github.com/USERNAME/almada-unified-wallet/actions)
|
| 67 |
+
2. اختر آخر build ناجح
|
| 68 |
+
3. حمل APK من Artifacts
|
| 69 |
+
|
| 70 |
+
## 🏗️ **البناء المحلي**
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
# استنساخ المشروع
|
| 74 |
+
git clone https://github.com/USERNAME/almada-unified-wallet.git
|
| 75 |
+
cd almada-unified-wallet
|
| 76 |
+
|
| 77 |
+
# تثبيت التبعيات
|
| 78 |
+
npm install
|
| 79 |
+
|
| 80 |
+
# بناء للأندرويد
|
| 81 |
+
npx cap sync android
|
| 82 |
+
cd android
|
| 83 |
+
./gradlew assembleDebug
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
## 📊 **معلومات التطبيق**
|
| 87 |
+
|
| 88 |
+
| المعلومة | القيمة |
|
| 89 |
+
|---------|--------|
|
| 90 |
+
| **اسم التطبيق** | محفظتي الموحدة |
|
| 91 |
+
| **Package ID** | com.almada.unifiedwallet |
|
| 92 |
+
| **الإصدار** | 1.0.0 |
|
| 93 |
+
| **حجم APK** | ~15-20 MB |
|
| 94 |
+
| **الحد الأدنى** | Android 7.0 (API 24) |
|
| 95 |
+
| **اللغة** | العربية (RTL) |
|
| 96 |
+
|
| 97 |
+
## 🤝 **المساهمة**
|
| 98 |
+
|
| 99 |
+
نرحب بالمساهمات! يرجى:
|
| 100 |
+
1. Fork المشروع
|
| 101 |
+
2. إنشاء branch للميزة الجديدة
|
| 102 |
+
3. Commit التغييرات
|
| 103 |
+
4. Push إلى Branch
|
| 104 |
+
5. فتح Pull Request
|
| 105 |
+
|
| 106 |
+
## 📄 **الترخيص**
|
| 107 |
+
|
| 108 |
+
هذا المشروع مرخص تحت رخصة MIT - راجع ملف [LICENSE](LICENSE) للتفاصيل.
|
| 109 |
+
|
| 110 |
+
## 📞 **التواصل**
|
| 111 |
+
|
| 112 |
+
- **الشركة:** المدى للخدمات البرمجية التسويقية والإعلانية
|
| 113 |
+
- **الموقع:** https://almada.com
|
| 114 |
+
- **البريد الإلكتروني:** info@almada.com
|
| 115 |
+
|
| 116 |
+
## 🎉 **شكر خاص**
|
| 117 |
+
|
| 118 |
+
شكر خاص لجميع مطوري المحافظ الإلكترونية اليمنية والمجتمع التقني اليمني.
|
| 119 |
+
|
| 120 |
+
---
|
| 121 |
+
|
| 122 |
+
**🚀 مبروك! تطبيقك متاح للعالم!**
|
| 123 |
+
|
| 124 |
+
*Made with ❤️ in Yemen*
|
STEP_BY_STEP_APK.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 📱 دليل بناء APK خطوة بخطوة - مصور
|
| 2 |
+
|
| 3 |
+
## 🎯 **الهدف:** الحصول على ملف APK جاهز للتثبيت في 15 دقيقة
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 🚀 **الطريقة الأسرع: ملف BAT التلقائي**
|
| 8 |
+
|
| 9 |
+
### **الخطوة 1: تشغيل الملف التلقائي**
|
| 10 |
+
1. **اذهب إلى مجلد المشروع:** `E:\almada`
|
| 11 |
+
2. **اضغط مرتين على:** `BUILD_APK_SIMPLE.bat`
|
| 12 |
+
3. **انتظر اكتمال العملية** (5-10 دقائق)
|
| 13 |
+
|
| 14 |
+
### **إذا نجحت العملية:**
|
| 15 |
+
- ✅ ستفتح نافذة تحتوي على ملف APK
|
| 16 |
+
- ✅ الملف سيكون: `app-debug.apk`
|
| 17 |
+
- ✅ انسخه إلى هاتفك وثبته
|
| 18 |
+
|
| 19 |
+
### **إذا فشلت العملية:**
|
| 20 |
+
- ❌ انتقل للطريقة الثانية أدناه
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## 🏗️ **الطريقة الثانية: Android Studio (مضمونة 100%)**
|
| 25 |
+
|
| 26 |
+
### **الخطوة 1: فتح Android Studio**
|
| 27 |
+
```
|
| 28 |
+
1. افتح Android Studio
|
| 29 |
+
2. اختر "Open an Existing Project"
|
| 30 |
+
3. انتقل إلى: E:\almada\android
|
| 31 |
+
4. اختر مجلد android واضغط OK
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
### **الخطوة 2: انتظار التحميل**
|
| 35 |
+
```
|
| 36 |
+
⏳ انتظر رسالة: "Gradle sync finished"
|
| 37 |
+
📦 قد يستغرق 5-10 دقائق في المرة الأولى
|
| 38 |
+
🌐 تأكد من اتصال الإنترنت
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
### **الخطوة 3: بناء APK**
|
| 42 |
+
```
|
| 43 |
+
1. في القائمة العلوية: Build
|
| 44 |
+
2. اختر: Build Bundle(s) / APK(s)
|
| 45 |
+
3. اختر: Build APK(s)
|
| 46 |
+
4. انتظر رسالة: "APK(s) generated successfully"
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
### **الخطوة 4: العثور على APK**
|
| 50 |
+
```
|
| 51 |
+
📂 المسار: E:\almada\android\app\build\outputs\apk\debug\
|
| 52 |
+
📱 الملف: app-debug.apk
|
| 53 |
+
💾 الحجم: ~15-20 MB
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
---
|
| 57 |
+
|
| 58 |
+
## 📲 **تثبيت التطبيق على الهاتف**
|
| 59 |
+
|
| 60 |
+
### **الطريقة 1: نسخ مباشر**
|
| 61 |
+
1. **انسخ ملف APK** إلى هاتفك (عبر USB أو البلوتوث)
|
| 62 |
+
2. **في الهاتف:** Settings > Security > Install from Unknown Sources ✅
|
| 63 |
+
3. **اضغط على ملف APK** واختر Install
|
| 64 |
+
4. **انتظر اكتمال التثبيت**
|
| 65 |
+
|
| 66 |
+
### **الطريقة 2: ADB (للمتقدمين)**
|
| 67 |
+
```bash
|
| 68 |
+
# وصل الهاتف بـ USB وفعل USB Debugging
|
| 69 |
+
adb install app-debug.apk
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
---
|
| 73 |
+
|
| 74 |
+
## 🔑 **اختبار التطبيق**
|
| 75 |
+
|
| 76 |
+
بعد التثبيت:
|
| 77 |
+
1. **افتح التطبيق:** "محفظتي الموحدة"
|
| 78 |
+
2. **أدخل البيانات:**
|
| 79 |
+
- رقم الهاتف: `777123456`
|
| 80 |
+
- رمز PIN: `1234`
|
| 81 |
+
3. **استكشف الميزات:**
|
| 82 |
+
- عرض المحافظ
|
| 83 |
+
- التنقل بين الصفحات
|
| 84 |
+
- اختبار الواجهة العربية
|
| 85 |
+
|
| 86 |
+
---
|
| 87 |
+
|
| 88 |
+
## 🆘 **حل المشاكل الشائعة**
|
| 89 |
+
|
| 90 |
+
### **مشكلة: "App not installed"**
|
| 91 |
+
```
|
| 92 |
+
الحل:
|
| 93 |
+
1. تأكد من تفعيل "Install from Unknown Sources"
|
| 94 |
+
2. احذف أي إصدار قديم من التطبيق
|
| 95 |
+
3. أعد تشغيل الهاتف وحاول مرة أخرى
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
### **مشكلة: "Parse error"**
|
| 99 |
+
```
|
| 100 |
+
الحل:
|
| 101 |
+
1. تأكد من أن ملف APK غير تالف
|
| 102 |
+
2. أعد تحميل/نسخ الملف
|
| 103 |
+
3. تأكد من توافق إصدار الأندرويد (7.0+)
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
### **مشكلة: "Gradle build failed"**
|
| 107 |
+
```
|
| 108 |
+
الحل:
|
| 109 |
+
1. تأكد من اتصال الإنترنت
|
| 110 |
+
2. في Android Studio: Build > Clean Project
|
| 111 |
+
3. ثم: Build > Rebuild Project
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
---
|
| 115 |
+
|
| 116 |
+
## 📊 **معلومات التطبيق النهائي**
|
| 117 |
+
|
| 118 |
+
| المعلومة | القيمة |
|
| 119 |
+
|---------|--------|
|
| 120 |
+
| **اسم التطبيق** | محفظتي الموحدة |
|
| 121 |
+
| **Package Name** | com.almada.unifiedwallet |
|
| 122 |
+
| **الإصدار** | 1.0.0 |
|
| 123 |
+
| **حجم APK** | ~15-20 MB |
|
| 124 |
+
| **الحد الأدنى** | Android 7.0 (API 24) |
|
| 125 |
+
| **اللغة** | العربية (RTL) |
|
| 126 |
+
| **النوع** | Debug APK |
|
| 127 |
+
|
| 128 |
+
---
|
| 129 |
+
|
| 130 |
+
## 🎯 **الميزات المتاحة**
|
| 131 |
+
|
| 132 |
+
### **الحالية:**
|
| 133 |
+
- ✅ تسجيل دخول آمن
|
| 134 |
+
- ✅ عرض 6 محافظ يمنية
|
| 135 |
+
- ✅ واجهة عربية كاملة
|
| 136 |
+
- ✅ تصميم متجاوب
|
| 137 |
+
- ✅ حفظ البيانات محلياً
|
| 138 |
+
|
| 139 |
+
### **المستقبلية:**
|
| 140 |
+
- 🔄 قراءة رسائل SMS
|
| 141 |
+
- 🔄 مصادقة بيومترية
|
| 142 |
+
- 🔄 إشعارات ذكية
|
| 143 |
+
- 🔄 تحليل الإنفاق
|
| 144 |
+
|
| 145 |
+
---
|
| 146 |
+
|
| 147 |
+
## 💡 **نصائح مهمة**
|
| 148 |
+
|
| 149 |
+
### **للبناء الناجح:**
|
| 150 |
+
- 🌐 تأكد من **اتصال الإنترنت** القوي
|
| 151 |
+
- ⏰ **لا تقاطع** عملية التحميل
|
| 152 |
+
- 💾 تأكد من **مساحة كافية** (5+ GB)
|
| 153 |
+
|
| 154 |
+
### **للاختبار:**
|
| 155 |
+
- 📱 اختبر على **أجهزة مختلفة**
|
| 156 |
+
- 🔄 اختبر **جميع الميزات**
|
| 157 |
+
- 📊 راقب **الأداء**
|
| 158 |
+
|
| 159 |
+
### **للمشاركة:**
|
| 160 |
+
- 📤 يمكنك **مشاركة APK** مع الآخرين
|
| 161 |
+
- 🔒 هذا **إصدار تجريبي** (Debug)
|
| 162 |
+
- 🏪 للنشر في Google Play تحتاج **إصدار Release**
|
| 163 |
+
|
| 164 |
+
---
|
| 165 |
+
|
| 166 |
+
## 🎉 **تهانينا!**
|
| 167 |
+
|
| 168 |
+
عند اكتمال هذه الخطوات، ستحصل على:
|
| 169 |
+
- 📱 **تطبيق أندرويد** كامل وجاهز
|
| 170 |
+
- 💼 **محفظة موحدة** لجميع المحافظ اليمنية
|
| 171 |
+
- �� **واجهة احترافية** باللغة العربية
|
| 172 |
+
- 🔒 **نظام أمان** متقدم
|
| 173 |
+
|
| 174 |
+
**🚀 مبروك! تطبيقك جاهز للعالم!**
|
| 175 |
+
|
| 176 |
+
---
|
| 177 |
+
|
| 178 |
+
## 📞 **تحتاج مساعدة؟**
|
| 179 |
+
|
| 180 |
+
إذا واجهت أي مشكلة:
|
| 181 |
+
1. راجع قسم "حل المشاكل" أعلاه
|
| 182 |
+
2. تأكد من اتباع الخطوات بالترتيب
|
| 183 |
+
3. تواصل للحصول على المساعدة
|
| 184 |
+
|
| 185 |
+
**💪 لا تستسلم - النجاح قريب!**
|
Windows
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
اختر: "Command line tools only"
|
actions/mentions.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
| 1 |
-
"use client";
|
| 2 |
-
|
| 3 |
-
import { File } from "@/lib/type";
|
| 4 |
-
|
| 5 |
-
export const searchMentions = async (query: string) => {
|
| 6 |
-
const promises = [searchModels(query), searchDatasets(query)];
|
| 7 |
-
const results = await Promise.all(promises);
|
| 8 |
-
return { models: results[0], datasets: results[1] };
|
| 9 |
-
};
|
| 10 |
-
|
| 11 |
-
const searchModels = async (query: string) => {
|
| 12 |
-
const response = await fetch(
|
| 13 |
-
`https://huggingface.co/api/quicksearch?q=${query}&type=model&limit=3`
|
| 14 |
-
);
|
| 15 |
-
const data = await response.json();
|
| 16 |
-
return data?.models ?? [];
|
| 17 |
-
};
|
| 18 |
-
|
| 19 |
-
const searchDatasets = async (query: string) => {
|
| 20 |
-
const response = await fetch(
|
| 21 |
-
`https://huggingface.co/api/quicksearch?q=${query}&type=dataset&limit=3`
|
| 22 |
-
);
|
| 23 |
-
const data = await response.json();
|
| 24 |
-
return data?.datasets ?? [];
|
| 25 |
-
};
|
| 26 |
-
|
| 27 |
-
export const searchFilesMentions = async (query: string, files: File[]) => {
|
| 28 |
-
if (!query) return files;
|
| 29 |
-
const lowerQuery = query.toLowerCase();
|
| 30 |
-
return files.filter((file) => file.path.toLowerCase().includes(lowerQuery));
|
| 31 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
actions/projects.ts
DELETED
|
@@ -1,175 +0,0 @@
|
|
| 1 |
-
"use server";
|
| 2 |
-
import {
|
| 3 |
-
downloadFile,
|
| 4 |
-
listCommits,
|
| 5 |
-
listFiles,
|
| 6 |
-
listSpaces,
|
| 7 |
-
RepoDesignation,
|
| 8 |
-
SpaceEntry,
|
| 9 |
-
spaceInfo,
|
| 10 |
-
} from "@huggingface/hub";
|
| 11 |
-
|
| 12 |
-
import { auth } from "@/lib/auth";
|
| 13 |
-
import { Commit, File } from "@/lib/type";
|
| 14 |
-
|
| 15 |
-
export interface ProjectWithCommits extends SpaceEntry {
|
| 16 |
-
commits?: Commit[];
|
| 17 |
-
medias?: string[];
|
| 18 |
-
}
|
| 19 |
-
|
| 20 |
-
const IGNORED_PATHS = ["README.md", ".gitignore", ".gitattributes"];
|
| 21 |
-
const IGNORED_FORMATS = [
|
| 22 |
-
".png",
|
| 23 |
-
".jpg",
|
| 24 |
-
".jpeg",
|
| 25 |
-
".gif",
|
| 26 |
-
".svg",
|
| 27 |
-
".webp",
|
| 28 |
-
".mp4",
|
| 29 |
-
".mp3",
|
| 30 |
-
".wav",
|
| 31 |
-
];
|
| 32 |
-
|
| 33 |
-
export const getProjects = async () => {
|
| 34 |
-
const projects: SpaceEntry[] = [];
|
| 35 |
-
const session = await auth();
|
| 36 |
-
if (!session?.user) {
|
| 37 |
-
return projects;
|
| 38 |
-
}
|
| 39 |
-
const token = session.accessToken;
|
| 40 |
-
for await (const space of listSpaces({
|
| 41 |
-
accessToken: token,
|
| 42 |
-
additionalFields: ["author", "cardData"],
|
| 43 |
-
search: {
|
| 44 |
-
owner: session.user.username,
|
| 45 |
-
},
|
| 46 |
-
})) {
|
| 47 |
-
if (
|
| 48 |
-
space.sdk === "static" &&
|
| 49 |
-
Array.isArray((space.cardData as { tags?: string[] })?.tags) &&
|
| 50 |
-
(space.cardData as { tags?: string[] })?.tags?.some((tag) =>
|
| 51 |
-
tag.includes("deepsite")
|
| 52 |
-
)
|
| 53 |
-
) {
|
| 54 |
-
projects.push(space);
|
| 55 |
-
}
|
| 56 |
-
}
|
| 57 |
-
return projects;
|
| 58 |
-
};
|
| 59 |
-
export const getProject = async (id: string, commitId?: string) => {
|
| 60 |
-
const session = await auth();
|
| 61 |
-
if (!session?.user) {
|
| 62 |
-
return null;
|
| 63 |
-
}
|
| 64 |
-
const token = session.accessToken;
|
| 65 |
-
try {
|
| 66 |
-
const project: ProjectWithCommits | null = await spaceInfo({
|
| 67 |
-
name: id,
|
| 68 |
-
accessToken: token,
|
| 69 |
-
additionalFields: ["author", "cardData"],
|
| 70 |
-
});
|
| 71 |
-
const repo: RepoDesignation = {
|
| 72 |
-
type: "space",
|
| 73 |
-
name: id,
|
| 74 |
-
};
|
| 75 |
-
const files: File[] = [];
|
| 76 |
-
const medias: string[] = [];
|
| 77 |
-
const params = { repo, accessToken: token };
|
| 78 |
-
if (commitId) {
|
| 79 |
-
Object.assign(params, { revision: commitId });
|
| 80 |
-
}
|
| 81 |
-
for await (const fileInfo of listFiles(params)) {
|
| 82 |
-
if (IGNORED_PATHS.includes(fileInfo.path)) continue;
|
| 83 |
-
if (IGNORED_FORMATS.some((format) => fileInfo.path.endsWith(format))) {
|
| 84 |
-
medias.push(
|
| 85 |
-
`https://huggingface.co/spaces/${id}/resolve/main/${fileInfo.path}`
|
| 86 |
-
);
|
| 87 |
-
continue;
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
if (fileInfo.type === "directory") {
|
| 91 |
-
for await (const subFile of listFiles({
|
| 92 |
-
repo,
|
| 93 |
-
accessToken: token,
|
| 94 |
-
path: fileInfo.path,
|
| 95 |
-
})) {
|
| 96 |
-
if (IGNORED_FORMATS.some((format) => subFile.path.endsWith(format))) {
|
| 97 |
-
medias.push(
|
| 98 |
-
`https://huggingface.co/spaces/${id}/resolve/main/${subFile.path}`
|
| 99 |
-
);
|
| 100 |
-
}
|
| 101 |
-
const blob = await downloadFile({
|
| 102 |
-
repo,
|
| 103 |
-
accessToken: token,
|
| 104 |
-
path: subFile.path,
|
| 105 |
-
raw: true,
|
| 106 |
-
...(commitId ? { revision: commitId } : {}),
|
| 107 |
-
}).catch((_) => {
|
| 108 |
-
return null;
|
| 109 |
-
});
|
| 110 |
-
if (!blob) {
|
| 111 |
-
continue;
|
| 112 |
-
}
|
| 113 |
-
const html = await blob?.text();
|
| 114 |
-
if (!html) {
|
| 115 |
-
continue;
|
| 116 |
-
}
|
| 117 |
-
files[subFile.path === "index.html" ? "unshift" : "push"]({
|
| 118 |
-
path: subFile.path,
|
| 119 |
-
content: html,
|
| 120 |
-
});
|
| 121 |
-
}
|
| 122 |
-
} else {
|
| 123 |
-
const blob = await downloadFile({
|
| 124 |
-
repo,
|
| 125 |
-
accessToken: token,
|
| 126 |
-
path: fileInfo.path,
|
| 127 |
-
raw: true,
|
| 128 |
-
...(commitId ? { revision: commitId } : {}),
|
| 129 |
-
}).catch((_) => {
|
| 130 |
-
return null;
|
| 131 |
-
});
|
| 132 |
-
if (!blob) {
|
| 133 |
-
continue;
|
| 134 |
-
}
|
| 135 |
-
const html = await blob?.text();
|
| 136 |
-
if (!html) {
|
| 137 |
-
continue;
|
| 138 |
-
}
|
| 139 |
-
files[fileInfo.path === "index.html" ? "unshift" : "push"]({
|
| 140 |
-
path: fileInfo.path,
|
| 141 |
-
content: html,
|
| 142 |
-
});
|
| 143 |
-
}
|
| 144 |
-
}
|
| 145 |
-
const commits: Commit[] = [];
|
| 146 |
-
const commitIterator = listCommits({ repo, accessToken: token });
|
| 147 |
-
for await (const commit of commitIterator) {
|
| 148 |
-
if (
|
| 149 |
-
commit.title?.toLowerCase() === "initial commit" ||
|
| 150 |
-
commit.title
|
| 151 |
-
?.toLowerCase()
|
| 152 |
-
?.includes("upload media files through deepsite")
|
| 153 |
-
)
|
| 154 |
-
continue;
|
| 155 |
-
commits.push({
|
| 156 |
-
title: commit.title,
|
| 157 |
-
oid: commit.oid,
|
| 158 |
-
date: commit.date,
|
| 159 |
-
});
|
| 160 |
-
if (commits.length >= 20) {
|
| 161 |
-
break;
|
| 162 |
-
}
|
| 163 |
-
}
|
| 164 |
-
|
| 165 |
-
project.commits = commits;
|
| 166 |
-
project.medias = medias;
|
| 167 |
-
|
| 168 |
-
return { project, files };
|
| 169 |
-
} catch (error) {
|
| 170 |
-
return {
|
| 171 |
-
project: null,
|
| 172 |
-
files: [],
|
| 173 |
-
};
|
| 174 |
-
}
|
| 175 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
angular.json
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
| 3 |
+
"version": 1,
|
| 4 |
+
"newProjectRoot": "projects",
|
| 5 |
+
"projects": {
|
| 6 |
+
"unified-wallet-app": {
|
| 7 |
+
"projectType": "application",
|
| 8 |
+
"schematics": {
|
| 9 |
+
"@ionic/angular-toolkit:component": {
|
| 10 |
+
"styleext": "scss"
|
| 11 |
+
},
|
| 12 |
+
"@ionic/angular-toolkit:page": {
|
| 13 |
+
"styleext": "scss"
|
| 14 |
+
}
|
| 15 |
+
},
|
| 16 |
+
"root": "",
|
| 17 |
+
"sourceRoot": "src",
|
| 18 |
+
"prefix": "app",
|
| 19 |
+
"architect": {
|
| 20 |
+
"build": {
|
| 21 |
+
"builder": "@angular-devkit/build-angular:browser",
|
| 22 |
+
"options": {
|
| 23 |
+
"outputPath": "dist/unified-wallet-app",
|
| 24 |
+
"index": "src/index.html",
|
| 25 |
+
"main": "src/main.ts",
|
| 26 |
+
"polyfills": [
|
| 27 |
+
"zone.js"
|
| 28 |
+
],
|
| 29 |
+
"tsConfig": "tsconfig.app.json",
|
| 30 |
+
"inlineStyleLanguage": "scss",
|
| 31 |
+
"assets": [
|
| 32 |
+
{
|
| 33 |
+
"glob": "**/*",
|
| 34 |
+
"input": "src/assets",
|
| 35 |
+
"output": "assets"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"glob": "**/*.svg",
|
| 39 |
+
"input": "node_modules/ionicons/dist/ionicons/svg",
|
| 40 |
+
"output": "./svg"
|
| 41 |
+
}
|
| 42 |
+
],
|
| 43 |
+
"styles": [
|
| 44 |
+
"src/theme/variables.scss",
|
| 45 |
+
"src/global.scss"
|
| 46 |
+
],
|
| 47 |
+
"scripts": []
|
| 48 |
+
},
|
| 49 |
+
"configurations": {
|
| 50 |
+
"production": {
|
| 51 |
+
"budgets": [
|
| 52 |
+
{
|
| 53 |
+
"type": "initial",
|
| 54 |
+
"maximumWarning": "2mb",
|
| 55 |
+
"maximumError": "5mb"
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"type": "anyComponentStyle",
|
| 59 |
+
"maximumWarning": "2kb",
|
| 60 |
+
"maximumError": "4kb"
|
| 61 |
+
}
|
| 62 |
+
],
|
| 63 |
+
"outputHashing": "all"
|
| 64 |
+
},
|
| 65 |
+
"development": {
|
| 66 |
+
"buildOptimizer": false,
|
| 67 |
+
"optimization": false,
|
| 68 |
+
"vendorChunk": true,
|
| 69 |
+
"extractLicenses": false,
|
| 70 |
+
"sourceMap": true,
|
| 71 |
+
"namedChunks": true
|
| 72 |
+
}
|
| 73 |
+
},
|
| 74 |
+
"defaultConfiguration": "production"
|
| 75 |
+
},
|
| 76 |
+
"serve": {
|
| 77 |
+
"builder": "@angular-devkit/build-angular:dev-server",
|
| 78 |
+
"configurations": {
|
| 79 |
+
"production": {
|
| 80 |
+
"buildTarget": "unified-wallet-app:build:production"
|
| 81 |
+
},
|
| 82 |
+
"development": {
|
| 83 |
+
"buildTarget": "unified-wallet-app:build:development"
|
| 84 |
+
}
|
| 85 |
+
},
|
| 86 |
+
"defaultConfiguration": "development"
|
| 87 |
+
},
|
| 88 |
+
"extract-i18n": {
|
| 89 |
+
"builder": "@angular-devkit/build-angular:extract-i18n",
|
| 90 |
+
"options": {
|
| 91 |
+
"buildTarget": "unified-wallet-app:build"
|
| 92 |
+
}
|
| 93 |
+
},
|
| 94 |
+
"test": {
|
| 95 |
+
"builder": "@angular-devkit/build-angular:karma",
|
| 96 |
+
"options": {
|
| 97 |
+
"polyfills": [
|
| 98 |
+
"zone.js",
|
| 99 |
+
"zone.js/testing"
|
| 100 |
+
],
|
| 101 |
+
"tsConfig": "tsconfig.spec.json",
|
| 102 |
+
"inlineStyleLanguage": "scss",
|
| 103 |
+
"assets": [
|
| 104 |
+
{
|
| 105 |
+
"glob": "**/*",
|
| 106 |
+
"input": "src/assets",
|
| 107 |
+
"output": "assets"
|
| 108 |
+
},
|
| 109 |
+
{
|
| 110 |
+
"glob": "**/*.svg",
|
| 111 |
+
"input": "node_modules/ionicons/dist/ionicons/svg",
|
| 112 |
+
"output": "./svg"
|
| 113 |
+
}
|
| 114 |
+
],
|
| 115 |
+
"styles": [
|
| 116 |
+
"src/theme/variables.scss",
|
| 117 |
+
"src/global.scss"
|
| 118 |
+
],
|
| 119 |
+
"scripts": []
|
| 120 |
+
}
|
| 121 |
+
},
|
| 122 |
+
"lint": {
|
| 123 |
+
"builder": "@angular-eslint/builder:lint",
|
| 124 |
+
"options": {
|
| 125 |
+
"lintFilePatterns": [
|
| 126 |
+
"src/**/*.ts",
|
| 127 |
+
"src/**/*.html"
|
| 128 |
+
]
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
},
|
| 134 |
+
"cli": {
|
| 135 |
+
"schematicCollections": [
|
| 136 |
+
"@ionic/angular-toolkit"
|
| 137 |
+
]
|
| 138 |
+
},
|
| 139 |
+
"schematics": {
|
| 140 |
+
"@ionic/angular-toolkit:component": {
|
| 141 |
+
"styleext": "scss"
|
| 142 |
+
},
|
| 143 |
+
"@ionic/angular-toolkit:page": {
|
| 144 |
+
"styleext": "scss"
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
}
|
app.js
ADDED
|
@@ -0,0 +1,780 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// التطبيق الرئيسي - محفظتي الموحدة
|
| 2 |
+
class UnifiedWalletApp {
|
| 3 |
+
constructor() {
|
| 4 |
+
this.currentUser = null;
|
| 5 |
+
this.currentScreen = 'login';
|
| 6 |
+
this.wallets = [];
|
| 7 |
+
this.transactions = [];
|
| 8 |
+
this.isBalanceHidden = false;
|
| 9 |
+
|
| 10 |
+
// تهيئة المدراء
|
| 11 |
+
this.authManager = new AuthenticationManager();
|
| 12 |
+
this.walletManager = new WalletManager();
|
| 13 |
+
this.notificationManager = new NotificationManager();
|
| 14 |
+
|
| 15 |
+
this.init();
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
// تهيئة التطبيق
|
| 19 |
+
init() {
|
| 20 |
+
this.setupEventListeners();
|
| 21 |
+
this.loadUserData();
|
| 22 |
+
this.showLoadingScreen();
|
| 23 |
+
|
| 24 |
+
// محاكاة تحميل البيانات
|
| 25 |
+
setTimeout(() => {
|
| 26 |
+
this.hideLoadingScreen();
|
| 27 |
+
this.checkAuthStatus();
|
| 28 |
+
}, 2000);
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
// إعداد مستمعي الأحداث
|
| 32 |
+
setupEventListeners() {
|
| 33 |
+
// تسجيل الدخول
|
| 34 |
+
document.getElementById('login-btn').addEventListener('click', () => this.handleLogin());
|
| 35 |
+
document.getElementById('biometric-login').addEventListener('click', () => this.handleBiometricLogin());
|
| 36 |
+
document.getElementById('register-link').addEventListener('click', (e) => {
|
| 37 |
+
e.preventDefault();
|
| 38 |
+
this.showRegisterModal();
|
| 39 |
+
});
|
| 40 |
+
|
| 41 |
+
// الشاشة الرئيسية
|
| 42 |
+
document.getElementById('refresh-balance').addEventListener('click', () => this.refreshBalances());
|
| 43 |
+
document.getElementById('hide-balance').addEventListener('click', () => this.toggleBalanceVisibility());
|
| 44 |
+
|
| 45 |
+
// الإجراءات السريعة
|
| 46 |
+
document.getElementById('transfer-btn').addEventListener('click', () => this.showTransferScreen());
|
| 47 |
+
document.getElementById('pay-bills-btn').addEventListener('click', () => this.showBillsScreen());
|
| 48 |
+
document.getElementById('recharge-btn').addEventListener('click', () => this.showRechargeScreen());
|
| 49 |
+
document.getElementById('qr-scan-btn').addEventListener('click', () => this.startQRScan());
|
| 50 |
+
|
| 51 |
+
// شريط التنقل
|
| 52 |
+
document.querySelectorAll('.nav-item').forEach(item => {
|
| 53 |
+
item.addEventListener('click', () => {
|
| 54 |
+
const screen = item.dataset.screen;
|
| 55 |
+
this.switchScreen(screen);
|
| 56 |
+
});
|
| 57 |
+
});
|
| 58 |
+
|
| 59 |
+
// أزرار الإعدادات والإشعارات
|
| 60 |
+
document.getElementById('settings-btn').addEventListener('click', () => this.showSettings());
|
| 61 |
+
document.getElementById('notifications-btn').addEventListener('click', () => this.showNotifications());
|
| 62 |
+
|
| 63 |
+
// أزرار الإدارة
|
| 64 |
+
document.getElementById('manage-wallets').addEventListener('click', () => this.showWalletManagement());
|
| 65 |
+
document.getElementById('view-all-transactions').addEventListener('click', () => this.showAllTransactions());
|
| 66 |
+
|
| 67 |
+
// أزرار الرجوع
|
| 68 |
+
document.getElementById('transfer-back').addEventListener('click', () => this.switchScreen('main'));
|
| 69 |
+
|
| 70 |
+
// شاشة التحويل
|
| 71 |
+
this.setupTransferScreen();
|
| 72 |
+
|
| 73 |
+
// إدخال رقم الهاتف
|
| 74 |
+
document.getElementById('phone-number').addEventListener('input', this.formatPhoneNumber);
|
| 75 |
+
document.getElementById('pin-code').addEventListener('keypress', (e) => {
|
| 76 |
+
if (e.key === 'Enter') this.handleLogin();
|
| 77 |
+
});
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
// عرض شاشة التحميل
|
| 81 |
+
showLoadingScreen() {
|
| 82 |
+
document.getElementById('loading-screen').style.display = 'flex';
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
// إخفاء شاشة التحميل
|
| 86 |
+
hideLoadingScreen() {
|
| 87 |
+
document.getElementById('loading-screen').style.display = 'none';
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
// فحص حالة المصادقة
|
| 91 |
+
checkAuthStatus() {
|
| 92 |
+
const savedUser = localStorage.getItem('unifiedWallet_user');
|
| 93 |
+
if (savedUser) {
|
| 94 |
+
this.currentUser = JSON.parse(savedUser);
|
| 95 |
+
this.switchScreen('main');
|
| 96 |
+
this.loadUserWallets();
|
| 97 |
+
} else {
|
| 98 |
+
this.switchScreen('login');
|
| 99 |
+
}
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
// تسجيل الدخول
|
| 103 |
+
async handleLogin() {
|
| 104 |
+
const phoneNumber = document.getElementById('phone-number').value;
|
| 105 |
+
const pinCode = document.getElementById('pin-code').value;
|
| 106 |
+
|
| 107 |
+
if (!phoneNumber || !pinCode) {
|
| 108 |
+
this.showAlert('يرجى إدخال رقم الهاتف ورمز PIN', 'error');
|
| 109 |
+
return;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
this.showLoading('جاري تسجيل الدخول...');
|
| 113 |
+
|
| 114 |
+
try {
|
| 115 |
+
const user = await this.authManager.loginWithPin(phoneNumber, pinCode);
|
| 116 |
+
this.currentUser = user;
|
| 117 |
+
|
| 118 |
+
this.hideLoading();
|
| 119 |
+
this.showAlert('تم تسجيل الدخول بنجاح', 'success');
|
| 120 |
+
this.switchScreen('main');
|
| 121 |
+
await this.loadUserWallets();
|
| 122 |
+
|
| 123 |
+
} catch (error) {
|
| 124 |
+
this.hideLoading();
|
| 125 |
+
this.showAlert(error.message, 'error');
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
// تسجيل الدخ��ل بالبصمة
|
| 130 |
+
async handleBiometricLogin() {
|
| 131 |
+
this.showLoading('جاري التحقق من البصمة...');
|
| 132 |
+
|
| 133 |
+
try {
|
| 134 |
+
const user = await this.authManager.loginWithBiometric();
|
| 135 |
+
this.currentUser = user;
|
| 136 |
+
|
| 137 |
+
this.hideLoading();
|
| 138 |
+
this.showAlert('تم تسجيل الدخول بالبصمة بنجاح', 'success');
|
| 139 |
+
this.switchScreen('main');
|
| 140 |
+
await this.loadUserWallets();
|
| 141 |
+
|
| 142 |
+
} catch (error) {
|
| 143 |
+
this.hideLoading();
|
| 144 |
+
this.showAlert(error.message, 'error');
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
// تحميل محافظ المستخدم
|
| 149 |
+
async loadUserWallets() {
|
| 150 |
+
this.showLoading('جاري تحميل المحافظ...');
|
| 151 |
+
|
| 152 |
+
try {
|
| 153 |
+
// الحصول على محافظ المستخدم من مدير المحافظ
|
| 154 |
+
this.wallets = this.walletManager.getUserWallets();
|
| 155 |
+
|
| 156 |
+
// إذا لم توجد محافظ، إنشاء محافظ تجريبية
|
| 157 |
+
if (this.wallets.length === 0) {
|
| 158 |
+
await this.createDemoWallets();
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
// تحديث أرصدة المحافظ
|
| 162 |
+
await this.walletManager.updateAllBalances();
|
| 163 |
+
this.wallets = this.walletManager.getUserWallets();
|
| 164 |
+
|
| 165 |
+
this.loadRecentTransactions();
|
| 166 |
+
this.updateUI();
|
| 167 |
+
this.hideLoading();
|
| 168 |
+
|
| 169 |
+
} catch (error) {
|
| 170 |
+
this.hideLoading();
|
| 171 |
+
this.showAlert('فشل في تحميل المحافظ', 'error');
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
// إنشاء محافظ تجريبية
|
| 176 |
+
async createDemoWallets() {
|
| 177 |
+
const demoWallets = [
|
| 178 |
+
{ id: 'jawali', accountNumber: '777123456', pin: '1234' },
|
| 179 |
+
{ id: 'onecash', accountNumber: '777234567', pin: '1234' },
|
| 180 |
+
{ id: 'cash', accountNumber: '777345678', pin: '1234' },
|
| 181 |
+
{ id: 'jaib', accountNumber: '777456789', pin: '1234' },
|
| 182 |
+
{ id: 'mfloos', accountNumber: '777567890', pin: '1234' },
|
| 183 |
+
{ id: 'mobilemoney', accountNumber: '777678901', pin: '1234' }
|
| 184 |
+
];
|
| 185 |
+
|
| 186 |
+
for (const wallet of demoWallets) {
|
| 187 |
+
try {
|
| 188 |
+
await this.walletManager.addWallet(wallet.id, wallet.accountNumber, wallet.pin);
|
| 189 |
+
} catch (error) {
|
| 190 |
+
console.warn(`فشل في إضافة محفظة ${wallet.id}:`, error.message);
|
| 191 |
+
}
|
| 192 |
+
}
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
// تحميل المعاملات الأخيرة
|
| 196 |
+
loadRecentTransactions() {
|
| 197 |
+
this.transactions = [
|
| 198 |
+
{
|
| 199 |
+
id: 1,
|
| 200 |
+
type: 'send',
|
| 201 |
+
title: 'تحويل إلى أحمد محمد',
|
| 202 |
+
wallet: 'جوالي',
|
| 203 |
+
amount: -500,
|
| 204 |
+
time: '10:30 ص',
|
| 205 |
+
date: new Date().toISOString()
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
id: 2,
|
| 209 |
+
type: 'receive',
|
| 210 |
+
title: 'استلام من سارة أحمد',
|
| 211 |
+
wallet: 'ONE Cash',
|
| 212 |
+
amount: 1200,
|
| 213 |
+
time: '09:15 ص',
|
| 214 |
+
date: new Date().toISOString()
|
| 215 |
+
},
|
| 216 |
+
{
|
| 217 |
+
id: 3,
|
| 218 |
+
type: 'bill',
|
| 219 |
+
title: 'فاتورة كهرباء',
|
| 220 |
+
wallet: 'Cash',
|
| 221 |
+
amount: -350,
|
| 222 |
+
time: 'أمس',
|
| 223 |
+
date: new Date(Date.now() - 86400000).toISOString()
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
id: 4,
|
| 227 |
+
type: 'send',
|
| 228 |
+
title: 'شحن رصيد',
|
| 229 |
+
wallet: 'Jaib',
|
| 230 |
+
amount: -100,
|
| 231 |
+
time: 'أمس',
|
| 232 |
+
date: new Date(Date.now() - 86400000).toISOString()
|
| 233 |
+
}
|
| 234 |
+
];
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
// تحديث واجهة المستخدم
|
| 238 |
+
updateUI() {
|
| 239 |
+
this.updateUserInfo();
|
| 240 |
+
this.updateTotalBalance();
|
| 241 |
+
this.renderWallets();
|
| 242 |
+
this.renderTransactions();
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
// تحديث معلومات المستخدم
|
| 246 |
+
updateUserInfo() {
|
| 247 |
+
if (this.currentUser) {
|
| 248 |
+
document.getElementById('user-name').textContent = `مرحباً ${this.currentUser.name}`;
|
| 249 |
+
document.getElementById('user-phone').textContent = this.currentUser.phone;
|
| 250 |
+
}
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
// تحديث الرصيد الإجمالي
|
| 254 |
+
updateTotalBalance() {
|
| 255 |
+
const total = this.wallets.reduce((sum, wallet) => sum + wallet.balance, 0);
|
| 256 |
+
const balanceElement = document.getElementById('total-balance');
|
| 257 |
+
|
| 258 |
+
if (this.isBalanceHidden) {
|
| 259 |
+
balanceElement.textContent = '••••• ر.ي';
|
| 260 |
+
} else {
|
| 261 |
+
balanceElement.textContent = `${total.toLocaleString()} ر.ي`;
|
| 262 |
+
}
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
// عرض المحافظ
|
| 266 |
+
renderWallets() {
|
| 267 |
+
const walletsList = document.getElementById('wallets-list');
|
| 268 |
+
walletsList.innerHTML = '';
|
| 269 |
+
|
| 270 |
+
this.wallets.forEach(wallet => {
|
| 271 |
+
const walletCard = this.createWalletCard(wallet);
|
| 272 |
+
walletsList.appendChild(walletCard);
|
| 273 |
+
});
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
// إنشاء بطاقة محفظة
|
| 277 |
+
createWalletCard(wallet) {
|
| 278 |
+
const card = document.createElement('div');
|
| 279 |
+
card.className = 'wallet-card';
|
| 280 |
+
card.onclick = () => this.openWalletDetails(wallet);
|
| 281 |
+
|
| 282 |
+
const balanceDisplay = this.isBalanceHidden ?
|
| 283 |
+
'•••••' : wallet.balance.toLocaleString();
|
| 284 |
+
|
| 285 |
+
card.innerHTML = `
|
| 286 |
+
<div class="wallet-info">
|
| 287 |
+
<div class="wallet-icon">
|
| 288 |
+
<img src="${wallet.icon}" alt="${wallet.name}" onerror="this.style.display='none'">
|
| 289 |
+
</div>
|
| 290 |
+
<div class="wallet-details">
|
| 291 |
+
<h4>${wallet.name}</h4>
|
| 292 |
+
<p>${wallet.provider}</p>
|
| 293 |
+
</div>
|
| 294 |
+
</div>
|
| 295 |
+
<div class="wallet-balance">
|
| 296 |
+
<div class="amount">${balanceDisplay}</div>
|
| 297 |
+
<div class="currency">ر.ي</div>
|
| 298 |
+
</div>
|
| 299 |
+
`;
|
| 300 |
+
|
| 301 |
+
return card;
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
// عرض المعاملات
|
| 305 |
+
renderTransactions() {
|
| 306 |
+
const transactionsList = document.getElementById('transactions-list');
|
| 307 |
+
transactionsList.innerHTML = '';
|
| 308 |
+
|
| 309 |
+
this.transactions.slice(0, 5).forEach(transaction => {
|
| 310 |
+
const transactionItem = this.createTransactionItem(transaction);
|
| 311 |
+
transactionsList.appendChild(transactionItem);
|
| 312 |
+
});
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
// إنشاء عنصر معاملة
|
| 316 |
+
createTransactionItem(transaction) {
|
| 317 |
+
const item = document.createElement('div');
|
| 318 |
+
item.className = 'transaction-item';
|
| 319 |
+
|
| 320 |
+
const iconClass = transaction.type === 'send' ? 'fas fa-arrow-up' :
|
| 321 |
+
transaction.type === 'receive' ? 'fas fa-arrow-down' :
|
| 322 |
+
'fas fa-file-invoice-dollar';
|
| 323 |
+
|
| 324 |
+
const amountClass = transaction.amount > 0 ? 'positive' : 'negative';
|
| 325 |
+
const amountSign = transaction.amount > 0 ? '+' : '';
|
| 326 |
+
|
| 327 |
+
item.innerHTML = `
|
| 328 |
+
<div class="transaction-info">
|
| 329 |
+
<div class="transaction-icon ${transaction.type}">
|
| 330 |
+
<i class="${iconClass}"></i>
|
| 331 |
+
</div>
|
| 332 |
+
<div class="transaction-details">
|
| 333 |
+
<h5>${transaction.title}</h5>
|
| 334 |
+
<p>${transaction.wallet}</p>
|
| 335 |
+
</div>
|
| 336 |
+
</div>
|
| 337 |
+
<div class="transaction-amount ${amountClass}">
|
| 338 |
+
<div class="amount">${amountSign}${Math.abs(transaction.amount).toLocaleString()} ر.ي</div>
|
| 339 |
+
<div class="time">${transaction.time}</div>
|
| 340 |
+
</div>
|
| 341 |
+
`;
|
| 342 |
+
|
| 343 |
+
return item;
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
// محاكاة استدعاء API
|
| 347 |
+
simulateAPICall(delay = 1000) {
|
| 348 |
+
return new Promise((resolve) => {
|
| 349 |
+
setTimeout(resolve, delay);
|
| 350 |
+
});
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
// عرض رسالة تنبيه
|
| 354 |
+
showAlert(message, type = 'info') {
|
| 355 |
+
this.notificationManager.showToast(message, type);
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
// عرض شاشة التحميل
|
| 359 |
+
showLoading(message = 'جاري التحميل...') {
|
| 360 |
+
this.loadingModal = this.notificationManager.showLoading(message);
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
// إخفاء شاشة التحميل
|
| 364 |
+
hideLoading() {
|
| 365 |
+
this.notificationManager.hideLoading();
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
// تبديل الشاشات
|
| 369 |
+
switchScreen(screenName) {
|
| 370 |
+
document.querySelectorAll('.screen').forEach(screen => {
|
| 371 |
+
screen.classList.remove('active');
|
| 372 |
+
});
|
| 373 |
+
|
| 374 |
+
document.getElementById(`${screenName}-screen`).classList.add('active');
|
| 375 |
+
|
| 376 |
+
// تحديث شريط التنقل
|
| 377 |
+
document.querySelectorAll('.nav-item').forEach(item => {
|
| 378 |
+
item.classList.remove('active');
|
| 379 |
+
});
|
| 380 |
+
|
| 381 |
+
const activeNavItem = document.querySelector(`[data-screen="${screenName}"]`);
|
| 382 |
+
if (activeNavItem) {
|
| 383 |
+
activeNavItem.classList.add('active');
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
this.currentScreen = screenName;
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
// تنسيق رقم الهاتف
|
| 390 |
+
formatPhoneNumber(e) {
|
| 391 |
+
let value = e.target.value.replace(/\D/g, '');
|
| 392 |
+
if (value.length > 9) {
|
| 393 |
+
value = value.slice(0, 9);
|
| 394 |
+
}
|
| 395 |
+
e.target.value = value;
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
// تحديث الأرصدة
|
| 399 |
+
async refreshBalances() {
|
| 400 |
+
this.showLoading('جاري تحديث الأرصدة...');
|
| 401 |
+
await this.simulateAPICall(1500);
|
| 402 |
+
this.updateTotalBalance();
|
| 403 |
+
this.renderWallets();
|
| 404 |
+
this.hideLoading();
|
| 405 |
+
this.showAlert('تم تحديث الأرصدة بنجاح', 'success');
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
// إخفاء/إظهار الرصيد
|
| 409 |
+
toggleBalanceVisibility() {
|
| 410 |
+
this.isBalanceHidden = !this.isBalanceHidden;
|
| 411 |
+
const hideBtn = document.getElementById('hide-balance');
|
| 412 |
+
const icon = hideBtn.querySelector('i');
|
| 413 |
+
|
| 414 |
+
if (this.isBalanceHidden) {
|
| 415 |
+
icon.className = 'fas fa-eye';
|
| 416 |
+
hideBtn.innerHTML = '<i class="fas fa-eye"></i> إظهار';
|
| 417 |
+
} else {
|
| 418 |
+
icon.className = 'fas fa-eye-slash';
|
| 419 |
+
hideBtn.innerHTML = '<i class="fas fa-eye-slash"></i> إخفاء';
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
this.updateTotalBalance();
|
| 423 |
+
this.renderWallets();
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
// تحميل بيانات المستخدم
|
| 427 |
+
loadUserData() {
|
| 428 |
+
// تحميل الإعدادات المحفوظة
|
| 429 |
+
const savedSettings = localStorage.getItem('unifiedWallet_settings');
|
| 430 |
+
if (savedSettings) {
|
| 431 |
+
const settings = JSON.parse(savedSettings);
|
| 432 |
+
this.isBalanceHidden = settings.hideBalance || false;
|
| 433 |
+
}
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
// حفظ بيانات المستخدم
|
| 437 |
+
saveUserData() {
|
| 438 |
+
const settings = {
|
| 439 |
+
hideBalance: this.isBalanceHidden
|
| 440 |
+
};
|
| 441 |
+
localStorage.setItem('unifiedWallet_settings', JSON.stringify(settings));
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
// إعداد شاشة التحويل
|
| 445 |
+
setupTransferScreen() {
|
| 446 |
+
this.currentTransferStep = 1;
|
| 447 |
+
this.selectedSourceWallet = null;
|
| 448 |
+
this.transferData = {};
|
| 449 |
+
|
| 450 |
+
// أزرار التنقل
|
| 451 |
+
document.getElementById('transfer-next-btn').addEventListener('click', () => this.nextTransferStep());
|
| 452 |
+
document.getElementById('transfer-prev-btn').addEventListener('click', () => this.prevTransferStep());
|
| 453 |
+
document.getElementById('transfer-confirm-btn').addEventListener('click', () => this.confirmTransfer());
|
| 454 |
+
|
| 455 |
+
// اقتراحات المبلغ
|
| 456 |
+
document.querySelectorAll('.amount-btn').forEach(btn => {
|
| 457 |
+
btn.addEventListener('click', () => {
|
| 458 |
+
document.getElementById('transfer-amount').value = btn.dataset.amount;
|
| 459 |
+
});
|
| 460 |
+
});
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
// عرض شاشة التحويل
|
| 464 |
+
showTransferScreen() {
|
| 465 |
+
this.switchScreen('transfer');
|
| 466 |
+
this.resetTransferForm();
|
| 467 |
+
this.renderSourceWallets();
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
// إعادة تعيين نموذج التحويل
|
| 471 |
+
resetTransferForm() {
|
| 472 |
+
this.currentTransferStep = 1;
|
| 473 |
+
this.selectedSourceWallet = null;
|
| 474 |
+
this.transferData = {};
|
| 475 |
+
|
| 476 |
+
// إعادة تعيين الخطوات
|
| 477 |
+
this.updateTransferSteps();
|
| 478 |
+
|
| 479 |
+
// مسح النموذج
|
| 480 |
+
document.getElementById('destination-wallet').value = '';
|
| 481 |
+
document.getElementById('recipient-number').value = '';
|
| 482 |
+
document.getElementById('transfer-amount').value = '';
|
| 483 |
+
document.getElementById('transfer-note').value = '';
|
| 484 |
+
document.getElementById('transfer-pin').value = '';
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
// تحديث مؤشر الخطوات
|
| 488 |
+
updateTransferSteps() {
|
| 489 |
+
for (let i = 1; i <= 3; i++) {
|
| 490 |
+
const step = document.getElementById(`transfer-step-${i}`);
|
| 491 |
+
const content = document.getElementById(`transfer-step-content-${i}`);
|
| 492 |
+
|
| 493 |
+
if (i < this.currentTransferStep) {
|
| 494 |
+
step.classList.add('completed');
|
| 495 |
+
step.classList.remove('active');
|
| 496 |
+
} else if (i === this.currentTransferStep) {
|
| 497 |
+
step.classList.add('active');
|
| 498 |
+
step.classList.remove('completed');
|
| 499 |
+
} else {
|
| 500 |
+
step.classList.remove('active', 'completed');
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
content.classList.toggle('active', i === this.currentTransferStep);
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
// تحديث أزرار التنقل
|
| 507 |
+
const prevBtn = document.getElementById('transfer-prev-btn');
|
| 508 |
+
const nextBtn = document.getElementById('transfer-next-btn');
|
| 509 |
+
const confirmBtn = document.getElementById('transfer-confirm-btn');
|
| 510 |
+
|
| 511 |
+
prevBtn.style.display = this.currentTransferStep > 1 ? 'block' : 'none';
|
| 512 |
+
nextBtn.style.display = this.currentTransferStep < 3 ? 'block' : 'none';
|
| 513 |
+
confirmBtn.style.display = this.currentTransferStep === 3 ? 'block' : 'none';
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
// عرض المحافظ المصدر
|
| 517 |
+
renderSourceWallets() {
|
| 518 |
+
const container = document.getElementById('source-wallet-selection');
|
| 519 |
+
container.innerHTML = '';
|
| 520 |
+
|
| 521 |
+
this.wallets.forEach(wallet => {
|
| 522 |
+
const option = this.createWalletOption(wallet);
|
| 523 |
+
container.appendChild(option);
|
| 524 |
+
});
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
// إنشاء خيار محفظة
|
| 528 |
+
createWalletOption(wallet) {
|
| 529 |
+
const option = document.createElement('div');
|
| 530 |
+
option.className = 'wallet-option';
|
| 531 |
+
option.onclick = () => this.selectSourceWallet(wallet);
|
| 532 |
+
|
| 533 |
+
const balanceDisplay = this.isBalanceHidden ?
|
| 534 |
+
'•••••' : wallet.balance.toLocaleString();
|
| 535 |
+
|
| 536 |
+
option.innerHTML = `
|
| 537 |
+
<div class="wallet-option-info">
|
| 538 |
+
<div class="wallet-option-icon">
|
| 539 |
+
<img src="${wallet.icon}" alt="${wallet.name}" onerror="this.style.display='none'">
|
| 540 |
+
</div>
|
| 541 |
+
<div class="wallet-option-details">
|
| 542 |
+
<h4>${wallet.name}</h4>
|
| 543 |
+
<p>${wallet.provider}</p>
|
| 544 |
+
</div>
|
| 545 |
+
</div>
|
| 546 |
+
<div class="wallet-option-balance">
|
| 547 |
+
<div class="amount">${balanceDisplay}</div>
|
| 548 |
+
<div class="currency">ر.ي</div>
|
| 549 |
+
</div>
|
| 550 |
+
`;
|
| 551 |
+
|
| 552 |
+
return option;
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
// اختيار المحفظة المصدر
|
| 556 |
+
selectSourceWallet(wallet) {
|
| 557 |
+
this.selectedSourceWallet = wallet;
|
| 558 |
+
|
| 559 |
+
// تحديث التحديد البصري
|
| 560 |
+
document.querySelectorAll('.wallet-option').forEach(option => {
|
| 561 |
+
option.classList.remove('selected');
|
| 562 |
+
});
|
| 563 |
+
event.currentTarget.classList.add('selected');
|
| 564 |
+
|
| 565 |
+
this.transferData.sourceWallet = wallet;
|
| 566 |
+
}
|
| 567 |
+
|
| 568 |
+
// الخطوة التالية
|
| 569 |
+
nextTransferStep() {
|
| 570 |
+
if (this.validateCurrentStep()) {
|
| 571 |
+
this.currentTransferStep++;
|
| 572 |
+
this.updateTransferSteps();
|
| 573 |
+
|
| 574 |
+
if (this.currentTransferStep === 3) {
|
| 575 |
+
this.updateTransferSummary();
|
| 576 |
+
}
|
| 577 |
+
}
|
| 578 |
+
}
|
| 579 |
+
|
| 580 |
+
// الخطوة السابقة
|
| 581 |
+
prevTransferStep() {
|
| 582 |
+
this.currentTransferStep--;
|
| 583 |
+
this.updateTransferSteps();
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
// التحقق من صحة الخطوة الحالية
|
| 587 |
+
validateCurrentStep() {
|
| 588 |
+
switch (this.currentTransferStep) {
|
| 589 |
+
case 1:
|
| 590 |
+
if (!this.selectedSourceWallet) {
|
| 591 |
+
this.showAlert('يرجى اختيار المحفظة المرسلة', 'error');
|
| 592 |
+
return false;
|
| 593 |
+
}
|
| 594 |
+
return true;
|
| 595 |
+
|
| 596 |
+
case 2:
|
| 597 |
+
const destinationWallet = document.getElementById('destination-wallet').value;
|
| 598 |
+
const recipientNumber = document.getElementById('recipient-number').value;
|
| 599 |
+
const amount = parseFloat(document.getElementById('transfer-amount').value);
|
| 600 |
+
|
| 601 |
+
if (!destinationWallet) {
|
| 602 |
+
this.showAlert('يرجى اختيار المحفظة المستقبلة', 'error');
|
| 603 |
+
return false;
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
if (!recipientNumber || recipientNumber.length !== 9) {
|
| 607 |
+
this.showAlert('يرجى إدخال رقم المستقبل صحيح (9 أرقام)', 'error');
|
| 608 |
+
return false;
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
if (!amount || amount <= 0) {
|
| 612 |
+
this.showAlert('يرجى إدخال مبلغ صحيح', 'error');
|
| 613 |
+
return false;
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
if (amount > this.selectedSourceWallet.balance) {
|
| 617 |
+
this.showAlert('المبلغ أكبر من الرصيد المتاح', 'error');
|
| 618 |
+
return false;
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
// حفظ بيانات التحويل
|
| 622 |
+
this.transferData.destinationWallet = destinationWallet;
|
| 623 |
+
this.transferData.recipientNumber = recipientNumber;
|
| 624 |
+
this.transferData.amount = amount;
|
| 625 |
+
this.transferData.note = document.getElementById('transfer-note').value;
|
| 626 |
+
|
| 627 |
+
return true;
|
| 628 |
+
|
| 629 |
+
default:
|
| 630 |
+
return true;
|
| 631 |
+
}
|
| 632 |
+
}
|
| 633 |
+
|
| 634 |
+
// تحديث ملخص التحويل
|
| 635 |
+
updateTransferSummary() {
|
| 636 |
+
const sourceWalletInfo = this.walletManager.getWalletInfo(this.transferData.sourceWallet.id);
|
| 637 |
+
const destinationWalletInfo = this.walletManager.getWalletInfo(this.transferData.destinationWallet);
|
| 638 |
+
|
| 639 |
+
const fee = sourceWalletInfo.fees.transfer;
|
| 640 |
+
const total = this.transferData.amount + fee;
|
| 641 |
+
|
| 642 |
+
document.getElementById('summary-from-wallet').textContent = this.transferData.sourceWallet.name;
|
| 643 |
+
document.getElementById('summary-to-wallet').textContent = destinationWalletInfo.name;
|
| 644 |
+
document.getElementById('summary-recipient').textContent = `+967${this.transferData.recipientNumber}`;
|
| 645 |
+
document.getElementById('summary-amount').textContent = `${this.transferData.amount.toLocaleString()} ر.ي`;
|
| 646 |
+
document.getElementById('summary-fee').textContent = `${fee.toLocaleString()} ر.ي`;
|
| 647 |
+
document.getElementById('summary-total').textContent = `${total.toLocaleString()} ر.ي`;
|
| 648 |
+
}
|
| 649 |
+
|
| 650 |
+
// تأكيد التحويل
|
| 651 |
+
async confirmTransfer() {
|
| 652 |
+
const pin = document.getElementById('transfer-pin').value;
|
| 653 |
+
|
| 654 |
+
if (!pin) {
|
| 655 |
+
this.showAlert('يرجى إدخال رمز PIN', 'error');
|
| 656 |
+
return;
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
this.showLoading('جاري تنفيذ التحويل...');
|
| 660 |
+
|
| 661 |
+
try {
|
| 662 |
+
const result = await this.walletManager.executeTransfer(
|
| 663 |
+
this.transferData.sourceWallet.id,
|
| 664 |
+
this.transferData.destinationWallet,
|
| 665 |
+
this.transferData.amount,
|
| 666 |
+
this.transferData.recipientNumber,
|
| 667 |
+
pin
|
| 668 |
+
);
|
| 669 |
+
|
| 670 |
+
this.hideLoading();
|
| 671 |
+
this.showTransferSuccess(result);
|
| 672 |
+
|
| 673 |
+
// تحديث البيانات
|
| 674 |
+
await this.loadUserWallets();
|
| 675 |
+
|
| 676 |
+
} catch (error) {
|
| 677 |
+
this.hideLoading();
|
| 678 |
+
this.showAlert(error.message, 'error');
|
| 679 |
+
}
|
| 680 |
+
}
|
| 681 |
+
|
| 682 |
+
// عرض نجاح التحويل
|
| 683 |
+
showTransferSuccess(result) {
|
| 684 |
+
const message = `
|
| 685 |
+
تم التحويل بنجاح!
|
| 686 |
+
|
| 687 |
+
رقم المعاملة: ${result.transactionId}
|
| 688 |
+
المبلغ المحول: ${result.amount.toLocaleString()} ر.ي
|
| 689 |
+
الرسوم: ${result.fee.toLocaleString()} ر.ي
|
| 690 |
+
الرصيد المتبقي: ${result.newBalance.toLocaleString()} ر.ي
|
| 691 |
+
`;
|
| 692 |
+
|
| 693 |
+
this.showAlert(message, 'success');
|
| 694 |
+
this.switchScreen('main');
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
// الوظائف التي سيتم تطويرها لاحقاً
|
| 698 |
+
showBillsScreen() { console.log('عرض شاشة الفواتير'); }
|
| 699 |
+
showRechargeScreen() { console.log('عرض شاشة الشحن'); }
|
| 700 |
+
startQRScan() { console.log('بدء مسح QR'); }
|
| 701 |
+
showSettings() { console.log('عرض الإعدادات'); }
|
| 702 |
+
showNotifications() {
|
| 703 |
+
this.notificationManager.showNotificationsList();
|
| 704 |
+
}
|
| 705 |
+
showWalletManagement() { console.log('إدارة المحافظ'); }
|
| 706 |
+
showAllTransactions() { console.log('عرض جميع المعاملات'); }
|
| 707 |
+
openWalletDetails(wallet) { console.log('تفاصيل المحفظة:', wallet.name); }
|
| 708 |
+
showRegisterModal() {
|
| 709 |
+
this.notificationManager.showModal(
|
| 710 |
+
'إنشاء حساب جديد',
|
| 711 |
+
`
|
| 712 |
+
<div class="form-group">
|
| 713 |
+
<label>رقم الهاتف</label>
|
| 714 |
+
<div class="input-group">
|
| 715 |
+
<span class="input-prefix">+967</span>
|
| 716 |
+
<input type="tel" id="register-phone" placeholder="7xxxxxxxx" maxlength="9">
|
| 717 |
+
</div>
|
| 718 |
+
</div>
|
| 719 |
+
<div class="form-group">
|
| 720 |
+
<label>رمز PIN</label>
|
| 721 |
+
<input type="password" id="register-pin" placeholder="أدخل رمز PIN" maxlength="6">
|
| 722 |
+
</div>
|
| 723 |
+
<div class="form-group">
|
| 724 |
+
<label>تأكيد رمز PIN</label>
|
| 725 |
+
<input type="password" id="register-pin-confirm" placeholder="أعد إدخال رمز PIN" maxlength="6">
|
| 726 |
+
</div>
|
| 727 |
+
`,
|
| 728 |
+
[
|
| 729 |
+
{
|
| 730 |
+
text: 'إنشاء الحساب',
|
| 731 |
+
class: 'btn-primary',
|
| 732 |
+
action: () => this.handleRegister()
|
| 733 |
+
},
|
| 734 |
+
{
|
| 735 |
+
text: 'إلغاء',
|
| 736 |
+
class: 'btn-secondary'
|
| 737 |
+
}
|
| 738 |
+
]
|
| 739 |
+
);
|
| 740 |
+
}
|
| 741 |
+
|
| 742 |
+
// التعامل مع التسجيل
|
| 743 |
+
handleRegister() {
|
| 744 |
+
const phone = document.getElementById('register-phone').value;
|
| 745 |
+
const pin = document.getElementById('register-pin').value;
|
| 746 |
+
const pinConfirm = document.getElementById('register-pin-confirm').value;
|
| 747 |
+
|
| 748 |
+
if (!phone || !pin || !pinConfirm) {
|
| 749 |
+
this.showAlert('يرجى ملء جميع الحقول', 'error');
|
| 750 |
+
return;
|
| 751 |
+
}
|
| 752 |
+
|
| 753 |
+
if (pin !== pinConfirm) {
|
| 754 |
+
this.showAlert('رمز PIN غير متطابق', 'error');
|
| 755 |
+
return;
|
| 756 |
+
}
|
| 757 |
+
|
| 758 |
+
if (phone.length !== 9) {
|
| 759 |
+
this.showAlert('رقم الهاتف يجب أن يكون 9 أرقام', 'error');
|
| 760 |
+
return;
|
| 761 |
+
}
|
| 762 |
+
|
| 763 |
+
if (pin.length < 4) {
|
| 764 |
+
this.showAlert('رمز PIN يجب أن يكون 4 أرقام على الأقل', 'error');
|
| 765 |
+
return;
|
| 766 |
+
}
|
| 767 |
+
|
| 768 |
+
// محاكاة إنشاء الحساب
|
| 769 |
+
this.showAlert('تم إنشاء الحساب بنجاح! يمكنك الآن تسجيل الدخول', 'success');
|
| 770 |
+
|
| 771 |
+
// ملء بيانات تسجيل الدخول
|
| 772 |
+
document.getElementById('phone-number').value = phone;
|
| 773 |
+
document.getElementById('pin-code').value = pin;
|
| 774 |
+
}
|
| 775 |
+
}
|
| 776 |
+
|
| 777 |
+
// تهيئة التطبيق عند تحميل الصفحة
|
| 778 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 779 |
+
window.app = new UnifiedWalletApp();
|
| 780 |
+
});
|
app/(public)/layout.tsx
CHANGED
|
@@ -1,12 +1,13 @@
|
|
| 1 |
-
import
|
| 2 |
|
| 3 |
-
export default function PublicLayout({
|
| 4 |
children,
|
| 5 |
}: Readonly<{
|
| 6 |
children: React.ReactNode;
|
| 7 |
}>) {
|
| 8 |
return (
|
| 9 |
-
<div className="min-h-screen
|
|
|
|
| 10 |
<Navigation />
|
| 11 |
{children}
|
| 12 |
</div>
|
|
|
|
| 1 |
+
import Navigation from "@/components/public/navigation";
|
| 2 |
|
| 3 |
+
export default async function PublicLayout({
|
| 4 |
children,
|
| 5 |
}: Readonly<{
|
| 6 |
children: React.ReactNode;
|
| 7 |
}>) {
|
| 8 |
return (
|
| 9 |
+
<div className="min-h-screen bg-black z-1 relative">
|
| 10 |
+
<div className="background__noisy" />
|
| 11 |
<Navigation />
|
| 12 |
{children}
|
| 13 |
</div>
|
app/(public)/page.tsx
CHANGED
|
@@ -1,25 +1,44 @@
|
|
| 1 |
-
import {
|
| 2 |
-
import {
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
import { Bento } from "@/components/public/bento";
|
| 6 |
-
|
| 7 |
-
export const dynamic = "force-dynamic";
|
| 8 |
-
|
| 9 |
-
export default async function Homepage() {
|
| 10 |
return (
|
| 11 |
<>
|
| 12 |
-
<
|
| 13 |
-
<
|
| 14 |
-
|
| 15 |
-
<AnimatedDotsBackground />
|
| 16 |
</div>
|
| 17 |
-
<
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</div>
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
</>
|
| 24 |
);
|
| 25 |
}
|
|
|
|
| 1 |
+
import { AskAi } from "@/components/space/ask-ai";
|
| 2 |
+
import { redirect } from "next/navigation";
|
| 3 |
+
export default function Home() {
|
| 4 |
+
redirect("/projects/new");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
return (
|
| 6 |
<>
|
| 7 |
+
<header className="container mx-auto pt-20 px-6 relative flex flex-col items-center justify-center text-center">
|
| 8 |
+
<div className="rounded-full border border-neutral-100/10 bg-neutral-100/5 text-xs text-neutral-300 px-3 py-1 max-w-max mx-auto mb-2">
|
| 9 |
+
✨ DeepSite Public Beta
|
|
|
|
| 10 |
</div>
|
| 11 |
+
<h1 className="text-8xl font-semibold text-white font-mono max-w-4xl">
|
| 12 |
+
Code your website with AI in seconds
|
| 13 |
+
</h1>
|
| 14 |
+
<p className="text-2xl text-neutral-300/80 mt-4 text-center max-w-2xl">
|
| 15 |
+
Vibe Coding has never been so easy.
|
| 16 |
+
</p>
|
| 17 |
+
<div className="mt-14 max-w-2xl w-full mx-auto">
|
| 18 |
+
<AskAi />
|
| 19 |
</div>
|
| 20 |
+
<div className="absolute inset-0 pointer-events-none -z-[1]">
|
| 21 |
+
<div className="w-full h-full bg-gradient-to-r from-purple-500 to-pink-500 opacity-10 blur-3xl rounded-full" />
|
| 22 |
+
<div className="w-2/3 h-3/4 bg-gradient-to-r from-blue-500 to-teal-500 opacity-24 blur-3xl absolute -top-20 right-10 transform rotate-12" />
|
| 23 |
+
<div className="w-1/2 h-1/2 bg-gradient-to-r from-amber-500 to-rose-500 opacity-20 blur-3xl absolute bottom-0 left-10 rounded-3xl" />
|
| 24 |
+
<div className="w-48 h-48 bg-gradient-to-r from-cyan-500 to-indigo-500 opacity-20 blur-3xl absolute top-1/3 right-1/3 rounded-lg transform -rotate-15" />
|
| 25 |
+
</div>
|
| 26 |
+
</header>
|
| 27 |
+
<div id="community" className="h-screen flex items-center justify-center">
|
| 28 |
+
<h1 className="text-7xl font-extrabold text-white font-mono">
|
| 29 |
+
Community Driven
|
| 30 |
+
</h1>
|
| 31 |
+
</div>
|
| 32 |
+
<div id="deploy" className="h-screen flex items-center justify-center">
|
| 33 |
+
<h1 className="text-7xl font-extrabold text-white font-mono">
|
| 34 |
+
Deploy your website in seconds
|
| 35 |
+
</h1>
|
| 36 |
+
</div>
|
| 37 |
+
<div id="features" className="h-screen flex items-center justify-center">
|
| 38 |
+
<h1 className="text-7xl font-extrabold text-white font-mono">
|
| 39 |
+
Features that make you smile
|
| 40 |
+
</h1>
|
| 41 |
+
</div>
|
| 42 |
</>
|
| 43 |
);
|
| 44 |
}
|
app/(public)/projects/page.tsx
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { redirect } from "next/navigation";
|
| 2 |
+
|
| 3 |
+
import { MyProjects } from "@/components/my-projects";
|
| 4 |
+
import { getProjects } from "@/app/actions/projects";
|
| 5 |
+
|
| 6 |
+
export default async function ProjectsPage() {
|
| 7 |
+
const { ok, projects } = await getProjects();
|
| 8 |
+
if (!ok) {
|
| 9 |
+
redirect("/");
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
return <MyProjects projects={projects} />;
|
| 13 |
+
}
|
app/(public)/signin/page.tsx
DELETED
|
@@ -1,21 +0,0 @@
|
|
| 1 |
-
import { LoginButtons } from "@/components/login/login-buttons";
|
| 2 |
-
|
| 3 |
-
export default async function SignInPage({
|
| 4 |
-
searchParams,
|
| 5 |
-
}: {
|
| 6 |
-
searchParams: Promise<{ callbackUrl: string }>;
|
| 7 |
-
}) {
|
| 8 |
-
const { callbackUrl } = await searchParams;
|
| 9 |
-
console.log(callbackUrl);
|
| 10 |
-
return (
|
| 11 |
-
<section className="min-h-screen font-sans">
|
| 12 |
-
<div className="px-6 py-16 max-w-5xl mx-auto text-center">
|
| 13 |
-
<h1 className="text-5xl font-bold mb-5">You shall not pass 🧙</h1>
|
| 14 |
-
<p className="text-lg text-muted-foreground mb-8">
|
| 15 |
-
You can't access this resource without being signed in.
|
| 16 |
-
</p>
|
| 17 |
-
<LoginButtons callbackUrl={callbackUrl ?? "/"} />
|
| 18 |
-
</div>
|
| 19 |
-
</section>
|
| 20 |
-
);
|
| 21 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/[owner]/[repoId]/page.tsx
DELETED
|
@@ -1,35 +0,0 @@
|
|
| 1 |
-
import { getProject } from "@/actions/projects";
|
| 2 |
-
import { AppEditor } from "@/components/editor";
|
| 3 |
-
import { auth } from "@/lib/auth";
|
| 4 |
-
import { notFound, redirect } from "next/navigation";
|
| 5 |
-
|
| 6 |
-
export default async function ProjectPage({
|
| 7 |
-
params,
|
| 8 |
-
searchParams,
|
| 9 |
-
}: {
|
| 10 |
-
params: Promise<{ owner: string; repoId: string }>;
|
| 11 |
-
searchParams: Promise<{ commit?: string }>;
|
| 12 |
-
}) {
|
| 13 |
-
const session = await auth();
|
| 14 |
-
|
| 15 |
-
const { owner, repoId } = await params;
|
| 16 |
-
const { commit } = await searchParams;
|
| 17 |
-
if (!session) {
|
| 18 |
-
redirect(
|
| 19 |
-
`/api/auth/signin?callbackUrl=/${owner}/${repoId}${
|
| 20 |
-
commit ? `?commit=${commit}` : ""
|
| 21 |
-
}`
|
| 22 |
-
);
|
| 23 |
-
}
|
| 24 |
-
const datas = await getProject(`${owner}/${repoId}`, commit);
|
| 25 |
-
if (!datas?.project) {
|
| 26 |
-
return notFound();
|
| 27 |
-
}
|
| 28 |
-
return (
|
| 29 |
-
<AppEditor
|
| 30 |
-
project={datas.project}
|
| 31 |
-
files={datas.files ?? []}
|
| 32 |
-
isHistoryView={!!commit}
|
| 33 |
-
/>
|
| 34 |
-
);
|
| 35 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/actions/auth.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use server";
|
| 2 |
+
|
| 3 |
+
import { headers } from "next/headers";
|
| 4 |
+
|
| 5 |
+
export async function getAuth() {
|
| 6 |
+
const authList = await headers();
|
| 7 |
+
const host = authList.get("host") ?? "localhost:3000";
|
| 8 |
+
const url = host.includes("/spaces/enzostvs")
|
| 9 |
+
? "enzostvs-deepsite.hf.space"
|
| 10 |
+
: host;
|
| 11 |
+
const redirect_uri =
|
| 12 |
+
`${host.includes("localhost") ? "http://" : "https://"}` +
|
| 13 |
+
url +
|
| 14 |
+
"/auth/callback";
|
| 15 |
+
|
| 16 |
+
const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
|
| 17 |
+
return loginRedirectUrl;
|
| 18 |
+
}
|
app/actions/projects.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use server";
|
| 2 |
+
|
| 3 |
+
import { isAuthenticated } from "@/lib/auth";
|
| 4 |
+
import { NextResponse } from "next/server";
|
| 5 |
+
import dbConnect from "@/lib/mongodb";
|
| 6 |
+
import Project from "@/models/Project";
|
| 7 |
+
import { Project as ProjectType } from "@/types";
|
| 8 |
+
|
| 9 |
+
export async function getProjects(): Promise<{
|
| 10 |
+
ok: boolean;
|
| 11 |
+
projects: ProjectType[];
|
| 12 |
+
}> {
|
| 13 |
+
const user = await isAuthenticated();
|
| 14 |
+
|
| 15 |
+
if (user instanceof NextResponse || !user) {
|
| 16 |
+
return {
|
| 17 |
+
ok: false,
|
| 18 |
+
projects: [],
|
| 19 |
+
};
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
await dbConnect();
|
| 23 |
+
const projects = await Project.find({
|
| 24 |
+
user_id: user?.id,
|
| 25 |
+
})
|
| 26 |
+
.sort({ _createdAt: -1 })
|
| 27 |
+
.limit(100)
|
| 28 |
+
.lean();
|
| 29 |
+
if (!projects) {
|
| 30 |
+
return {
|
| 31 |
+
ok: false,
|
| 32 |
+
projects: [],
|
| 33 |
+
};
|
| 34 |
+
}
|
| 35 |
+
return {
|
| 36 |
+
ok: true,
|
| 37 |
+
projects: JSON.parse(JSON.stringify(projects)) as ProjectType[],
|
| 38 |
+
};
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
export async function getProject(
|
| 42 |
+
namespace: string,
|
| 43 |
+
repoId: string
|
| 44 |
+
): Promise<ProjectType | null> {
|
| 45 |
+
const user = await isAuthenticated();
|
| 46 |
+
|
| 47 |
+
if (user instanceof NextResponse || !user) {
|
| 48 |
+
return null;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
await dbConnect();
|
| 52 |
+
const project = await Project.findOne({
|
| 53 |
+
user_id: user.id,
|
| 54 |
+
namespace,
|
| 55 |
+
repoId,
|
| 56 |
+
}).lean();
|
| 57 |
+
|
| 58 |
+
if (!project) {
|
| 59 |
+
return null;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
return JSON.parse(JSON.stringify(project)) as ProjectType;
|
| 63 |
+
}
|
app/api/ask-ai/route.ts
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
+
import type { NextRequest } from "next/server";
|
| 3 |
+
import { NextResponse } from "next/server";
|
| 4 |
+
import { headers } from "next/headers";
|
| 5 |
+
import { InferenceClient } from "@huggingface/inference";
|
| 6 |
+
|
| 7 |
+
import { MODELS, PROVIDERS } from "@/lib/providers";
|
| 8 |
+
import {
|
| 9 |
+
DIVIDER,
|
| 10 |
+
FOLLOW_UP_SYSTEM_PROMPT,
|
| 11 |
+
INITIAL_SYSTEM_PROMPT,
|
| 12 |
+
MAX_REQUESTS_PER_IP,
|
| 13 |
+
REPLACE_END,
|
| 14 |
+
SEARCH_START,
|
| 15 |
+
} from "@/lib/prompts";
|
| 16 |
+
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
| 17 |
+
|
| 18 |
+
const ipAddresses = new Map();
|
| 19 |
+
|
| 20 |
+
export async function POST(request: NextRequest) {
|
| 21 |
+
const authHeaders = await headers();
|
| 22 |
+
const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
|
| 23 |
+
|
| 24 |
+
const body = await request.json();
|
| 25 |
+
const { prompt, provider, model, redesignMarkdown, html } = body;
|
| 26 |
+
|
| 27 |
+
if (!model || (!prompt && !redesignMarkdown)) {
|
| 28 |
+
return NextResponse.json(
|
| 29 |
+
{ ok: false, error: "Missing required fields" },
|
| 30 |
+
{ status: 400 }
|
| 31 |
+
);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
const selectedModel = MODELS.find(
|
| 35 |
+
(m) => m.value === model || m.label === model
|
| 36 |
+
);
|
| 37 |
+
if (!selectedModel) {
|
| 38 |
+
return NextResponse.json(
|
| 39 |
+
{ ok: false, error: "Invalid model selected" },
|
| 40 |
+
{ status: 400 }
|
| 41 |
+
);
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
if (!selectedModel.providers.includes(provider) && provider !== "auto") {
|
| 45 |
+
return NextResponse.json(
|
| 46 |
+
{
|
| 47 |
+
ok: false,
|
| 48 |
+
error: `The selected model does not support the ${provider} provider.`,
|
| 49 |
+
openSelectProvider: true,
|
| 50 |
+
},
|
| 51 |
+
{ status: 400 }
|
| 52 |
+
);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
let token = userToken;
|
| 56 |
+
let billTo: string | null = null;
|
| 57 |
+
|
| 58 |
+
/**
|
| 59 |
+
* Handle local usage token, this bypass the need for a user token
|
| 60 |
+
* and allows local testing without authentication.
|
| 61 |
+
* This is useful for development and testing purposes.
|
| 62 |
+
*/
|
| 63 |
+
if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
|
| 64 |
+
token = process.env.HF_TOKEN;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
const ip = authHeaders.get("x-forwarded-for")?.includes(",")
|
| 68 |
+
? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
|
| 69 |
+
: authHeaders.get("x-forwarded-for");
|
| 70 |
+
|
| 71 |
+
if (!token) {
|
| 72 |
+
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
| 73 |
+
if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
| 74 |
+
return NextResponse.json(
|
| 75 |
+
{
|
| 76 |
+
ok: false,
|
| 77 |
+
openLogin: true,
|
| 78 |
+
message: "Log In to continue using the service",
|
| 79 |
+
},
|
| 80 |
+
{ status: 429 }
|
| 81 |
+
);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
token = process.env.DEFAULT_HF_TOKEN as string;
|
| 85 |
+
billTo = "huggingface";
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
const DEFAULT_PROVIDER = PROVIDERS.novita;
|
| 89 |
+
const selectedProvider =
|
| 90 |
+
provider === "auto"
|
| 91 |
+
? PROVIDERS[selectedModel.autoProvider as keyof typeof PROVIDERS]
|
| 92 |
+
: PROVIDERS[provider as keyof typeof PROVIDERS] ?? DEFAULT_PROVIDER;
|
| 93 |
+
|
| 94 |
+
try {
|
| 95 |
+
// Create a stream response
|
| 96 |
+
const encoder = new TextEncoder();
|
| 97 |
+
const stream = new TransformStream();
|
| 98 |
+
const writer = stream.writable.getWriter();
|
| 99 |
+
|
| 100 |
+
// Start the response
|
| 101 |
+
const response = new NextResponse(stream.readable, {
|
| 102 |
+
headers: {
|
| 103 |
+
"Content-Type": "text/plain; charset=utf-8",
|
| 104 |
+
"Cache-Control": "no-cache",
|
| 105 |
+
Connection: "keep-alive",
|
| 106 |
+
},
|
| 107 |
+
});
|
| 108 |
+
|
| 109 |
+
(async () => {
|
| 110 |
+
let completeResponse = "";
|
| 111 |
+
try {
|
| 112 |
+
const client = new InferenceClient(token);
|
| 113 |
+
const chatCompletion = client.chatCompletionStream(
|
| 114 |
+
{
|
| 115 |
+
model: selectedModel.value,
|
| 116 |
+
provider: selectedProvider.id as any,
|
| 117 |
+
messages: [
|
| 118 |
+
{
|
| 119 |
+
role: "system",
|
| 120 |
+
content: INITIAL_SYSTEM_PROMPT,
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
role: "user",
|
| 124 |
+
content: redesignMarkdown
|
| 125 |
+
? `Here is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown.`
|
| 126 |
+
: html
|
| 127 |
+
? `Here is my current HTML code:\n\n\`\`\`html\n${html}\n\`\`\`\n\nNow, please create a new design based on this HTML.`
|
| 128 |
+
: prompt,
|
| 129 |
+
},
|
| 130 |
+
],
|
| 131 |
+
max_tokens: selectedProvider.max_tokens,
|
| 132 |
+
},
|
| 133 |
+
billTo ? { billTo } : {}
|
| 134 |
+
);
|
| 135 |
+
|
| 136 |
+
while (true) {
|
| 137 |
+
const { done, value } = await chatCompletion.next();
|
| 138 |
+
if (done) {
|
| 139 |
+
break;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
const chunk = value.choices[0]?.delta?.content;
|
| 143 |
+
if (chunk) {
|
| 144 |
+
let newChunk = chunk;
|
| 145 |
+
if (!selectedModel?.isThinker) {
|
| 146 |
+
if (provider !== "sambanova") {
|
| 147 |
+
await writer.write(encoder.encode(chunk));
|
| 148 |
+
completeResponse += chunk;
|
| 149 |
+
|
| 150 |
+
if (completeResponse.includes("</html>")) {
|
| 151 |
+
break;
|
| 152 |
+
}
|
| 153 |
+
} else {
|
| 154 |
+
if (chunk.includes("</html>")) {
|
| 155 |
+
newChunk = newChunk.replace(/<\/html>[\s\S]*/, "</html>");
|
| 156 |
+
}
|
| 157 |
+
completeResponse += newChunk;
|
| 158 |
+
await writer.write(encoder.encode(newChunk));
|
| 159 |
+
if (newChunk.includes("</html>")) {
|
| 160 |
+
break;
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
} else {
|
| 164 |
+
const lastThinkTagIndex =
|
| 165 |
+
completeResponse.lastIndexOf("</think>");
|
| 166 |
+
completeResponse += newChunk;
|
| 167 |
+
await writer.write(encoder.encode(newChunk));
|
| 168 |
+
if (lastThinkTagIndex !== -1) {
|
| 169 |
+
const afterLastThinkTag = completeResponse.slice(
|
| 170 |
+
lastThinkTagIndex + "</think>".length
|
| 171 |
+
);
|
| 172 |
+
if (afterLastThinkTag.includes("</html>")) {
|
| 173 |
+
break;
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
}
|
| 178 |
+
}
|
| 179 |
+
} catch (error: any) {
|
| 180 |
+
if (error.message?.includes("exceeded your monthly included credits")) {
|
| 181 |
+
await writer.write(
|
| 182 |
+
encoder.encode(
|
| 183 |
+
JSON.stringify({
|
| 184 |
+
ok: false,
|
| 185 |
+
openProModal: true,
|
| 186 |
+
message: error.message,
|
| 187 |
+
})
|
| 188 |
+
)
|
| 189 |
+
);
|
| 190 |
+
} else {
|
| 191 |
+
await writer.write(
|
| 192 |
+
encoder.encode(
|
| 193 |
+
JSON.stringify({
|
| 194 |
+
ok: false,
|
| 195 |
+
message:
|
| 196 |
+
error.message ||
|
| 197 |
+
"An error occurred while processing your request.",
|
| 198 |
+
})
|
| 199 |
+
)
|
| 200 |
+
);
|
| 201 |
+
}
|
| 202 |
+
} finally {
|
| 203 |
+
await writer?.close();
|
| 204 |
+
}
|
| 205 |
+
})();
|
| 206 |
+
|
| 207 |
+
return response;
|
| 208 |
+
} catch (error: any) {
|
| 209 |
+
return NextResponse.json(
|
| 210 |
+
{
|
| 211 |
+
ok: false,
|
| 212 |
+
openSelectProvider: true,
|
| 213 |
+
message:
|
| 214 |
+
error?.message || "An error occurred while processing your request.",
|
| 215 |
+
},
|
| 216 |
+
{ status: 500 }
|
| 217 |
+
);
|
| 218 |
+
}
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
export async function PUT(request: NextRequest) {
|
| 222 |
+
const authHeaders = await headers();
|
| 223 |
+
const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
|
| 224 |
+
|
| 225 |
+
const body = await request.json();
|
| 226 |
+
const { prompt, html, previousPrompt, provider, selectedElementHtml, model } =
|
| 227 |
+
body;
|
| 228 |
+
|
| 229 |
+
if (!prompt || !html) {
|
| 230 |
+
return NextResponse.json(
|
| 231 |
+
{ ok: false, error: "Missing required fields" },
|
| 232 |
+
{ status: 400 }
|
| 233 |
+
);
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
const selectedModel = MODELS.find(
|
| 237 |
+
(m) => m.value === model || m.label === model
|
| 238 |
+
);
|
| 239 |
+
if (!selectedModel) {
|
| 240 |
+
return NextResponse.json(
|
| 241 |
+
{ ok: false, error: "Invalid model selected" },
|
| 242 |
+
{ status: 400 }
|
| 243 |
+
);
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
let token = userToken;
|
| 247 |
+
let billTo: string | null = null;
|
| 248 |
+
|
| 249 |
+
/**
|
| 250 |
+
* Handle local usage token, this bypass the need for a user token
|
| 251 |
+
* and allows local testing without authentication.
|
| 252 |
+
* This is useful for development and testing purposes.
|
| 253 |
+
*/
|
| 254 |
+
if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
|
| 255 |
+
token = process.env.HF_TOKEN;
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
const ip = authHeaders.get("x-forwarded-for")?.includes(",")
|
| 259 |
+
? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
|
| 260 |
+
: authHeaders.get("x-forwarded-for");
|
| 261 |
+
|
| 262 |
+
if (!token) {
|
| 263 |
+
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
| 264 |
+
if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
| 265 |
+
return NextResponse.json(
|
| 266 |
+
{
|
| 267 |
+
ok: false,
|
| 268 |
+
openLogin: true,
|
| 269 |
+
message: "Log In to continue using the service",
|
| 270 |
+
},
|
| 271 |
+
{ status: 429 }
|
| 272 |
+
);
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
token = process.env.DEFAULT_HF_TOKEN as string;
|
| 276 |
+
billTo = "huggingface";
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
const client = new InferenceClient(token);
|
| 280 |
+
|
| 281 |
+
const DEFAULT_PROVIDER = PROVIDERS.novita;
|
| 282 |
+
const selectedProvider =
|
| 283 |
+
provider === "auto"
|
| 284 |
+
? PROVIDERS[selectedModel.autoProvider as keyof typeof PROVIDERS]
|
| 285 |
+
: PROVIDERS[provider as keyof typeof PROVIDERS] ?? DEFAULT_PROVIDER;
|
| 286 |
+
|
| 287 |
+
try {
|
| 288 |
+
const response = await client.chatCompletion(
|
| 289 |
+
{
|
| 290 |
+
model: selectedModel.value,
|
| 291 |
+
provider: selectedProvider.id as any,
|
| 292 |
+
messages: [
|
| 293 |
+
{
|
| 294 |
+
role: "system",
|
| 295 |
+
content: FOLLOW_UP_SYSTEM_PROMPT,
|
| 296 |
+
},
|
| 297 |
+
{
|
| 298 |
+
role: "user",
|
| 299 |
+
content: previousPrompt
|
| 300 |
+
? previousPrompt
|
| 301 |
+
: "You are modifying the HTML file based on the user's request.",
|
| 302 |
+
},
|
| 303 |
+
{
|
| 304 |
+
role: "assistant",
|
| 305 |
+
|
| 306 |
+
content: `The current code is: \n\`\`\`html\n${html}\n\`\`\` ${
|
| 307 |
+
selectedElementHtml
|
| 308 |
+
? `\n\nYou have to update ONLY the following element, NOTHING ELSE: \n\n\`\`\`html\n${selectedElementHtml}\n\`\`\``
|
| 309 |
+
: ""
|
| 310 |
+
}`,
|
| 311 |
+
},
|
| 312 |
+
{
|
| 313 |
+
role: "user",
|
| 314 |
+
content: prompt,
|
| 315 |
+
},
|
| 316 |
+
],
|
| 317 |
+
...(selectedProvider.id !== "sambanova"
|
| 318 |
+
? {
|
| 319 |
+
max_tokens: selectedProvider.max_tokens,
|
| 320 |
+
}
|
| 321 |
+
: {}),
|
| 322 |
+
},
|
| 323 |
+
billTo ? { billTo } : {}
|
| 324 |
+
);
|
| 325 |
+
|
| 326 |
+
const chunk = response.choices[0]?.message?.content;
|
| 327 |
+
if (!chunk) {
|
| 328 |
+
return NextResponse.json(
|
| 329 |
+
{ ok: false, message: "No content returned from the model" },
|
| 330 |
+
{ status: 400 }
|
| 331 |
+
);
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
if (chunk) {
|
| 335 |
+
const updatedLines: number[][] = [];
|
| 336 |
+
let newHtml = html;
|
| 337 |
+
let position = 0;
|
| 338 |
+
let moreBlocks = true;
|
| 339 |
+
|
| 340 |
+
while (moreBlocks) {
|
| 341 |
+
const searchStartIndex = chunk.indexOf(SEARCH_START, position);
|
| 342 |
+
if (searchStartIndex === -1) {
|
| 343 |
+
moreBlocks = false;
|
| 344 |
+
continue;
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
const dividerIndex = chunk.indexOf(DIVIDER, searchStartIndex);
|
| 348 |
+
if (dividerIndex === -1) {
|
| 349 |
+
moreBlocks = false;
|
| 350 |
+
continue;
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
const replaceEndIndex = chunk.indexOf(REPLACE_END, dividerIndex);
|
| 354 |
+
if (replaceEndIndex === -1) {
|
| 355 |
+
moreBlocks = false;
|
| 356 |
+
continue;
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
const searchBlock = chunk.substring(
|
| 360 |
+
searchStartIndex + SEARCH_START.length,
|
| 361 |
+
dividerIndex
|
| 362 |
+
);
|
| 363 |
+
const replaceBlock = chunk.substring(
|
| 364 |
+
dividerIndex + DIVIDER.length,
|
| 365 |
+
replaceEndIndex
|
| 366 |
+
);
|
| 367 |
+
|
| 368 |
+
if (searchBlock.trim() === "") {
|
| 369 |
+
newHtml = `${replaceBlock}\n${newHtml}`;
|
| 370 |
+
updatedLines.push([1, replaceBlock.split("\n").length]);
|
| 371 |
+
} else {
|
| 372 |
+
const blockPosition = newHtml.indexOf(searchBlock);
|
| 373 |
+
if (blockPosition !== -1) {
|
| 374 |
+
const beforeText = newHtml.substring(0, blockPosition);
|
| 375 |
+
const startLineNumber = beforeText.split("\n").length;
|
| 376 |
+
const replaceLines = replaceBlock.split("\n").length;
|
| 377 |
+
const endLineNumber = startLineNumber + replaceLines - 1;
|
| 378 |
+
|
| 379 |
+
updatedLines.push([startLineNumber, endLineNumber]);
|
| 380 |
+
newHtml = newHtml.replace(searchBlock, replaceBlock);
|
| 381 |
+
}
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
position = replaceEndIndex + REPLACE_END.length;
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
return NextResponse.json({
|
| 388 |
+
ok: true,
|
| 389 |
+
html: newHtml,
|
| 390 |
+
updatedLines,
|
| 391 |
+
});
|
| 392 |
+
} else {
|
| 393 |
+
return NextResponse.json(
|
| 394 |
+
{ ok: false, message: "No content returned from the model" },
|
| 395 |
+
{ status: 400 }
|
| 396 |
+
);
|
| 397 |
+
}
|
| 398 |
+
} catch (error: any) {
|
| 399 |
+
if (error.message?.includes("exceeded your monthly included credits")) {
|
| 400 |
+
return NextResponse.json(
|
| 401 |
+
{
|
| 402 |
+
ok: false,
|
| 403 |
+
openProModal: true,
|
| 404 |
+
message: error.message,
|
| 405 |
+
},
|
| 406 |
+
{ status: 402 }
|
| 407 |
+
);
|
| 408 |
+
}
|
| 409 |
+
return NextResponse.json(
|
| 410 |
+
{
|
| 411 |
+
ok: false,
|
| 412 |
+
openSelectProvider: true,
|
| 413 |
+
message:
|
| 414 |
+
error.message || "An error occurred while processing your request.",
|
| 415 |
+
},
|
| 416 |
+
{ status: 500 }
|
| 417 |
+
);
|
| 418 |
+
}
|
| 419 |
+
}
|
app/api/ask/route.ts
DELETED
|
@@ -1,183 +0,0 @@
|
|
| 1 |
-
import { NextResponse } from "next/server";
|
| 2 |
-
import { InferenceClient } from "@huggingface/inference";
|
| 3 |
-
|
| 4 |
-
import { FOLLOW_UP_SYSTEM_PROMPT, INITIAL_SYSTEM_PROMPT } from "@/lib/prompts";
|
| 5 |
-
import { auth } from "@/lib/auth";
|
| 6 |
-
import { File, Message } from "@/lib/type";
|
| 7 |
-
import { DEFAULT_MODEL, MODELS } from "@/lib/providers";
|
| 8 |
-
|
| 9 |
-
export async function POST(request: Request) {
|
| 10 |
-
const session = await auth();
|
| 11 |
-
if (!session) {
|
| 12 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 13 |
-
}
|
| 14 |
-
const token = session.accessToken;
|
| 15 |
-
|
| 16 |
-
const body = await request.json();
|
| 17 |
-
const {
|
| 18 |
-
prompt,
|
| 19 |
-
previousMessages = [],
|
| 20 |
-
files = [],
|
| 21 |
-
provider,
|
| 22 |
-
model,
|
| 23 |
-
redesignMd,
|
| 24 |
-
medias,
|
| 25 |
-
} = body;
|
| 26 |
-
|
| 27 |
-
if (!prompt) {
|
| 28 |
-
return NextResponse.json({ error: "Prompt is required" }, { status: 400 });
|
| 29 |
-
}
|
| 30 |
-
if (!model || !MODELS.find((m: (typeof MODELS)[0]) => m.value === model)) {
|
| 31 |
-
return NextResponse.json({ error: "Model is required" }, { status: 400 });
|
| 32 |
-
}
|
| 33 |
-
|
| 34 |
-
const client = new InferenceClient(token);
|
| 35 |
-
|
| 36 |
-
try {
|
| 37 |
-
const encoder = new TextEncoder();
|
| 38 |
-
const stream = new TransformStream();
|
| 39 |
-
const writer = stream.writable.getWriter();
|
| 40 |
-
|
| 41 |
-
const response = new NextResponse(stream.readable, {
|
| 42 |
-
headers: {
|
| 43 |
-
"Content-Type": "text/plain; charset=utf-8",
|
| 44 |
-
"Cache-Control": "no-cache",
|
| 45 |
-
Connection: "keep-alive",
|
| 46 |
-
},
|
| 47 |
-
});
|
| 48 |
-
(async () => {
|
| 49 |
-
let hasRetried = false;
|
| 50 |
-
let currentModel = model;
|
| 51 |
-
|
| 52 |
-
const tryGeneration = async (): Promise<void> => {
|
| 53 |
-
try {
|
| 54 |
-
const chatCompletion = client.chatCompletionStream({
|
| 55 |
-
model: currentModel + (provider !== "auto" ? `:${provider}` : ""),
|
| 56 |
-
messages: [
|
| 57 |
-
{
|
| 58 |
-
role: "system",
|
| 59 |
-
content:
|
| 60 |
-
files.length > 0
|
| 61 |
-
? FOLLOW_UP_SYSTEM_PROMPT
|
| 62 |
-
: INITIAL_SYSTEM_PROMPT,
|
| 63 |
-
},
|
| 64 |
-
...previousMessages.map((message: Message) => ({
|
| 65 |
-
role: message.role,
|
| 66 |
-
content: message.content,
|
| 67 |
-
})),
|
| 68 |
-
...(files?.length > 0
|
| 69 |
-
? [
|
| 70 |
-
{
|
| 71 |
-
role: "user",
|
| 72 |
-
content: `Here are the files that the user has provider:${files
|
| 73 |
-
.map(
|
| 74 |
-
(file: File) =>
|
| 75 |
-
`File: ${file.path}\nContent: ${file.content}`
|
| 76 |
-
)
|
| 77 |
-
.join("\n")}\n\n${prompt}`,
|
| 78 |
-
},
|
| 79 |
-
]
|
| 80 |
-
: []),
|
| 81 |
-
{
|
| 82 |
-
role: "user",
|
| 83 |
-
content: `${
|
| 84 |
-
redesignMd?.url &&
|
| 85 |
-
`Redesign the following website ${redesignMd.url}, try to use the same images and content, but you can still improve it if needed. Do the best version possibile. Here is the markdown:\n ${redesignMd.md} \n\n`
|
| 86 |
-
}${prompt} ${
|
| 87 |
-
medias && medias.length > 0
|
| 88 |
-
? `\nHere is the list of my media files: ${medias.join(
|
| 89 |
-
", "
|
| 90 |
-
)}\n`
|
| 91 |
-
: ""
|
| 92 |
-
}`,
|
| 93 |
-
}
|
| 94 |
-
],
|
| 95 |
-
stream: true,
|
| 96 |
-
max_tokens: 16_000,
|
| 97 |
-
});
|
| 98 |
-
while (true) {
|
| 99 |
-
const { done, value } = await chatCompletion.next();
|
| 100 |
-
if (done) {
|
| 101 |
-
break;
|
| 102 |
-
}
|
| 103 |
-
|
| 104 |
-
const chunk = value.choices[0]?.delta?.content;
|
| 105 |
-
if (chunk) {
|
| 106 |
-
await writer.write(encoder.encode(chunk));
|
| 107 |
-
}
|
| 108 |
-
}
|
| 109 |
-
|
| 110 |
-
await writer.close();
|
| 111 |
-
} catch (error) {
|
| 112 |
-
const errorMessage =
|
| 113 |
-
error instanceof Error
|
| 114 |
-
? error.message
|
| 115 |
-
: "An error occurred while processing your request";
|
| 116 |
-
|
| 117 |
-
if (
|
| 118 |
-
!hasRetried &&
|
| 119 |
-
errorMessage?.includes(
|
| 120 |
-
"Failed to perform inference: Model not found"
|
| 121 |
-
)
|
| 122 |
-
) {
|
| 123 |
-
hasRetried = true;
|
| 124 |
-
if (model === DEFAULT_MODEL) {
|
| 125 |
-
const availableFallbackModels = MODELS.filter(
|
| 126 |
-
(m) => m.value !== model
|
| 127 |
-
);
|
| 128 |
-
const randomIndex = Math.floor(
|
| 129 |
-
Math.random() * availableFallbackModels.length
|
| 130 |
-
);
|
| 131 |
-
currentModel = availableFallbackModels[randomIndex];
|
| 132 |
-
} else {
|
| 133 |
-
currentModel = DEFAULT_MODEL;
|
| 134 |
-
}
|
| 135 |
-
const switchMessage = `\n\n_Note: The selected model was not available. Switched to \`${currentModel}\`._\n\n`;
|
| 136 |
-
await writer.write(encoder.encode(switchMessage));
|
| 137 |
-
|
| 138 |
-
return tryGeneration();
|
| 139 |
-
}
|
| 140 |
-
|
| 141 |
-
try {
|
| 142 |
-
let errorPayload = "";
|
| 143 |
-
if (
|
| 144 |
-
errorMessage?.includes("exceeded your monthly included credits") ||
|
| 145 |
-
errorMessage?.includes("reached the free monthly usage limit")
|
| 146 |
-
) {
|
| 147 |
-
errorPayload = JSON.stringify({
|
| 148 |
-
messageError: errorMessage,
|
| 149 |
-
showProMessage: true,
|
| 150 |
-
isError: true,
|
| 151 |
-
});
|
| 152 |
-
} else {
|
| 153 |
-
errorPayload = JSON.stringify({
|
| 154 |
-
messageError: errorMessage,
|
| 155 |
-
isError: true,
|
| 156 |
-
});
|
| 157 |
-
}
|
| 158 |
-
await writer.write(encoder.encode(`\n\n__ERROR__:${errorPayload}`));
|
| 159 |
-
await writer.close();
|
| 160 |
-
} catch (closeError) {
|
| 161 |
-
console.error("Failed to send error message:", closeError);
|
| 162 |
-
try {
|
| 163 |
-
await writer.abort(error);
|
| 164 |
-
} catch (abortError) {
|
| 165 |
-
console.error("Failed to abort writer:", abortError);
|
| 166 |
-
}
|
| 167 |
-
}
|
| 168 |
-
}
|
| 169 |
-
};
|
| 170 |
-
|
| 171 |
-
await tryGeneration();
|
| 172 |
-
})();
|
| 173 |
-
|
| 174 |
-
return response;
|
| 175 |
-
} catch (error) {
|
| 176 |
-
return NextResponse.json(
|
| 177 |
-
{
|
| 178 |
-
error: error instanceof Error ? error.message : "Internal Server Error",
|
| 179 |
-
},
|
| 180 |
-
{ status: 500 }
|
| 181 |
-
);
|
| 182 |
-
}
|
| 183 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/auth/[...nextauth]/route.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
| 1 |
-
import NextAuth from "next-auth";
|
| 2 |
-
import { authOptions } from "@/lib/auth";
|
| 3 |
-
|
| 4 |
-
const handler = NextAuth(authOptions);
|
| 5 |
-
|
| 6 |
-
export { handler as GET, handler as POST };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/auth/route.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
|
| 3 |
+
export async function POST(req: NextRequest) {
|
| 4 |
+
const body = await req.json();
|
| 5 |
+
const { code } = body;
|
| 6 |
+
|
| 7 |
+
if (!code) {
|
| 8 |
+
return NextResponse.json(
|
| 9 |
+
{ error: "Code is required" },
|
| 10 |
+
{
|
| 11 |
+
status: 400,
|
| 12 |
+
headers: {
|
| 13 |
+
"Content-Type": "application/json",
|
| 14 |
+
},
|
| 15 |
+
}
|
| 16 |
+
);
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
const Authorization = `Basic ${Buffer.from(
|
| 20 |
+
`${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}`
|
| 21 |
+
).toString("base64")}`;
|
| 22 |
+
|
| 23 |
+
const host =
|
| 24 |
+
req.headers.get("host") ?? req.headers.get("origin") ?? "localhost:3000";
|
| 25 |
+
|
| 26 |
+
const url = host.includes("/spaces/enzostvs")
|
| 27 |
+
? "enzostvs-deepsite.hf.space"
|
| 28 |
+
: host;
|
| 29 |
+
const redirect_uri =
|
| 30 |
+
`${host.includes("localhost") ? "http://" : "https://"}` +
|
| 31 |
+
url +
|
| 32 |
+
"/auth/callback";
|
| 33 |
+
const request_auth = await fetch("https://huggingface.co/oauth/token", {
|
| 34 |
+
method: "POST",
|
| 35 |
+
headers: {
|
| 36 |
+
"Content-Type": "application/x-www-form-urlencoded",
|
| 37 |
+
Authorization,
|
| 38 |
+
},
|
| 39 |
+
body: new URLSearchParams({
|
| 40 |
+
grant_type: "authorization_code",
|
| 41 |
+
code,
|
| 42 |
+
redirect_uri,
|
| 43 |
+
}),
|
| 44 |
+
});
|
| 45 |
+
|
| 46 |
+
const response = await request_auth.json();
|
| 47 |
+
if (!response.access_token) {
|
| 48 |
+
return NextResponse.json(
|
| 49 |
+
{ error: "Failed to retrieve access token" },
|
| 50 |
+
{
|
| 51 |
+
status: 400,
|
| 52 |
+
headers: {
|
| 53 |
+
"Content-Type": "application/json",
|
| 54 |
+
},
|
| 55 |
+
}
|
| 56 |
+
);
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
|
| 60 |
+
headers: {
|
| 61 |
+
Authorization: `Bearer ${response.access_token}`,
|
| 62 |
+
},
|
| 63 |
+
});
|
| 64 |
+
|
| 65 |
+
if (!userResponse.ok) {
|
| 66 |
+
return NextResponse.json(
|
| 67 |
+
{ user: null, errCode: userResponse.status },
|
| 68 |
+
{ status: userResponse.status }
|
| 69 |
+
);
|
| 70 |
+
}
|
| 71 |
+
const user = await userResponse.json();
|
| 72 |
+
|
| 73 |
+
return NextResponse.json(
|
| 74 |
+
{
|
| 75 |
+
access_token: response.access_token,
|
| 76 |
+
expires_in: response.expires_in,
|
| 77 |
+
user,
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
status: 200,
|
| 81 |
+
headers: {
|
| 82 |
+
"Content-Type": "application/json",
|
| 83 |
+
},
|
| 84 |
+
}
|
| 85 |
+
);
|
| 86 |
+
}
|
app/api/healthcheck/route.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
| 1 |
-
import { NextResponse } from "next/server";
|
| 2 |
-
|
| 3 |
-
export async function GET() {
|
| 4 |
-
return NextResponse.json({ status: "ok" }, { status: 200 });
|
| 5 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/me/projects/[namespace]/[repoId]/route.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
import { RepoDesignation, spaceInfo, uploadFile } from "@huggingface/hub";
|
| 3 |
+
|
| 4 |
+
import { isAuthenticated } from "@/lib/auth";
|
| 5 |
+
import Project from "@/models/Project";
|
| 6 |
+
import dbConnect from "@/lib/mongodb";
|
| 7 |
+
import { getPTag } from "@/lib/utils";
|
| 8 |
+
|
| 9 |
+
export async function GET(
|
| 10 |
+
req: NextRequest,
|
| 11 |
+
{ params }: { params: Promise<{ namespace: string; repoId: string }> }
|
| 12 |
+
) {
|
| 13 |
+
const user = await isAuthenticated();
|
| 14 |
+
|
| 15 |
+
if (user instanceof NextResponse || !user) {
|
| 16 |
+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
await dbConnect();
|
| 20 |
+
const param = await params;
|
| 21 |
+
const { namespace, repoId } = param;
|
| 22 |
+
|
| 23 |
+
const project = await Project.findOne({
|
| 24 |
+
user_id: user.id,
|
| 25 |
+
space_id: `${namespace}/${repoId}`,
|
| 26 |
+
}).lean();
|
| 27 |
+
if (!project) {
|
| 28 |
+
return NextResponse.json(
|
| 29 |
+
{
|
| 30 |
+
ok: false,
|
| 31 |
+
error: "Project not found",
|
| 32 |
+
},
|
| 33 |
+
{ status: 404 }
|
| 34 |
+
);
|
| 35 |
+
}
|
| 36 |
+
const space_url = `https://huggingface.co/spaces/${namespace}/${repoId}/raw/main/index.html`;
|
| 37 |
+
try {
|
| 38 |
+
const space = await spaceInfo({
|
| 39 |
+
name: namespace + "/" + repoId,
|
| 40 |
+
accessToken: user.token as string,
|
| 41 |
+
additionalFields: ["author"],
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
if (!space || space.sdk !== "static") {
|
| 45 |
+
return NextResponse.json(
|
| 46 |
+
{
|
| 47 |
+
ok: false,
|
| 48 |
+
error: "Space is not a static space",
|
| 49 |
+
},
|
| 50 |
+
{ status: 404 }
|
| 51 |
+
);
|
| 52 |
+
}
|
| 53 |
+
if (space.author !== user.name) {
|
| 54 |
+
return NextResponse.json(
|
| 55 |
+
{
|
| 56 |
+
ok: false,
|
| 57 |
+
error: "Space does not belong to the authenticated user",
|
| 58 |
+
},
|
| 59 |
+
{ status: 403 }
|
| 60 |
+
);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
const response = await fetch(space_url);
|
| 64 |
+
if (!response.ok) {
|
| 65 |
+
return NextResponse.json(
|
| 66 |
+
{
|
| 67 |
+
ok: false,
|
| 68 |
+
error: "Failed to fetch space HTML",
|
| 69 |
+
},
|
| 70 |
+
{ status: 404 }
|
| 71 |
+
);
|
| 72 |
+
}
|
| 73 |
+
let html = await response.text();
|
| 74 |
+
// remove the last p tag including this url https://enzostvs-deepsite.hf.space
|
| 75 |
+
html = html.replace(getPTag(namespace + "/" + repoId), "");
|
| 76 |
+
|
| 77 |
+
return NextResponse.json(
|
| 78 |
+
{
|
| 79 |
+
project: {
|
| 80 |
+
...project,
|
| 81 |
+
html,
|
| 82 |
+
},
|
| 83 |
+
ok: true,
|
| 84 |
+
},
|
| 85 |
+
{ status: 200 }
|
| 86 |
+
);
|
| 87 |
+
|
| 88 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 89 |
+
} catch (error: any) {
|
| 90 |
+
if (error.statusCode === 404) {
|
| 91 |
+
await Project.deleteOne({
|
| 92 |
+
user_id: user.id,
|
| 93 |
+
space_id: `${namespace}/${repoId}`,
|
| 94 |
+
});
|
| 95 |
+
return NextResponse.json(
|
| 96 |
+
{ error: "Space not found", ok: false },
|
| 97 |
+
{ status: 404 }
|
| 98 |
+
);
|
| 99 |
+
}
|
| 100 |
+
return NextResponse.json(
|
| 101 |
+
{ error: error.message, ok: false },
|
| 102 |
+
{ status: 500 }
|
| 103 |
+
);
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
export async function PUT(
|
| 108 |
+
req: NextRequest,
|
| 109 |
+
{ params }: { params: Promise<{ namespace: string; repoId: string }> }
|
| 110 |
+
) {
|
| 111 |
+
const user = await isAuthenticated();
|
| 112 |
+
|
| 113 |
+
if (user instanceof NextResponse || !user) {
|
| 114 |
+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
await dbConnect();
|
| 118 |
+
const param = await params;
|
| 119 |
+
const { namespace, repoId } = param;
|
| 120 |
+
const { html, prompts } = await req.json();
|
| 121 |
+
|
| 122 |
+
const project = await Project.findOne({
|
| 123 |
+
user_id: user.id,
|
| 124 |
+
space_id: `${namespace}/${repoId}`,
|
| 125 |
+
}).lean();
|
| 126 |
+
if (!project) {
|
| 127 |
+
return NextResponse.json(
|
| 128 |
+
{
|
| 129 |
+
ok: false,
|
| 130 |
+
error: "Project not found",
|
| 131 |
+
},
|
| 132 |
+
{ status: 404 }
|
| 133 |
+
);
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
const repo: RepoDesignation = {
|
| 137 |
+
type: "space",
|
| 138 |
+
name: `${namespace}/${repoId}`,
|
| 139 |
+
};
|
| 140 |
+
|
| 141 |
+
const newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}</body>`);
|
| 142 |
+
const file = new File([newHtml], "index.html", { type: "text/html" });
|
| 143 |
+
await uploadFile({
|
| 144 |
+
repo,
|
| 145 |
+
file,
|
| 146 |
+
accessToken: user.token as string,
|
| 147 |
+
commitTitle: `${prompts[prompts.length - 1]} - Follow Up Deployment`,
|
| 148 |
+
});
|
| 149 |
+
|
| 150 |
+
await Project.updateOne(
|
| 151 |
+
{ user_id: user.id, space_id: `${namespace}/${repoId}` },
|
| 152 |
+
{
|
| 153 |
+
$set: {
|
| 154 |
+
prompts: [
|
| 155 |
+
...(project && "prompts" in project ? project.prompts : []),
|
| 156 |
+
...prompts,
|
| 157 |
+
],
|
| 158 |
+
},
|
| 159 |
+
}
|
| 160 |
+
);
|
| 161 |
+
return NextResponse.json({ ok: true }, { status: 200 });
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
export async function POST(
|
| 165 |
+
req: NextRequest,
|
| 166 |
+
{ params }: { params: Promise<{ namespace: string; repoId: string }> }
|
| 167 |
+
) {
|
| 168 |
+
const user = await isAuthenticated();
|
| 169 |
+
|
| 170 |
+
if (user instanceof NextResponse || !user) {
|
| 171 |
+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
await dbConnect();
|
| 175 |
+
const param = await params;
|
| 176 |
+
const { namespace, repoId } = param;
|
| 177 |
+
|
| 178 |
+
const space = await spaceInfo({
|
| 179 |
+
name: namespace + "/" + repoId,
|
| 180 |
+
accessToken: user.token as string,
|
| 181 |
+
additionalFields: ["author"],
|
| 182 |
+
});
|
| 183 |
+
|
| 184 |
+
if (!space || space.sdk !== "static") {
|
| 185 |
+
return NextResponse.json(
|
| 186 |
+
{
|
| 187 |
+
ok: false,
|
| 188 |
+
error: "Space is not a static space",
|
| 189 |
+
},
|
| 190 |
+
{ status: 404 }
|
| 191 |
+
);
|
| 192 |
+
}
|
| 193 |
+
if (space.author !== user.name) {
|
| 194 |
+
return NextResponse.json(
|
| 195 |
+
{
|
| 196 |
+
ok: false,
|
| 197 |
+
error: "Space does not belong to the authenticated user",
|
| 198 |
+
},
|
| 199 |
+
{ status: 403 }
|
| 200 |
+
);
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
const project = await Project.findOne({
|
| 204 |
+
user_id: user.id,
|
| 205 |
+
space_id: `${namespace}/${repoId}`,
|
| 206 |
+
}).lean();
|
| 207 |
+
if (project) {
|
| 208 |
+
// redirect to the project page if it already exists
|
| 209 |
+
return NextResponse.json(
|
| 210 |
+
{
|
| 211 |
+
ok: false,
|
| 212 |
+
error: "Project already exists",
|
| 213 |
+
redirect: `/projects/${namespace}/${repoId}`,
|
| 214 |
+
},
|
| 215 |
+
{ status: 400 }
|
| 216 |
+
);
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
const newProject = new Project({
|
| 220 |
+
user_id: user.id,
|
| 221 |
+
space_id: `${namespace}/${repoId}`,
|
| 222 |
+
prompts: [],
|
| 223 |
+
});
|
| 224 |
+
|
| 225 |
+
await newProject.save();
|
| 226 |
+
return NextResponse.json(
|
| 227 |
+
{
|
| 228 |
+
ok: true,
|
| 229 |
+
project: {
|
| 230 |
+
id: newProject._id,
|
| 231 |
+
space_id: newProject.space_id,
|
| 232 |
+
prompts: newProject.prompts,
|
| 233 |
+
},
|
| 234 |
+
},
|
| 235 |
+
{ status: 201 }
|
| 236 |
+
);
|
| 237 |
+
}
|
app/api/me/projects/route.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
import { createRepo, RepoDesignation, uploadFiles } from "@huggingface/hub";
|
| 3 |
+
|
| 4 |
+
import { isAuthenticated } from "@/lib/auth";
|
| 5 |
+
import Project from "@/models/Project";
|
| 6 |
+
import dbConnect from "@/lib/mongodb";
|
| 7 |
+
import { COLORS, getPTag } from "@/lib/utils";
|
| 8 |
+
// import type user
|
| 9 |
+
export async function GET() {
|
| 10 |
+
const user = await isAuthenticated();
|
| 11 |
+
|
| 12 |
+
if (user instanceof NextResponse || !user) {
|
| 13 |
+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
await dbConnect();
|
| 17 |
+
|
| 18 |
+
const projects = await Project.find({
|
| 19 |
+
user_id: user?.id,
|
| 20 |
+
})
|
| 21 |
+
.sort({ _createdAt: -1 })
|
| 22 |
+
.limit(100)
|
| 23 |
+
.lean();
|
| 24 |
+
if (!projects) {
|
| 25 |
+
return NextResponse.json(
|
| 26 |
+
{
|
| 27 |
+
ok: false,
|
| 28 |
+
projects: [],
|
| 29 |
+
},
|
| 30 |
+
{ status: 404 }
|
| 31 |
+
);
|
| 32 |
+
}
|
| 33 |
+
return NextResponse.json(
|
| 34 |
+
{
|
| 35 |
+
ok: true,
|
| 36 |
+
projects,
|
| 37 |
+
},
|
| 38 |
+
{ status: 200 }
|
| 39 |
+
);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
/**
|
| 43 |
+
* This API route creates a new project in Hugging Face Spaces.
|
| 44 |
+
* It requires an Authorization header with a valid token and a JSON body with the project details.
|
| 45 |
+
*/
|
| 46 |
+
export async function POST(request: NextRequest) {
|
| 47 |
+
const user = await isAuthenticated();
|
| 48 |
+
|
| 49 |
+
if (user instanceof NextResponse || !user) {
|
| 50 |
+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
const { title, html, prompts } = await request.json();
|
| 54 |
+
|
| 55 |
+
if (!title || !html) {
|
| 56 |
+
return NextResponse.json(
|
| 57 |
+
{ message: "Title and HTML content are required.", ok: false },
|
| 58 |
+
{ status: 400 }
|
| 59 |
+
);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
await dbConnect();
|
| 63 |
+
|
| 64 |
+
try {
|
| 65 |
+
let readme = "";
|
| 66 |
+
let newHtml = html;
|
| 67 |
+
|
| 68 |
+
const newTitle = title
|
| 69 |
+
.toLowerCase()
|
| 70 |
+
.replace(/[^a-z0-9]+/g, "-")
|
| 71 |
+
.split("-")
|
| 72 |
+
.filter(Boolean)
|
| 73 |
+
.join("-")
|
| 74 |
+
.slice(0, 96);
|
| 75 |
+
|
| 76 |
+
const repo: RepoDesignation = {
|
| 77 |
+
type: "space",
|
| 78 |
+
name: `${user.name}/${newTitle}`,
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
+
const { repoUrl } = await createRepo({
|
| 82 |
+
repo,
|
| 83 |
+
accessToken: user.token as string,
|
| 84 |
+
});
|
| 85 |
+
const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
|
| 86 |
+
const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
|
| 87 |
+
readme = `---
|
| 88 |
+
title: ${newTitle}
|
| 89 |
+
emoji: 🐳
|
| 90 |
+
colorFrom: ${colorFrom}
|
| 91 |
+
colorTo: ${colorTo}
|
| 92 |
+
sdk: static
|
| 93 |
+
pinned: false
|
| 94 |
+
tags:
|
| 95 |
+
- deepsite
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference`;
|
| 99 |
+
|
| 100 |
+
newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}</body>`);
|
| 101 |
+
const file = new File([newHtml], "index.html", { type: "text/html" });
|
| 102 |
+
const readmeFile = new File([readme], "README.md", {
|
| 103 |
+
type: "text/markdown",
|
| 104 |
+
});
|
| 105 |
+
const files = [file, readmeFile];
|
| 106 |
+
await uploadFiles({
|
| 107 |
+
repo,
|
| 108 |
+
files,
|
| 109 |
+
accessToken: user.token as string,
|
| 110 |
+
commitTitle: `${prompts[prompts.length - 1]} - Initial Deployment`,
|
| 111 |
+
});
|
| 112 |
+
const path = repoUrl.split("/").slice(-2).join("/");
|
| 113 |
+
const project = await Project.create({
|
| 114 |
+
user_id: user.id,
|
| 115 |
+
space_id: path,
|
| 116 |
+
prompts,
|
| 117 |
+
});
|
| 118 |
+
return NextResponse.json({ project, path, ok: true }, { status: 201 });
|
| 119 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 120 |
+
} catch (err: any) {
|
| 121 |
+
return NextResponse.json(
|
| 122 |
+
{ error: err.message, ok: false },
|
| 123 |
+
{ status: 500 }
|
| 124 |
+
);
|
| 125 |
+
}
|
| 126 |
+
}
|
app/api/me/route.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { headers } from "next/headers";
|
| 2 |
+
import { NextResponse } from "next/server";
|
| 3 |
+
|
| 4 |
+
export async function GET() {
|
| 5 |
+
const authHeaders = await headers();
|
| 6 |
+
const token = authHeaders.get("Authorization");
|
| 7 |
+
if (!token) {
|
| 8 |
+
return NextResponse.json({ user: null, errCode: 401 }, { status: 401 });
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
|
| 12 |
+
headers: {
|
| 13 |
+
Authorization: `${token}`,
|
| 14 |
+
},
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
if (!userResponse.ok) {
|
| 18 |
+
return NextResponse.json(
|
| 19 |
+
{ user: null, errCode: userResponse.status },
|
| 20 |
+
{ status: userResponse.status }
|
| 21 |
+
);
|
| 22 |
+
}
|
| 23 |
+
const user = await userResponse.json();
|
| 24 |
+
return NextResponse.json({ user, errCode: null }, { status: 200 });
|
| 25 |
+
}
|
app/api/projects/[repoId]/[commitId]/route.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
| 1 |
-
import { auth } from "@/lib/auth";
|
| 2 |
-
import { createBranch, RepoDesignation } from "@huggingface/hub";
|
| 3 |
-
import { format } from "date-fns";
|
| 4 |
-
import { NextResponse } from "next/server";
|
| 5 |
-
|
| 6 |
-
export async function POST(
|
| 7 |
-
request: Request,
|
| 8 |
-
{ params }: { params: Promise<{ repoId: string; commitId: string }> }
|
| 9 |
-
) {
|
| 10 |
-
const { repoId, commitId }: { repoId: string; commitId: string } =
|
| 11 |
-
await params;
|
| 12 |
-
const session = await auth();
|
| 13 |
-
if (!session) {
|
| 14 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 15 |
-
}
|
| 16 |
-
const token = session.accessToken;
|
| 17 |
-
|
| 18 |
-
const repo: RepoDesignation = {
|
| 19 |
-
type: "space",
|
| 20 |
-
name: session.user?.username + "/" + repoId,
|
| 21 |
-
};
|
| 22 |
-
|
| 23 |
-
const commitTitle = `🔖 ${format(new Date(), "dd/MM")} - ${format(
|
| 24 |
-
new Date(),
|
| 25 |
-
"HH:mm"
|
| 26 |
-
)} - Set commit ${commitId} as default.`;
|
| 27 |
-
|
| 28 |
-
await fetch(
|
| 29 |
-
`https://huggingface.co/api/spaces/${session.user?.username}/${repoId}/branch/main`,
|
| 30 |
-
{
|
| 31 |
-
method: "POST",
|
| 32 |
-
headers: {
|
| 33 |
-
Authorization: `Bearer ${token}`,
|
| 34 |
-
"Content-Type": "application/json",
|
| 35 |
-
},
|
| 36 |
-
body: JSON.stringify({
|
| 37 |
-
startingPoint: commitId,
|
| 38 |
-
overwrite: true,
|
| 39 |
-
}),
|
| 40 |
-
}
|
| 41 |
-
).catch((error) => {
|
| 42 |
-
return NextResponse.json(
|
| 43 |
-
{ error: error ?? "Failed to create branch" },
|
| 44 |
-
{ status: 500 }
|
| 45 |
-
);
|
| 46 |
-
});
|
| 47 |
-
|
| 48 |
-
return NextResponse.json({ success: true }, { status: 200 });
|
| 49 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/projects/[repoId]/download/route.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
| 1 |
-
import { auth } from "@/lib/auth";
|
| 2 |
-
import { downloadFile, listFiles, RepoDesignation } from "@huggingface/hub";
|
| 3 |
-
import { NextResponse } from "next/server";
|
| 4 |
-
import JSZip from "jszip";
|
| 5 |
-
|
| 6 |
-
export async function GET(
|
| 7 |
-
request: Request,
|
| 8 |
-
{ params }: { params: Promise<{ repoId: string }> }
|
| 9 |
-
) {
|
| 10 |
-
const { repoId }: { repoId: string } = await params;
|
| 11 |
-
const session = await auth();
|
| 12 |
-
if (!session) {
|
| 13 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 14 |
-
}
|
| 15 |
-
const token = session.accessToken;
|
| 16 |
-
const repo: RepoDesignation = {
|
| 17 |
-
type: "space",
|
| 18 |
-
name: session.user?.username + "/" + repoId,
|
| 19 |
-
};
|
| 20 |
-
|
| 21 |
-
try {
|
| 22 |
-
const zip = new JSZip();
|
| 23 |
-
for await (const fileInfo of listFiles({
|
| 24 |
-
repo,
|
| 25 |
-
accessToken: token as string,
|
| 26 |
-
recursive: true,
|
| 27 |
-
})) {
|
| 28 |
-
if (fileInfo.type === "directory" || fileInfo.path.startsWith(".")) {
|
| 29 |
-
continue;
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
try {
|
| 33 |
-
const blob = await downloadFile({
|
| 34 |
-
repo,
|
| 35 |
-
accessToken: token as string,
|
| 36 |
-
path: fileInfo.path,
|
| 37 |
-
raw: true
|
| 38 |
-
}).catch((error) => {
|
| 39 |
-
return null;
|
| 40 |
-
});
|
| 41 |
-
if (!blob) {
|
| 42 |
-
continue;
|
| 43 |
-
}
|
| 44 |
-
|
| 45 |
-
if (blob) {
|
| 46 |
-
const arrayBuffer = await blob.arrayBuffer();
|
| 47 |
-
zip.file(fileInfo.path, arrayBuffer);
|
| 48 |
-
}
|
| 49 |
-
} catch (error) {
|
| 50 |
-
console.error(`Error downloading file ${fileInfo.path}:`, error);
|
| 51 |
-
}
|
| 52 |
-
}
|
| 53 |
-
|
| 54 |
-
const zipBlob = await zip.generateAsync({
|
| 55 |
-
type: "blob",
|
| 56 |
-
compression: "DEFLATE",
|
| 57 |
-
compressionOptions: {
|
| 58 |
-
level: 6
|
| 59 |
-
}
|
| 60 |
-
});
|
| 61 |
-
|
| 62 |
-
const projectName = `${session.user?.username}-${repoId}`.replace(/[^a-zA-Z0-9-_]/g, '_');
|
| 63 |
-
const filename = `${projectName}.zip`;
|
| 64 |
-
|
| 65 |
-
return new NextResponse(zipBlob, {
|
| 66 |
-
headers: {
|
| 67 |
-
"Content-Type": "application/zip",
|
| 68 |
-
"Content-Disposition": `attachment; filename="${filename}"`,
|
| 69 |
-
"Content-Length": zipBlob.size.toString(),
|
| 70 |
-
},
|
| 71 |
-
});
|
| 72 |
-
} catch (error) {
|
| 73 |
-
console.error("Error downloading project:", error);
|
| 74 |
-
return NextResponse.json({ error: "Failed to download project" }, { status: 500 });
|
| 75 |
-
}
|
| 76 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/projects/[repoId]/medias/route.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
| 1 |
-
import { auth } from "@/lib/auth";
|
| 2 |
-
import { RepoDesignation, uploadFiles } from "@huggingface/hub";
|
| 3 |
-
import { NextResponse } from "next/server";
|
| 4 |
-
|
| 5 |
-
export async function POST(
|
| 6 |
-
request: Request,
|
| 7 |
-
{ params }: { params: Promise<{ repoId: string }> }
|
| 8 |
-
) {
|
| 9 |
-
const { repoId }: { repoId: string } = await params;
|
| 10 |
-
const session = await auth();
|
| 11 |
-
if (!session) {
|
| 12 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 13 |
-
}
|
| 14 |
-
const token = session.accessToken;
|
| 15 |
-
|
| 16 |
-
const repo: RepoDesignation = {
|
| 17 |
-
type: "space",
|
| 18 |
-
name: session.user?.username + "/" + repoId,
|
| 19 |
-
};
|
| 20 |
-
|
| 21 |
-
const formData = await request.formData();
|
| 22 |
-
const newMedias = formData.getAll("images") as File[];
|
| 23 |
-
|
| 24 |
-
const filesToUpload: File[] = [];
|
| 25 |
-
|
| 26 |
-
if (!newMedias || newMedias.length === 0) {
|
| 27 |
-
return NextResponse.json(
|
| 28 |
-
{
|
| 29 |
-
ok: false,
|
| 30 |
-
error: "At least one media file is required under the 'images' key",
|
| 31 |
-
},
|
| 32 |
-
{ status: 400 }
|
| 33 |
-
);
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
try {
|
| 37 |
-
for (const media of newMedias) {
|
| 38 |
-
const isImage = media.type.startsWith("image/");
|
| 39 |
-
const isVideo = media.type.startsWith("video/");
|
| 40 |
-
const isAudio = media.type.startsWith("audio/");
|
| 41 |
-
|
| 42 |
-
const folderPath = isImage
|
| 43 |
-
? "images/"
|
| 44 |
-
: isVideo
|
| 45 |
-
? "videos/"
|
| 46 |
-
: isAudio
|
| 47 |
-
? "audios/"
|
| 48 |
-
: null;
|
| 49 |
-
|
| 50 |
-
if (!folderPath) {
|
| 51 |
-
return NextResponse.json(
|
| 52 |
-
{ ok: false, error: "Unsupported media type: " + media.type },
|
| 53 |
-
{ status: 400 }
|
| 54 |
-
);
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
const mediaName = `${folderPath}${media.name}`;
|
| 58 |
-
const processedFile = new File([media], mediaName, { type: media.type });
|
| 59 |
-
filesToUpload.push(processedFile);
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
await uploadFiles({
|
| 63 |
-
repo,
|
| 64 |
-
files: filesToUpload,
|
| 65 |
-
accessToken: token,
|
| 66 |
-
commitTitle: `📁 Upload media files through DeepSite`,
|
| 67 |
-
});
|
| 68 |
-
|
| 69 |
-
return NextResponse.json(
|
| 70 |
-
{
|
| 71 |
-
success: true,
|
| 72 |
-
medias: filesToUpload.map(
|
| 73 |
-
(file) =>
|
| 74 |
-
`https://huggingface.co/spaces/${session.user?.username}/${repoId}/resolve/main/${file.name}`
|
| 75 |
-
),
|
| 76 |
-
},
|
| 77 |
-
{ status: 200 }
|
| 78 |
-
);
|
| 79 |
-
} catch (error) {
|
| 80 |
-
return NextResponse.json(
|
| 81 |
-
{ ok: false, error: error ?? "Failed to upload media files" },
|
| 82 |
-
{ status: 500 }
|
| 83 |
-
);
|
| 84 |
-
}
|
| 85 |
-
|
| 86 |
-
return NextResponse.json({ success: true }, { status: 200 });
|
| 87 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/projects/[repoId]/rename/route.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
| 1 |
-
import { auth } from "@/lib/auth";
|
| 2 |
-
import { downloadFile, RepoDesignation, uploadFile } from "@huggingface/hub";
|
| 3 |
-
import { format } from "date-fns";
|
| 4 |
-
import { NextResponse } from "next/server";
|
| 5 |
-
|
| 6 |
-
export async function PUT(
|
| 7 |
-
request: Request,
|
| 8 |
-
{ params }: { params: Promise<{ repoId: string }> }
|
| 9 |
-
) {
|
| 10 |
-
const { repoId }: { repoId: string } = await params;
|
| 11 |
-
const session = await auth();
|
| 12 |
-
if (!session) {
|
| 13 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 14 |
-
}
|
| 15 |
-
const token = session.accessToken;
|
| 16 |
-
|
| 17 |
-
const body = await request.json();
|
| 18 |
-
const { newTitle } = body;
|
| 19 |
-
|
| 20 |
-
if (!newTitle) {
|
| 21 |
-
return NextResponse.json(
|
| 22 |
-
{ error: "newTitle is required" },
|
| 23 |
-
{ status: 400 }
|
| 24 |
-
);
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
const repo: RepoDesignation = {
|
| 28 |
-
type: "space",
|
| 29 |
-
name: session.user?.username + "/" + repoId,
|
| 30 |
-
};
|
| 31 |
-
|
| 32 |
-
const blob = await downloadFile({
|
| 33 |
-
repo,
|
| 34 |
-
accessToken: token,
|
| 35 |
-
path: "README.md",
|
| 36 |
-
raw: true,
|
| 37 |
-
}).catch((_) => {
|
| 38 |
-
return null;
|
| 39 |
-
});
|
| 40 |
-
|
| 41 |
-
if (!blob) {
|
| 42 |
-
return NextResponse.json(
|
| 43 |
-
{ error: "Could not fetch README.md" },
|
| 44 |
-
{ status: 500 }
|
| 45 |
-
);
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
const readmeFile = await blob?.text();
|
| 49 |
-
if (!readmeFile) {
|
| 50 |
-
return NextResponse.json(
|
| 51 |
-
{ error: "Could not read README.md content" },
|
| 52 |
-
{ status: 500 }
|
| 53 |
-
);
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
// Escape YAML values to prevent injection attacks
|
| 57 |
-
const escapeYamlValue = (value: string): string => {
|
| 58 |
-
if (/[:|>]|^[-*#]|^\s|['"]/.test(value) || value.includes("\n")) {
|
| 59 |
-
return `"${value.replace(/"/g, '\\"')}"`;
|
| 60 |
-
}
|
| 61 |
-
return value;
|
| 62 |
-
};
|
| 63 |
-
|
| 64 |
-
// Escape commit message to prevent injection
|
| 65 |
-
const escapeCommitMessage = (message: string): string => {
|
| 66 |
-
return message.replace(/[\r\n]/g, " ").slice(0, 200);
|
| 67 |
-
};
|
| 68 |
-
|
| 69 |
-
const updatedReadmeFile = readmeFile.replace(
|
| 70 |
-
/^title:\s*(.*)$/m,
|
| 71 |
-
`title: ${escapeYamlValue(newTitle)}`
|
| 72 |
-
);
|
| 73 |
-
|
| 74 |
-
await uploadFile({
|
| 75 |
-
repo,
|
| 76 |
-
accessToken: token,
|
| 77 |
-
file: new File([updatedReadmeFile], "README.md", { type: "text/markdown" }),
|
| 78 |
-
commitTitle: escapeCommitMessage(
|
| 79 |
-
`🐳 ${format(new Date(), "dd/MM")} - ${format(
|
| 80 |
-
new Date(),
|
| 81 |
-
"HH:mm"
|
| 82 |
-
)} - Rename project to "${newTitle}"`
|
| 83 |
-
),
|
| 84 |
-
});
|
| 85 |
-
|
| 86 |
-
return NextResponse.json(
|
| 87 |
-
{
|
| 88 |
-
success: true,
|
| 89 |
-
},
|
| 90 |
-
{ status: 200 }
|
| 91 |
-
);
|
| 92 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/projects/[repoId]/route.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
| 1 |
-
import { auth } from "@/lib/auth";
|
| 2 |
-
import { RepoDesignation, deleteRepo, uploadFiles } from "@huggingface/hub";
|
| 3 |
-
import { format } from "date-fns";
|
| 4 |
-
import { NextResponse } from "next/server";
|
| 5 |
-
|
| 6 |
-
export async function PUT(
|
| 7 |
-
request: Request,
|
| 8 |
-
{ params }: { params: Promise<{ repoId: string }> }
|
| 9 |
-
) {
|
| 10 |
-
const { repoId }: { repoId: string } = await params;
|
| 11 |
-
const session = await auth();
|
| 12 |
-
if (!session) {
|
| 13 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 14 |
-
}
|
| 15 |
-
const token = session.accessToken;
|
| 16 |
-
|
| 17 |
-
const body = await request.json();
|
| 18 |
-
const { files, prompt, isManualChanges } = body;
|
| 19 |
-
|
| 20 |
-
if (!files) {
|
| 21 |
-
return NextResponse.json({ error: "Files are required" }, { status: 400 });
|
| 22 |
-
}
|
| 23 |
-
|
| 24 |
-
if (!prompt) {
|
| 25 |
-
return NextResponse.json({ error: "Prompt is required" }, { status: 400 });
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
const repo: RepoDesignation = {
|
| 29 |
-
type: "space",
|
| 30 |
-
name: session.user?.username + "/" + repoId,
|
| 31 |
-
};
|
| 32 |
-
|
| 33 |
-
const filesToUpload: File[] = [];
|
| 34 |
-
for (const file of files) {
|
| 35 |
-
let mimeType = "text/x-python";
|
| 36 |
-
if (file.path.endsWith(".txt")) {
|
| 37 |
-
mimeType = "text/plain";
|
| 38 |
-
} else if (file.path.endsWith(".md")) {
|
| 39 |
-
mimeType = "text/markdown";
|
| 40 |
-
} else if (file.path.endsWith(".json")) {
|
| 41 |
-
mimeType = "application/json";
|
| 42 |
-
}
|
| 43 |
-
filesToUpload.push(new File([file.content], file.path, { type: mimeType }));
|
| 44 |
-
}
|
| 45 |
-
// Escape commit title to prevent injection
|
| 46 |
-
const escapeCommitTitle = (title: string): string => {
|
| 47 |
-
return title.replace(/[\r\n]/g, " ").slice(0, 200);
|
| 48 |
-
};
|
| 49 |
-
|
| 50 |
-
const baseTitle = isManualChanges
|
| 51 |
-
? ""
|
| 52 |
-
: `🐳 ${format(new Date(), "dd/MM")} - ${format(new Date(), "HH:mm")} - `;
|
| 53 |
-
const commitTitle = escapeCommitTitle(
|
| 54 |
-
baseTitle + (prompt ?? "Follow-up DeepSite commit")
|
| 55 |
-
);
|
| 56 |
-
const response = await uploadFiles({
|
| 57 |
-
repo,
|
| 58 |
-
files: filesToUpload,
|
| 59 |
-
accessToken: token,
|
| 60 |
-
commitTitle,
|
| 61 |
-
});
|
| 62 |
-
|
| 63 |
-
return NextResponse.json(
|
| 64 |
-
{
|
| 65 |
-
success: true,
|
| 66 |
-
commit: {
|
| 67 |
-
oid: response.commit,
|
| 68 |
-
title: commitTitle,
|
| 69 |
-
date: new Date(),
|
| 70 |
-
},
|
| 71 |
-
},
|
| 72 |
-
{ status: 200 }
|
| 73 |
-
);
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
export async function DELETE(
|
| 77 |
-
request: Request,
|
| 78 |
-
{ params }: { params: Promise<{ repoId: string }> }
|
| 79 |
-
) {
|
| 80 |
-
const { repoId }: { repoId: string } = await params;
|
| 81 |
-
const session = await auth();
|
| 82 |
-
if (!session) {
|
| 83 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 84 |
-
}
|
| 85 |
-
const token = session.accessToken;
|
| 86 |
-
|
| 87 |
-
const repo: RepoDesignation = {
|
| 88 |
-
type: "space",
|
| 89 |
-
name: session.user?.username + "/" + repoId,
|
| 90 |
-
};
|
| 91 |
-
|
| 92 |
-
try {
|
| 93 |
-
await deleteRepo({
|
| 94 |
-
repo,
|
| 95 |
-
accessToken: token as string,
|
| 96 |
-
});
|
| 97 |
-
|
| 98 |
-
return NextResponse.json({ success: true }, { status: 200 });
|
| 99 |
-
} catch (error) {
|
| 100 |
-
const errMsg =
|
| 101 |
-
error instanceof Error ? error.message : "Failed to delete project";
|
| 102 |
-
return NextResponse.json({ error: errMsg }, { status: 500 });
|
| 103 |
-
}
|
| 104 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/projects/route.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
| 1 |
-
import { NextResponse } from "next/server";
|
| 2 |
-
import { RepoDesignation, createRepo, uploadFiles } from "@huggingface/hub";
|
| 3 |
-
|
| 4 |
-
import { auth } from "@/lib/auth";
|
| 5 |
-
import {
|
| 6 |
-
COLORS,
|
| 7 |
-
EMOJIS_FOR_SPACE,
|
| 8 |
-
injectDeepSiteBadge,
|
| 9 |
-
isIndexPage,
|
| 10 |
-
} from "@/lib/utils";
|
| 11 |
-
|
| 12 |
-
// todo: catch error while publishing project, and return the error to the user
|
| 13 |
-
// if space has been created, but can't push, try again or catch well the error and return the error to the user
|
| 14 |
-
|
| 15 |
-
export async function POST(request: Request) {
|
| 16 |
-
const session = await auth();
|
| 17 |
-
if (!session) {
|
| 18 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 19 |
-
}
|
| 20 |
-
const token = session.accessToken;
|
| 21 |
-
|
| 22 |
-
const body = await request.json();
|
| 23 |
-
const { projectTitle, files, prompt } = body;
|
| 24 |
-
|
| 25 |
-
if (!files) {
|
| 26 |
-
return NextResponse.json(
|
| 27 |
-
{ error: "Project title and files are required" },
|
| 28 |
-
{ status: 400 }
|
| 29 |
-
);
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
const title =
|
| 33 |
-
projectTitle || projectTitle !== "" ? projectTitle : "DeepSite Project";
|
| 34 |
-
|
| 35 |
-
let formattedTitle = title
|
| 36 |
-
.toLowerCase()
|
| 37 |
-
.replace(/[^a-z0-9]+/g, "-")
|
| 38 |
-
.split("-")
|
| 39 |
-
.filter(Boolean)
|
| 40 |
-
.join("-")
|
| 41 |
-
.slice(0, 75);
|
| 42 |
-
|
| 43 |
-
formattedTitle =
|
| 44 |
-
formattedTitle + "-" + Math.random().toString(36).substring(2, 7);
|
| 45 |
-
|
| 46 |
-
const repo: RepoDesignation = {
|
| 47 |
-
type: "space",
|
| 48 |
-
name: session.user?.username + "/" + formattedTitle,
|
| 49 |
-
};
|
| 50 |
-
|
| 51 |
-
// Escape YAML values to prevent injection attacks
|
| 52 |
-
const escapeYamlValue = (value: string): string => {
|
| 53 |
-
if (/[:|>]|^[-*#]|^\s|['"]/.test(value) || value.includes("\n")) {
|
| 54 |
-
return `"${value.replace(/"/g, '\\"')}"`;
|
| 55 |
-
}
|
| 56 |
-
return value;
|
| 57 |
-
};
|
| 58 |
-
|
| 59 |
-
// Escape markdown headers to prevent injection
|
| 60 |
-
const escapeMarkdownHeader = (value: string): string => {
|
| 61 |
-
return value.replace(/^#+\s*/g, "").replace(/\n/g, " ");
|
| 62 |
-
};
|
| 63 |
-
|
| 64 |
-
const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
|
| 65 |
-
const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
|
| 66 |
-
const emoji =
|
| 67 |
-
EMOJIS_FOR_SPACE[Math.floor(Math.random() * EMOJIS_FOR_SPACE.length)];
|
| 68 |
-
const README = `---
|
| 69 |
-
title: ${escapeYamlValue(projectTitle)}
|
| 70 |
-
colorFrom: ${colorFrom}
|
| 71 |
-
colorTo: ${colorTo}
|
| 72 |
-
sdk: static
|
| 73 |
-
emoji: ${emoji}
|
| 74 |
-
tags:
|
| 75 |
-
- deepsite-v4
|
| 76 |
-
---
|
| 77 |
-
|
| 78 |
-
# ${escapeMarkdownHeader(title)}
|
| 79 |
-
|
| 80 |
-
This project has been created with [DeepSite](https://deepsite.hf.co) AI Vibe Coding.
|
| 81 |
-
`;
|
| 82 |
-
|
| 83 |
-
const filesToUpload: File[] = [
|
| 84 |
-
new File([README], "README.md", { type: "text/markdown" }),
|
| 85 |
-
];
|
| 86 |
-
for (const file of files) {
|
| 87 |
-
let mimeType = "text/html";
|
| 88 |
-
if (file.path.endsWith(".css")) {
|
| 89 |
-
mimeType = "text/css";
|
| 90 |
-
} else if (file.path.endsWith(".js")) {
|
| 91 |
-
mimeType = "text/javascript";
|
| 92 |
-
}
|
| 93 |
-
const content =
|
| 94 |
-
mimeType === "text/html" && isIndexPage(file.path)
|
| 95 |
-
? injectDeepSiteBadge(file.content)
|
| 96 |
-
: file.content;
|
| 97 |
-
|
| 98 |
-
filesToUpload.push(new File([content], file.path, { type: mimeType }));
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
let repoUrl: string | undefined;
|
| 102 |
-
|
| 103 |
-
try {
|
| 104 |
-
// Create the space first
|
| 105 |
-
const createResult = await createRepo({
|
| 106 |
-
accessToken: token as string,
|
| 107 |
-
repo: repo,
|
| 108 |
-
sdk: "static",
|
| 109 |
-
});
|
| 110 |
-
repoUrl = createResult.repoUrl;
|
| 111 |
-
|
| 112 |
-
// Escape commit message to prevent injection
|
| 113 |
-
const escapeCommitMessage = (message: string): string => {
|
| 114 |
-
return message.replace(/[\r\n]/g, " ").slice(0, 200);
|
| 115 |
-
};
|
| 116 |
-
const commitMessage = escapeCommitMessage(prompt ?? "Initial DeepSite commit");
|
| 117 |
-
|
| 118 |
-
// Upload files to the created space
|
| 119 |
-
await uploadFiles({
|
| 120 |
-
repo,
|
| 121 |
-
files: filesToUpload,
|
| 122 |
-
accessToken: token as string,
|
| 123 |
-
commitTitle: commitMessage,
|
| 124 |
-
});
|
| 125 |
-
|
| 126 |
-
const path = repoUrl.split("/").slice(-2).join("/");
|
| 127 |
-
|
| 128 |
-
return NextResponse.json({ repoUrl: path }, { status: 200 });
|
| 129 |
-
} catch (error) {
|
| 130 |
-
const errMsg =
|
| 131 |
-
error instanceof Error ? error.message : "Failed to create or upload to space";
|
| 132 |
-
|
| 133 |
-
// If space was created but upload failed, include the repo URL in the error
|
| 134 |
-
if (repoUrl) {
|
| 135 |
-
const path = repoUrl.split("/").slice(-2).join("/");
|
| 136 |
-
return NextResponse.json({
|
| 137 |
-
error: `${errMsg}. Space was created at ${path} but files could not be uploaded.`,
|
| 138 |
-
repoUrl: path,
|
| 139 |
-
partialSuccess: true
|
| 140 |
-
}, { status: 500 });
|
| 141 |
-
}
|
| 142 |
-
|
| 143 |
-
return NextResponse.json({ error: errMsg }, { status: 500 });
|
| 144 |
-
}
|
| 145 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/re-design/route.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
|
| 3 |
+
export async function PUT(request: NextRequest) {
|
| 4 |
+
const body = await request.json();
|
| 5 |
+
const { url } = body;
|
| 6 |
+
|
| 7 |
+
if (!url) {
|
| 8 |
+
return NextResponse.json({ error: "URL is required" }, { status: 400 });
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
try {
|
| 12 |
+
const response = await fetch(
|
| 13 |
+
`https://r.jina.ai/${encodeURIComponent(url)}`,
|
| 14 |
+
{
|
| 15 |
+
method: "POST",
|
| 16 |
+
}
|
| 17 |
+
);
|
| 18 |
+
if (!response.ok) {
|
| 19 |
+
return NextResponse.json(
|
| 20 |
+
{ error: "Failed to fetch redesign" },
|
| 21 |
+
{ status: 500 }
|
| 22 |
+
);
|
| 23 |
+
}
|
| 24 |
+
const markdown = await response.text();
|
| 25 |
+
return NextResponse.json(
|
| 26 |
+
{
|
| 27 |
+
ok: true,
|
| 28 |
+
markdown,
|
| 29 |
+
},
|
| 30 |
+
{ status: 200 }
|
| 31 |
+
);
|
| 32 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 33 |
+
} catch (error: any) {
|
| 34 |
+
return NextResponse.json(
|
| 35 |
+
{ error: error.message || "An error occurred" },
|
| 36 |
+
{ status: 500 }
|
| 37 |
+
);
|
| 38 |
+
}
|
| 39 |
+
}
|
app/api/redesign/route.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
| 1 |
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
-
import { NextRequest, NextResponse } from "next/server";
|
| 3 |
-
|
| 4 |
-
const FETCH_TIMEOUT = 30_000;
|
| 5 |
-
export const maxDuration = 60;
|
| 6 |
-
|
| 7 |
-
export async function PUT(request: NextRequest) {
|
| 8 |
-
const body = await request.json();
|
| 9 |
-
const { url } = body;
|
| 10 |
-
|
| 11 |
-
if (!url) {
|
| 12 |
-
return NextResponse.json({ error: "URL is required" }, { status: 400 });
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
try {
|
| 16 |
-
const controller = new AbortController();
|
| 17 |
-
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
|
| 18 |
-
|
| 19 |
-
try {
|
| 20 |
-
const response = await fetch(
|
| 21 |
-
`https://r.jina.ai/${encodeURIComponent(url)}`,
|
| 22 |
-
{
|
| 23 |
-
method: "POST",
|
| 24 |
-
signal: controller.signal,
|
| 25 |
-
}
|
| 26 |
-
);
|
| 27 |
-
|
| 28 |
-
clearTimeout(timeoutId);
|
| 29 |
-
|
| 30 |
-
if (!response.ok) {
|
| 31 |
-
return NextResponse.json(
|
| 32 |
-
{ error: "Failed to fetch redesign" },
|
| 33 |
-
{ status: 500 }
|
| 34 |
-
);
|
| 35 |
-
}
|
| 36 |
-
const markdown = await response.text();
|
| 37 |
-
return NextResponse.json(
|
| 38 |
-
{
|
| 39 |
-
ok: true,
|
| 40 |
-
markdown,
|
| 41 |
-
},
|
| 42 |
-
{ status: 200 }
|
| 43 |
-
);
|
| 44 |
-
} catch (fetchError: any) {
|
| 45 |
-
clearTimeout(timeoutId);
|
| 46 |
-
|
| 47 |
-
if (fetchError.name === "AbortError") {
|
| 48 |
-
return NextResponse.json(
|
| 49 |
-
{
|
| 50 |
-
error:
|
| 51 |
-
"Request timeout: The external service took too long to respond. Please try again.",
|
| 52 |
-
},
|
| 53 |
-
{ status: 504 }
|
| 54 |
-
);
|
| 55 |
-
}
|
| 56 |
-
throw fetchError;
|
| 57 |
-
}
|
| 58 |
-
} catch (error: any) {
|
| 59 |
-
if (error.name === "AbortError" || error.message?.includes("timeout")) {
|
| 60 |
-
return NextResponse.json(
|
| 61 |
-
{
|
| 62 |
-
error:
|
| 63 |
-
"Request timeout: The external service took too long to respond. Please try again.",
|
| 64 |
-
},
|
| 65 |
-
{ status: 504 }
|
| 66 |
-
);
|
| 67 |
-
}
|
| 68 |
-
return NextResponse.json(
|
| 69 |
-
{ error: error.message || "An error occurred" },
|
| 70 |
-
{ status: 500 }
|
| 71 |
-
);
|
| 72 |
-
}
|
| 73 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/auth/callback/page.tsx
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import Link from "next/link";
|
| 3 |
+
import { useUser } from "@/hooks/useUser";
|
| 4 |
+
import { use, useState } from "react";
|
| 5 |
+
import { useMount, useTimeoutFn } from "react-use";
|
| 6 |
+
|
| 7 |
+
import { Button } from "@/components/ui/button";
|
| 8 |
+
export default function AuthCallback({
|
| 9 |
+
searchParams,
|
| 10 |
+
}: {
|
| 11 |
+
searchParams: Promise<{ code: string }>;
|
| 12 |
+
}) {
|
| 13 |
+
const [showButton, setShowButton] = useState(false);
|
| 14 |
+
const { code } = use(searchParams);
|
| 15 |
+
const { loginFromCode } = useUser();
|
| 16 |
+
|
| 17 |
+
useMount(async () => {
|
| 18 |
+
if (code) {
|
| 19 |
+
await loginFromCode(code);
|
| 20 |
+
}
|
| 21 |
+
});
|
| 22 |
+
|
| 23 |
+
useTimeoutFn(
|
| 24 |
+
() => setShowButton(true),
|
| 25 |
+
7000 // Show button after 5 seconds
|
| 26 |
+
);
|
| 27 |
+
|
| 28 |
+
return (
|
| 29 |
+
<div className="h-screen flex flex-col justify-center items-center">
|
| 30 |
+
<div className="!rounded-2xl !p-0 !bg-white !border-neutral-100 min-w-xs text-center overflow-hidden ring-[8px] ring-white/20">
|
| 31 |
+
<header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
|
| 32 |
+
<div className="flex items-center justify-center -space-x-4 mb-3">
|
| 33 |
+
<div className="size-9 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
| 34 |
+
🚀
|
| 35 |
+
</div>
|
| 36 |
+
<div className="size-11 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-2xl z-2">
|
| 37 |
+
👋
|
| 38 |
+
</div>
|
| 39 |
+
<div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
| 40 |
+
🙌
|
| 41 |
+
</div>
|
| 42 |
+
</div>
|
| 43 |
+
<p className="text-xl font-semibold text-neutral-950">
|
| 44 |
+
Login In Progress...
|
| 45 |
+
</p>
|
| 46 |
+
<p className="text-sm text-neutral-500 mt-1.5">
|
| 47 |
+
Wait a moment while we log you in with your code.
|
| 48 |
+
</p>
|
| 49 |
+
</header>
|
| 50 |
+
<main className="space-y-4 p-6">
|
| 51 |
+
<div>
|
| 52 |
+
<p className="text-sm text-neutral-700 mb-4 max-w-xs">
|
| 53 |
+
If you are not redirected automatically in the next 5 seconds,
|
| 54 |
+
please click the button below
|
| 55 |
+
</p>
|
| 56 |
+
{showButton ? (
|
| 57 |
+
<Link href="/">
|
| 58 |
+
<Button variant="black" className="relative">
|
| 59 |
+
Go to Home
|
| 60 |
+
</Button>
|
| 61 |
+
</Link>
|
| 62 |
+
) : (
|
| 63 |
+
<p className="text-xs text-neutral-500">
|
| 64 |
+
Please wait, we are logging you in...
|
| 65 |
+
</p>
|
| 66 |
+
)}
|
| 67 |
+
</div>
|
| 68 |
+
</main>
|
| 69 |
+
</div>
|
| 70 |
+
</div>
|
| 71 |
+
);
|
| 72 |
+
}
|
app/auth/page.tsx
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { redirect } from "next/navigation";
|
| 2 |
+
import { Metadata } from "next";
|
| 3 |
+
|
| 4 |
+
import { getAuth } from "@/app/actions/auth";
|
| 5 |
+
|
| 6 |
+
export const revalidate = 1;
|
| 7 |
+
|
| 8 |
+
export const metadata: Metadata = {
|
| 9 |
+
robots: "noindex, nofollow",
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
export default async function Auth() {
|
| 13 |
+
const loginRedirectUrl = await getAuth();
|
| 14 |
+
if (loginRedirectUrl) {
|
| 15 |
+
redirect(loginRedirectUrl);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
return (
|
| 19 |
+
<div className="p-4">
|
| 20 |
+
<div className="border bg-red-500/10 border-red-500/20 text-red-500 px-5 py-3 rounded-lg">
|
| 21 |
+
<h1 className="text-xl font-bold">Error</h1>
|
| 22 |
+
<p className="text-sm">
|
| 23 |
+
An error occurred while trying to log in. Please try again later.
|
| 24 |
+
</p>
|
| 25 |
+
</div>
|
| 26 |
+
</div>
|
| 27 |
+
);
|
| 28 |
+
}
|
app/layout.tsx
CHANGED
|
@@ -1,33 +1,53 @@
|
|
|
|
|
| 1 |
import type { Metadata, Viewport } from "next";
|
| 2 |
-
import {
|
| 3 |
-
import {
|
| 4 |
-
import Script from "next/script";
|
| 5 |
|
| 6 |
-
import "@/
|
| 7 |
-
import
|
| 8 |
-
import { AuthProvider } from "@/components/providers/session";
|
| 9 |
import { Toaster } from "@/components/ui/sonner";
|
| 10 |
-
import
|
| 11 |
-
import {
|
| 12 |
-
import
|
|
|
|
| 13 |
|
| 14 |
-
const
|
| 15 |
-
variable: "--font-
|
| 16 |
subsets: ["latin"],
|
| 17 |
});
|
| 18 |
|
| 19 |
-
const
|
| 20 |
-
variable: "--font-
|
| 21 |
subsets: ["latin"],
|
|
|
|
| 22 |
});
|
| 23 |
|
| 24 |
export const metadata: Metadata = {
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
title: "DeepSite | Build with AI ✨",
|
| 27 |
description:
|
| 28 |
"DeepSite is a web development tool that helps you build websites with AI, no code required. Let's deploy your website with DeepSite and enjoy the magic of AI.",
|
| 29 |
-
|
| 30 |
-
}
|
| 31 |
appleWebApp: {
|
| 32 |
capable: true,
|
| 33 |
title: "DeepSite",
|
|
@@ -38,70 +58,50 @@ export const metadata: Metadata = {
|
|
| 38 |
shortcut: "/logo.svg",
|
| 39 |
apple: "/logo.svg",
|
| 40 |
},
|
| 41 |
-
verification: {
|
| 42 |
-
google: process.env.GOOGLE_SITE_VERIFICATION,
|
| 43 |
-
},
|
| 44 |
};
|
| 45 |
|
| 46 |
export const viewport: Viewport = {
|
| 47 |
initialScale: 1,
|
| 48 |
maximumScale: 1,
|
| 49 |
-
themeColor: "#
|
| 50 |
};
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
export default async function RootLayout({
|
| 53 |
children,
|
| 54 |
}: Readonly<{
|
| 55 |
children: React.ReactNode;
|
| 56 |
}>) {
|
| 57 |
-
const
|
| 58 |
-
name: "DeepSite",
|
| 59 |
-
description: "Build websites with AI, no code required",
|
| 60 |
-
url: "https://deepsite.hf.co",
|
| 61 |
-
});
|
| 62 |
-
const organizationData = generateStructuredData("Organization", {
|
| 63 |
-
name: "DeepSite",
|
| 64 |
-
url: "https://deepsite.hf.co",
|
| 65 |
-
});
|
| 66 |
-
|
| 67 |
return (
|
| 68 |
-
<html lang="en"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
<body
|
| 70 |
-
className={`${
|
| 71 |
>
|
| 72 |
-
<
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
}}
|
| 77 |
-
/>
|
| 78 |
-
<script
|
| 79 |
-
type="application/ld+json"
|
| 80 |
-
dangerouslySetInnerHTML={{
|
| 81 |
-
__html: JSON.stringify(organizationData),
|
| 82 |
-
}}
|
| 83 |
-
/>
|
| 84 |
-
<Script
|
| 85 |
-
defer
|
| 86 |
-
data-domain="deepsite.hf.co"
|
| 87 |
-
src="https://plausible.io/js/script.js"
|
| 88 |
-
/>
|
| 89 |
-
<Toaster richColors />
|
| 90 |
-
<AuthProvider>
|
| 91 |
-
<ReactQueryProvider>
|
| 92 |
-
<ThemeProvider
|
| 93 |
-
attribute="class"
|
| 94 |
-
defaultTheme="dark"
|
| 95 |
-
enableSystem
|
| 96 |
-
disableTransitionOnChange
|
| 97 |
-
>
|
| 98 |
-
<NextStepProvider>
|
| 99 |
-
{children}
|
| 100 |
-
<NotAuthorizedDomain />
|
| 101 |
-
</NextStepProvider>
|
| 102 |
-
</ThemeProvider>
|
| 103 |
-
</ReactQueryProvider>
|
| 104 |
-
</AuthProvider>
|
| 105 |
</body>
|
| 106 |
</html>
|
| 107 |
);
|
|
|
|
| 1 |
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
import type { Metadata, Viewport } from "next";
|
| 3 |
+
import { Inter, PT_Sans } from "next/font/google";
|
| 4 |
+
import { cookies } from "next/headers";
|
|
|
|
| 5 |
|
| 6 |
+
import TanstackProvider from "@/components/providers/tanstack-query-provider";
|
| 7 |
+
import "@/assets/globals.css";
|
|
|
|
| 8 |
import { Toaster } from "@/components/ui/sonner";
|
| 9 |
+
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
| 10 |
+
import { apiServer } from "@/lib/api";
|
| 11 |
+
import AppContext from "@/components/contexts/app-context";
|
| 12 |
+
import Script from "next/script";
|
| 13 |
|
| 14 |
+
const inter = Inter({
|
| 15 |
+
variable: "--font-inter-sans",
|
| 16 |
subsets: ["latin"],
|
| 17 |
});
|
| 18 |
|
| 19 |
+
const ptSans = PT_Sans({
|
| 20 |
+
variable: "--font-ptSans-mono",
|
| 21 |
subsets: ["latin"],
|
| 22 |
+
weight: ["400", "700"],
|
| 23 |
});
|
| 24 |
|
| 25 |
export const metadata: Metadata = {
|
| 26 |
+
title: "DeepSite | Build with AI ✨",
|
| 27 |
+
description:
|
| 28 |
+
"DeepSite is a web development tool that helps you build websites with AI, no code required. Let's deploy your website with DeepSite and enjoy the magic of AI.",
|
| 29 |
+
openGraph: {
|
| 30 |
+
title: "DeepSite | Build with AI ✨",
|
| 31 |
+
description:
|
| 32 |
+
"DeepSite is a web development tool that helps you build websites with AI, no code required. Let's deploy your website with DeepSite and enjoy the magic of AI.",
|
| 33 |
+
url: "https://deepsite.hf.co",
|
| 34 |
+
siteName: "DeepSite",
|
| 35 |
+
images: [
|
| 36 |
+
{
|
| 37 |
+
url: "https://deepsite.hf.co/banner.png",
|
| 38 |
+
width: 1200,
|
| 39 |
+
height: 630,
|
| 40 |
+
alt: "DeepSite Open Graph Image",
|
| 41 |
+
},
|
| 42 |
+
],
|
| 43 |
+
},
|
| 44 |
+
twitter: {
|
| 45 |
+
card: "summary_large_image",
|
| 46 |
title: "DeepSite | Build with AI ✨",
|
| 47 |
description:
|
| 48 |
"DeepSite is a web development tool that helps you build websites with AI, no code required. Let's deploy your website with DeepSite and enjoy the magic of AI.",
|
| 49 |
+
images: ["https://deepsite.hf.co/banner.png"],
|
| 50 |
+
},
|
| 51 |
appleWebApp: {
|
| 52 |
capable: true,
|
| 53 |
title: "DeepSite",
|
|
|
|
| 58 |
shortcut: "/logo.svg",
|
| 59 |
apple: "/logo.svg",
|
| 60 |
},
|
|
|
|
|
|
|
|
|
|
| 61 |
};
|
| 62 |
|
| 63 |
export const viewport: Viewport = {
|
| 64 |
initialScale: 1,
|
| 65 |
maximumScale: 1,
|
| 66 |
+
themeColor: "#000000",
|
| 67 |
};
|
| 68 |
|
| 69 |
+
async function getMe() {
|
| 70 |
+
const cookieStore = await cookies();
|
| 71 |
+
const token = cookieStore.get(MY_TOKEN_KEY())?.value;
|
| 72 |
+
if (!token) return { user: null, errCode: null };
|
| 73 |
+
try {
|
| 74 |
+
const res = await apiServer.get("/me", {
|
| 75 |
+
headers: {
|
| 76 |
+
Authorization: `Bearer ${token}`,
|
| 77 |
+
},
|
| 78 |
+
});
|
| 79 |
+
return { user: res.data.user, errCode: null };
|
| 80 |
+
} catch (err: any) {
|
| 81 |
+
return { user: null, errCode: err.status };
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
export default async function RootLayout({
|
| 86 |
children,
|
| 87 |
}: Readonly<{
|
| 88 |
children: React.ReactNode;
|
| 89 |
}>) {
|
| 90 |
+
const data = await getMe();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
return (
|
| 92 |
+
<html lang="en">
|
| 93 |
+
<Script
|
| 94 |
+
defer
|
| 95 |
+
data-domain="deepsite.hf.co"
|
| 96 |
+
src="https://plausible.io/js/script.js"
|
| 97 |
+
></Script>
|
| 98 |
<body
|
| 99 |
+
className={`${inter.variable} ${ptSans.variable} antialiased bg-black dark h-[100dvh] overflow-hidden`}
|
| 100 |
>
|
| 101 |
+
<Toaster richColors position="bottom-center" />
|
| 102 |
+
<TanstackProvider>
|
| 103 |
+
<AppContext me={data}>{children}</AppContext>
|
| 104 |
+
</TanstackProvider>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
</body>
|
| 106 |
</html>
|
| 107 |
);
|