flow_chat 0.5.2 → 0.6.1

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.
@@ -0,0 +1,889 @@
1
+ # FlowChat Comprehensive Demo: Restaurant Ordering System
2
+ # This flow demonstrates all major FlowChat features:
3
+ # - Cross-platform compatibility (USSD + WhatsApp)
4
+ # - Media support with graceful degradation
5
+ # - Input validation and transformation
6
+ # - Complex menu selection (arrays, hashes, large lists)
7
+ # - Session state management
8
+ # - Error handling and validation
9
+ # - Platform-specific features with fallbacks
10
+ # - Rich interactive elements
11
+
12
+ class DemoRestaurantFlow < FlowChat::Flow
13
+ def main_page
14
+ # Welcome with media (logo)
15
+ app.say "🍽️ Welcome to FlowChat Restaurant!",
16
+ media: {
17
+ type: :image,
18
+ url: "https://flowchat-demo.com/restaurant-logo.jpg"
19
+ }
20
+
21
+ # Check if returning customer
22
+ returning_customer = check_returning_customer
23
+
24
+ if returning_customer
25
+ name = app.session.get(:customer_name)
26
+ app.say "Welcome back, #{name}! 😊"
27
+ main_menu
28
+ else
29
+ customer_registration
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def check_returning_customer
36
+ # Check if we have customer data in session
37
+ app.session.get(:customer_name).present?
38
+ end
39
+
40
+ def customer_registration
41
+ app.say "Let's get you registered! 📝"
42
+
43
+ # Name with transformation
44
+ name = app.screen(:customer_name) do |prompt|
45
+ prompt.ask "What's your name?",
46
+ transform: ->(input) { input.strip.titleize },
47
+ validate: ->(input) {
48
+ return "Name must be at least 2 characters" if input.length < 2
49
+ return "Name can't be longer than 50 characters" if input.length > 50
50
+ return "Name can only contain letters and spaces" unless input.match?(/\A[a-zA-Z\s]+\z/)
51
+ nil
52
+ }
53
+ end
54
+
55
+ # Phone number with validation (international format)
56
+ phone = app.screen(:customer_phone) do |prompt|
57
+ prompt.ask "Enter your phone number (e.g., +1234567890):",
58
+ transform: ->(input) { input.strip.gsub(/[\s\-\(\)]/, '') },
59
+ validate: ->(input) {
60
+ return "Phone number must start with +" unless input.start_with?('+')
61
+ return "Phone number must be 8-15 digits after +" unless input[1..-1].match?(/\A\d{8,15}\z/)
62
+ nil
63
+ }
64
+ end
65
+
66
+ # Age with conversion and validation
67
+ age = app.screen(:customer_age) do |prompt|
68
+ prompt.ask "How old are you?",
69
+ convert: ->(input) { input.to_i },
70
+ validate: ->(age) {
71
+ return "You must be at least 13 to order" if age < 13
72
+ return "Age must be reasonable (under 120)" if age > 120
73
+ nil
74
+ }
75
+ end
76
+
77
+ # Dietary preferences with hash-based selection
78
+ dietary_preference = app.screen(:dietary_preference) do |prompt|
79
+ prompt.select "Any dietary preferences?", {
80
+ "none" => "No restrictions",
81
+ "vegetarian" => "Vegetarian",
82
+ "vegan" => "Vegan",
83
+ "gluten_free" => "Gluten-free",
84
+ "keto" => "Keto",
85
+ "halal" => "Halal"
86
+ }
87
+ end
88
+
89
+ # Store customer data
90
+ customer_data = {
91
+ name: name,
92
+ phone: phone,
93
+ age: age,
94
+ dietary_preference: dietary_preference,
95
+ registered_at: Time.current.iso8601
96
+ }
97
+
98
+ app.session.set(:customer_data, customer_data)
99
+
100
+ app.say "Thanks for registering, #{name}! 🎉",
101
+ media: {
102
+ type: :sticker,
103
+ url: "https://flowchat-demo.com/welcome-sticker.webp"
104
+ }
105
+
106
+ main_menu
107
+ end
108
+
109
+ def main_menu
110
+ choice = app.screen(:main_menu) do |prompt|
111
+ prompt.select "What would you like to do?", [
112
+ "Browse Menu",
113
+ "View Cart",
114
+ "Order History",
115
+ "Account Settings",
116
+ "Contact Support"
117
+ ]
118
+ end
119
+
120
+ case choice
121
+ when "Browse Menu"
122
+ browse_menu
123
+ when "View Cart"
124
+ view_cart
125
+ when "Order History"
126
+ order_history
127
+ when "Account Settings"
128
+ account_settings
129
+ when "Contact Support"
130
+ contact_support
131
+ end
132
+ end
133
+
134
+ def browse_menu
135
+ app.say "Here's our delicious menu! 📋",
136
+ media: {
137
+ type: :image,
138
+ url: "https://flowchat-demo.com/menu-hero.jpg"
139
+ }
140
+
141
+ category = app.screen(:menu_category) do |prompt|
142
+ prompt.select "Choose a category:", {
143
+ "appetizers" => "🥗 Appetizers",
144
+ "mains" => "🍽️ Main Courses",
145
+ "desserts" => "🍰 Desserts",
146
+ "beverages" => "🥤 Beverages",
147
+ "specials" => "⭐ Today's Specials"
148
+ }
149
+ end
150
+
151
+ show_category_items(category)
152
+ end
153
+
154
+ def show_category_items(category)
155
+ items = get_menu_items(category)
156
+
157
+ app.say "#{category.titleize} Menu:",
158
+ media: {
159
+ type: :image,
160
+ url: "https://flowchat-demo.com/categories/#{category}.jpg"
161
+ }
162
+
163
+ # Large list selection to test pagination on USSD
164
+ item_choice = app.screen("#{category}_selection".to_sym) do |prompt|
165
+ prompt.select "Choose an item:", items
166
+ end
167
+
168
+ show_item_details(category, item_choice)
169
+ end
170
+
171
+ def get_menu_items(category)
172
+ # Simulate menu items (large list to test pagination)
173
+ case category
174
+ when "appetizers"
175
+ {
176
+ "caesar_salad" => "Caesar Salad - $12",
177
+ "bruschetta" => "Bruschetta - $8",
178
+ "calamari" => "Fried Calamari - $14",
179
+ "wings" => "Buffalo Wings - $10",
180
+ "nachos" => "Loaded Nachos - $11",
181
+ "shrimp_cocktail" => "Shrimp Cocktail - $16",
182
+ "cheese_board" => "Artisan Cheese Board - $18",
183
+ "soup" => "Soup of the Day - $7"
184
+ }
185
+ when "mains"
186
+ # Large list to test USSD pagination
187
+ items = {}
188
+ dishes = [
189
+ "Grilled Salmon", "Ribeye Steak", "Chicken Parmesan", "Lobster Tail",
190
+ "Lamb Chops", "Fish Tacos", "Beef Stroganoff", "Chicken Alfredo",
191
+ "Pork Tenderloin", "Seafood Paella", "Duck Confit", "Vegetarian Lasagna",
192
+ "BBQ Ribs", "Fish & Chips", "Stuffed Peppers", "Beef Wellington"
193
+ ]
194
+ dishes.each_with_index do |dish, index|
195
+ price = 18 + (index * 2)
196
+ items["dish_#{index}"] = "#{dish} - $#{price}"
197
+ end
198
+ items
199
+ when "desserts"
200
+ {
201
+ "tiramisu" => "Tiramisu - $8",
202
+ "cheesecake" => "New York Cheesecake - $7",
203
+ "chocolate_cake" => "Chocolate Lava Cake - $9",
204
+ "ice_cream" => "Artisan Ice Cream - $6"
205
+ }
206
+ when "beverages"
207
+ {
208
+ "wine_red" => "House Red Wine - $8",
209
+ "wine_white" => "House White Wine - $8",
210
+ "beer" => "Craft Beer - $6",
211
+ "cocktail" => "Signature Cocktail - $12",
212
+ "coffee" => "Espresso Coffee - $4",
213
+ "tea" => "Premium Tea - $3",
214
+ "juice" => "Fresh Juice - $5",
215
+ "soda" => "Soft Drinks - $3"
216
+ }
217
+ when "specials"
218
+ {
219
+ "special_1" => "Chef's Special Pasta - $22",
220
+ "special_2" => "Today's Catch - Market Price",
221
+ "special_3" => "Tasting Menu (5 courses) - $65"
222
+ }
223
+ else
224
+ {}
225
+ end
226
+ end
227
+
228
+ def show_item_details(category, item_key)
229
+ item_name = get_menu_items(category)[item_key]
230
+
231
+ app.say "You selected: #{item_name}",
232
+ media: {
233
+ type: :image,
234
+ url: "https://flowchat-demo.com/items/#{item_key}.jpg"
235
+ }
236
+
237
+ # Show detailed description with document menu
238
+ show_detailed_info = app.screen("#{item_key}_details".to_sym) do |prompt|
239
+ prompt.yes? "Would you like to see detailed nutritional information?"
240
+ end
241
+
242
+ if show_detailed_info
243
+ app.say "Here's the detailed information:",
244
+ media: {
245
+ type: :document,
246
+ url: "https://flowchat-demo.com/nutrition/#{item_key}.pdf",
247
+ filename: "#{item_key}_nutrition.pdf"
248
+ }
249
+ end
250
+
251
+ # Quantity selection
252
+ quantity = app.screen("#{item_key}_quantity".to_sym) do |prompt|
253
+ prompt.ask "How many would you like?",
254
+ convert: ->(input) { input.to_i },
255
+ validate: ->(qty) {
256
+ return "Quantity must be at least 1" if qty < 1
257
+ return "Maximum 10 items per order" if qty > 10
258
+ nil
259
+ }
260
+ end
261
+
262
+ # Special instructions
263
+ special_instructions = app.screen("#{item_key}_instructions".to_sym) do |prompt|
264
+ prompt.ask "Any special instructions? (or type 'none')",
265
+ transform: ->(input) { input.strip.downcase == 'none' ? nil : input.strip }
266
+ end
267
+
268
+ # Add to cart
269
+ add_to_cart(item_key, item_name, quantity, special_instructions)
270
+
271
+ # Continue shopping?
272
+ continue_shopping = app.screen(:continue_shopping) do |prompt|
273
+ prompt.yes? "Item added to cart! Continue shopping?"
274
+ end
275
+
276
+ if continue_shopping
277
+ browse_menu
278
+ else
279
+ view_cart
280
+ end
281
+ end
282
+
283
+ def add_to_cart(item_key, item_name, quantity, instructions)
284
+ cart = app.session.get(:cart) || []
285
+
286
+ cart_item = {
287
+ key: item_key,
288
+ name: item_name,
289
+ quantity: quantity,
290
+ instructions: instructions,
291
+ added_at: Time.current.iso8601
292
+ }
293
+
294
+ cart << cart_item
295
+ app.session.set(:cart, cart)
296
+ end
297
+
298
+ def view_cart
299
+ cart = app.session.get(:cart) || []
300
+
301
+ if cart.empty?
302
+ app.say "Your cart is empty! 🛒"
303
+
304
+ browse_now = app.screen(:browse_from_empty_cart) do |prompt|
305
+ prompt.yes? "Would you like to browse our menu?"
306
+ end
307
+
308
+ if browse_now
309
+ browse_menu
310
+ else
311
+ main_menu
312
+ end
313
+ return
314
+ end
315
+
316
+ # Show cart contents
317
+ cart_summary = build_cart_summary(cart)
318
+ app.say "Your Cart:\n\n#{cart_summary}"
319
+
320
+ # Cart actions
321
+ action = app.screen(:cart_action) do |prompt|
322
+ prompt.select "What would you like to do?", [
323
+ "Proceed to Checkout",
324
+ "Continue Shopping",
325
+ "Clear Cart",
326
+ "Remove Item"
327
+ ]
328
+ end
329
+
330
+ case action
331
+ when "Proceed to Checkout"
332
+ checkout_process
333
+ when "Continue Shopping"
334
+ browse_menu
335
+ when "Clear Cart"
336
+ clear_cart
337
+ when "Remove Item"
338
+ remove_item_from_cart
339
+ end
340
+ end
341
+
342
+ def build_cart_summary(cart)
343
+ total = 0
344
+ summary = cart.map.with_index do |item, index|
345
+ # Extract price from item name (simplified)
346
+ price_match = item[:name].match(/\$(\d+)/)
347
+ price = price_match ? price_match[1].to_f : 0
348
+ item_total = price * item[:quantity]
349
+ total += item_total
350
+
351
+ instructions_text = item[:instructions] ? "\n Special: #{item[:instructions]}" : ""
352
+ "#{index + 1}. #{item[:name]} x#{item[:quantity]} = $#{item_total}#{instructions_text}"
353
+ end.join("\n")
354
+
355
+ summary + "\n\nTotal: $#{total.round(2)}"
356
+ end
357
+
358
+ def checkout_process
359
+ cart = app.session.get(:cart)
360
+ customer_data = app.session.get(:customer_data)
361
+
362
+ # Delivery or pickup
363
+ order_type = app.screen(:order_type) do |prompt|
364
+ prompt.select "How would you like your order?", {
365
+ "delivery" => "🚚 Delivery",
366
+ "pickup" => "🏃 Pickup"
367
+ }
368
+ end
369
+
370
+ if order_type == "delivery"
371
+ handle_delivery_address
372
+ else
373
+ handle_pickup_time
374
+ end
375
+
376
+ # Payment method
377
+ payment_method = app.screen(:payment_method) do |prompt|
378
+ prompt.select "Payment method:", {
379
+ "card" => "💳 Credit/Debit Card",
380
+ "cash" => "💵 Cash",
381
+ "mobile" => "📱 Mobile Payment"
382
+ }
383
+ end
384
+
385
+ if payment_method == "card"
386
+ handle_card_payment
387
+ end
388
+
389
+ # Order confirmation
390
+ order_confirmation
391
+ end
392
+
393
+ def handle_delivery_address
394
+ address = app.screen(:delivery_address) do |prompt|
395
+ prompt.ask "Enter your delivery address:",
396
+ validate: ->(addr) {
397
+ return "Address must be at least 10 characters" if addr.length < 10
398
+ nil
399
+ }
400
+ end
401
+
402
+ # Delivery time
403
+ delivery_time = app.screen(:delivery_time) do |prompt|
404
+ prompt.select "Preferred delivery time:", [
405
+ "ASAP (45-60 mins)",
406
+ "1-2 hours",
407
+ "2-3 hours",
408
+ "This evening",
409
+ "Schedule for later"
410
+ ]
411
+ end
412
+
413
+ app.session.set(:delivery_info, {
414
+ type: "delivery",
415
+ address: address,
416
+ time: delivery_time
417
+ })
418
+ end
419
+
420
+ def handle_pickup_time
421
+ pickup_time = app.screen(:pickup_time) do |prompt|
422
+ prompt.select "When would you like to pick up?", [
423
+ "20-30 minutes",
424
+ "30-45 minutes",
425
+ "1 hour",
426
+ "Schedule for later"
427
+ ]
428
+ end
429
+
430
+ app.session.set(:delivery_info, {
431
+ type: "pickup",
432
+ time: pickup_time
433
+ })
434
+ end
435
+
436
+ def handle_card_payment
437
+ # Card number validation (simplified for demo)
438
+ card_number = app.screen(:card_number) do |prompt|
439
+ prompt.ask "Enter card number (16 digits):",
440
+ transform: ->(input) { input.gsub(/\s/, '') },
441
+ validate: ->(card) {
442
+ return "Card number must be 16 digits" unless card.match?(/\A\d{16}\z/)
443
+ return "Invalid card number" unless luhn_valid?(card)
444
+ nil
445
+ }
446
+ end
447
+
448
+ # Expiry date
449
+ expiry = app.screen(:card_expiry) do |prompt|
450
+ prompt.ask "Expiry date (MM/YY):",
451
+ validate: ->(exp) {
452
+ return "Format must be MM/YY" unless exp.match?(/\A\d{2}\/\d{2}\z/)
453
+ month, year = exp.split('/').map(&:to_i)
454
+ return "Invalid month" unless (1..12).include?(month)
455
+ return "Card expired" if (2000 + year) < Date.current.year
456
+ nil
457
+ }
458
+ end
459
+
460
+ # CVV
461
+ cvv = app.screen(:card_cvv) do |prompt|
462
+ prompt.ask "CVV (3 digits):",
463
+ validate: ->(cvv) {
464
+ return "CVV must be 3 digits" unless cvv.match?(/\A\d{3}\z/)
465
+ nil
466
+ }
467
+ end
468
+
469
+ app.session.set(:payment_info, {
470
+ method: "card",
471
+ card_last_four: card_number[-4..-1],
472
+ status: "processed"
473
+ })
474
+ end
475
+
476
+ def order_confirmation
477
+ order_id = generate_order_id
478
+ cart = app.session.get(:cart)
479
+ customer_data = app.session.get(:customer_data)
480
+ delivery_info = app.session.get(:delivery_info)
481
+ payment_info = app.session.get(:payment_info)
482
+
483
+ # Save order
484
+ order_data = {
485
+ id: order_id,
486
+ customer: customer_data,
487
+ items: cart,
488
+ delivery: delivery_info,
489
+ payment: payment_info,
490
+ status: "confirmed",
491
+ created_at: Time.current.iso8601
492
+ }
493
+
494
+ orders = app.session.get(:orders) || []
495
+ orders << order_data
496
+ app.session.set(:orders, orders)
497
+
498
+ # Clear cart
499
+ app.session.set(:cart, [])
500
+
501
+ # Confirmation message with receipt
502
+ app.say "🎉 Order Confirmed!\n\nOrder ##{order_id}\nThank you, #{customer_data[:name]}!",
503
+ media: {
504
+ type: :document,
505
+ url: "https://flowchat-demo.com/receipts/#{order_id}.pdf",
506
+ filename: "receipt_#{order_id}.pdf"
507
+ }
508
+
509
+ # Send tracking info via audio message (WhatsApp feature)
510
+ app.say "Here's your order tracking information:",
511
+ media: {
512
+ type: :audio,
513
+ url: "https://flowchat-demo.com/tracking/#{order_id}.mp3"
514
+ }
515
+
516
+ # Ask for feedback
517
+ feedback_now = app.screen(:feedback_prompt) do |prompt|
518
+ prompt.yes? "Would you like to provide feedback about your ordering experience?"
519
+ end
520
+
521
+ if feedback_now
522
+ collect_feedback
523
+ else
524
+ post_order_options
525
+ end
526
+ end
527
+
528
+ def collect_feedback
529
+ rating = app.screen(:feedback_rating) do |prompt|
530
+ prompt.select "How would you rate your experience?", {
531
+ "5" => "⭐⭐⭐⭐⭐ Excellent",
532
+ "4" => "⭐⭐⭐⭐ Good",
533
+ "3" => "⭐⭐⭐ Average",
534
+ "2" => "⭐⭐ Poor",
535
+ "1" => "⭐ Very Poor"
536
+ }
537
+ end
538
+
539
+ if rating.to_i >= 4
540
+ app.say "Thank you for the great rating! 😊"
541
+ else
542
+ # Collect detailed feedback for lower ratings
543
+ feedback_details = app.screen(:feedback_details) do |prompt|
544
+ prompt.ask "We're sorry to hear that. Could you tell us what went wrong?",
545
+ validate: ->(feedback) {
546
+ return "Feedback must be at least 10 characters" if feedback.length < 10
547
+ nil
548
+ }
549
+ end
550
+
551
+ app.say "Thank you for your feedback. We'll use it to improve! 🙏"
552
+ end
553
+
554
+ post_order_options
555
+ end
556
+
557
+ def post_order_options
558
+ action = app.screen(:post_order_action) do |prompt|
559
+ prompt.select "What would you like to do now?", [
560
+ "Track Order",
561
+ "Order Again",
562
+ "Browse Menu",
563
+ "Contact Support",
564
+ "Exit"
565
+ ]
566
+ end
567
+
568
+ case action
569
+ when "Track Order"
570
+ track_order
571
+ when "Order Again"
572
+ browse_menu
573
+ when "Browse Menu"
574
+ browse_menu
575
+ when "Contact Support"
576
+ contact_support
577
+ when "Exit"
578
+ app.say "Thank you for choosing FlowChat Restaurant! 👋"
579
+ end
580
+ end
581
+
582
+ def order_history
583
+ orders = app.session.get(:orders) || []
584
+
585
+ if orders.empty?
586
+ app.say "No previous orders found."
587
+ main_menu
588
+ return
589
+ end
590
+
591
+ order_list = orders.map.with_index do |order, index|
592
+ "#{index + 1}. Order ##{order[:id]} - #{order[:created_at]}"
593
+ end
594
+
595
+ selected_index = app.screen(:order_history_selection) do |prompt|
596
+ prompt.select "Select an order to view:", order_list.map.with_index { |order, i| [i, order] }.to_h
597
+ end
598
+
599
+ show_order_details(orders[selected_index])
600
+ end
601
+
602
+ def show_order_details(order)
603
+ details = "Order ##{order[:id]}\n"
604
+ details += "Status: #{order[:status].titleize}\n"
605
+ details += "Date: #{order[:created_at]}\n\n"
606
+ details += "Items:\n"
607
+
608
+ order[:items].each do |item|
609
+ details += "- #{item[:name]} x#{item[:quantity]}\n"
610
+ end
611
+
612
+ app.say details
613
+
614
+ action = app.screen(:order_detail_action) do |prompt|
615
+ prompt.select "What would you like to do?", [
616
+ "Track Order",
617
+ "Reorder Items",
618
+ "Contact Support",
619
+ "Back to Menu"
620
+ ]
621
+ end
622
+
623
+ case action
624
+ when "Track Order"
625
+ track_order(order[:id])
626
+ when "Reorder Items"
627
+ reorder_items(order[:items])
628
+ when "Contact Support"
629
+ contact_support
630
+ when "Back to Menu"
631
+ main_menu
632
+ end
633
+ end
634
+
635
+ def track_order(order_id = nil)
636
+ order_id ||= app.session.get(:orders)&.last&.dig(:id)
637
+
638
+ if order_id.nil?
639
+ app.say "No orders to track."
640
+ main_menu
641
+ return
642
+ end
643
+
644
+ # Simulate tracking with video
645
+ app.say "Here's your order tracking:",
646
+ media: {
647
+ type: :video,
648
+ url: "https://flowchat-demo.com/tracking/#{order_id}.mp4"
649
+ }
650
+
651
+ app.say "📍 Order ##{order_id}\n🕐 Estimated time: 25 minutes\n📍 Status: Being prepared"
652
+
653
+ main_menu
654
+ end
655
+
656
+ def account_settings
657
+ customer_data = app.session.get(:customer_data)
658
+
659
+ setting = app.screen(:account_setting) do |prompt|
660
+ prompt.select "Account Settings:", [
661
+ "Update Name",
662
+ "Update Phone",
663
+ "Change Dietary Preferences",
664
+ "View Account Info",
665
+ "Delete Account"
666
+ ]
667
+ end
668
+
669
+ case setting
670
+ when "Update Name"
671
+ update_customer_name
672
+ when "Update Phone"
673
+ update_customer_phone
674
+ when "Change Dietary Preferences"
675
+ update_dietary_preferences
676
+ when "View Account Info"
677
+ show_account_info
678
+ when "Delete Account"
679
+ delete_account
680
+ end
681
+ end
682
+
683
+ def update_customer_name
684
+ new_name = app.screen(:update_name) do |prompt|
685
+ prompt.ask "Enter your new name:",
686
+ transform: ->(input) { input.strip.titleize },
687
+ validate: ->(input) {
688
+ return "Name must be at least 2 characters" if input.length < 2
689
+ nil
690
+ }
691
+ end
692
+
693
+ customer_data = app.session.get(:customer_data)
694
+ customer_data[:name] = new_name
695
+ app.session.set(:customer_data, customer_data)
696
+
697
+ app.say "Name updated successfully! ✅"
698
+ account_settings
699
+ end
700
+
701
+ def contact_support
702
+ issue_type = app.screen(:support_issue_type) do |prompt|
703
+ prompt.select "What can we help you with?", {
704
+ "order_issue" => "Order Issue",
705
+ "payment_problem" => "Payment Problem",
706
+ "delivery_issue" => "Delivery Issue",
707
+ "food_quality" => "Food Quality Concern",
708
+ "technical_support" => "Technical Support",
709
+ "general_inquiry" => "General Inquiry"
710
+ }
711
+ end
712
+
713
+ # Collect issue description
714
+ description = app.screen(:support_description) do |prompt|
715
+ prompt.ask "Please describe your issue:",
716
+ validate: ->(desc) {
717
+ return "Description must be at least 20 characters" if desc.length < 20
718
+ nil
719
+ }
720
+ end
721
+
722
+ # Contact preference
723
+ contact_method = app.screen(:support_contact_method) do |prompt|
724
+ prompt.select "How would you like us to contact you?", {
725
+ "whatsapp" => "📱 WhatsApp",
726
+ "phone" => "📞 Phone Call",
727
+ "email" => "📧 Email"
728
+ }
729
+ end
730
+
731
+ # Generate support ticket
732
+ ticket_id = "SUP#{Time.current.to_i}"
733
+
734
+ # Confirmation with support document
735
+ app.say "Support ticket created: ##{ticket_id}\n\nWe'll contact you within 24 hours via #{contact_method}.",
736
+ media: {
737
+ type: :document,
738
+ url: "https://flowchat-demo.com/support/#{ticket_id}.pdf",
739
+ filename: "support_ticket_#{ticket_id}.pdf"
740
+ }
741
+
742
+ main_menu
743
+ end
744
+
745
+ def clear_cart
746
+ confirm = app.screen(:confirm_clear_cart) do |prompt|
747
+ prompt.yes? "Are you sure you want to clear your cart?"
748
+ end
749
+
750
+ if confirm
751
+ app.session.set(:cart, [])
752
+ app.say "Cart cleared! 🗑️"
753
+ end
754
+
755
+ main_menu
756
+ end
757
+
758
+ def remove_item_from_cart
759
+ cart = app.session.get(:cart) || []
760
+
761
+ if cart.empty?
762
+ app.say "Cart is already empty!"
763
+ main_menu
764
+ return
765
+ end
766
+
767
+ # Show items to remove
768
+ item_options = cart.map.with_index do |item, index|
769
+ [index, "#{index + 1}. #{item[:name]} x#{item[:quantity]}"]
770
+ end.to_h
771
+
772
+ selected_index = app.screen(:remove_item_selection) do |prompt|
773
+ prompt.select "Which item would you like to remove?", item_options
774
+ end
775
+
776
+ removed_item = cart.delete_at(selected_index)
777
+ app.session.set(:cart, cart)
778
+
779
+ app.say "Removed: #{removed_item[:name]} ❌"
780
+ view_cart
781
+ end
782
+
783
+ # Helper methods
784
+ def generate_order_id
785
+ "ORD#{Time.current.to_i}#{rand(100..999)}"
786
+ end
787
+
788
+ def luhn_valid?(card_number)
789
+ # Simplified Luhn algorithm for demo
790
+ digits = card_number.chars.map(&:to_i).reverse
791
+ sum = digits.each_with_index.sum do |digit, index|
792
+ if index.odd?
793
+ doubled = digit * 2
794
+ doubled > 9 ? doubled - 9 : doubled
795
+ else
796
+ digit
797
+ end
798
+ end
799
+ sum % 10 == 0
800
+ end
801
+
802
+ def reorder_items(items)
803
+ # Add items back to cart
804
+ cart = app.session.get(:cart) || []
805
+ items.each { |item| cart << item }
806
+ app.session.set(:cart, cart)
807
+
808
+ app.say "Items added to your cart! 🛒"
809
+ view_cart
810
+ end
811
+
812
+ def show_account_info
813
+ customer_data = app.session.get(:customer_data)
814
+ orders = app.session.get(:orders) || []
815
+
816
+ info = "Account Information:\n\n"
817
+ info += "Name: #{customer_data[:name]}\n"
818
+ info += "Phone: #{customer_data[:phone]}\n"
819
+ info += "Age: #{customer_data[:age]}\n"
820
+ info += "Dietary Preference: #{customer_data[:dietary_preference].titleize}\n"
821
+ info += "Total Orders: #{orders.length}\n"
822
+ info += "Member Since: #{customer_data[:registered_at]}"
823
+
824
+ app.say info
825
+ account_settings
826
+ end
827
+
828
+ def update_dietary_preferences
829
+ new_preference = app.screen(:update_dietary_preference) do |prompt|
830
+ prompt.select "Update dietary preferences:", {
831
+ "none" => "No restrictions",
832
+ "vegetarian" => "Vegetarian",
833
+ "vegan" => "Vegan",
834
+ "gluten_free" => "Gluten-free",
835
+ "keto" => "Keto",
836
+ "halal" => "Halal"
837
+ }
838
+ end
839
+
840
+ customer_data = app.session.get(:customer_data)
841
+ customer_data[:dietary_preference] = new_preference
842
+ app.session.set(:customer_data, customer_data)
843
+
844
+ app.say "Dietary preferences updated! ✅"
845
+ account_settings
846
+ end
847
+
848
+ def update_customer_phone
849
+ new_phone = app.screen(:update_phone) do |prompt|
850
+ prompt.ask "Enter your new phone number:",
851
+ transform: ->(input) { input.strip.gsub(/[\s\-\(\)]/, '') },
852
+ validate: ->(input) {
853
+ return "Phone number must start with +" unless input.start_with?('+')
854
+ return "Phone number must be 8-15 digits after +" unless input[1..-1].match?(/\A\d{8,15}\z/)
855
+ nil
856
+ }
857
+ end
858
+
859
+ customer_data = app.session.get(:customer_data)
860
+ customer_data[:phone] = new_phone
861
+ app.session.set(:customer_data, customer_data)
862
+
863
+ app.say "Phone number updated successfully! ✅"
864
+ account_settings
865
+ end
866
+
867
+ def delete_account
868
+ confirm = app.screen(:confirm_delete_account) do |prompt|
869
+ prompt.yes? "⚠️ Are you sure you want to delete your account? This cannot be undone."
870
+ end
871
+
872
+ if confirm
873
+ double_confirm = app.screen(:double_confirm_delete) do |prompt|
874
+ prompt.yes? "This will permanently delete all your data. Are you absolutely sure?"
875
+ end
876
+
877
+ if double_confirm
878
+ # Clear all session data
879
+ app.session.clear
880
+ app.say "Your account has been deleted. Thank you for using FlowChat Restaurant! 👋"
881
+ else
882
+ app.say "Account deletion cancelled."
883
+ account_settings
884
+ end
885
+ else
886
+ account_settings
887
+ end
888
+ end
889
+ end