Quellcode durchsuchen

feat: :sparkles: feat (app na loja) ajustes finais para subir app na loja

foram feitos os ajustes finais para subir o app na loja, incluindo splash, icones, nomes, hotfixes para safearea, correcoes no mapa e pagamentos.

fase:dev | origin:escopo
Gustavo Zanatta vor 4 Wochen
Ursprung
Commit
bf52ef3c6f
64 geänderte Dateien mit 274 neuen und 416 gelöschten Zeilen
  1. 1 1
      package.json
  2. 2 2
      src-capacitor/android/app/build.gradle
  3. 3 2
      src-capacitor/android/app/capacitor.build.gradle
  4. 1 1
      src-capacitor/android/app/src/main/AndroidManifest.xml
  5. 6 0
      src-capacitor/android/app/src/main/java/br/com/diarista/client/MainActivity.java
  6. BIN
      src-capacitor/android/app/src/main/res/drawable-land-hdpi/splash.png
  7. BIN
      src-capacitor/android/app/src/main/res/drawable-land-mdpi/splash.png
  8. BIN
      src-capacitor/android/app/src/main/res/drawable-land-xhdpi/splash.png
  9. BIN
      src-capacitor/android/app/src/main/res/drawable-land-xxhdpi/splash.png
  10. BIN
      src-capacitor/android/app/src/main/res/drawable-land-xxxhdpi/splash.png
  11. BIN
      src-capacitor/android/app/src/main/res/drawable-port-hdpi/splash.png
  12. BIN
      src-capacitor/android/app/src/main/res/drawable-port-mdpi/splash.png
  13. BIN
      src-capacitor/android/app/src/main/res/drawable-port-xhdpi/splash.png
  14. BIN
      src-capacitor/android/app/src/main/res/drawable-port-xxhdpi/splash.png
  15. BIN
      src-capacitor/android/app/src/main/res/drawable-port-xxxhdpi/splash.png
  16. 0 170
      src-capacitor/android/app/src/main/res/drawable/ic_launcher_background.xml
  17. BIN
      src-capacitor/android/app/src/main/res/drawable/splash.png
  18. 3 2
      src-capacitor/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  19. 3 2
      src-capacitor/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  20. BIN
      src-capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
  21. BIN
      src-capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
  22. BIN
      src-capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
  23. BIN
      src-capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
  24. BIN
      src-capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  25. BIN
      src-capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
  26. BIN
      src-capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
  27. BIN
      src-capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
  28. BIN
      src-capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
  29. BIN
      src-capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  30. BIN
      src-capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  31. BIN
      src-capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
  32. BIN
      src-capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
  33. BIN
      src-capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
  34. BIN
      src-capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  35. BIN
      src-capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  36. BIN
      src-capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
  37. BIN
      src-capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
  38. BIN
      src-capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png
  39. BIN
      src-capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  40. BIN
      src-capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  41. BIN
      src-capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
  42. BIN
      src-capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
  43. BIN
      src-capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
  44. BIN
      src-capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  45. 2 2
      src-capacitor/android/app/src/main/res/values/strings.xml
  46. 9 6
      src-capacitor/android/capacitor.settings.gradle
  47. 9 1
      src-capacitor/capacitor.config.json
  48. BIN
      src-capacitor/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png
  49. BIN
      src-capacitor/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png
  50. BIN
      src-capacitor/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png
  51. 10 0
      src-capacitor/package-lock.json
  52. 3 2
      src-capacitor/package.json
  53. 2 4
      src/api/user.js
  54. 1 1
      src/components/dashboard/DashboardHeaderBar.vue
  55. 12 2
      src/components/profile/ProfileAddressFormDialog.vue
  56. 44 163
      src/components/profile/ProfilePaymentAddDialog.vue
  57. 72 12
      src/components/shared/LocationMapDialog.vue
  58. 12 1
      src/composables/useAuth.js
  59. 33 6
      src/composables/useGeocodingApi.js
  60. 4 0
      src/css/app.scss
  61. 5 6
      src/layouts/MainLayout.vue
  62. 20 10
      src/pages/LoginPage.vue
  63. 1 1
      src/pages/agenda/CalendarPage.vue
  64. 16 19
      src/pages/profile/ProfileEditDialog.vue

+ 1 - 1
package.json

@@ -2,7 +2,7 @@
   "name": "diarista-cliente-app",
   "version": "0.0.1",
   "description": "Aplicativo para o cliente do Diarista",
-  "productName": "Diarista Cliente App",
+  "productName": "Diarista App",
   "author": "zntt <zanattagg@gmail.com>",
   "type": "module",
   "private": true,

+ 2 - 2
src-capacitor/android/app/build.gradle

@@ -13,8 +13,8 @@ android {
         applicationId "br.com.diarista.client"
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 1
-        versionName "1.0"
+        versionCode 4
+        versionName "1.3"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
              // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

+ 3 - 2
src-capacitor/android/app/capacitor.build.gradle

@@ -12,11 +12,12 @@ dependencies {
     implementation project(':capacitor-app')
     implementation project(':capacitor-browser')
     implementation project(':capacitor-device')
-    implementation project(':capacitor-keyboard')
-    implementation project(':capacitor-preferences')
     implementation project(':capacitor-geolocation')
     implementation project(':capacitor-google-maps')
+    implementation project(':capacitor-keyboard')
+    implementation project(':capacitor-preferences')
     implementation project(':capacitor-push-notifications')
+    implementation project(':capacitor-splash-screen')
     implementation project(':capacitor-status-bar')
 
 }

+ 1 - 1
src-capacitor/android/app/src/main/AndroidManifest.xml

@@ -11,7 +11,7 @@
 
         <meta-data
             android:name="com.google.android.geo.API_KEY"
-            android:value="@string/maps_api_key"/>
+            android:value="AIzaSyDrVs7amXuXKEu8sYMr-dl8VjNZbgS2frk"/>
 
         <activity
             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"

+ 6 - 0
src-capacitor/android/app/src/main/java/br/com/diarista/client/MainActivity.java

@@ -1,5 +1,6 @@
 package br.com.diarista.client;
 
+import android.graphics.Color;
 import android.os.Bundle;
 import android.webkit.WebView;
 
@@ -9,6 +10,11 @@ public class MainActivity extends BridgeActivity {
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        // Necessário para @capacitor/google-maps: o mapa nativo renderiza atrás do
+        // WebView, então o WebView precisa ser transparente onde o mapa é exibido.
+        // (O background do body cobre isso nas telas normais — só o LocationMapDialog
+        //  torna a cadeia html/body/#q-app transparente para revelar o mapa.)
+        getBridge().getWebView().setBackgroundColor(Color.TRANSPARENT);
     }
 
     @Override

BIN
src-capacitor/android/app/src/main/res/drawable-land-hdpi/splash.png


BIN
src-capacitor/android/app/src/main/res/drawable-land-mdpi/splash.png


BIN
src-capacitor/android/app/src/main/res/drawable-land-xhdpi/splash.png


BIN
src-capacitor/android/app/src/main/res/drawable-land-xxhdpi/splash.png


BIN
src-capacitor/android/app/src/main/res/drawable-land-xxxhdpi/splash.png


BIN
src-capacitor/android/app/src/main/res/drawable-port-hdpi/splash.png


BIN
src-capacitor/android/app/src/main/res/drawable-port-mdpi/splash.png


BIN
src-capacitor/android/app/src/main/res/drawable-port-xhdpi/splash.png


BIN
src-capacitor/android/app/src/main/res/drawable-port-xxhdpi/splash.png


BIN
src-capacitor/android/app/src/main/res/drawable-port-xxxhdpi/splash.png


+ 0 - 170
src-capacitor/android/app/src/main/res/drawable/ic_launcher_background.xml

@@ -1,170 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportHeight="108"
-    android:viewportWidth="108">
-    <path
-        android:fillColor="#26A69A"
-        android:pathData="M0,0h108v108h-108z" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M9,0L9,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,0L19,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,0L29,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,0L39,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,0L49,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,0L59,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,0L69,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,0L79,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M89,0L89,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M99,0L99,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,9L108,9"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,19L108,19"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,29L108,29"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,39L108,39"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,49L108,49"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,59L108,59"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,69L108,69"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,79L108,79"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,89L108,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,99L108,99"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,29L89,29"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,39L89,39"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,49L89,49"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,59L89,59"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,69L89,69"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,79L89,79"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,19L29,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,19L39,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,19L49,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,19L59,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,19L69,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,19L79,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-</vector>

BIN
src-capacitor/android/app/src/main/res/drawable/splash.png


+ 3 - 2
src-capacitor/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@color/ic_launcher_background"/>
+    <background android:drawable="@mipmap/ic_launcher_background"/>
     <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
-</adaptive-icon>
+    <monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
+</adaptive-icon>

+ 3 - 2
src-capacitor/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@color/ic_launcher_background"/>
+    <background android:drawable="@mipmap/ic_launcher_background"/>
     <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
-</adaptive-icon>
+    <monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
+</adaptive-icon>

BIN
src-capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
src-capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png


BIN
src-capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png


BIN
src-capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png


BIN
src-capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png


BIN
src-capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
src-capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png


BIN
src-capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png


BIN
src-capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png


BIN
src-capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png


BIN
src-capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


+ 2 - 2
src-capacitor/android/app/src/main/res/values/strings.xml

@@ -1,7 +1,7 @@
 <?xml version='1.0' encoding='utf-8'?>
 <resources>
-    <string name="app_name">Diarista Cliente</string>
-    <string name="title_activity_main">Diarista Cliente</string>
+    <string name="app_name">Diária</string>
+    <string name="title_activity_main">Diária</string>
     <string name="package_name">br.com.diarista.client</string>
     <string name="custom_url_scheme">br.com.diarista.client</string>
 </resources>

+ 9 - 6
src-capacitor/android/capacitor.settings.gradle

@@ -11,20 +11,23 @@ project(':capacitor-browser').projectDir = new File('../node_modules/@capacitor/
 include ':capacitor-device'
 project(':capacitor-device').projectDir = new File('../node_modules/@capacitor/device/android')
 
-include ':capacitor-keyboard'
-project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor/keyboard/android')
-
-include ':capacitor-preferences'
-project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
-
 include ':capacitor-geolocation'
 project(':capacitor-geolocation').projectDir = new File('../node_modules/@capacitor/geolocation/android')
 
 include ':capacitor-google-maps'
 project(':capacitor-google-maps').projectDir = new File('../node_modules/@capacitor/google-maps/android')
 
+include ':capacitor-keyboard'
+project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor/keyboard/android')
+
+include ':capacitor-preferences'
+project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
+
 include ':capacitor-push-notifications'
 project(':capacitor-push-notifications').projectDir = new File('../node_modules/@capacitor/push-notifications/android')
 
+include ':capacitor-splash-screen'
+project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capacitor/splash-screen/android')
+
 include ':capacitor-status-bar'
 project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')

+ 9 - 1
src-capacitor/capacitor.config.json

@@ -1,6 +1,6 @@
 {
   "appId": "br.com.diarista.client",
-  "appName": "Diarista Cliente",
+  "appName": "Diarista",
   "webDir": "www",
   "plugins": {
     "Keyboard": {
@@ -10,6 +10,14 @@
     "StatusBar": {
       "overlaysWebView": true
     },
+    "SplashScreen": {
+      "launchAutoHide": false,
+      "backgroundColor": "#8B5CF6",
+      "androidScaleType": "CENTER_CROP",
+      "showSpinner": false,
+      "splashFullScreen": true,
+      "splashImmersive": true
+    },
     "CapacitorCookies": {
       "enabled": true
     },

BIN
src-capacitor/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png


BIN
src-capacitor/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png


BIN
src-capacitor/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png


+ 10 - 0
src-capacitor/package-lock.json

@@ -20,6 +20,7 @@
         "@capacitor/keyboard": "^7.0.1",
         "@capacitor/preferences": "^7.0.1",
         "@capacitor/push-notifications": "^8.1.1",
+        "@capacitor/splash-screen": "^7.0.5",
         "@capacitor/status-bar": "^7.0.1"
       }
     },
@@ -159,6 +160,15 @@
         "@capacitor/core": ">=8.0.0"
       }
     },
+    "node_modules/@capacitor/splash-screen": {
+      "version": "7.0.5",
+      "resolved": "https://registry.npmjs.org/@capacitor/splash-screen/-/splash-screen-7.0.5.tgz",
+      "integrity": "sha512-bPG2SamFL7VT5I3XsgsGgJhkiyxq3OXCOVKEDupSieVdtEiG7j2vLbSD0xL+91zteF/qLuxsjbugGy5LD8mS2A==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@capacitor/core": ">=7.0.0"
+      }
+    },
     "node_modules/@capacitor/status-bar": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-7.0.1.tgz",

+ 3 - 2
src-capacitor/package.json

@@ -11,12 +11,13 @@
     "@capacitor/cli": "^8.3.3",
     "@capacitor/core": "^7.0.0",
     "@capacitor/device": "^7.0.1",
+    "@capacitor/geolocation": "^8.2.0",
+    "@capacitor/google-maps": "^8.0.1",
     "@capacitor/ios": "^7.2.0",
     "@capacitor/keyboard": "^7.0.1",
     "@capacitor/preferences": "^7.0.1",
-    "@capacitor/geolocation": "^8.2.0",
-    "@capacitor/google-maps": "^8.0.1",
     "@capacitor/push-notifications": "^8.1.1",
+    "@capacitor/splash-screen": "^7.0.5",
     "@capacitor/status-bar": "^7.0.1"
   }
 }

+ 2 - 4
src/api/user.js

@@ -45,9 +45,7 @@ export const createUserAndClient = async (data) => {
   return response;
 }
 
-export const updateMe = async (data, hasFile = false) => {
-  const { data: res } = hasFile
-    ? await api.post('/me', data, { headers: { 'Content-Type': 'multipart/form-data' } })
-    : await api.put('/me', data);
+export const updateMe = async (data) => {
+  const { data: res } = await api.put('/me', data);
   return res.payload;
 };

+ 1 - 1
src/components/dashboard/DashboardHeaderBar.vue

@@ -86,7 +86,7 @@ const goToNotifications = () => {
 
 <style scoped lang="scss">
 .dashboard-header {
-  padding-top: calc(env(safe-area-inset-top) + 8px);
+  padding-top: 8px;
   width: 100%;
   box-sizing: border-box;
 }

+ 12 - 2
src/components/profile/ProfileAddressFormDialog.vue

@@ -227,6 +227,9 @@ const openMapDialog = async () => {
   let initialLng = null;
 
   const hasAddress = form.address || form.zip_code;
+
+  $q.notify({ color: 'indigo-9', textColor: 'white', position: 'top', message: '[ADDR] openMapDialog iniciado', caption: `hasAddress: ${hasAddress}\naddress: ${form.address}\nzip_code: ${form.zip_code}\nlat: ${form.latitude}\nlng: ${form.longitude}`, timeout: 8000, multiLine: true });
+
   if (hasAddress) {
     geocodingCep.value = true;
     try {
@@ -241,18 +244,25 @@ const openMapDialog = async () => {
       if (geo) {
         initialLat = geo.lat;
         initialLng = geo.lng;
+        $q.notify({ color: 'indigo-9', textColor: 'white', position: 'top', message: '[ADDR] geocodeFullAddress OK', caption: `lat: ${geo.lat}\nlng: ${geo.lng}`, timeout: 8000, multiLine: true });
+      } else {
+        $q.notify({ color: 'indigo-9', textColor: 'white', position: 'top', message: '[ADDR] geocodeFullAddress retornou null — usando coord default', timeout: 6000 });
       }
-    } catch {
-      // fallback to default
+    } catch (err) {
+      $q.notify({ color: 'indigo-9', textColor: 'white', position: 'top', message: '[ADDR] geocodeFullAddress ERRO — usando coord default', caption: `erro: ${err?.message ?? String(err)}`, timeout: 8000, multiLine: true });
     } finally {
       geocodingCep.value = false;
     }
+  } else {
+    $q.notify({ color: 'indigo-9', textColor: 'white', position: 'top', message: '[ADDR] sem endereço preenchido — abrindo mapa com coord default', timeout: 6000 });
   }
 
   const dialogProps = initialLat !== null
     ? { initialLat, initialLng }
     : {};
 
+  $q.notify({ color: 'indigo-9', textColor: 'white', position: 'top', message: '[ADDR] abrindo LocationMapDialog', caption: `initialLat: ${dialogProps.initialLat ?? 'default'}\ninitialLng: ${dialogProps.initialLng ?? 'default'}`, timeout: 8000, multiLine: true });
+
   $q.dialog({
     component: LocationMapDialog,
     componentProps: dialogProps,

+ 44 - 163
src/components/profile/ProfilePaymentAddDialog.vue

@@ -7,49 +7,6 @@
     transition-show="slide-up"
   >
     <div class="bg-page full-height column no-shadow">
-      <form
-        ref="pagarmeFormRef"
-        action="#"
-        class="pagarme-token-form"
-        data-pagarmecheckout-form
-        method="POST"
-      >
-        <input
-          v-model="form.holder_name"
-          data-pagarmecheckout-element="holder_name"
-          name="holder-name"
-          type="text"
-        >
-
-        <input
-          v-model="pagarmeCardNumber"
-          data-pagarmecheckout-element="number"
-          name="card-number"
-          type="text"
-        >
-
-        <input
-          v-model="pagarmeExpirationMonth"
-          data-pagarmecheckout-element="exp_month"
-          name="card-exp-month"
-          type="text"
-        >
-
-        <input
-          v-model="pagarmeExpirationYear"
-          data-pagarmecheckout-element="exp_year"
-          name="card-exp-year"
-          type="text"
-        >
-
-        <input
-          v-model="pagarmeCvv"
-          data-pagarmecheckout-element="cvv"
-          name="cvv"
-          type="text"
-        >
-      </form>
-
       <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile bg-surface">
         <q-btn
           color="primary"
@@ -306,8 +263,6 @@ const {
 
 const formRef = ref(null);
 
-const pagarmeFormRef = ref(null);
-
 const tokenizing = ref(false);
 
 const brandDisplayName = computed(() => {
@@ -369,99 +324,31 @@ const maskedCardNumberPreview = computed(() => {
   return '**** **** **** ****';
 });
 
-const pagarmeCardNumber = computed({
-  get: () => (
-    form.card_number
-      ? String(form.card_number).replace(/\D/g, '')
-      : ''
-  ),
-  set: () => {},
-});
-
-const pagarmeCvv = computed({
-  get: () => (
-    form.cvv
-      ? String(form.cvv).replace(/\D/g, '')
-      : ''
-  ),
-  set: () => {},
-});
+const pagarmeCardNumber = computed(() => (
+  form.card_number
+    ? String(form.card_number).replace(/\D/g, '')
+    : ''
+));
 
-const pagarmeExpirationMonth = computed({
-  get: () => expirationMonth.value,
-  set: () => {},
-});
+const pagarmeCvv = computed(() => (
+  form.cvv
+    ? String(form.cvv).replace(/\D/g, '')
+    : ''
+));
 
-const pagarmeExpirationYear = computed({
-  get: () => expirationYear.value,
-  set: () => {},
-});
+const pagarmeExpirationMonth = computed(() => expirationMonth.value);
 
-const PAGARME_SCRIPT_URL = 'https://checkout.pagar.me/v1/tokenizecard.js';
+// Pagar.me espera o ano com 2 digitos (YY)
+const pagarmeExpirationYear = computed(() => {
+  const year = expirationYear.value || '';
 
-let pagarmeScriptPromise = null;
+  return year.length === 4 ? year.slice(-2) : year;
+});
 
 const getPagarmePublicKey = () => (
   process.env.PAGARME_PUBLIC_KEY
 );
 
-const getPagarmeTokenFromResponse = (data) => {
-  const tokenKey = Object.keys(data || {}).find((key) => (
-    key.startsWith('pagarmetoken')
-  ));
-
-  return tokenKey ? data[tokenKey] : null;
-};
-
-const loadPagarmeScript = () => {
-  if (window.PagarmeCheckout) {
-    return Promise.resolve(window.PagarmeCheckout);
-  }
-
-  if (!pagarmeScriptPromise) {
-    pagarmeScriptPromise = new Promise((resolve, reject) => {
-      const publicKey = getPagarmePublicKey();
-
-      if (!publicKey) {
-        reject(new Error('PAGARME_PUBLIC_KEY não configurada'));
-        return;
-      }
-
-      const existingScript = document.querySelector('script[data-pagarmecheckout-script="true"]');
-
-      if (existingScript) {
-        existingScript.addEventListener(
-          'error',
-          () => reject(new Error('Falha ao carregar o tokenizecard.js')),
-          { once: true },
-        );
-
-        existingScript.addEventListener(
-          'load',
-          () => resolve(window.PagarmeCheckout),
-          { once: true },
-        );
-
-        return;
-      }
-
-      const script = document.createElement('script');
-
-      script.async = true;
-      script.onerror = () => reject(new Error('Falha ao carregar o tokenizecard.js'));
-      script.onload = () => resolve(window.PagarmeCheckout);
-      script.src = PAGARME_SCRIPT_URL;
-
-      script.setAttribute('data-pagarmecheckout-app-id', publicKey);
-      script.setAttribute('data-pagarmecheckout-script', 'true');
-
-      document.body.appendChild(script);
-    });
-  }
-
-  return pagarmeScriptPromise;
-};
-
 const onCardNumberChange = () => {
   serverErrors.value.card_number = null;
 
@@ -496,38 +383,41 @@ const tokenizeCard = async () => {
     throw new Error('PAGARME_PUBLIC_KEY não configurada');
   }
 
-  await loadPagarmeScript();
+  const url = `https://api.pagar.me/core/v5/tokens?appId=${encodeURIComponent(publicKey)}`;
 
-  return new Promise((resolve, reject) => {
-    const success = (data) => {
-      const token = getPagarmeTokenFromResponse(data);
+  const payload = {
+    card: {
+      cvv: pagarmeCvv.value,
+      exp_month: Number(pagarmeExpirationMonth.value),
+      exp_year: Number(pagarmeExpirationYear.value),
+      holder_name: form.holder_name,
+      number: pagarmeCardNumber.value,
+    },
+    type: 'card',
+  };
 
-      if (!token) {
-        reject(new Error('Token do cartão não retornado pela Pagar.me.'));
-        return false;
-      }
+  let response;
 
-      resolve(token);
-      return false;
-    };
+  try {
+    response = await fetch(url, {
+      body: JSON.stringify(payload),
+      headers: { 'Content-Type': 'application/json' },
+      method: 'POST',
+    });
+  } catch (error) {
+    throw new Error(`Falha de rede ao tokenizar o cartão: ${error?.message || error}`);
+  }
 
-    const fail = (error) => {
-      reject(error);
-      return false;
-    };
+  const data = await response.json().catch(() => ({}));
 
-    window.PagarmeCheckout.init(success, fail);
+  if (!response.ok || !data?.id) {
+    const detail = data?.message
+      || (data?.errors ? JSON.stringify(data.errors) : JSON.stringify(data));
 
-    if (!pagarmeFormRef.value) {
-      reject(new Error('Formulário de tokenização não encontrado.'));
-      return;
-    }
+    throw new Error(`Pagar.me recusou a tokenização (${response.status}): ${String(detail).slice(0, 200)}`);
+  }
 
-    pagarmeFormRef.value.dispatchEvent(new Event('submit', {
-      bubbles: true,
-      cancelable: true,
-    }));
-  });
+  return data.id;
 };
 
 const validateExpiration = (val) => {
@@ -613,15 +503,6 @@ const onOKClick = async () => {
   color: white;
 }
 
-.pagarme-token-form {
-  height: 0;
-  opacity: 0;
-  overflow: hidden;
-  pointer-events: none;
-  position: absolute;
-  width: 0;
-}
-
 .nfc-icon {
   opacity: 0.85;
 }

+ 72 - 12
src/components/shared/LocationMapDialog.vue

@@ -1,5 +1,5 @@
 <template>
-  <q-dialog ref="dialogRef" persistent maximized @show="initMap">
+  <q-dialog ref="dialogRef" persistent maximized class="map-dialog-transparent" @show="initMap" @hide="onDialogHide">
     <div class="location-map-dialog">
       <div ref="mapRef" class="location-map-container" />
 
@@ -41,7 +41,7 @@
 </template>
 
 <script setup>
-import { ref, onUnmounted } from 'vue';
+import { ref, nextTick, onUnmounted } from 'vue';
 import { useDialogPluginComponent, useQuasar } from 'quasar';
 import { useI18n } from 'vue-i18n';
 import { GoogleMap } from '@capacitor/google-maps';
@@ -73,13 +73,46 @@ const currentGeoData = ref(null);
 let googleMap = null;
 const markerId = ref(null);
 
+let hiddenEls = [];
+
+const applyMapTransparency = () => {
+  document.documentElement.style.setProperty('background', 'transparent', 'important');
+  document.body.style.setProperty('background', 'transparent', 'important');
+  const qApp = document.getElementById('q-app');
+  if (qApp) qApp.style.setProperty('background', 'transparent', 'important');
+
+  hiddenEls = [];
+  document
+    .querySelectorAll('.q-layout, .q-dialog:not(.map-dialog-transparent)')
+    .forEach((el) => {
+      el.style.setProperty('visibility', 'hidden', 'important');
+      hiddenEls.push(el);
+    });
+};
+
+const removeMapTransparency = () => {
+  document.documentElement.style.removeProperty('background');
+  document.body.style.removeProperty('background');
+  const qApp = document.getElementById('q-app');
+  if (qApp) qApp.style.removeProperty('background');
+
+  hiddenEls.forEach((el) => el.style.removeProperty('visibility'));
+  hiddenEls = [];
+};
+
+const onDialogHide = () => {
+  removeMapTransparency();
+};
+
 const handleCancel = async () => {
+  removeMapTransparency();
   await destroyMap();
   onDialogCancel();
 };
 
 const handleConfirm = async () => {
   if (!currentGeoData.value) return;
+  removeMapTransparency();
   await destroyMap();
   onDialogOK(currentGeoData.value);
 };
@@ -124,15 +157,31 @@ const moveMarkerTo = async (lat, lng) => {
 };
 
 const initMap = async () => {
-  googleMap = await GoogleMap.create({
-    id: 'location-map-dialog',
-    element: mapRef.value,
-    apiKey: process.env.GOOGLE_MAPS_API_KEY,
-    config: {
-      center: { lat: props.initialLat, lng: props.initialLng },
-      zoom: 17,
-    },
-  });
+  applyMapTransparency();
+  await nextTick();
+  await new Promise(resolve => setTimeout(resolve, 100));
+
+  const apiKey = process.env.GOOGLE_MAPS_API_KEY;
+
+  if (!apiKey) {
+    $q.notify({ type: 'negative', message: 'API key do mapa não configurada.' });
+    return;
+  }
+
+  try {
+    googleMap = await GoogleMap.create({
+      id: 'location-map-dialog',
+      element: mapRef.value,
+      apiKey,
+      config: {
+        center: { lat: props.initialLat, lng: props.initialLng },
+        zoom: 17,
+      },
+    });
+  } catch (err) {
+    $q.notify({ type: 'negative', message: `Erro ao abrir o mapa: ${err?.message ?? err}` });
+    return;
+  }
 
   markerId.value = await googleMap.addMarker({
     coordinate: { lat: props.initialLat, lng: props.initialLng },
@@ -154,22 +203,33 @@ const initMap = async () => {
 };
 
 onUnmounted(async () => {
+  removeMapTransparency();
   await destroyMap();
 });
 </script>
 
+<style lang="scss">
+.map-dialog-transparent .q-dialog__inner {
+  background: transparent !important;
+}
+.map-dialog-transparent .q-dialog__backdrop {
+  display: none !important;
+}
+</style>
+
 <style lang="scss" scoped>
 .location-map-dialog {
   position: relative;
   width: 100vw;
   height: 100dvh;
   overflow: hidden;
-  background: #e5e3df;
+  background: transparent;
 }
 
 .location-map-container {
   width: 100%;
   height: 100%;
+  background: transparent;
 }
 
 .location-map-back-btn {

+ 12 - 1
src/composables/useAuth.js

@@ -4,6 +4,8 @@ import { userStore } from "src/stores/user";
 import { useAuthStorage } from "src/composables/useAuthStorage";
 import { registerPendingFcmToken } from "src/boot/push-notifications";
 
+let refreshPromise = null;
+
 export const useAuth = () => {
   const { setRefreshToken, getRefreshToken, clearRefreshToken } = useAuthStorage();
 
@@ -50,7 +52,7 @@ export const useAuth = () => {
     }
   };
 
-  const refresh = async () => {
+  const doRefresh = async () => {
     try {
       const refreshToken = await getRefreshToken();
 
@@ -72,6 +74,15 @@ export const useAuth = () => {
     }
   };
 
+  const refresh = () => {
+    if (!refreshPromise) {
+      refreshPromise = doRefresh().finally(() => {
+        refreshPromise = null;
+      });
+    }
+    return refreshPromise;
+  };
+
   return {
     setAuthDataFromPayload,
     clearAuthData,

+ 33 - 6
src/composables/useGeocodingApi.js

@@ -1,7 +1,15 @@
 import axios from 'axios';
+import { Notify } from 'quasar';
 
 const GEOCODING_URL = 'https://maps.googleapis.com/maps/api/geocode/json';
 
+// ── DEBUG via notificação ─────────────────────────────────────────
+const dbg = (title, data = {}) => {
+  const lines = Object.entries(data).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join('\n');
+  Notify.create({ color: 'teal-9', textColor: 'white', position: 'top', message: `[GEO] ${title}`, caption: lines || undefined, timeout: 8000, multiLine: true });
+};
+// ─────────────────────────────────────────────────────────────────
+
 const parseAddressComponents = (components) => {
   const get = (type, nameType = 'long_name') =>
     components.find((c) => c.types.includes(type))?.[nameType] ?? '';
@@ -25,9 +33,14 @@ const geocodeViaGoogleMaps = async (request) => {
 export const useGeocodingApi = () => {
   const geocodeCep = async (cep) => {
     const cleaned = cep.replace(/\D/g, '');
+    const apiKey = process.env.GOOGLE_MAPS_API_KEY;
+    const usandoGeocoderNativo = !!window.google?.maps?.Geocoder;
+
+    dbg('geocodeCep', { cep: cleaned, via: usandoGeocoderNativo ? 'Geocoder nativo' : 'HTTP', apiKey: apiKey ? `${apiKey.substring(0, 8)}...` : 'UNDEFINED' });
 
-    if (window.google?.maps?.Geocoder) {
+    if (usandoGeocoderNativo) {
       const results = await geocodeViaGoogleMaps({ address: `${cleaned}, Brazil` });
+      dbg('geocodeCep nativo result', { total: results.length, encontrou: results.length > 0 });
       if (!results.length) return null;
       const result = results[0];
       const parsed = parseAddressComponents(result.address_components);
@@ -39,8 +52,9 @@ export const useGeocodingApi = () => {
     }
 
     const { data } = await axios.get(GEOCODING_URL, {
-      params: { address: `${cleaned}, Brazil`, key: process.env.GOOGLE_MAPS_API_KEY },
+      params: { address: `${cleaned}, Brazil`, key: apiKey },
     });
+    dbg('geocodeCep HTTP result', { status: data.status, total: data.results?.length ?? 0 });
     if (data.status !== 'OK' || !data.results.length) return null;
     const result = data.results[0];
     const { lat, lng } = result.geometry.location;
@@ -48,15 +62,22 @@ export const useGeocodingApi = () => {
   };
 
   const reverseGeocode = async (lat, lng) => {
-    if (window.google?.maps?.Geocoder) {
+    const apiKey = process.env.GOOGLE_MAPS_API_KEY;
+    const usandoGeocoderNativo = !!window.google?.maps?.Geocoder;
+
+    dbg('reverseGeocode chamado', { lat, lng, via: usandoGeocoderNativo ? 'Geocoder nativo' : 'HTTP', apiKey: apiKey ? `${apiKey.substring(0, 8)}...` : 'UNDEFINED' });
+
+    if (usandoGeocoderNativo) {
       const results = await geocodeViaGoogleMaps({ location: { lat, lng } });
+      dbg('reverseGeocode nativo result', { total: results.length });
       if (!results.length) return null;
       return { lat, lng, ...parseAddressComponents(results[0].address_components) };
     }
 
     const { data } = await axios.get(GEOCODING_URL, {
-      params: { latlng: `${lat},${lng}`, key: process.env.GOOGLE_MAPS_API_KEY },
+      params: { latlng: `${lat},${lng}`, key: apiKey },
     });
+    dbg('reverseGeocode HTTP result', { status: data.status, total: data.results?.length ?? 0, errorMessage: data.error_message ?? null });
     if (data.status !== 'OK' || !data.results.length) return null;
     const result = data.results[0];
     return { lat, lng, ...parseAddressComponents(result.address_components) };
@@ -65,9 +86,14 @@ export const useGeocodingApi = () => {
   const geocodeFullAddress = async ({ address, number, district, zip_code, city, state }) => {
     const parts = [address, number, district, zip_code, city, state, 'Brazil'].filter(Boolean);
     const query = parts.join(', ');
+    const apiKey = process.env.GOOGLE_MAPS_API_KEY;
+    const usandoGeocoderNativo = !!window.google?.maps?.Geocoder;
+
+    dbg('geocodeFullAddress', { query, via: usandoGeocoderNativo ? 'Geocoder nativo' : 'HTTP', apiKey: apiKey ? `${apiKey.substring(0, 8)}...` : 'UNDEFINED' });
 
-    if (window.google?.maps?.Geocoder) {
+    if (usandoGeocoderNativo) {
       const results = await geocodeViaGoogleMaps({ address: query });
+      dbg('geocodeFullAddress nativo result', { total: results.length });
       if (!results.length) return null;
       const result = results[0];
       return {
@@ -78,8 +104,9 @@ export const useGeocodingApi = () => {
     }
 
     const { data } = await axios.get(GEOCODING_URL, {
-      params: { address: query, key: process.env.GOOGLE_MAPS_API_KEY },
+      params: { address: query, key: apiKey },
     });
+    dbg('geocodeFullAddress HTTP result', { status: data.status, total: data.results?.length ?? 0, errorMessage: data.error_message ?? null });
     if (data.status !== 'OK' || !data.results.length) return null;
     const result = data.results[0];
     const { lat, lng } = result.geometry.location;

+ 4 - 0
src/css/app.scss

@@ -97,6 +97,10 @@ input[type="number"]::-webkit-outer-spin-button {
   margin-top: env(safe-area-inset-top) !important;
 }
 
+.q-dialog__inner--maximized > div > .row:first-child {
+  padding-top: calc(env(safe-area-inset-top) + 12px) !important;
+}
+
 .remove-header-expansion-item {
   .q-list--dense > .q-item,
   .q-item--dense {

+ 5 - 6
src/layouts/MainLayout.vue

@@ -2,12 +2,11 @@
   <q-layout class="main-layout relative" view="hHh lpR fFf">
     <q-header
       v-if="$q.screen.lt.sm"
-      class="bg-surface column justify-end"
+      class="bg-surface"
       :style="{
-        height: `calc(50px + env(safe-area-inset-top))`,
+        height: `env(safe-area-inset-top)`,
       }"
     >
-
     </q-header>
 
     <q-page-container>
@@ -98,7 +97,7 @@ const isNavItemActive = (item) => route.name === item.name;
 
 const scrollAreaHeight = computed(() => {
   if ($q.screen.lt.sm) {
-    return 'height: calc(100dvh - 50px - env(safe-area-inset-top) - 80px - env(safe-area-inset-bottom)) !important;';
+    return 'height: calc(100dvh - env(safe-area-inset-top) - 60px - env(safe-area-inset-bottom)) !important;';
   }
 
   return 'height: 100dvh !important;';
@@ -177,8 +176,8 @@ watch(
   display: grid;
   grid-template-columns: repeat(5, minmax(0, 1fr));
   align-items: end;
-  height: 60px;
-  padding: 0px 0px calc(12px + env(safe-area-inset-bottom));
+  height: calc(60px + env(safe-area-inset-bottom));
+  padding: 0 0 env(safe-area-inset-bottom);
 }
 
 .provider-bottom-nav-item {

+ 20 - 10
src/pages/LoginPage.vue

@@ -79,7 +79,7 @@
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { onMounted, ref } from 'vue';
 import { createUserAndClient, sendCode, validateCode } from 'src/api/user';
 import { useAuth } from 'src/composables/useAuth';
 import { useRouter } from 'vue-router';
@@ -122,6 +122,12 @@ const stepThreeForm = ref({
 const steps = ref(1); // 1 = credentials | 2 = code | 3 = user fields | 4 = cep only
 const clicked = ref(false);
 const submitting = ref(false);
+
+onMounted(() => {
+  setTimeout(() => {
+    clicked.value = true;
+  }, 1500);
+});
 const loginForm = ref(null);
 const isLogin = ref(false);
 
@@ -183,16 +189,20 @@ const onDeviceLocation = ({ lat, lng }) => {
 };
 
 const onSubmit = async () => {
-  switch (steps.value) {
-    case 1: 
-      { 
-        let response = await sendValidationCode();
+  submitting.value = true;
+  try {
+    switch (steps.value) {
+      case 1: {
+        const response = await sendValidationCode();
         isLogin.value = response.data.payload.isLogin;
-        break; 
+        break;
       }
-    case 2: await validateCodeInput(); break;
-    case 3: await registerUserAndClient(); break;
-    default: break;
+      case 2: await validateCodeInput(); break;
+      case 3: await registerUserAndClient(); break;
+      default: break;
+    }
+  } finally {
+    submitting.value = false;
   }
 };
 </script>
@@ -306,6 +316,6 @@ const onSubmit = async () => {
 }
 
 .flow-footer {
-  padding: 20px 0 12px;
+  padding: 20px 0 calc(12px + env(safe-area-inset-bottom));
 }
 </style>

+ 1 - 1
src/pages/agenda/CalendarPage.vue

@@ -339,7 +339,7 @@ onMounted(async () => {
 
 <style scoped lang="scss">
 .calendar-header {
-  padding-top: calc(env(safe-area-inset-top) + 12px);
+  padding-top: 12px;
   padding-bottom: 12px;
   box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.08);
 }

+ 16 - 19
src/pages/profile/ProfileEditDialog.vue

@@ -278,34 +278,31 @@ const onLocaleChange = (val) => {
   Cookies.set("locale", val, { expires: 365, path: "/" });
 };
 
+const fileToBase64 = (file) =>
+  new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.onload = () => resolve(reader.result);
+    reader.onerror = () => reject(reader.error);
+    reader.readAsDataURL(file);
+  });
+
 const onSubmit = async () => {
   const valid = await formRef.value.validate();
 
   if (!valid) return;
 
-  let payload;
+  const payload = {
+    name:     form.name,
+    email:    form.email,
+    phone:    form.phone,
+    document: form.document || null,
+  };
 
   if (avatarFile.value) {
-    payload = new FormData();
-
-    payload.append("name", form.name);
-    payload.append("email", form.email);
-    payload.append("phone", form.phone);
-
-    if (form.document) payload.append("document", form.document);
-
-    payload.append("avatar", avatarFile.value);
-    payload.append("_method", "PUT");
-  } else {
-    payload = {
-      name:     form.name,
-      email:    form.email,
-      phone:    form.phone,
-      document: form.document || null,
-    };
+    payload.avatar_base64 = await fileToBase64(avatarFile.value);
   }
 
-  await submitForm(() => updateMe(payload, !!avatarFile.value));
+  await submitForm(() => updateMe(payload));
 };
 
 onMounted(async () => {