eid-salami 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 (65) hide show
  1. package/DEPLOY.md +199 -0
  2. package/README.md +133 -0
  3. package/backend/.env.example +25 -0
  4. package/backend/.keystone_temp/admin/next.config.js +14 -0
  5. package/backend/.keystone_temp/admin/pages/_app.js +22 -0
  6. package/backend/.keystone_temp/admin/pages/index.js +1 -0
  7. package/backend/.keystone_temp/admin/pages/init.js +5 -0
  8. package/backend/.keystone_temp/admin/pages/no-access.js +3 -0
  9. package/backend/.keystone_temp/admin/pages/orders/[id].js +3 -0
  10. package/backend/.keystone_temp/admin/pages/orders/create.js +3 -0
  11. package/backend/.keystone_temp/admin/pages/orders/index.js +3 -0
  12. package/backend/.keystone_temp/admin/pages/salami-packages/[id].js +3 -0
  13. package/backend/.keystone_temp/admin/pages/salami-packages/create.js +3 -0
  14. package/backend/.keystone_temp/admin/pages/salami-packages/index.js +3 -0
  15. package/backend/.keystone_temp/admin/pages/signin.js +3 -0
  16. package/backend/.keystone_temp/admin/pages/users/[id].js +3 -0
  17. package/backend/.keystone_temp/admin/pages/users/create.js +3 -0
  18. package/backend/.keystone_temp/admin/pages/users/index.js +3 -0
  19. package/backend/.keystone_temp/admin/public/favicon.ico +0 -0
  20. package/backend/.keystone_temp/config.js +369 -0
  21. package/backend/.keystone_temp/config.js.map +7 -0
  22. package/backend/bkash/bkash.service.ts +184 -0
  23. package/backend/keystone/routes.ts +193 -0
  24. package/backend/keystone/schema.ts +143 -0
  25. package/backend/keystone.ts +54 -0
  26. package/backend/package.json +23 -0
  27. package/backend/schema.graphql +530 -0
  28. package/backend/schema.prisma +55 -0
  29. package/backend/seed.ts +53 -0
  30. package/backend/tsconfig.json +15 -0
  31. package/frontend/.env.example +6 -0
  32. package/frontend/index.html +16 -0
  33. package/frontend/package.json +24 -0
  34. package/frontend/src/App.js +11 -0
  35. package/frontend/src/App.tsx +22 -0
  36. package/frontend/src/api/client.js +11 -0
  37. package/frontend/src/api/client.ts +57 -0
  38. package/frontend/src/components/Footer.js +5 -0
  39. package/frontend/src/components/Footer.module.css +27 -0
  40. package/frontend/src/components/Footer.tsx +13 -0
  41. package/frontend/src/components/Navbar.js +6 -0
  42. package/frontend/src/components/Navbar.module.css +54 -0
  43. package/frontend/src/components/Navbar.tsx +16 -0
  44. package/frontend/src/components/PackageCard.js +5 -0
  45. package/frontend/src/components/PackageCard.module.css +64 -0
  46. package/frontend/src/components/PackageCard.tsx +24 -0
  47. package/frontend/src/main.js +7 -0
  48. package/frontend/src/main.tsx +13 -0
  49. package/frontend/src/pages/HomePage.js +31 -0
  50. package/frontend/src/pages/HomePage.module.css +416 -0
  51. package/frontend/src/pages/HomePage.tsx +146 -0
  52. package/frontend/src/pages/OrderPage.js +98 -0
  53. package/frontend/src/pages/OrderPage.module.css +624 -0
  54. package/frontend/src/pages/OrderPage.tsx +221 -0
  55. package/frontend/src/pages/PaymentCallbackPage.js +25 -0
  56. package/frontend/src/pages/PaymentCallbackPage.module.css +38 -0
  57. package/frontend/src/pages/PaymentCallbackPage.tsx +37 -0
  58. package/frontend/src/pages/PaymentResultPage.js +28 -0
  59. package/frontend/src/pages/PaymentResultPage.module.css +182 -0
  60. package/frontend/src/pages/PaymentResultPage.tsx +92 -0
  61. package/frontend/src/styles/global.css +66 -0
  62. package/frontend/src/vite-env.d.ts +5 -0
  63. package/frontend/tsconfig.json +15 -0
  64. package/frontend/vite.config.ts +15 -0
  65. package/package.json +14 -0
@@ -0,0 +1,416 @@
1
+ /* ── Hero ─────────────────────────────────────────────────────────────────── */
2
+ .hero {
3
+ position: relative;
4
+ min-height: calc(100vh - 68px);
5
+ background: linear-gradient(160deg, #0a2e22 0%, #0d3b2e 45%, #0f4535 100%);
6
+ display: grid;
7
+ grid-template-columns: 1fr 1fr;
8
+ align-items: center;
9
+ gap: 2rem;
10
+ padding: 4rem clamp(1.5rem, 6vw, 6rem);
11
+ overflow: hidden;
12
+ }
13
+
14
+ @media (max-width: 768px) {
15
+ .hero { grid-template-columns: 1fr; padding: 3rem 1.5rem; }
16
+ .heroArt { display: none; }
17
+ }
18
+
19
+ .starfield {
20
+ position: absolute;
21
+ inset: 0;
22
+ pointer-events: none;
23
+ }
24
+
25
+ .star {
26
+ position: absolute;
27
+ background: var(--gold-pale);
28
+ border-radius: 50%;
29
+ opacity: 0;
30
+ animation: twinkle 3s ease-in-out infinite;
31
+ }
32
+
33
+ @keyframes twinkle {
34
+ 0%, 100% { opacity: 0; transform: scale(0.5); }
35
+ 50% { opacity: 0.7; transform: scale(1); }
36
+ }
37
+
38
+ .heroContent {
39
+ position: relative;
40
+ z-index: 1;
41
+ display: flex;
42
+ flex-direction: column;
43
+ gap: 1.2rem;
44
+ }
45
+
46
+ .eyebrow {
47
+ font-family: var(--font-bengali);
48
+ font-size: 1rem;
49
+ color: var(--gold-bright);
50
+ font-weight: 600;
51
+ letter-spacing: 0.05em;
52
+ animation: fadeUp 0.6s ease both;
53
+ }
54
+
55
+ .heroTitle {
56
+ font-size: clamp(2.8rem, 5vw, 4.5rem);
57
+ color: var(--cream);
58
+ font-weight: 900;
59
+ line-height: 1.08;
60
+ animation: fadeUp 0.7s ease 0.1s both;
61
+ }
62
+
63
+ .heroTitle em {
64
+ font-style: italic;
65
+ color: var(--gold-bright);
66
+ }
67
+
68
+ .heroSub {
69
+ font-family: var(--font-bengali);
70
+ font-size: 1.1rem;
71
+ color: rgba(253, 247, 236, 0.75);
72
+ max-width: 480px;
73
+ animation: fadeUp 0.7s ease 0.2s both;
74
+ }
75
+
76
+ .heroCtas {
77
+ display: flex;
78
+ gap: 1rem;
79
+ flex-wrap: wrap;
80
+ margin-top: 0.5rem;
81
+ animation: fadeUp 0.7s ease 0.3s both;
82
+ }
83
+
84
+ .btnPrimary {
85
+ display: inline-flex;
86
+ align-items: center;
87
+ gap: 0.4rem;
88
+ font-family: var(--font-bengali);
89
+ font-weight: 700;
90
+ font-size: 1rem;
91
+ color: var(--emerald);
92
+ background: linear-gradient(135deg, var(--gold-bright), var(--gold));
93
+ padding: 0.85rem 2rem;
94
+ border-radius: 100px;
95
+ transition: all 0.2s;
96
+ box-shadow: 0 4px 20px rgba(200, 153, 26, 0.4);
97
+ }
98
+
99
+ .btnPrimary:hover {
100
+ transform: translateY(-2px);
101
+ box-shadow: 0 8px 30px rgba(200, 153, 26, 0.5);
102
+ }
103
+
104
+ .btnGhost {
105
+ display: inline-flex;
106
+ align-items: center;
107
+ font-family: var(--font-bengali);
108
+ font-size: 1rem;
109
+ color: var(--gold-pale);
110
+ border: 1.5px solid rgba(245, 225, 160, 0.4);
111
+ padding: 0.85rem 1.8rem;
112
+ border-radius: 100px;
113
+ transition: all 0.2s;
114
+ }
115
+
116
+ .btnGhost:hover {
117
+ background: rgba(245, 225, 160, 0.08);
118
+ border-color: var(--gold-pale);
119
+ }
120
+
121
+ .heroStats {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 1.5rem;
125
+ margin-top: 1rem;
126
+ animation: fadeUp 0.7s ease 0.4s both;
127
+ }
128
+
129
+ .stat { display: flex; flex-direction: column; gap: 0.2rem; }
130
+ .statNum {
131
+ font-family: var(--font-bengali);
132
+ font-size: 1.1rem;
133
+ font-weight: 700;
134
+ color: var(--gold-bright);
135
+ }
136
+ .statLabel {
137
+ font-size: 0.75rem;
138
+ color: rgba(245, 225, 160, 0.5);
139
+ text-transform: uppercase;
140
+ letter-spacing: 0.06em;
141
+ }
142
+ .statDivider { width: 1px; height: 36px; background: rgba(200,153,26,0.3); }
143
+
144
+ /* ── Hero art ─────────────────────────────────────────────────────────────── */
145
+ .heroArt {
146
+ position: relative;
147
+ display: flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ height: 420px;
151
+ }
152
+
153
+ .moonRing {
154
+ width: 300px;
155
+ height: 300px;
156
+ border-radius: 50%;
157
+ border: 2px solid rgba(200, 153, 26, 0.3);
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ animation: slowSpin 30s linear infinite;
162
+ position: relative;
163
+ }
164
+
165
+ .moonRing::before {
166
+ content: '';
167
+ position: absolute;
168
+ inset: 20px;
169
+ border-radius: 50%;
170
+ border: 1px dashed rgba(200,153,26,0.15);
171
+ }
172
+
173
+ .moonInner {
174
+ font-size: 7rem;
175
+ color: var(--gold-bright);
176
+ filter: drop-shadow(0 0 40px rgba(200,153,26,0.5));
177
+ animation: moonGlow 4s ease-in-out infinite;
178
+ }
179
+
180
+ @keyframes moonGlow {
181
+ 0%, 100% { filter: drop-shadow(0 0 30px rgba(200,153,26,0.4)); }
182
+ 50% { filter: drop-shadow(0 0 60px rgba(200,153,26,0.7)); }
183
+ }
184
+
185
+ @keyframes slowSpin {
186
+ from { transform: rotate(0deg); }
187
+ to { transform: rotate(360deg); }
188
+ }
189
+
190
+ .lantern {
191
+ position: absolute;
192
+ bottom: 60px;
193
+ right: 60px;
194
+ font-size: 3rem;
195
+ animation: float 3s ease-in-out infinite;
196
+ }
197
+
198
+ .stars2 {
199
+ position: absolute;
200
+ top: 40px;
201
+ right: 30px;
202
+ font-size: 0.8rem;
203
+ color: var(--gold-bright);
204
+ letter-spacing: 0.6em;
205
+ opacity: 0.6;
206
+ }
207
+
208
+ @keyframes float {
209
+ 0%, 100% { transform: translateY(0); }
210
+ 50% { transform: translateY(-12px); }
211
+ }
212
+
213
+ /* ── How it works ─────────────────────────────────────────────────────────── */
214
+ .howSection {
215
+ background: var(--cream);
216
+ padding: 5rem clamp(1.5rem, 6vw, 6rem);
217
+ }
218
+
219
+ .sectionInner {
220
+ max-width: 1100px;
221
+ margin: 0 auto;
222
+ }
223
+
224
+ .sectionTitle {
225
+ font-family: var(--font-bengali);
226
+ font-size: clamp(1.8rem, 3vw, 2.6rem);
227
+ color: var(--emerald);
228
+ text-align: center;
229
+ font-weight: 700;
230
+ }
231
+
232
+ .sectionSub {
233
+ text-align: center;
234
+ color: var(--text-light);
235
+ margin-top: 0.5rem;
236
+ font-size: 1rem;
237
+ }
238
+
239
+ .steps {
240
+ display: grid;
241
+ grid-template-columns: repeat(4, 1fr);
242
+ gap: 1.5rem;
243
+ margin-top: 3rem;
244
+ position: relative;
245
+ }
246
+
247
+ .steps::before {
248
+ content: '';
249
+ position: absolute;
250
+ top: 38px;
251
+ left: calc(12.5% + 20px);
252
+ right: calc(12.5% + 20px);
253
+ height: 2px;
254
+ background: linear-gradient(90deg, var(--gold), var(--gold-bright), var(--gold));
255
+ opacity: 0.3;
256
+ }
257
+
258
+ @media (max-width: 768px) {
259
+ .steps { grid-template-columns: 1fr 1fr; }
260
+ .steps::before { display: none; }
261
+ }
262
+
263
+ @media (max-width: 480px) {
264
+ .steps { grid-template-columns: 1fr; }
265
+ }
266
+
267
+ .step {
268
+ display: flex;
269
+ flex-direction: column;
270
+ align-items: center;
271
+ gap: 0.7rem;
272
+ text-align: center;
273
+ padding: 2rem 1rem;
274
+ background: var(--white);
275
+ border-radius: var(--radius);
276
+ border: 1px solid rgba(200,153,26,0.12);
277
+ transition: transform 0.2s, box-shadow 0.2s;
278
+ }
279
+
280
+ .step:hover {
281
+ transform: translateY(-4px);
282
+ box-shadow: 0 12px 40px rgba(0,0,0,0.08);
283
+ }
284
+
285
+ .stepNum {
286
+ font-family: var(--font-display);
287
+ font-size: 0.7rem;
288
+ font-weight: 700;
289
+ color: var(--white);
290
+ background: var(--gold);
291
+ width: 28px;
292
+ height: 28px;
293
+ border-radius: 50%;
294
+ display: flex;
295
+ align-items: center;
296
+ justify-content: center;
297
+ }
298
+
299
+ .stepEmoji { font-size: 2.2rem; }
300
+
301
+ .stepTitle {
302
+ font-family: var(--font-bengali);
303
+ font-size: 1rem;
304
+ font-weight: 700;
305
+ color: var(--emerald);
306
+ }
307
+
308
+ .stepSub {
309
+ font-size: 0.82rem;
310
+ color: var(--text-light);
311
+ }
312
+
313
+ /* ── Packages ─────────────────────────────────────────────────────────────── */
314
+ .packagesSection {
315
+ background: linear-gradient(180deg, #f5f0e8 0%, var(--cream) 100%);
316
+ padding: 5rem clamp(1.5rem, 6vw, 6rem);
317
+ }
318
+
319
+ .packagesGrid {
320
+ display: grid;
321
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
322
+ gap: 1.2rem;
323
+ margin-top: 3rem;
324
+ }
325
+
326
+ .pkgCard {
327
+ display: flex;
328
+ flex-direction: column;
329
+ align-items: center;
330
+ gap: 0.6rem;
331
+ padding: 1.8rem 1rem;
332
+ background: var(--white);
333
+ border: 2px solid transparent;
334
+ border-radius: var(--radius);
335
+ text-decoration: none;
336
+ transition: all 0.2s;
337
+ box-shadow: 0 2px 12px rgba(0,0,0,0.05);
338
+ }
339
+
340
+ .pkgCard:hover {
341
+ border-color: var(--gold-bright);
342
+ transform: translateY(-4px);
343
+ box-shadow: 0 12px 40px rgba(200,153,26,0.2);
344
+ }
345
+
346
+ .pkgEmoji { font-size: 2.2rem; }
347
+
348
+ .pkgName {
349
+ font-family: var(--font-bengali);
350
+ font-size: 0.88rem;
351
+ font-weight: 600;
352
+ color: var(--emerald);
353
+ text-align: center;
354
+ }
355
+
356
+ .pkgAmount {
357
+ font-family: var(--font-display);
358
+ font-size: 1.4rem;
359
+ font-weight: 700;
360
+ color: var(--gold);
361
+ }
362
+
363
+ .pkgBtn {
364
+ font-size: 0.78rem;
365
+ font-weight: 500;
366
+ color: var(--emerald-light);
367
+ opacity: 0.7;
368
+ }
369
+
370
+ /* ── CTA Banner ───────────────────────────────────────────────────────────── */
371
+ .ctaBanner {
372
+ background: linear-gradient(135deg, var(--emerald) 0%, #0f4535 100%);
373
+ padding: 5rem clamp(1.5rem, 6vw, 6rem);
374
+ position: relative;
375
+ overflow: hidden;
376
+ }
377
+
378
+ .ctaBanner::before {
379
+ content: '☽';
380
+ position: absolute;
381
+ right: 8%;
382
+ top: 50%;
383
+ transform: translateY(-50%);
384
+ font-size: 12rem;
385
+ color: rgba(200,153,26,0.06);
386
+ pointer-events: none;
387
+ }
388
+
389
+ .ctaInner {
390
+ max-width: 600px;
391
+ margin: 0 auto;
392
+ text-align: center;
393
+ display: flex;
394
+ flex-direction: column;
395
+ align-items: center;
396
+ gap: 1rem;
397
+ }
398
+
399
+ .ctaInner h2 {
400
+ font-family: var(--font-bengali);
401
+ font-size: clamp(1.8rem, 3vw, 2.5rem);
402
+ color: var(--cream);
403
+ font-weight: 700;
404
+ }
405
+
406
+ .ctaInner p {
407
+ font-family: var(--font-bengali);
408
+ color: rgba(245,225,160,0.7);
409
+ font-size: 1rem;
410
+ }
411
+
412
+ /* ── Animations ───────────────────────────────────────────────────────────── */
413
+ @keyframes fadeUp {
414
+ from { opacity: 0; transform: translateY(24px); }
415
+ to { opacity: 1; transform: translateY(0); }
416
+ }
@@ -0,0 +1,146 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { Link } from 'react-router-dom'
3
+ import { getPackages, type Package } from '../api/client'
4
+ import styles from './HomePage.module.css'
5
+
6
+ const STARS = Array.from({ length: 28 }, (_, i) => ({
7
+ id: i,
8
+ x: Math.random() * 100,
9
+ y: Math.random() * 60,
10
+ size: 2 + Math.random() * 3,
11
+ delay: Math.random() * 3,
12
+ }))
13
+
14
+ const STEPS = [
15
+ { emoji: '🎁', title: 'প্যাকেজ বেছে নিন', sub: 'Choose a salami amount' },
16
+ { emoji: '✍️', title: 'তথ্য পূরণ করুন', sub: 'Fill sender & recipient details' },
17
+ { emoji: '💚', title: 'bKash-এ পেমেন্ট', sub: 'Pay securely via bKash' },
18
+ { emoji: '🌙', title: 'সালামি পৌঁছে যাবে', sub: 'Salami delivered instantly' },
19
+ ]
20
+
21
+ export default function HomePage() {
22
+ const [packages, setPackages] = useState<Package[]>([])
23
+
24
+ useEffect(() => {
25
+ getPackages().then(setPackages).catch(() => {})
26
+ }, [])
27
+
28
+ return (
29
+ <main>
30
+ {/* ── Hero ─────────────────────────────────────────────────────────── */}
31
+ <section className={styles.hero}>
32
+ <div className={styles.starfield}>
33
+ {STARS.map(s => (
34
+ <span
35
+ key={s.id}
36
+ className={styles.star}
37
+ style={{
38
+ left: `${s.x}%`,
39
+ top: `${s.y}%`,
40
+ width: s.size,
41
+ height: s.size,
42
+ animationDelay: `${s.delay}s`,
43
+ }}
44
+ />
45
+ ))}
46
+ </div>
47
+
48
+ <div className={styles.heroContent}>
49
+ <p className={styles.eyebrow}>ঈদ মোবারক ✨</p>
50
+ <h1 className={styles.heroTitle}>
51
+ Send Eid Salami<br />
52
+ <em>with Love & Joy</em>
53
+ </h1>
54
+ <p className={styles.heroSub}>
55
+ প্রিয়জনকে ঈদের সালামি পাঠান — সহজে, দ্রুত, bKash-এর মাধ্যমে।
56
+ </p>
57
+
58
+ <div className={styles.heroCtas}>
59
+ <Link to="/send" className={styles.btnPrimary}>
60
+ এখনই সালামি পাঠান →
61
+ </Link>
62
+ <a href="#how" className={styles.btnGhost}>
63
+ কীভাবে কাজ করে?
64
+ </a>
65
+ </div>
66
+
67
+ <div className={styles.heroStats}>
68
+ <div className={styles.stat}>
69
+ <span className={styles.statNum}>৳100</span>
70
+ <span className={styles.statLabel}>থেকে শুরু</span>
71
+ </div>
72
+ <div className={styles.statDivider} />
73
+ <div className={styles.stat}>
74
+ <span className={styles.statNum}>bKash</span>
75
+ <span className={styles.statLabel}>পেমেন্ট</span>
76
+ </div>
77
+ <div className={styles.statDivider} />
78
+ <div className={styles.stat}>
79
+ <span className={styles.statNum}>তাৎক্ষণিক</span>
80
+ <span className={styles.statLabel}>ডেলিভারি</span>
81
+ </div>
82
+ </div>
83
+ </div>
84
+
85
+ <div className={styles.heroArt}>
86
+ <div className={styles.moonRing}>
87
+ <div className={styles.moonInner}>☽</div>
88
+ </div>
89
+ <div className={styles.lantern}>🪔</div>
90
+ <div className={styles.stars2}>✦ ✦ ✦</div>
91
+ </div>
92
+ </section>
93
+
94
+ {/* ── How it works ─────────────────────────────────────────────────── */}
95
+ <section id="how" className={styles.howSection}>
96
+ <div className={styles.sectionInner}>
97
+ <h2 className={styles.sectionTitle}>কীভাবে কাজ করে</h2>
98
+ <p className={styles.sectionSub}>মাত্র ৪টি সহজ ধাপে সালামি পাঠান</p>
99
+
100
+ <div className={styles.steps}>
101
+ {STEPS.map((step, i) => (
102
+ <div key={i} className={styles.step}>
103
+ <div className={styles.stepNum}>{i + 1}</div>
104
+ <div className={styles.stepEmoji}>{step.emoji}</div>
105
+ <h3 className={styles.stepTitle}>{step.title}</h3>
106
+ <p className={styles.stepSub}>{step.sub}</p>
107
+ </div>
108
+ ))}
109
+ </div>
110
+ </div>
111
+ </section>
112
+
113
+ {/* ── Packages preview ─────────────────────────────────────────────── */}
114
+ {packages.length > 0 && (
115
+ <section className={styles.packagesSection}>
116
+ <div className={styles.sectionInner}>
117
+ <h2 className={styles.sectionTitle}>সালামি প্যাকেজ</h2>
118
+ <p className={styles.sectionSub}>Choose the perfect amount for your loved one</p>
119
+
120
+ <div className={styles.packagesGrid}>
121
+ {packages.map(pkg => (
122
+ <Link to={`/send?package=${pkg.id}`} key={pkg.id} className={styles.pkgCard}>
123
+ <span className={styles.pkgEmoji}>{pkg.emoji}</span>
124
+ <span className={styles.pkgName}>{pkg.name}</span>
125
+ <span className={styles.pkgAmount}>৳{pkg.amount.toLocaleString()}</span>
126
+ <span className={styles.pkgBtn}>পাঠান →</span>
127
+ </Link>
128
+ ))}
129
+ </div>
130
+ </div>
131
+ </section>
132
+ )}
133
+
134
+ {/* ── CTA Banner ───────────────────────────────────────────────────── */}
135
+ <section className={styles.ctaBanner}>
136
+ <div className={styles.ctaInner}>
137
+ <h2>আজই শুরু করুন 🌙</h2>
138
+ <p>ঈদের আনন্দ ভাগ করে নিন প্রিয়জনের সাথে</p>
139
+ <Link to="/send" className={styles.btnPrimary}>
140
+ সালামি পাঠান
141
+ </Link>
142
+ </div>
143
+ </section>
144
+ </main>
145
+ )
146
+ }
@@ -0,0 +1,98 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { useSearchParams } from 'react-router-dom';
4
+ import { getPackages, createOrder, submitTrxID } from '../api/client';
5
+ import PackageCard from '../components/PackageCard';
6
+ import styles from './OrderPage.module.css';
7
+ const INITIAL = {
8
+ senderName: '', senderPhone: '', senderMessage: '',
9
+ packageId: '',
10
+ };
11
+ export default function OrderPage() {
12
+ const [searchParams] = useSearchParams();
13
+ const [step, setStep] = useState('package');
14
+ const [packages, setPackages] = useState([]);
15
+ const [form, setForm] = useState({ ...INITIAL, packageId: searchParams.get('package') ?? '' });
16
+ const [orderResult, setOrderResult] = useState(null);
17
+ const [trxID, setTrxID] = useState('');
18
+ const [loading, setLoading] = useState(false);
19
+ const [error, setError] = useState('');
20
+ const [copied, setCopied] = useState(false);
21
+ const [pkgsLoading, setPkgsLoading] = useState(true);
22
+ useEffect(() => {
23
+ getPackages()
24
+ .then(pkgs => {
25
+ setPackages(pkgs);
26
+ if (form.packageId && pkgs.some(p => p.id === form.packageId))
27
+ setStep('details');
28
+ })
29
+ .catch(() => setError('প্যাকেজ লোড হয়নি।'))
30
+ .finally(() => setPkgsLoading(false));
31
+ }, []);
32
+ const selectedPkg = packages.find(p => p.id === form.packageId);
33
+ const set = (f) => (e) => setForm(prev => ({ ...prev, [f]: e.target.value }));
34
+ const validateDetails = () => {
35
+ if (!form.senderName.trim())
36
+ return 'আপনার নাম লিখুন';
37
+ if (!/^01[3-9]\d{8}$/.test(form.senderPhone))
38
+ return 'বৈধ bKash নম্বর দিন (01XXXXXXXXX)';
39
+ return '';
40
+ };
41
+ // Step 2 → 3: Create order in backend, get bKash number to send to
42
+ const handleCreateOrder = async () => {
43
+ setError('');
44
+ setLoading(true);
45
+ try {
46
+ const result = await createOrder({
47
+ senderName: form.senderName,
48
+ senderPhone: form.senderPhone,
49
+ senderMessage: form.senderMessage,
50
+ packageId: form.packageId,
51
+ });
52
+ setOrderResult(result);
53
+ setStep('pay');
54
+ }
55
+ catch (err) {
56
+ setError(err?.response?.data?.error ?? 'অর্ডার তৈরিতে সমস্যা হয়েছে।');
57
+ }
58
+ finally {
59
+ setLoading(false);
60
+ }
61
+ };
62
+ // Step 3 → 4: Submit TrxID after user has sent the money
63
+ const handleSubmitTrxID = async () => {
64
+ if (!trxID.trim()) {
65
+ setError('TrxID লিখুন');
66
+ return;
67
+ }
68
+ setError('');
69
+ setLoading(true);
70
+ try {
71
+ await submitTrxID(orderResult.orderId, trxID.trim());
72
+ setStep('submitted');
73
+ }
74
+ catch (err) {
75
+ setError(err?.response?.data?.error ?? 'TrxID জমা দিতে সমস্যা হয়েছে।');
76
+ }
77
+ finally {
78
+ setLoading(false);
79
+ }
80
+ };
81
+ const copyNumber = () => {
82
+ navigator.clipboard.writeText(orderResult?.bkashNumber ?? '');
83
+ setCopied(true);
84
+ setTimeout(() => setCopied(false), 2000);
85
+ };
86
+ const STEP_LABELS = ['প্যাকেজ', 'তথ্য', 'পেমেন্ট', 'সম্পন্ন'];
87
+ const STEP_KEYS = ['package', 'details', 'pay', 'submitted'];
88
+ const stepIdx = STEP_KEYS.indexOf(step);
89
+ return (_jsx("main", { className: styles.page, children: _jsxs("div", { className: styles.container, children: [_jsx("h1", { className: styles.title, children: "\u0988\u09A6 \u09B8\u09BE\u09B2\u09BE\u09AE\u09BF \u09AA\u09BE\u09A0\u09BE\u09A8 \uD83C\uDF19" }), _jsx("div", { className: styles.progress, children: STEP_LABELS.map((label, i) => (_jsxs("div", { className: `${styles.progressItem} ${i <= stepIdx ? styles.active : ''}`, children: [_jsx("div", { className: styles.progressDot, children: i < stepIdx ? '✓' : i + 1 }), _jsx("span", { className: styles.progressLabel, children: label }), i < 3 && _jsx("div", { className: `${styles.progressLine} ${i < stepIdx ? styles.lineDone : ''}` })] }, label))) }), step === 'package' && (_jsxs("div", { className: styles.stepPanel, children: [_jsx("h2", { className: styles.stepHeading, children: "\u09AA\u09CD\u09AF\u09BE\u0995\u09C7\u099C \u09AC\u09C7\u099B\u09C7 \u09A8\u09BF\u09A8" }), pkgsLoading ? (_jsx("div", { className: styles.loader, children: "\u09B2\u09CB\u09A1 \u09B9\u099A\u09CD\u099B\u09C7\u2026" })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.packagesGrid, children: packages.map(pkg => (_jsx(PackageCard, { pkg: pkg, selected: form.packageId === pkg.id, onSelect: id => setForm(prev => ({ ...prev, packageId: id })) }, pkg.id))) }), _jsx("button", { className: styles.btnNext, disabled: !form.packageId, onClick: () => setStep('details'), children: "\u09AA\u09B0\u09AC\u09B0\u09CD\u09A4\u09C0 \u2192" })] }))] })), step === 'details' && (_jsxs("div", { className: styles.stepPanel, children: [_jsx("h2", { className: styles.stepHeading, children: "\u0986\u09AA\u09A8\u09BE\u09B0 \u09A4\u09A5\u09CD\u09AF" }), _jsx("div", { className: styles.formGrid, children: _jsxs("div", { className: styles.formSection, children: [_jsx("h3", { className: styles.formSectionTitle, children: "\uD83D\uDE4B \u0986\u09AA\u09A8\u09BE\u09B0 \u09A4\u09A5\u09CD\u09AF" }), _jsxs("label", { className: styles.label, children: ["\u0986\u09AA\u09A8\u09BE\u09B0 \u09A8\u09BE\u09AE *", _jsx("input", { className: styles.input, value: form.senderName, onChange: set('senderName'), placeholder: "\u0986\u09AA\u09A8\u09BE\u09B0 \u09A8\u09BE\u09AE" })] }), _jsxs("label", { className: styles.label, children: ["\u0986\u09AA\u09A8\u09BE\u09B0 bKash \u09A8\u09AE\u09CD\u09AC\u09B0 *", _jsx("input", { className: styles.input, value: form.senderPhone, onChange: set('senderPhone'), placeholder: "01XXXXXXXXX", maxLength: 11 })] }), _jsxs("label", { className: styles.label, children: ["\u09B6\u09C1\u09AD\u09C7\u099A\u09CD\u099B\u09BE \u09AC\u09BE\u09B0\u09CD\u09A4\u09BE (\u0990\u099A\u09CD\u099B\u09BF\u0995)", _jsx("textarea", { className: styles.textarea, value: form.senderMessage, onChange: set('senderMessage'), placeholder: "\u0988\u09A6 \u09AE\u09CB\u09AC\u09BE\u09B0\u0995! \u09A4\u09CB\u09AE\u09BE\u0995\u09C7 \u09AD\u09BE\u09B2\u09CB\u09AC\u09BE\u09B8\u09BF\u2026", rows: 3 })] })] }) }), error && _jsx("p", { className: styles.error, children: error }), _jsxs("div", { className: styles.navBtns, children: [_jsx("button", { className: styles.btnBack, onClick: () => setStep('package'), children: "\u2190 \u09AA\u09C7\u099B\u09A8\u09C7" }), _jsx("button", { className: styles.btnNext, disabled: loading, onClick: () => {
90
+ const err = validateDetails();
91
+ if (err) {
92
+ setError(err);
93
+ return;
94
+ }
95
+ setError('');
96
+ handleCreateOrder();
97
+ }, children: loading ? 'অর্ডার তৈরি হচ্ছে…' : 'পরবর্তী →' })] })] })), step === 'pay' && orderResult && (_jsxs("div", { className: styles.stepPanel, children: [_jsx("h2", { className: styles.stepHeading, children: "\u09AA\u09C7\u09AE\u09C7\u09A8\u09CD\u099F \u09B8\u09AE\u09CD\u09AA\u09A8\u09CD\u09A8 \u0995\u09B0\u09C1\u09A8" }), _jsxs("div", { className: styles.paymentInfo, children: [_jsxs("p", { className: styles.payText, children: ["\u09A8\u09BF\u099A\u09C7\u09B0 \u09A8\u09AE\u09CD\u09AC\u09B0\u09C7 ", _jsxs("strong", { children: ["\u09F3", orderResult.amount] }), " \u09B8\u09C7\u09A8\u09CD\u09A1 \u09AE\u09BE\u09A8\u09BF \u0995\u09B0\u09C1\u09A8:"] }), _jsxs("div", { className: styles.copyBox, children: [_jsx("code", { className: styles.bkashNum, children: orderResult.bkashNumber }), _jsx("button", { className: styles.copyBtn, onClick: copyNumber, children: copied ? 'কপি হয়েছে!' : 'কপি করুন' })] }), _jsxs("p", { className: styles.refText, children: ["\u09B0\u09C7\u09AB\u09BE\u09B0\u09C7\u09A8\u09CD\u09B8\u09C7 \u09A6\u09BF\u09A8: ", _jsx("strong", { children: orderResult.invoiceRef })] })] }), _jsxs("div", { className: styles.trxInputSection, children: [_jsx("h3", { className: styles.formSectionTitle, children: "\u09AA\u09C7\u09AE\u09C7\u09A8\u09CD\u099F \u0995\u09B0\u09BE\u09B0 \u09AA\u09B0 TrxID \u09A6\u09BF\u09A8:" }), _jsx("input", { className: styles.input, value: trxID, onChange: e => setTrxID(e.target.value), placeholder: "Ex: AB12CD34EF" }), error && _jsx("p", { className: styles.error, children: error }), _jsx("button", { className: styles.btnSubmit, disabled: loading, onClick: handleSubmitTrxID, children: loading ? 'যাচাই হচ্ছে…' : 'সালামি পাঠান 🚀' })] })] })), step === 'submitted' && (_jsxs("div", { className: `${styles.stepPanel} ${styles.center}`, children: [_jsx("div", { className: styles.successIcon, children: "\uD83C\uDF89" }), _jsx("h2", { className: styles.successTitle, children: "\u09B8\u09BE\u09B2\u09BE\u09AE\u09BF \u09B8\u09AB\u09B2\u09AD\u09BE\u09AC\u09C7 \u09AA\u09BE\u09A0\u09BE\u09A8\u09CB \u09B9\u09DF\u09C7\u099B\u09C7!" }), _jsx("p", { className: styles.successText, children: "\u0986\u09AA\u09A8\u09BE\u09B0 \u09AA\u09C7\u09AE\u09C7\u09A8\u09CD\u099F \u09AF\u09BE\u099A\u09BE\u0987 \u0995\u09B0\u09BE \u09B9\u099A\u09CD\u099B\u09C7\u0964 \u0995\u09BF\u099B\u09C1\u0995\u09CD\u09B7\u09A3\u09C7\u09B0 \u09AE\u09A7\u09CD\u09AF\u09C7\u0987 \u09AA\u09CD\u09B0\u09BE\u09AA\u0995 \u09B8\u09BE\u09B2\u09BE\u09AE\u09BF \u09AA\u09C7\u09DF\u09C7 \u09AF\u09BE\u09AC\u09C7\u09A8 \u0987\u09A8\u09B6\u09BE\u0986\u09B2\u09CD\u09B2\u09BE\u09B9\u0964" }), _jsx("button", { className: styles.btnHome, onClick: () => window.location.href = '/', children: "\u0986\u09AC\u09BE\u09B0 \u09AA\u09BE\u09A0\u09BE\u09A8" })] }))] }) }));
98
+ }