@fleetbase/storefront-engine 0.3.20 → 0.3.22

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.
@@ -59,3 +59,11 @@
59
59
  .status-badge.order-canceled-status-badge > span svg {
60
60
  color: #fca5a5;
61
61
  }
62
+
63
+ /** hotfix nav tab */
64
+ body[data-theme='dark'] .ui-tabs.overlay-content-panel > ul .nav-item.active,
65
+ body[data-theme='dark'] .ui-tabs.overlay-content-panel > nav .nav-item.active,
66
+ body[data-theme='dark'] .ui-tabs.overlay-content-panel > ul .ui-tab.active,
67
+ body[data-theme='dark'] .ui-tabs.overlay-content-panel > nav .ui-tab.active {
68
+ background-color: #202A37;
69
+ }
@@ -142,7 +142,7 @@
142
142
  <Button @text="New Variant" @icon="plus" @iconPrefix="fas" @onClick={{this.createProductVariant}} @permission="storefront create product-variant" />
143
143
  </div>
144
144
  </div>
145
- <Tabs as |Tab|>
145
+ <Tabs class="overlay-content-panel" as |Tab|>
146
146
  {{#each this.product.variants as |variant|}}
147
147
  <Tab @title={{variant.name}}>
148
148
  <div class="px-4 py-3">
package/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/storefront-api",
3
- "version": "0.3.20",
3
+ "version": "0.3.22",
4
4
  "description": "Headless Commerce & Marketplace Extension for Fleetbase",
5
5
  "keywords": [
6
6
  "fleetbase-extension",
package/extension.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Storefront",
3
- "version": "0.3.20",
3
+ "version": "0.3.22",
4
4
  "description": "Headless Commerce & Marketplace Extension for Fleetbase",
5
5
  "repository": "https://github.com/fleetbase/storefront",
6
6
  "license": "AGPL-3.0-or-later",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/storefront-engine",
3
- "version": "0.3.20",
3
+ "version": "0.3.22",
4
4
  "description": "Headless Commerce & Marketplace Extension for Fleetbase",
5
5
  "fleetbase": {
6
6
  "route": "storefront",
@@ -6,6 +6,11 @@
6
6
  * -------------------------------------------
7
7
  */
8
8
  return [
9
+ /*
10
+ |--------------------------------------------------------------------------
11
+ | API Config
12
+ |--------------------------------------------------------------------------
13
+ */
9
14
  'api' => [
10
15
  'version' => '0.0.1',
11
16
  'routing' => [
@@ -13,6 +18,21 @@ return [
13
18
  'internal_prefix' => 'int'
14
19
  ],
15
20
  ],
21
+
22
+ /*
23
+ |--------------------------------------------------------------------------
24
+ | Storefront App
25
+ |--------------------------------------------------------------------------
26
+ */
27
+ 'storefront_app' => [
28
+ 'bypass_verification_code' => env('STOREFRONT_BYPASS_VERIFICATION_CODE', '999000')
29
+ ],
30
+
31
+ /*
32
+ |--------------------------------------------------------------------------
33
+ | Database Connection
34
+ |--------------------------------------------------------------------------
35
+ */
16
36
  'connection' => [
17
37
  'db' => env('STOREFRONT_DB_CONNECTION', 'storefront')
18
38
  ]
@@ -0,0 +1,118 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Storefront\Console\Commands;
4
+
5
+ use Fleetbase\FleetOps\Models\Order;
6
+ use Fleetbase\FleetOps\Support\Utils;
7
+ use Illuminate\Console\Command;
8
+
9
+ class SendOrderNotification extends Command
10
+ {
11
+ /**
12
+ * The name and signature of the console command.
13
+ *
14
+ * @var string
15
+ */
16
+ protected $signature = 'storefront:send-notification {--id= : The ID of the order} {--event= : The name of the event to trigger}';
17
+
18
+ /**
19
+ * The console command description.
20
+ *
21
+ * @var string
22
+ */
23
+ protected $description = 'Manually trigger a push notification for an order';
24
+
25
+ /**
26
+ * Mapping of event names to notification classes.
27
+ *
28
+ * @var array
29
+ */
30
+ protected $eventToNotification = [
31
+ 'created' => \Fleetbase\Storefront\Notifications\StorefrontOrderCreated::class,
32
+ 'canceled' => \Fleetbase\Storefront\Notifications\StorefrontOrderCanceled::class,
33
+ 'completed' => \Fleetbase\Storefront\Notifications\StorefrontOrderCompleted::class,
34
+ 'driver_assigned' => \Fleetbase\Storefront\Notifications\StorefrontOrderDriverAssigned::class,
35
+ 'enroute' => \Fleetbase\Storefront\Notifications\StorefrontOrderEnroute::class,
36
+ 'preparing' => \Fleetbase\Storefront\Notifications\StorefrontOrderPreparing::class,
37
+ 'ready_for_pickup' => \Fleetbase\Storefront\Notifications\StorefrontOrderReadyForPickup::class,
38
+ 'nearby' => \Fleetbase\Storefront\Notifications\StorefrontOrderNearby::class,
39
+ ];
40
+
41
+ /**
42
+ * Execute the console command.
43
+ *
44
+ * @return int
45
+ */
46
+ public function handle()
47
+ {
48
+ // Get order ID and event from options
49
+ $orderId = $this->option('id');
50
+ $event = $this->option('event');
51
+
52
+ // Prompt user to search for order ID if not provided
53
+ if (!$orderId) {
54
+ $orderId = $this->ask('Enter the order ID to trigger the notification');
55
+ }
56
+
57
+ // Attempt to find the order
58
+ $order = Order::where('public_id', $orderId)->first();
59
+
60
+ if (!$order) {
61
+ $this->error('Order not found!');
62
+
63
+ return 1;
64
+ }
65
+
66
+ // Load customer relation
67
+ $order->loadMissing('customer');
68
+
69
+ if (!$order->customer) {
70
+ $this->error('Order does not have an associated customer!');
71
+
72
+ return 1;
73
+ }
74
+
75
+ // Prompt user to select event if not provided
76
+ if (!$event) {
77
+ $event = $this->choice(
78
+ 'Select the event to trigger',
79
+ array_keys($this->eventToNotification),
80
+ 'created' // Default event
81
+ );
82
+ }
83
+
84
+ // Resolve notification class
85
+ $notificationClass = $this->eventToNotification[$event] ?? null;
86
+
87
+ if (!$notificationClass) {
88
+ $this->error('Invalid event selected!');
89
+
90
+ return 1;
91
+ }
92
+
93
+ // nearby notification requires more arguments
94
+ try {
95
+ if ($event === 'nearby') {
96
+ $origin = $order->payload->getPickupOrFirstWaypoint();
97
+ $destination = $order->payload->getDropoffOrLastWaypoint();
98
+ $matrix = Utils::getDrivingDistanceAndTime($origin, $destination);
99
+ $distance = $matrix->distance;
100
+ $time = $matrix->time;
101
+
102
+ // Trigger notification
103
+ $order->customer->notify(new $notificationClass($order, $distance, $time));
104
+ } else {
105
+ // Trigger notification
106
+ $order->customer->notify(new $notificationClass($order));
107
+ }
108
+ } catch (\Exception $e) {
109
+ $this->error($e->getMessage());
110
+
111
+ return 0;
112
+ }
113
+
114
+ $this->info("Notification '{$event}' has been triggered for order ID '{$orderId}'.");
115
+
116
+ return 0;
117
+ }
118
+ }
@@ -10,7 +10,6 @@ use Fleetbase\Storefront\Imports\ProductsImport;
10
10
  use Fleetbase\Storefront\Jobs\DownloadProductImageUrl;
11
11
  use Fleetbase\Storefront\Models\Product;
12
12
  use Fleetbase\Storefront\Models\Store;
13
- use Fleetbase\Support\Http;
14
13
  use Illuminate\Http\Request;
15
14
  use Illuminate\Support\Arr;
16
15
  use Illuminate\Support\Str;
@@ -25,53 +24,6 @@ class ProductController extends StorefrontController
25
24
  */
26
25
  public $resource = 'product';
27
26
 
28
- /**
29
- * Update a Product record.
30
- * This update method was overwritten because the ProductObserver
31
- * isn't firing on the `updated` callback.
32
- *
33
- * @return \Fleetbase\Storefront\Http\Resources\Product
34
- */
35
- public function updateRecord(Request $request, string $id)
36
- {
37
- try {
38
- $this->validateRequest($request);
39
- $record = $this->model->updateRecordFromRequest($request, $id, function (&$request, Product &$product) {
40
- $addonCategories = $request->array('product.addon_categories');
41
- $variants = $request->array('product.variants');
42
- $files = $request->array('product.files');
43
-
44
- // dd($addonCategories);
45
-
46
- // save addon categories
47
- $product->setAddonCategories($addonCategories);
48
-
49
- // save product variants
50
- $product->setProductVariants($variants);
51
-
52
- // set keys on files
53
- foreach ($files as $file) {
54
- $fileRecord = File::where('uuid', $file['uuid'])->first();
55
- $fileRecord->setKey($product);
56
- }
57
- });
58
-
59
- if (Http::isInternalRequest($request)) {
60
- $this->resource::wrap($this->resourceSingularlName);
61
-
62
- return new $this->resource($record);
63
- }
64
-
65
- return new $this->resource($record);
66
- } catch (\Exception $e) {
67
- return response()->error($e->getMessage());
68
- } catch (\Illuminate\Database\QueryException $e) {
69
- return response()->error($e->getMessage());
70
- } catch (\Fleetbase\Exceptions\FleetbaseRequestValidationException $e) {
71
- return response()->error($e->getErrors());
72
- }
73
- }
74
-
75
27
  /**
76
28
  * List all activity options for current order.
77
29
  *
@@ -186,7 +186,7 @@ class CheckoutController extends Controller
186
186
 
187
187
  // Check if customer has a saved default payment method
188
188
  if (StripeUtils::isCustomerPaymentMethodValid($customer)) {
189
- $paymentIntentData['payment_method'] = $$customer->getMeta('stripe_payment_method_id');
189
+ $paymentIntentData['payment_method'] = $customer->getMeta('stripe_payment_method_id');
190
190
  }
191
191
 
192
192
  try {
@@ -246,7 +246,7 @@ class CheckoutController extends Controller
246
246
 
247
247
  // Check if customer has a saved default payment method
248
248
  if (StripeUtils::isCustomerPaymentMethodValid($customer)) {
249
- $paymentIntentData['payment_method'] = $$customer->getMeta('stripe_payment_method_id');
249
+ $paymentIntentData['payment_method'] = $customer->getMeta('stripe_payment_method_id');
250
250
  }
251
251
 
252
252
  try {
@@ -283,6 +283,7 @@ class CheckoutController extends Controller
283
283
  'setupIntent' => $setupIntent->id,
284
284
  'clientSecret' => $setupIntent->client_secret,
285
285
  'defaultPaymentMethod' => $defaultPaymentMethod,
286
+ 'customerId' => $customer->getMeta('stripe_id'),
286
287
  ]);
287
288
  } catch (\Exception $e) {
288
289
  return response()->apiError($e->getMessage());
@@ -2,6 +2,8 @@
2
2
 
3
3
  namespace Fleetbase\Storefront\Http\Controllers\v1;
4
4
 
5
+ use Fleetbase\Auth\AppleVerifier;
6
+ use Fleetbase\Auth\GoogleVerifier;
5
7
  use Fleetbase\FleetOps\Exceptions\UserAlreadyExistsException;
6
8
  use Fleetbase\FleetOps\Http\Requests\UpdateContactRequest;
7
9
  use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
@@ -36,7 +38,7 @@ class CustomerController extends Controller
36
38
  $customer = Storefront::getCustomerFromToken();
37
39
 
38
40
  if (!$customer) {
39
- return response()->error('Not authorized to register device for cutomer');
41
+ return response()->apiError('Not authorized to register device for cutomer');
40
42
  }
41
43
 
42
44
  $device = UserDevice::firstOrCreate(
@@ -68,7 +70,7 @@ class CustomerController extends Controller
68
70
  $customer = Storefront::getCustomerFromToken();
69
71
 
70
72
  if (!$customer) {
71
- return response()->error('Not authorized to view customers orders');
73
+ return response()->apiError('Not authorized to view customers orders');
72
74
  }
73
75
 
74
76
  $results = Order::queryWithRequest($request, function (&$query) use ($customer) {
@@ -96,7 +98,7 @@ class CustomerController extends Controller
96
98
  $customer = Storefront::getCustomerFromToken();
97
99
 
98
100
  if (!$customer) {
99
- return response()->error('Not authorized to view customers places');
101
+ return response()->apiError('Not authorized to view customers places');
100
102
  }
101
103
 
102
104
  $results = Place::queryWithRequest($request, function (&$query) use ($customer) {
@@ -121,7 +123,7 @@ class CustomerController extends Controller
121
123
 
122
124
  // validate identity
123
125
  if ($mode === 'email' && !$isEmail) {
124
- return response()->error('Invalid email provided for identity');
126
+ return response()->apiError('Invalid email provided for identity');
125
127
  }
126
128
 
127
129
  // prepare phone number
@@ -176,7 +178,7 @@ class CustomerController extends Controller
176
178
  // verify code
177
179
  $isVerified = VerificationCode::where(['code' => $code, 'for' => 'storefront_create_customer', 'meta->identity' => $identity])->exists();
178
180
  if (!$isVerified) {
179
- return response()->error('Invalid verification code provided!');
181
+ return response()->apiError('Invalid verification code provided!');
180
182
  }
181
183
 
182
184
  // check for existing user to attach contact to
@@ -250,7 +252,7 @@ class CustomerController extends Controller
250
252
  try {
251
253
  $contact = Contact::findRecordOrFail($id);
252
254
  } catch (ModelNotFoundException $exception) {
253
- return response()->error('Customer resource not found.');
255
+ return response()->apiError('Customer resource not found.');
254
256
  }
255
257
 
256
258
  // get request input
@@ -303,7 +305,7 @@ class CustomerController extends Controller
303
305
  try {
304
306
  $contact = Contact::findRecordOrFail($id);
305
307
  } catch (ModelNotFoundException $exception) {
306
- return response()->error('Customer resource not found.');
308
+ return response()->apiError('Customer resource not found.');
307
309
  }
308
310
 
309
311
  // response the customer resource
@@ -325,7 +327,7 @@ class CustomerController extends Controller
325
327
  try {
326
328
  $contact = Contact::findRecordOrFail($id);
327
329
  } catch (ModelNotFoundException $exception) {
328
- return response()->error('Customer resource not found.');
330
+ return response()->apiError('Customer resource not found.');
329
331
  }
330
332
 
331
333
  // delete the product
@@ -349,7 +351,7 @@ class CustomerController extends Controller
349
351
  $user = User::where('email', $identity)->orWhere('phone', static::phone($identity))->first();
350
352
 
351
353
  if (!Hash::check($password, $user->password)) {
352
- return response()->error('Authentication failed using password provided.', 401);
354
+ return response()->apiError('Authentication failed using password provided.', 401);
353
355
  }
354
356
 
355
357
  // get the storefront or network logging in for
@@ -376,7 +378,7 @@ class CustomerController extends Controller
376
378
  try {
377
379
  $token = $user->createToken($contact->uuid);
378
380
  } catch (\Exception $e) {
379
- return response()->error($e->getMessage());
381
+ return response()->apiError($e->getMessage());
380
382
  }
381
383
 
382
384
  $contact->token = $token->plainTextToken;
@@ -397,7 +399,7 @@ class CustomerController extends Controller
397
399
  $user = User::where('phone', $phone)->whereNull('deleted_at')->withoutGlobalScopes()->first();
398
400
 
399
401
  if (!$user) {
400
- return response()->error('No customer with this phone # found.');
402
+ return response()->apiError('No customer with this phone # found.');
401
403
  }
402
404
 
403
405
  // get the storefront or network logging in for
@@ -413,6 +415,243 @@ class CustomerController extends Controller
413
415
  return response()->json(['status' => 'OK']);
414
416
  }
415
417
 
418
+ /**
419
+ * Handles user authentication via Apple Sign-In.
420
+ *
421
+ * This method validates the Apple ID token, checks if the user exists in the system,
422
+ * and creates a new user if necessary. It then ensures a contact record exists for
423
+ * the user and generates an authentication token.
424
+ *
425
+ * @param Request $request
426
+ * The HTTP request containing the following required fields:
427
+ * - `identityToken` (string): The token generated by Apple to identify the user.
428
+ * - `authorizationCode` (string): The one-time code issued by Apple during login.
429
+ * - `email` (string|null): The user's email address (provided on first login).
430
+ * - `phone` (string|null): The user's phone number (optional).
431
+ * - `name` (string|null): The user's full name (optional).
432
+ * - `appleUserId` (string): A unique identifier for the user assigned by Apple.
433
+ *
434
+ * @return \Illuminate\Http\JsonResponse
435
+ * A JSON response containing the authenticated customer's details, including an access token
436
+ *
437
+ * @throws \Exception
438
+ * If Apple authentication fails or any other error occurs during the process
439
+ */
440
+ public function loginWithApple(Request $request)
441
+ {
442
+ $identityToken = $request->input('identityToken');
443
+ $authorizationCode = $request->input('authorizationCode');
444
+ $email = $request->input('email');
445
+ $phone = $request->input('phone');
446
+ $name = $request->input('name');
447
+ $appleUserId = $request->input('appleUserId');
448
+
449
+ if (!$identityToken || !$authorizationCode) {
450
+ return response()->apiError('Missing required Apple authentication parameters.', 400);
451
+ }
452
+
453
+ try {
454
+ // Verify the Apple token using the utility function
455
+ $isValid = AppleVerifier::verifyAppleJwt($identityToken);
456
+ if (!$isValid) {
457
+ return response()->apiError('Apple ID authentication is not valid.', 400);
458
+ }
459
+
460
+ // Check if the user exists in the system
461
+ $user = User::where(function ($query) use ($email, $appleUserId) {
462
+ if ($email) {
463
+ $query->where('email', $email);
464
+ $query->orWhere('apple_user_id', $appleUserId);
465
+ } else {
466
+ $query->where('apple_user_id', $appleUserId);
467
+ }
468
+ })->first();
469
+
470
+ if (!$user) {
471
+ // Create a new user
472
+ $user = User::create([
473
+ 'email' => $email,
474
+ 'phone' => $phone,
475
+ 'name' => $name,
476
+ 'apple_user_id' => $appleUserId,
477
+ 'type' => 'customer',
478
+ 'company_uuid' => session('company'),
479
+ ]);
480
+ } else {
481
+ // Update the `apple_user_id` if it's not already set
482
+ if (!$user->apple_user_id) {
483
+ $user->apple_user_id = $appleUserId;
484
+ $user->save();
485
+ }
486
+ }
487
+
488
+ // Ensure a customer contact exists
489
+ $contact = Contact::firstOrCreate(
490
+ ['user_uuid' => $user->uuid, 'company_uuid' => session('company')],
491
+ ['name' => $user->name, 'email' => $user->email, 'phone' => $user->phone, 'meta' => ['apple_user_id' => $appleUserId], 'type' => 'customer']
492
+ );
493
+
494
+ // Generate an auth token
495
+ $token = $user->createToken($contact->uuid);
496
+ $contact->token = $token->plainTextToken;
497
+
498
+ return new Customer($contact);
499
+ } catch (\Exception $e) {
500
+ return response()->apiError($e->getMessage(), 500);
501
+ }
502
+ }
503
+
504
+ /**
505
+ * Handles user authentication via Facebook Sign-In.
506
+ *
507
+ * This method checks if the user exists in the system based on their email or Facebook ID.
508
+ * If the user does not exist, it creates a new user and ensures a contact record is created.
509
+ * Finally, it generates an authentication token for the user.
510
+ *
511
+ * @param Request $request
512
+ * The HTTP request containing the following required fields:
513
+ * - `email` (string|null): The user's email address.
514
+ * - `name` (string|null): The user's full name.
515
+ * - `facebookUserId` (string): A unique identifier for the user assigned by Facebook.
516
+ *
517
+ * @return \Illuminate\Http\JsonResponse
518
+ * A JSON response containing the authenticated customer's details, including an access token
519
+ *
520
+ * @throws \Exception
521
+ * If Facebook authentication fails or any other error occurs during the process
522
+ */
523
+ public function loginWithFacebook(Request $request)
524
+ {
525
+ $email = $request->input('email');
526
+ $name = $request->input('name');
527
+ $facebookUserId = $request->input('facebookUserId');
528
+
529
+ try {
530
+ // Check if the user exists in the system
531
+ $user = User::where(function ($query) use ($email, $facebookUserId) {
532
+ if ($email) {
533
+ $query->where('email', $email);
534
+ $query->orWhere('facebook_user_id', $facebookUserId);
535
+ } else {
536
+ $query->where('facebook_user_id', $facebookUserId);
537
+ }
538
+ })->first();
539
+
540
+ if (!$user) {
541
+ // Create a new user
542
+ $user = User::create([
543
+ 'email' => $email,
544
+ 'name' => $name,
545
+ 'facebook_user_id' => $facebookUserId,
546
+ 'type' => 'customer',
547
+ 'company_uuid' => session('company'),
548
+ ]);
549
+ } else {
550
+ // Update the `facebook_user_id` if it's not already set
551
+ if (!$user->facebook_user_id) {
552
+ $user->facebook_user_id = $facebookUserId;
553
+ $user->save();
554
+ }
555
+ }
556
+
557
+ // Ensure a customer contact exists
558
+ $contact = Contact::firstOrCreate(
559
+ ['user_uuid' => $user->uuid, 'company_uuid' => session('company')],
560
+ ['name' => $user->name, 'email' => $user->email, 'phone' => $user->phone, 'meta' => ['facebook_user_id' => $facebookUserId], 'type' => 'customer']
561
+ );
562
+
563
+ // Generate an auth token
564
+ $token = $user->createToken($contact->uuid);
565
+ $contact->token = $token->plainTextToken;
566
+
567
+ return new Customer($contact);
568
+ } catch (\Exception $e) {
569
+ return response()->apiError($e->getMessage(), 500);
570
+ }
571
+ }
572
+
573
+ /**
574
+ * Handles user authentication via Google Sign-In.
575
+ *
576
+ * This method validates the Google ID token, retrieves user details from the token payload,
577
+ * checks if the user exists in the system, and creates a new user if necessary.
578
+ * It ensures a contact record exists for the user and generates an authentication token.
579
+ *
580
+ * @param Request $request
581
+ * The HTTP request containing the following required fields:
582
+ * - `idToken` (string): The token generated by Google to identify the user.
583
+ * - `clientId` (string): The client ID associated with the app.
584
+ *
585
+ * @return \Illuminate\Http\JsonResponse
586
+ * A JSON response containing the authenticated customer's details, including an access token
587
+ *
588
+ * @throws \Exception
589
+ * If Google authentication fails or any other error occurs during the process
590
+ */
591
+ public function loginWithGoogle(Request $request)
592
+ {
593
+ $idToken = $request->input('idToken');
594
+ $clientId = $request->input('clientId');
595
+ if (!$idToken || !$clientId) {
596
+ return response()->apiError('Missing required Google authentication parameters.', 400);
597
+ }
598
+
599
+ try {
600
+ // Verify the Google ID token using the utility function
601
+ $payload = GoogleVerifier::verifyIdToken($idToken, $clientId);
602
+ if (!$payload) {
603
+ return response()->apiError('Google Sign-In authentication is not valid.', 400);
604
+ }
605
+
606
+ // Extract user details from the payload
607
+ $email = data_get($payload, 'email');
608
+ $name = data_get($payload, 'name');
609
+ $googleUserId = data_get($payload, 'sub');
610
+ $avatarUrl = data_get($payload, 'picture');
611
+
612
+ // Check if the user exists in the system
613
+ $user = User::where(function ($query) use ($email, $googleUserId) {
614
+ if ($email) {
615
+ $query->where('email', $email);
616
+ $query->orWhere('google_user_id', $googleUserId);
617
+ } else {
618
+ $query->where('google_user_id', $googleUserId);
619
+ }
620
+ })->first();
621
+
622
+ if (!$user) {
623
+ // Create a new user
624
+ $user = User::create([
625
+ 'email' => $email,
626
+ 'name' => $name,
627
+ 'google_user_id' => $googleUserId,
628
+ 'type' => 'customer',
629
+ 'company_uuid' => session('company'),
630
+ ]);
631
+ } else {
632
+ // Update the `google_user_id` if it's not already set
633
+ if (!$user->google_user_id) {
634
+ $user->google_user_id = $googleUserId;
635
+ $user->save();
636
+ }
637
+ }
638
+
639
+ // Ensure a customer contact exists
640
+ $contact = Contact::firstOrCreate(
641
+ ['user_uuid' => $user->uuid, 'company_uuid' => session('company')],
642
+ ['name' => $user->name, 'email' => $user->email, 'phone' => $user->phone, 'meta' => ['google_user_id' => $googleUserId], 'type' => 'customer']
643
+ );
644
+
645
+ // Generate an auth token
646
+ $token = $user->createToken($contact->uuid);
647
+ $contact->token = $token->plainTextToken;
648
+
649
+ return new Customer($contact);
650
+ } catch (\Exception $e) {
651
+ return response()->apiError($e->getMessage(), 500);
652
+ }
653
+ }
654
+
416
655
  /**
417
656
  * Verifys SMS code and sends auth token with customer resource.
418
657
  *
@@ -433,14 +672,13 @@ class CustomerController extends Controller
433
672
  $user = User::where('phone', $identity)->orWhere('email', $identity)->first();
434
673
 
435
674
  if (!$user) {
436
- return response()->error('Unable to verify code.');
675
+ return response()->apiError('Unable to verify code.');
437
676
  }
438
677
 
439
678
  // find and verify code
440
679
  $verificationCode = VerificationCode::where(['subject_uuid' => $user->uuid, 'code' => $code, 'for' => $for])->exists();
441
-
442
- if (!$verificationCode && $code !== '999000') {
443
- return response()->error('Invalid verification code!');
680
+ if (!$verificationCode && $code !== config('storefront.storefront_app.bypass_verification_code')) {
681
+ return response()->apiError('Invalid verification code!');
444
682
  }
445
683
 
446
684
  // get the storefront or network logging in for
@@ -467,7 +705,7 @@ class CustomerController extends Controller
467
705
  try {
468
706
  $token = $user->createToken($contact->uuid);
469
707
  } catch (\Exception $e) {
470
- return response()->error($e->getMessage());
708
+ return response()->apiError($e->getMessage());
471
709
  }
472
710
 
473
711
  $contact->token = $token->plainTextToken;
@@ -495,7 +733,7 @@ class CustomerController extends Controller
495
733
  {
496
734
  $customer = Storefront::getCustomerFromToken();
497
735
  if (!$customer) {
498
- return response()->error('Not authorized to view customers places');
736
+ return response()->apiError('Not authorized to view customers places');
499
737
  }
500
738
 
501
739
  $gateway = Storefront::findGateway('stripe');
@@ -518,8 +756,8 @@ class CustomerController extends Controller
518
756
  );
519
757
 
520
758
  return response()->json([
521
- 'ephemeralKey' => $ephemeralKey->secret,
522
- 'customer' => $customer->getMeta('stripe_id'),
759
+ 'ephemeralKey' => $ephemeralKey->secret,
760
+ 'customerId' => $customer->getMeta('stripe_id'),
523
761
  ]);
524
762
  } catch (\Exception $e) {
525
763
  return response()->apiError($e->getMessage());
@@ -530,7 +768,7 @@ class CustomerController extends Controller
530
768
  {
531
769
  $customer = Storefront::getCustomerFromToken();
532
770
  if (!$customer) {
533
- return response()->error('Not authorized to view customers places');
771
+ return response()->apiError('Not authorized to view customers places');
534
772
  }
535
773
 
536
774
  $gateway = Storefront::findGateway('stripe');
@@ -554,6 +792,7 @@ class CustomerController extends Controller
554
792
  return response()->json([
555
793
  'setupIntentId' => $setupIntent->id,
556
794
  'setupIntent' => $setupIntent->client_secret,
795
+ 'customerId' => $customer->getMeta('stripe_id'),
557
796
  ]);
558
797
  } catch (\Exception $e) {
559
798
  return response()->apiError($e->getMessage());
@@ -5,7 +5,7 @@ namespace Fleetbase\Storefront\Http\Requests;
5
5
  use Fleetbase\Http\Requests\FleetbaseRequest;
6
6
  use Fleetbase\Storefront\Rules\CustomerExists;
7
7
 
8
- class CustomerRequest extends FleetbaseRequest
8
+ class StorefrontCustomerRequest extends FleetbaseRequest
9
9
  {
10
10
  /**
11
11
  * Determine if the user is authorized to make this request.
@@ -44,7 +44,7 @@ class Cart extends FleetbaseResource
44
44
  $items = $this->items ?? [];
45
45
 
46
46
  return array_map(function ($cartItem) {
47
- $product = Product::select(['public_id', 'primary_image_uuid', 'name', 'description'])->where('public_id', data_get($cartItem, 'product_id'))->first();
47
+ $product = Product::select(['uuid', 'public_id', 'primary_image_uuid', 'name', 'description'])->with(['files'])->where('public_id', data_get($cartItem, 'product_id'))->first();
48
48
  if ($product) {
49
49
  data_set($cartItem, 'name', $product->name);
50
50
  data_set($cartItem, 'description', $product->description);
@@ -2,15 +2,9 @@
2
2
 
3
3
  namespace Fleetbase\Storefront\Observers;
4
4
 
5
- use Fleetbase\FleetOps\Support\Utils;
6
5
  use Fleetbase\Models\File;
7
6
  use Fleetbase\Storefront\Models\Product;
8
- use Fleetbase\Storefront\Models\ProductAddonCategory;
9
- use Fleetbase\Storefront\Models\ProductVariant;
10
- use Fleetbase\Storefront\Models\ProductVariantOption;
11
- use Illuminate\Support\Arr;
12
7
  use Illuminate\Support\Facades\Request;
13
- use Illuminate\Support\Str;
14
8
 
15
9
  class ProductObserver
16
10
  {
@@ -47,56 +41,18 @@ class ProductObserver
47
41
  {
48
42
  $addonCategories = Request::input('product.addon_categories', []);
49
43
  $variants = Request::input('product.variants', []);
44
+ $files = Request::input('product.files', []);
50
45
 
51
- // update addon categories
52
- foreach ($addonCategories as $productAddonCategory) {
53
- if (isset($productAddonCategory['uuid']) && Str::isUuid($productAddonCategory['uuid'])) {
54
- ProductAddonCategory::where('uuid', $productAddonCategory['uuid'])->update(Arr::except($productAddonCategory, ['uuid', 'name', 'category']));
55
- continue;
56
- }
57
-
58
- // add new addon category
59
- $productAddonCategory['product_uuid'] = $product->uuid;
60
- ProductAddonCategory::create(Arr::except($productAddonCategory, ['category']));
61
- }
62
-
63
- // update product variants
64
- foreach ($variants as $variant) {
65
- if (isset($variant['uuid']) && Str::isUuid($variant['uuid'])) {
66
- // update product variante
67
- ProductVariant::where('uuid', $variant['uuid'])->update(Arr::except($variant, ['uuid', 'options']));
68
-
69
- // update product variant options
70
- foreach ($variant['options'] as $option) {
71
- if (!empty($option['uuid'])) {
72
- // make sure additional cost is always numbers only
73
- if (isset($option['additional_cost'])) {
74
- $option['additional_cost'] = Utils::numbersOnly($option['additional_cost']);
75
- }
76
-
77
- $updateAttrs = Arr::except($option, ['uuid']);
78
-
79
- ProductVariantOption::where('uuid', $option['uuid'])->update($updateAttrs);
80
- continue;
81
- }
82
-
83
- $option['product_variant_uuid'] = $variant['uuid'];
84
- ProductVariantOption::create($option);
85
- }
86
- continue;
87
- }
88
-
89
- // create new variant
90
- $variant['created_by_uuid'] = Request::session()->get('user');
91
- $variant['company_uuid'] = Request::session()->get('company');
92
- $variant['product_uuid'] = $product->uuid;
46
+ // save addon categories
47
+ $product->setAddonCategories($addonCategories);
93
48
 
94
- $productVariant = ProductVariant::create(Arr::except($variant, ['options']));
49
+ // save product variants
50
+ $product->setProductVariants($variants);
95
51
 
96
- foreach ($variant['options'] as $option) {
97
- $option['product_variant_uuid'] = $productVariant->uuid;
98
- ProductVariantOption::create($option);
99
- }
52
+ // set keys on files
53
+ foreach ($files as $file) {
54
+ $fileRecord = File::where('uuid', $file['uuid'])->first();
55
+ $fileRecord->setKey($product);
100
56
  }
101
57
  }
102
58
  }
@@ -54,6 +54,7 @@ class StorefrontServiceProvider extends CoreServiceProvider
54
54
  */
55
55
  public $commands = [
56
56
  \Fleetbase\Storefront\Console\Commands\NotifyStorefrontOrderNearby::class,
57
+ \Fleetbase\Storefront\Console\Commands\SendOrderNotification::class,
57
58
  ];
58
59
 
59
60
  /**
@@ -20,7 +20,7 @@ Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace(
20
20
  | Public/Callback Receivable Storefront API Routes
21
21
  |--------------------------------------------------------------------------
22
22
  |
23
- | End-user API routes, these are routes that the SDK and applications will interface with, and require API credentials.
23
+ | End-user API routes, these are routes that the SDK and applications will interface with, and DO NOT require API credentials.
24
24
  */
25
25
  Route::group(['prefix' => 'v1', 'namespace' => 'v1'], function ($router) {
26
26
  // storefront/v1/checkouts
@@ -94,6 +94,9 @@ Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace(
94
94
  $router->get('{id}', 'CustomerController@find');
95
95
  $router->post('/', 'CustomerController@create');
96
96
  $router->post('login-with-sms', 'CustomerController@loginWithPhone');
97
+ $router->post('login-with-apple', 'CustomerController@loginWithApple');
98
+ $router->post('login-with-facebook', 'CustomerController@loginWithFacebook');
99
+ $router->post('login-with-google', 'CustomerController@loginWithGoogle');
97
100
  $router->post('verify-code', 'CustomerController@verifyCode');
98
101
  $router->post('login', 'CustomerController@login');
99
102
  $router->post('request-creation-code', 'CustomerController@requestCustomerCreationCode');