@ma7moudsalama/falak-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +378 -0
  2. package/bin/falak.js +157 -0
  3. package/index.js +5 -0
  4. package/lib/scaffold.js +23 -0
  5. package/package.json +46 -0
  6. package/template/_env.example +34 -0
  7. package/template/_gitignore +8 -0
  8. package/template/firebase-rules.json +36 -0
  9. package/template/index.html +21 -0
  10. package/template/package.json +36 -0
  11. package/template/postcss.config.js +6 -0
  12. package/template/public/favicon.svg +5 -0
  13. package/template/src/App.vue +95 -0
  14. package/template/src/assets/main.css +100 -0
  15. package/template/src/components/layout/AppLayout.vue +163 -0
  16. package/template/src/composables/useAuth.js +393 -0
  17. package/template/src/composables/useCrypto.js +153 -0
  18. package/template/src/composables/useDatabase.js +341 -0
  19. package/template/src/composables/useGroq.js +237 -0
  20. package/template/src/composables/usePaymob.js +392 -0
  21. package/template/src/firebase/index.js +87 -0
  22. package/template/src/i18n/index.js +66 -0
  23. package/template/src/i18n/locales/ar.json +121 -0
  24. package/template/src/i18n/locales/en.json +121 -0
  25. package/template/src/main.js +59 -0
  26. package/template/src/router/index.js +127 -0
  27. package/template/src/stores/auth.js +14 -0
  28. package/template/src/views/AdminView.vue +67 -0
  29. package/template/src/views/DashboardView.vue +253 -0
  30. package/template/src/views/HomeView.vue +13 -0
  31. package/template/src/views/NotFoundView.vue +8 -0
  32. package/template/src/views/ProfileView.vue +134 -0
  33. package/template/src/views/auth/ForgotView.vue +57 -0
  34. package/template/src/views/auth/LoginView.vue +169 -0
  35. package/template/src/views/auth/RegisterView.vue +103 -0
  36. package/template/tailwind.config.js +41 -0
  37. package/template/vite.config.js +29 -0
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" dir="ltr">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <meta name="theme-color" content="#2563eb" />
8
+ <title>__APP_NAME__</title>
9
+ <!-- Google Fonts: Inter + Cairo (Arabic) -->
10
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
12
+ <link
13
+ href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap"
14
+ rel="stylesheet"
15
+ />
16
+ </head>
17
+ <body>
18
+ <div id="app"></div>
19
+ <script type="module" src="/src/main.js"></script>
20
+ </body>
21
+ </html>
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "falak-project",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "lint": "eslint . --fix"
11
+ },
12
+ "dependencies": {
13
+ "@primevue/themes": "^4.2.0",
14
+ "axios": "^1.7.4",
15
+ "crypto-js": "^4.2.0",
16
+ "firebase": "^11.0.0",
17
+ "groq-sdk": "^0.7.0",
18
+ "idb": "^8.0.0",
19
+ "pinia": "^2.2.0",
20
+ "primevue": "^4.2.0",
21
+ "vue": "^3.5.0",
22
+ "vue-i18n": "^10.0.0",
23
+ "vue-router": "^4.4.0"
24
+ },
25
+ "devDependencies": {
26
+ "@tailwindcss/forms": "^0.5.9",
27
+ "@tailwindcss/typography": "^0.5.15",
28
+ "@vitejs/plugin-vue": "^5.1.0",
29
+ "autoprefixer": "^10.4.20",
30
+ "eslint": "^9.0.0",
31
+ "eslint-plugin-vue": "^9.28.0",
32
+ "postcss": "^8.4.47",
33
+ "tailwindcss": "^3.4.14",
34
+ "vite": "^5.4.0"
35
+ }
36
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {}
5
+ }
6
+ }
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <rect width="32" height="32" rx="8" fill="#2563eb"/>
3
+ <text x="16" y="23" font-family="Arial,sans-serif" font-size="20" font-weight="bold"
4
+ fill="white" text-anchor="middle">F</text>
5
+ </svg>
@@ -0,0 +1,95 @@
1
+ <template>
2
+ <div :class="{ dark: isDark }" :dir="locale === 'ar' ? 'rtl' : 'ltr'">
3
+ <!-- Offline Banner -->
4
+ <Transition name="slide-down">
5
+ <div
6
+ v-if="!isOnline"
7
+ class="fixed top-0 inset-x-0 z-50 bg-amber-500 text-white text-sm font-medium
8
+ text-center py-2 px-4 flex items-center justify-center gap-2"
9
+ >
10
+ <i class="pi pi-wifi text-base" />
11
+ {{ $t('offline.message') }}
12
+ </div>
13
+ </Transition>
14
+
15
+ <!-- Router View -->
16
+ <RouterView v-slot="{ Component, route }">
17
+ <Transition :name="route.meta.transition || 'fade'" mode="out-in">
18
+ <component :is="Component" :key="route.path" />
19
+ </Transition>
20
+ </RouterView>
21
+
22
+ <!-- Global Toast -->
23
+ <Toast position="top-right" />
24
+
25
+ <!-- Global Confirm Dialog -->
26
+ <ConfirmDialog />
27
+ </div>
28
+ </template>
29
+
30
+ <script setup>
31
+ import { ref, watch, onMounted } from 'vue'
32
+ import { useI18n } from 'vue-i18n'
33
+ import { RouterView } from 'vue-router'
34
+ import Toast from 'primevue/toast'
35
+ import ConfirmDialog from 'primevue/confirmdialog'
36
+ import { useDatabase } from '@/composables/useDatabase.js'
37
+
38
+ const { locale } = useI18n()
39
+ const db = useDatabase()
40
+ const { isOnline } = db
41
+
42
+ // ── Dark mode ──────────────────────────────────
43
+ const isDark = ref(
44
+ localStorage.getItem('falak_theme') === 'dark' ||
45
+ (!localStorage.getItem('falak_theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)
46
+ )
47
+
48
+ // Expose toggle for child components via provide
49
+ import { provide } from 'vue'
50
+ provide('isDark', isDark)
51
+ provide('toggleDark', () => {
52
+ isDark.value = !isDark.value
53
+ localStorage.setItem('falak_theme', isDark.value ? 'dark' : 'light')
54
+ })
55
+
56
+ // ── RTL sync ───────────────────────────────────
57
+ watch(locale, (val) => {
58
+ document.documentElement.lang = val
59
+ document.documentElement.dir = val === 'ar' ? 'rtl' : 'ltr'
60
+ }, { immediate: true })
61
+
62
+ onMounted(() => {
63
+ document.documentElement.lang = locale.value
64
+ document.documentElement.dir = locale.value === 'ar' ? 'rtl' : 'ltr'
65
+ })
66
+ </script>
67
+
68
+ <style>
69
+ /* ── Page Transitions ─────────────────────────── */
70
+ .fade-enter-active,
71
+ .fade-leave-active {
72
+ transition: opacity 0.2s ease;
73
+ }
74
+ .fade-enter-from,
75
+ .fade-leave-to {
76
+ opacity: 0;
77
+ }
78
+
79
+ .slide-down-enter-active,
80
+ .slide-down-leave-active {
81
+ transition: transform 0.3s ease, opacity 0.3s ease;
82
+ }
83
+ .slide-down-enter-from,
84
+ .slide-down-leave-to {
85
+ transform: translateY(-100%);
86
+ opacity: 0;
87
+ }
88
+
89
+ .slide-enter-active,
90
+ .slide-leave-active {
91
+ transition: transform 0.25s ease;
92
+ }
93
+ .slide-enter-from { transform: translateX(20px); opacity: 0; }
94
+ .slide-leave-to { transform: translateX(-20px); opacity: 0; }
95
+ </style>
@@ -0,0 +1,100 @@
1
+ /* ── Tailwind Base Layers ─────────────────────── */
2
+ @tailwind base;
3
+ @tailwind components;
4
+ @tailwind utilities;
5
+
6
+ /* ── PrimeVue Icons ───────────────────────────── */
7
+ @import 'primeicons/primeicons.css';
8
+
9
+ /* ── Base Styles ──────────────────────────────── */
10
+ @layer base {
11
+ :root {
12
+ --font-sans: 'Inter', ui-sans-serif, system-ui;
13
+ --font-arabic: 'Cairo', ui-sans-serif, system-ui;
14
+ }
15
+
16
+ html {
17
+ font-family: var(--font-sans);
18
+ -webkit-font-smoothing: antialiased;
19
+ -moz-osx-font-smoothing: grayscale;
20
+ }
21
+
22
+ html[dir='rtl'],
23
+ html[lang='ar'] {
24
+ font-family: var(--font-arabic);
25
+ }
26
+
27
+ body {
28
+ @apply bg-white text-gray-900 dark:bg-gray-950 dark:text-gray-100;
29
+ }
30
+
31
+ * {
32
+ @apply border-gray-200 dark:border-gray-800;
33
+ }
34
+
35
+ /* Smooth scrolling */
36
+ html {
37
+ scroll-behavior: smooth;
38
+ }
39
+ }
40
+
41
+ /* ── Utility Classes ──────────────────────────── */
42
+ @layer components {
43
+ .btn {
44
+ @apply inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-lg
45
+ transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2
46
+ disabled:opacity-50 disabled:cursor-not-allowed;
47
+ }
48
+
49
+ .btn-primary {
50
+ @apply btn bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500;
51
+ }
52
+
53
+ .btn-secondary {
54
+ @apply btn bg-gray-100 text-gray-700 hover:bg-gray-200 focus:ring-gray-400
55
+ dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700;
56
+ }
57
+
58
+ .btn-danger {
59
+ @apply btn bg-red-600 text-white hover:bg-red-700 focus:ring-red-500;
60
+ }
61
+
62
+ .card {
63
+ @apply bg-white dark:bg-gray-900 rounded-xl border shadow-sm p-6;
64
+ }
65
+
66
+ .input {
67
+ @apply w-full px-3 py-2 text-sm border rounded-lg bg-white dark:bg-gray-900
68
+ focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent
69
+ placeholder:text-gray-400 dark:placeholder:text-gray-600;
70
+ }
71
+
72
+ .label {
73
+ @apply block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1;
74
+ }
75
+
76
+ .badge {
77
+ @apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
78
+ }
79
+
80
+ .badge-success {
81
+ @apply badge bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400;
82
+ }
83
+
84
+ .badge-warning {
85
+ @apply badge bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400;
86
+ }
87
+
88
+ .badge-danger {
89
+ @apply badge bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400;
90
+ }
91
+
92
+ .badge-info {
93
+ @apply badge bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400;
94
+ }
95
+
96
+ /* RTL-aware flex direction */
97
+ [dir='rtl'] .rtl-reverse {
98
+ @apply flex-row-reverse;
99
+ }
100
+ }
@@ -0,0 +1,163 @@
1
+ <template>
2
+ <div class="min-h-screen bg-gray-50 dark:bg-gray-950 flex">
3
+ <!-- Sidebar -->
4
+ <aside
5
+ :class="[
6
+ 'fixed inset-y-0 start-0 z-40 flex flex-col bg-white dark:bg-gray-900 border-e shadow-sm',
7
+ 'transition-all duration-300',
8
+ sidebarOpen ? 'w-64' : 'w-16'
9
+ ]"
10
+ >
11
+ <!-- Logo -->
12
+ <div class="flex items-center h-16 px-4 border-b gap-3">
13
+ <div class="w-8 h-8 rounded-lg bg-primary-600 flex items-center justify-center shrink-0">
14
+ <span class="text-white font-bold text-sm">F</span>
15
+ </div>
16
+ <Transition name="fade">
17
+ <span v-if="sidebarOpen" class="font-bold text-gray-900 dark:text-white truncate">
18
+ {{ $t('app.name') }}
19
+ </span>
20
+ </Transition>
21
+ </div>
22
+
23
+ <!-- Nav -->
24
+ <nav class="flex-1 py-4 overflow-y-auto">
25
+ <ul class="space-y-1 px-2">
26
+ <li v-for="item in navItems" :key="item.name">
27
+ <RouterLink
28
+ :to="item.to"
29
+ class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium
30
+ text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800
31
+ hover:text-gray-900 dark:hover:text-white transition-colors group"
32
+ active-class="!bg-primary-50 !text-primary-700 dark:!bg-primary-900/20 dark:!text-primary-400"
33
+ v-tooltip.right="!sidebarOpen ? $t(item.label) : undefined"
34
+ >
35
+ <i :class="`pi ${item.icon} text-base`" />
36
+ <Transition name="fade">
37
+ <span v-if="sidebarOpen">{{ $t(item.label) }}</span>
38
+ </Transition>
39
+ </RouterLink>
40
+ </li>
41
+ </ul>
42
+ </nav>
43
+
44
+ <!-- User / Logout -->
45
+ <div class="border-t p-3">
46
+ <button
47
+ @click="handleLogout"
48
+ class="flex items-center gap-3 w-full px-3 py-2 rounded-lg text-sm font-medium
49
+ text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
50
+ v-tooltip.right="!sidebarOpen ? $t('nav.logout') : undefined"
51
+ >
52
+ <i class="pi pi-sign-out text-base" />
53
+ <Transition name="fade">
54
+ <span v-if="sidebarOpen">{{ $t('nav.logout') }}</span>
55
+ </Transition>
56
+ </button>
57
+ </div>
58
+ </aside>
59
+
60
+ <!-- Main content -->
61
+ <div :class="['flex-1 flex flex-col transition-all duration-300', sidebarOpen ? 'ms-64' : 'ms-16']">
62
+ <!-- Topbar -->
63
+ <header class="sticky top-0 z-30 h-16 bg-white dark:bg-gray-900 border-b flex items-center px-4 gap-4">
64
+ <!-- Toggle sidebar -->
65
+ <button
66
+ @click="sidebarOpen = !sidebarOpen"
67
+ class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-500"
68
+ >
69
+ <i :class="`pi ${sidebarOpen ? 'pi-align-left' : 'pi-bars'}`" />
70
+ </button>
71
+
72
+ <!-- Page title -->
73
+ <h1 class="text-base font-semibold text-gray-900 dark:text-white flex-1">
74
+ {{ pageTitle }}
75
+ </h1>
76
+
77
+ <!-- Actions -->
78
+ <div class="flex items-center gap-2">
79
+ <!-- Language toggle -->
80
+ <button
81
+ @click="toggleLocale"
82
+ class="px-3 py-1.5 text-xs font-semibold rounded-lg border hover:bg-gray-50 dark:hover:bg-gray-800
83
+ text-gray-600 dark:text-gray-400 transition-colors"
84
+ >
85
+ {{ locale === 'ar' ? 'EN' : 'عربي' }}
86
+ </button>
87
+
88
+ <!-- Dark mode -->
89
+ <button
90
+ @click="toggleDark()"
91
+ class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-500"
92
+ >
93
+ <i :class="`pi ${isDark ? 'pi-sun' : 'pi-moon'}`" />
94
+ </button>
95
+
96
+ <!-- User avatar -->
97
+ <div v-if="currentUser" class="flex items-center gap-2">
98
+ <Avatar
99
+ :image="currentUser.photoURL || undefined"
100
+ :label="!currentUser.photoURL ? userInitials : undefined"
101
+ shape="circle"
102
+ class="cursor-pointer"
103
+ size="normal"
104
+ @click="$router.push('/profile')"
105
+ />
106
+ </div>
107
+ </div>
108
+ </header>
109
+
110
+ <!-- Page -->
111
+ <main class="flex-1 p-6">
112
+ <slot />
113
+ </main>
114
+ </div>
115
+ </div>
116
+ </template>
117
+
118
+ <script setup>
119
+ import { ref, computed, inject } from 'vue'
120
+ import { useRouter, useRoute } from 'vue-router'
121
+ import { useI18n } from 'vue-i18n'
122
+ import { useToast } from 'primevue/usetoast'
123
+ import Avatar from 'primevue/avatar'
124
+ import { useAuth } from '@/composables/useAuth.js'
125
+ import { setLocale } from '@/i18n/index.js'
126
+
127
+ const { locale, t } = useI18n()
128
+ const router = useRouter()
129
+ const route = useRoute()
130
+ const toast = useToast()
131
+ const { currentUser, logout } = useAuth()
132
+ const isDark = inject('isDark')
133
+ const toggleDark = inject('toggleDark')
134
+
135
+ const sidebarOpen = ref(true)
136
+
137
+ const navItems = [
138
+ { name: 'dashboard', to: '/dashboard', icon: 'pi-home', label: 'nav.dashboard' },
139
+ { name: 'profile', to: '/profile', icon: 'pi-user', label: 'nav.profile' },
140
+ { name: 'admin', to: '/admin', icon: 'pi-cog', label: 'nav.admin' }
141
+ ]
142
+
143
+ const pageTitle = computed(() => route.meta.title ? t(`nav.${route.name}`, route.meta.title) : t('app.name'))
144
+ const userInitials = computed(() => {
145
+ const name = currentUser.value?.displayName || currentUser.value?.email || '?'
146
+ return name.slice(0, 2).toUpperCase()
147
+ })
148
+
149
+ function toggleLocale() {
150
+ setLocale(locale.value === 'ar' ? 'en' : 'ar')
151
+ }
152
+
153
+ async function handleLogout() {
154
+ await logout()
155
+ toast.add({ severity: 'success', summary: t('auth.logoutSuccess'), life: 2000 })
156
+ router.push('/login')
157
+ }
158
+ </script>
159
+
160
+ <style scoped>
161
+ .fade-enter-active, .fade-leave-active { transition: opacity 0.15s; }
162
+ .fade-enter-from, .fade-leave-to { opacity: 0; }
163
+ </style>