npm package: @ma7moudsalama/falak-app
Description: Production-ready Vue 3 boilerplate with Firebase, PrimeVue, Tailwind, i18n, Groq AI, Paymob & more
Author: MA7MOUD SALAMA
Latest version: 1.0.2

# فَلَك · Falak App

> **Production-ready Vue 3 boilerplate** with Firebase, PrimeVue, Tailwind CSS, i18n (Arabic + English), Groq AI, Paymob payments, offline IndexedDB sync, and AES encryption — all wired up and ready to build on.

[![npm version](https://img.shields.io/npm/v/falak-app)](https://www.npmjs.com/package/@ma7moudsalama/falak-app)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

---

## Quick Start

```bash
# Scaffold a new project
npx @ma7moudsalama/falak-app create my-project

# Or install globally
npm i -g @ma7moudsalama/falak-app
falak create my-project

# Then
cd my-project
cp .env.example .env     # fill in your keys
npm run dev
```

---

## What's Inside

| Layer | Package | Status |
|---|---|---|
| Framework | Vue 3 + Vite | ✅ Configured |
| UI Library | PrimeVue 4 (Aura theme) | ✅ Configured |
| Styling | Tailwind CSS 3 | ✅ Configured |
| State | Pinia | ✅ Configured |
| Routing | Vue Router 4 | ✅ Configured + guards |
| Translation | Vue I18n 10 (EN + AR/RTL) | ✅ Configured |
| Auth | Firebase Auth (Email, Google, Facebook) | ✅ Composable ready |
| Database | Firebase Realtime DB | ✅ Composable ready |
| Offline | IndexedDB via `idb` | ✅ Auto-sync |
| Encryption | AES-256 via `crypto-js` | ✅ Composable ready |
| AI | Groq SDK (LLaMA 3.3) | ✅ Composable ready |
| Payments | Paymob | ✅ Composable + subscriptions |

---

## Project Structure

```
src/
├── assets/
│   └── main.css              ← Tailwind + custom utilities
├── components/
│   └── layout/
│       └── AppLayout.vue     ← Sidebar + topbar layout shell
├── composables/
│   ├── useAuth.js            ← Firebase auth + roles + sessions
│   ├── useDatabase.js        ← RTDB + IndexedDB offline manager
│   ├── useCrypto.js          ← AES encrypt/decrypt
│   ├── useGroq.js            ← Groq AI chat + streaming
│   └── usePaymob.js          ← Paymob payments + subscriptions
├── firebase/
│   └── index.js              ← Firebase app init + all services
├── i18n/
│   ├── index.js              ← Vue I18n setup + locale switcher
│   └── locales/
│       ├── en.json
│       └── ar.json
├── router/
│   └── index.js              ← Routes + auth/role guards
├── stores/
│   └── auth.js               ← Pinia wrapper for useAuth
├── views/
│   ├── auth/
│   │   ├── LoginView.vue
│   │   ├── RegisterView.vue
│   │   └── ForgotView.vue
│   ├── HomeView.vue
│   ├── DashboardView.vue     ← Live data + AI + subscription demo
│   ├── ProfileView.vue
│   ├── AdminView.vue
│   └── NotFoundView.vue
├── App.vue
└── main.js
```

---

## Configuration

### 1. Firebase

Edit `src/firebase/index.js` or set your `.env`:

```env
VITE_FIREBASE_API_KEY=...
VITE_FIREBASE_AUTH_DOMAIN=...
VITE_FIREBASE_DATABASE_URL=...
VITE_FIREBASE_PROJECT_ID=...
VITE_FIREBASE_STORAGE_BUCKET=...
VITE_FIREBASE_MESSAGING_SENDER_ID=...
VITE_FIREBASE_APP_ID=...
```

Enable in Firebase Console:
- Authentication → Email/Password, Google, Facebook
- Realtime Database → create database
- Upload `firebase-rules.json` as your security rules

### 2. Groq AI

```env
VITE_GROQ_API_KEY=gsk_...
VITE_GROQ_MODEL=llama-3.3-70b-versatile
```

Get your key at [console.groq.com](https://console.groq.com).

### 3. Paymob

```env
VITE_PAYMOB_API_KEY=...
VITE_PAYMOB_INTEGRATION_ID=...
VITE_PAYMOB_IFRAME_ID=...
VITE_PAYMOB_HMAC_SECRET=...
```

Get credentials from [accept.paymob.com](https://accept.paymob.com).

### 4. Encryption

```env
VITE_ENCRYPTION_KEY=your_strong_random_secret
```

---

## Composables API

### `useAuth()`

```js
import { useAuth } from '@/composables/useAuth.js'

const {
  currentUser,      // Ref<FirebaseUser | null>
  userProfile,      // Ref<Object>        — RTDB profile
  userRole,         // Ref<string>        — live from RTDB
  isAuthenticated,  // ComputedRef<bool>
  isAdmin,          // ComputedRef<bool>
  isSuperAdmin,     // ComputedRef<bool>
  isAuthReady,      // Ref<bool>          — true after first auth check
  isLoading,        // Ref<bool>
  authError,        // Ref<string | null>

  register,         // ({ email, password, displayName }) → { success, user }
  login,            // ({ email, password })              → { success, user }
  logout,           //                                    → { success }
  resetPassword,    // (email)                            → { success }
  changePassword,   // ({ currentPassword, newPassword }) → { success }
  loginWithGoogle,  // (useRedirect?)                     → { success, user }
  loginWithFacebook,// (useRedirect?)                     → { success, user }
  updateUserProfile,// (data)                             → { success }
  setUserRole,      // (uid, role)                        → { success }   ← admin only
  deleteAccount,    //                                    → { success }
  hasPermission,    // (permission) → bool
  hasRole,          // (...roles)   → bool
  ROLES             // { SUPER_ADMIN, ADMIN, USER, GUEST }
} = useAuth()
```

**User roles** are stored in `/users/{uid}/role` in RTDB and listened to in real-time.
**Session data** (online, lastSeen) is written to `/sessions/{uid}` with `onDisconnect` handling.

---

### `useDatabase(options?)`

```js
import { useDatabase } from '@/composables/useDatabase.js'

const db = useDatabase({
  encryptedPaths: ['payments/*'],   // paths to auto-encrypt
  encryptFields:  ['ssn', 'card']   // field names to encrypt
})

// One-time read (offline-aware)
const user = await db.get('users/uid123')

// Write (blocked offline with error)
await db.set('todos/1', { text: 'Buy milk', done: false })
await db.update('todos/1', { done: true })
await db.push('todos', { text: 'New item' })   // → { success, key }
await db.remove('todos/1')

// Real-time listener (reactive ref + IDB sync)
const { data, unsubscribe } = db.listen('todos')
// data.value updates live

// Real-time child events (efficient for large lists)
const { items, unsubscribe } = db.listenList('messages')
// items.value = { key1: {...}, key2: {...} }

// Offline-only reads
const cached = await db.getOffline('todos')
const all    = await db.getAllOffline('todos')

// Network status
db.isOnline   // Ref<bool>
```

**Offline behavior:**
- All write operations return `{ success: false, error: 'You are offline. Data is read-only.' }` when offline.
- Read operations return IndexedDB cached data.
- Remote changes are auto-synced to IDB when online.

---

### `useCrypto()`

```js
import { useCrypto } from '@/composables/useCrypto.js'

const { encrypt, decrypt, encryptObject, decryptObject,
        encryptFields, decryptFields, hash, hmac, randomToken } = useCrypto()

const cipher = encrypt('sensitive data')
const plain  = decrypt(cipher)

const encObj = encryptObject({ ssn: '123-45-6789', name: 'Max' })
const obj    = decryptObject(encObj)

// Encrypt only specific fields
const safe = encryptFields(userData, ['ssn', 'bankAccount'])
const back = decryptFields(safe, ['ssn', 'bankAccount'])

// SHA256 hash
const hashed = hash('mypassword')

// HMAC (used by Paymob webhook verification)
const sig = hmac('message', 'secret')
```

---

### `useGroq(systemPrompt?)`

```js
import { useGroq } from '@/composables/useGroq.js'

const groq = useGroq('You are a helpful assistant.')

// Single prompt
const reply = await groq.chat('What is Vue.js?')

// Streaming
await groq.streamChat('Tell me a story', (chunk, full) => {
  output.value = full
})

// Multi-turn conversation
await groq.sendConversation('Hello!')
await groq.sendConversation('What did I just say?')

// Streaming multi-turn
await groq.streamConversation('Continue...', (chunk) => { ... })

// Audio transcription (Whisper)
const text = await groq.transcribe(audioBlob, 'ar')

groq.clearConversation()
groq.setSystemPrompt('New system prompt')
```

---

### `usePaymob()` + `useSubscription()`

```js
import { usePaymob, useSubscription, PLANS } from '@/composables/usePaymob.js'

// ── Payments ──
const paymob = usePaymob()

// Full payment flow → opens iframe URL
const result = await paymob.initPayment({
  amountCents: 14900,       // 149.00 EGP
  currency:    'EGP',
  billingData: { ... }
})
// result.iframeUrl  → embed or open

// Open in new tab
await paymob.openPaymentTab({ amountCents: 14900, ... })

// Verify Paymob webhook HMAC
const valid = paymob.verifyHmac(callbackData)

// ── Subscriptions ──
const sub = useSubscription()

// Initialize free plan for new users
await sub.initFreeplan(userId)

// Activate after successful payment
await sub.activateSubscription(userId, 'PRO', { paymobOrderId: 12345, amountCents: 14900 })

// Real-time subscription listener
const { data } = sub.listenSubscription(userId)
// data.value = { planId, status, expiresAt, features, ... }

// Feature gating
const canUseAPI = await sub.hasFeature(userId, 'api_access')
const active    = await sub.isActive(userId)

await sub.cancelSubscription(userId)
```

**Subscription data in RTDB** (`/subscriptions/{uid}`):
```json
{
  "planId":    "pro",
  "planName":  "Pro",
  "status":    "active",
  "features":  ["read", "write", "advanced_features", "api_access"],
  "startedAt": 1700000000000,
  "expiresAt": 1702592000000,
  "autoRenew": true,
  "paymentHistory": [{ "paidAt": 1700000000000, "amountCents": 14900 }]
}
```

---

## i18n

```js
import { setLocale } from '@/i18n/index.js'

setLocale('ar')   // switches to Arabic + sets dir="rtl"
setLocale('en')   // switches to English + sets dir="ltr"
```

In templates:
```vue
<p>{{ $t('auth.email') }}</p>
<p>{{ $d(date, 'short') }}</p>
<p>{{ $n(149, 'egp') }}</p>
```

To add a new language, add its locale file to `src/i18n/locales/` and register it in `src/i18n/index.js`.

---

## Security Rules

Upload `firebase-rules.json` to your Firebase Realtime Database rules:

```
Firebase Console → Realtime Database → Rules → paste contents
```

---

## Scripts

```bash
npm run dev       # Start dev server (http://localhost:5173)
npm run build     # Production build → dist/
npm run preview   # Preview production build
npm run lint      # Lint + auto-fix
```

---

## License

MIT © Falak App