Переглянути джерело

feat: :sparkles: feat (chatbot) implementado chatbot

foi implementado chatbot no perfil do cliente e do prestador

fase:dev | origin:escopo
Gustavo Zanatta 6 днів тому
батько
коміт
4f729aa915

+ 259 - 0
quasar.config.js.temporary.compiled.1780666187782.mjs

@@ -0,0 +1,259 @@
+/* eslint-disable */
+/**
+ * THIS FILE IS GENERATED AUTOMATICALLY.
+ * 1. DO NOT edit this file directly as it won't do anything.
+ * 2. EDIT the original quasar.config file INSTEAD.
+ * 3. DO NOT git commit this file. It should be ignored.
+ *
+ * This file is still here because there was an error in
+ * the original quasar.config file and this allows you to
+ * investigate the Node.js stack error.
+ *
+ * After you fix the original file, this file will be
+ * deleted automatically.
+ **/
+
+
+// quasar.config.js
+import { defineConfig } from "@quasar/app-vite/wrappers";
+import { existsSync, readFileSync } from "node:fs";
+import { fileURLToPath } from "node:url";
+import { resolve } from "node:path";
+var __quasar_inject_import_meta_url__ = "file:///home/softpar/projetos/spas/sfp_front_vue_diarista_prestador/quasar.config.js";
+var envFiles = {
+  dev: ".env.app.dev",
+  staging: ".env.app.staging",
+  prod: ".env.app.prod"
+};
+var loadAppEnv = (ctx) => {
+  const appEnv = process.env.APP_ENV || (ctx.dev ? "dev" : "prod");
+  const envFile = envFiles[appEnv];
+  if (!envFile) {
+    throw new Error(`APP_ENV invalido: "${appEnv}". Use dev, staging ou prod.`);
+  }
+  const fileEnv = parseEnvFile(resolve(process.cwd(), envFile));
+  return {
+    APP_ENV: appEnv,
+    API_URL: fileEnv.API_URL,
+    PASSWORD: fileEnv.PASSWORD,
+    WEBSOCKET_API: fileEnv.WEBSOCKET_API,
+    WEBSOCKET_PATH: fileEnv.WEBSOCKET_PATH,
+    WEBSOCKET_ROOM: fileEnv.WEBSOCKET_ROOM,
+    WEBSOCKET_API_KEY: fileEnv.WEBSOCKET_API_KEY
+  };
+};
+var parseEnvFile = (filePath) => {
+  if (!existsSync(filePath)) {
+    return {};
+  }
+  return readFileSync(filePath, "utf8").split(/\r?\n/).reduce((env, line) => {
+    const trimmedLine = line.trim();
+    if (!trimmedLine || trimmedLine.startsWith("#")) {
+      return env;
+    }
+    const separatorIndex = trimmedLine.indexOf("=");
+    if (separatorIndex === -1) {
+      return env;
+    }
+    const key = trimmedLine.slice(0, separatorIndex).trim();
+    const value = trimmedLine.slice(separatorIndex + 1).trim();
+    env[key] = value.replace(/^["']|["']$/g, "");
+    return env;
+  }, {});
+};
+var quasar_config_default = defineConfig((ctx) => {
+  const appEnv = loadAppEnv(ctx);
+  return {
+    bin: {
+      linuxAndroidStudio: "/snap/bin/android-studio"
+    },
+    // https://v2.quasar.dev/quasar-cli-vite/prefetch-feature
+    // preFetch: true,
+    // app boot file (/src/boot)
+    // --> boot files are part of "main.js"
+    // https://v2.quasar.dev/quasar-cli-vite/boot-files
+    boot: [
+      "axios",
+      "i18n",
+      "defaultPropsComponents",
+      "push-notifications"
+      // "socket.io",
+    ],
+    // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
+    css: ["app.scss"],
+    // https://github.com/quasarframework/quasar/tree/dev/extras
+    extras: [
+      // 'ionicons-v4',
+      "mdi-v7",
+      // 'fontawesome-v6',
+      // 'eva-icons',
+      // 'themify',
+      // "line-awesome",
+      // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
+      "roboto-font",
+      // optional, you are not bound to it
+      "material-icons"
+      // optional, you are not bound to it
+    ],
+    // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
+    build: {
+      target: {
+        browser: ["es2022", "firefox115", "chrome115", "safari14"],
+        node: "node22"
+      },
+      vueRouterMode: "history",
+      // available values: 'hash', 'history'
+      // vueRouterBase,
+      // vueDevtools,
+      // vueOptionsAPI: false,
+      // rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
+      // publicPath: '/',
+      // analyze: true,
+      env: appEnv,
+      // rawDefine: {}
+      // ignorePublicFolder: true,
+      // minify: false,
+      // polyfillModulePreload: true,
+      // distDir
+      // extendViteConf (viteConf) {},
+      // viteVuePluginOptions: {},
+      vitePlugins: [
+        [
+          "@intlify/unplugin-vue-i18n/vite",
+          {
+            include: [fileURLToPath(new URL("./src/i18n", __quasar_inject_import_meta_url__))],
+            ssr: ctx.modeName === "ssr"
+          }
+        ],
+        [
+          "vite-plugin-checker",
+          {
+            eslint: {
+              lintCommand: 'eslint -c ./eslint.config.js "./src*/**/*.{js,mjs,cjs,vue}"',
+              useFlatConfig: true
+            }
+          },
+          { server: false }
+        ]
+      ]
+    },
+    // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
+    devServer: {
+      // https: true
+      open: false
+      // opens browser window automatically
+    },
+    // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
+    framework: {
+      lang: "pt-BR",
+      config: {
+        dark: "auto",
+        notify: {
+          position: "top-right"
+        }
+      },
+      // iconSet: 'material-icons', // Quasar icon set
+      // lang: 'en-US', // Quasar language pack
+      // For special cases outside of where the auto-import strategy can have an impact
+      // (like functional components as one of the examples),
+      // you can manually specify Quasar components/directives to be available everywhere:
+      //
+      // components: [],
+      // directives: [],
+      // Quasar plugins
+      plugins: ["Dialog", "Notify", "Loading", "Cookies", "Dark"]
+    },
+    // animations: 'all', // --- includes all animations
+    // https://v2.quasar.dev/options/animations
+    animations: [],
+    // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#property-sourcefiles
+    // sourceFiles: {
+    //   rootComponent: 'src/App.vue',
+    //   router: 'src/router/index',
+    //   store: 'src/store/index',
+    //   pwaRegisterServiceWorker: 'src-pwa/register-service-worker',
+    //   pwaServiceWorker: 'src-pwa/custom-service-worker',
+    //   pwaManifestFile: 'src-pwa/manifest.json',
+    //   electronMain: 'src-electron/electron-main',
+    //   electronPreload: 'src-electron/electron-preload'
+    //   bexManifestFile: 'src-bex/manifest.json
+    // },
+    // https://v2.quasar.dev/quasar-cli-vite/developing-ssr/configuring-ssr
+    ssr: {
+      prodPort: 3e3,
+      // The default port that the production server should use
+      // (gets superseded if process.env.PORT is specified at runtime)
+      middlewares: [
+        "render"
+        // keep this as last one
+      ],
+      // extendPackageJson (json) {},
+      // extendSSRWebserverConf (esbuildConf) {},
+      // manualStoreSerialization: true,
+      // manualStoreSsrContextInjection: true,
+      // manualStoreHydration: true,
+      // manualPostHydrationTrigger: true,
+      pwa: false
+      // pwaOfflineHtmlFilename: 'offline.html', // do NOT use index.html as name!
+      // will mess up SSR
+      // pwaExtendGenerateSWOptions (cfg) {},
+      // pwaExtendInjectManifestOptions (cfg) {}
+    },
+    // https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa
+    pwa: {
+      workboxMode: "GenerateSW"
+      // 'GenerateSW' or 'InjectManifest'
+      // swFilename: 'sw.js',
+      // manifestFilename: 'manifest.json'
+      // extendManifestJson (json) {},
+      // useCredentialsForManifestTag: true,
+      // injectPwaMetaTags: false,
+      // extendPWACustomSWConf (esbuildConf) {},
+      // extendGenerateSWOptions (cfg) {},
+      // extendInjectManifestOptions (cfg) {}
+    },
+    // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-cordova-apps/configuring-cordova
+    cordova: {
+      // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
+    },
+    // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor
+    capacitor: {
+      hideSplashscreen: true
+    },
+    // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron
+    electron: {
+      // extendElectronMainConf (esbuildConf) {},
+      // extendElectronPreloadConf (esbuildConf) {},
+      // extendPackageJson (json) {},
+      // Electron preload scripts (if any) from /src-electron, WITHOUT file extension
+      preloadScripts: ["electron-preload"],
+      // specify the debugging port to use for the Electron app when running in development mode
+      inspectPort: 5858,
+      bundler: "packager",
+      // 'packager' or 'builder'
+      packager: {
+        // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
+        // OS X / Mac App Store
+        // appBundleId: '',
+        // appCategoryType: '',
+        // osxSign: '',
+        // protocol: 'myapp://path',
+        // Windows only
+        // win32metadata: { ... }
+      },
+      builder: {
+        // https://www.electron.build/configuration/configuration
+        appId: "quasar-skeleton"
+      }
+    },
+    // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
+    bex: {
+      // extendBexScriptsConf (esbuildConf) {},
+      // extendBexManifestJson (json) {},
+      contentScripts: ["my-content-script"]
+    }
+  };
+});
+export {
+  quasar_config_default as default
+};

+ 6 - 0
src/api/chatbot.js

@@ -0,0 +1,6 @@
+import api from 'src/api';
+
+export const sendChatMessage = async ({ message, history = [] }) => {
+  const { data } = await api.post('/chatbot/message', { message, history });
+  return data.payload.reply;
+};

+ 6 - 3
src/api/user.js

@@ -45,7 +45,7 @@ const removeEmptyRegistrationFields = (data) => {
     return data.map(removeEmptyRegistrationFields);
   }
 
-  if (!data || typeof data !== "object" || data instanceof FormData) {
+  if (!data || typeof data !== "object") {
     return data;
   }
 
@@ -64,6 +64,9 @@ const removeEmptyRegistrationFields = (data) => {
 };
 
 export const createUserAndProvider = async (data) => {
-  const response = await api.post("/register-provider", data);
+  const response = await api.post(
+    "/register-provider",
+    removeEmptyRegistrationFields(data),
+  );
   return response;
-}
+};

+ 163 - 51
src/components/profile/ProfileHelpDialog.vue

@@ -1,12 +1,23 @@
 <template>
   <q-dialog ref="dialogRef" persistent maximized transition-show="slide-left" transition-hide="slide-right">
     <div class="bg-page full-height column no-shadow">
+
       <div class="row items-center q-px-md q-pt-md q-pb-sm bg-white shadow-profile bg-surface">
         <q-btn v-close-popup icon="mdi-chevron-left" flat round dense color="primary" />
         <q-space />
         <span class="text-subtitle1 text-weight-bold text-primary">{{ $t('profile.help.title') }}</span>
         <q-space />
-        <div style="width: 32px"></div>
+        <q-btn
+          v-if="store.messages.length > 1"
+          flat
+          round
+          dense
+          icon="mdi-delete-outline"
+          color="grey-5"
+          size="sm"
+          @click="clearChat"
+        />
+        <div v-else style="width: 32px"></div>
       </div>
 
       <div class="col overflow-auto">
@@ -39,28 +50,43 @@
           </div>
         </div>
 
-        <div class="q-px-md q-pt-lg q-pb-xl">
+        <div ref="messagesContainer" class="q-px-md q-pt-lg q-pb-xl messages-area">
 
-          <q-card class="bg-surface shadow-card q-mb-lg border-message-support" style="border-radius: 16px;">
-            <q-card-section class="q-pb-xs">
-              <div class="row items-center q-gutter-x-sm q-mb-sm">
-                <q-icon name="mdi-message-outline" color="primary" size="18px" />
-                <span class="text-caption text-weight-bold text-primary">{{ $t('profile.help.virtual_assistant') }}</span>
+          <div
+            v-for="(msg, idx) in store.messages"
+            :key="idx"
+            class="q-mb-md"
+            :class="msg.role === 'user' ? 'row justify-end' : 'row justify-start'"
+          >
+            <div
+              class="bubble q-pa-sm"
+              :class="msg.role === 'user' ? 'bubble-user' : 'bubble-bot'"
+            >
+              <p class="q-mb-xs bubble-text">{{ msg.text }}</p>
+              <span class="bubble-time">{{ msg.time }}</span>
+            </div>
+          </div>
+
+          <div v-if="loading" class="row justify-start q-mb-md">
+            <div class="bubble bubble-bot q-pa-sm">
+              <div class="row items-center q-gutter-x-xs typing-indicator">
+                <span /><span /><span />
               </div>
-              <p class="text-text q-mb-xs">{{ $t('profile.help.greeting_message') }}</p>
-              <span class="text-caption text-grey-5">{{ currentTime }}</span>
-            </q-card-section>
-          </q-card>
-          <div class="q-pt-sm">
+            </div>
+          </div>
+
+          <div v-if="store.messages.length === 1" class="q-pt-sm">
             <div class="col-12 text-caption text-grey-6 q-mb-sm">{{ $t('profile.help.quick_suggestions') }}</div>
-            <div 
+            <div
               v-for="suggestion in suggestions"
               :key="suggestion"
               class="row col-12 q-py-xs"
+              @click="sendMessage(suggestion)"
             >
-              <span class="text-text bg-surface suggestion-btn q-py-sm q-px-md">{{ suggestion }}</span>
+              <span class="text-text bg-surface suggestion-btn q-py-sm q-px-md cursor-pointer">{{ suggestion }}</span>
             </div>
           </div>
+
         </div>
       </div>
 
@@ -73,7 +99,8 @@
             borderless
             input-class="text-text"
             :placeholder="$t('profile.help.message_placeholder')"
-            @keyup.enter="sendMessage"
+            :disable="loading"
+            @keyup.enter="sendMessage()"
           />
           <q-btn
             round
@@ -81,8 +108,9 @@
             color="primary"
             icon="mdi-send"
             size="sm"
-            :disable="!messageInput.trim()"
-            @click="sendMessage"
+            :disable="!messageInput.trim() || loading"
+            :loading="loading"
+            @click="sendMessage()"
           />
         </div>
         <div class="footer-disclaimer text-text text-center q-my-md">
@@ -95,21 +123,22 @@
 </template>
 
 <script setup>
-import { ref, computed } from 'vue';
-import { useDialogPluginComponent, useQuasar } from 'quasar';
+import { ref, computed, nextTick, onMounted } from 'vue';
+import { useDialogPluginComponent } from 'quasar';
 import { useI18n } from 'vue-i18n';
+import { chatbotStore } from 'src/stores/chatbot';
+import { sendChatMessage } from 'src/api/chatbot';
 import diarinho_suporte from 'src/assets/diarinho_suporte.svg';
+
 defineEmits([...useDialogPluginComponent.emits]);
 
 const { dialogRef } = useDialogPluginComponent();
-const $q = useQuasar();
 const { t } = useI18n();
+const store = chatbotStore();
 
 const messageInput = ref('');
-
-const currentTime = computed(() => {
-  return new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
-});
+const loading = ref(false);
+const messagesContainer = ref(null);
 
 const suggestions = computed(() => [
   t('profile.help.suggestion_cancel'),
@@ -118,17 +147,56 @@ const suggestions = computed(() => [
   t('profile.help.suggestion_human'),
 ]);
 
-const sendMessage = () => {
-  if (!messageInput.value.trim()) return;
-  $q.notify({ type: 'info', message: t('profile.help.coming_soon') });
+const scrollToBottom = async () => {
+  await nextTick();
+  if (messagesContainer.value) {
+    messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
+  }
+};
+
+const sendMessage = async (text) => {
+  const content = (text ?? messageInput.value).trim();
+  if (!content || loading.value) return;
+
   messageInput.value = '';
+  store.addMessage('user', content);
+  await scrollToBottom();
+
+  loading.value = true;
+
+  try {
+    const history = store.messages
+      .slice(-6, -1)
+      .map(({ role, text: msgText }) => ({ role, text: msgText }));
+
+    const reply = await sendChatMessage({ message: content, history });
+    store.addMessage('model', reply);
+  } catch {
+    store.addMessage('model', t('profile.help.error_message'));
+  } finally {
+    loading.value = false;
+    await scrollToBottom();
+  }
 };
+
+const clearChat = () => {
+  store.clear();
+  store.addMessage('model', t('profile.help.greeting_message'));
+};
+
+onMounted(() => {
+  if (store.messages.length === 0) {
+    store.addMessage('model', t('profile.help.greeting_message'));
+  }
+  scrollToBottom();
+});
 </script>
 
 <style scoped lang="scss">
 .support-banner {
   background: linear-gradient(180deg, #6C54C1 0%, #9A7FF6 100%);
   min-height: 120px;
+  flex-shrink: 0;
 }
 
 .support-avatar-placeholder {
@@ -138,30 +206,88 @@ const sendMessage = () => {
   background: rgba(255, 255, 255, 0.2);
 }
 
+.messages-area {
+  overflow-y: auto;
+}
+
+.bubble {
+  max-width: 78%;
+  border-radius: 16px;
+  word-break: break-word;
+}
+
+.bubble-bot {
+  background: white;
+  border: 1px solid rgba(0, 0, 0, 0.08);
+  border-bottom-left-radius: 4px;
+}
+
+.bubble-user {
+  background: #6C54C1;
+  border-bottom-right-radius: 4px;
+
+  .bubble-text { color: white; }
+  .bubble-time { color: rgba(255, 255, 255, 0.7); }
+}
+
+.bubble-text {
+  font-size: 14px;
+  line-height: 1.45;
+  color: #2c2c2c;
+  margin: 0 0 4px 0;
+  white-space: pre-wrap;
+}
+
+.bubble-time {
+  font-size: 10px;
+  color: #aaa;
+  display: block;
+  text-align: right;
+}
+
+.typing-indicator {
+  span {
+    width: 7px;
+    height: 7px;
+    border-radius: 50%;
+    background: #aaa;
+    display: inline-block;
+    animation: typing-bounce 1.2s infinite ease-in-out;
+
+    &:nth-child(1) { animation-delay: 0s; }
+    &:nth-child(2) { animation-delay: 0.2s; }
+    &:nth-child(3) { animation-delay: 0.4s; }
+  }
+}
+
+@keyframes typing-bounce {
+  0%, 60%, 100% { transform: translateY(0); }
+  30%           { transform: translateY(-6px); }
+}
+
 .suggestion-btn {
   border-radius: 32px;
   font-size: 12px;
-  height: auto;
-  min-height: unset;
   border: 1.15px solid #d8d7d7ce;
   font-family: Inter;
   font-weight: 400;
-  font-style: Regular;
-  font-size: 12px;
   line-height: 16px;
-  letter-spacing: 0px;
+  transition: background 0.2s;
+
+  &:active { background: #ede8fc !important; }
 }
 
 .chat-footer {
   border-top: 1px solid rgba(0, 0, 0, 0.06);
+  flex-shrink: 0;
 }
 
 .shadow-up {
   box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.08);
 }
 
-.border-message-support {
-  border: 1px solid rgba(0, 0, 0, 0.151);
+.shadow-profile {
+  box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.1);
 }
 
 .input-suporte {
@@ -176,27 +302,16 @@ const sendMessage = () => {
     background: var(--q-page, #f5f5f5);
     border: 1px solid rgba(0, 0, 0, 0.12);
     border-radius: 32px;
-
     padding: 10px 14px;
   }
 
-  :deep(.q-field__native) {
-    padding: 0 !important;
-  }
+  :deep(.q-field__native) { padding: 0 !important; }
 
   :deep(.q-field__control::before),
-  :deep(.q-field__control::after) {
-    display: none !important;
-  }
-
-  :deep(.q-field--focused .q-field__control-container) {
-    box-shadow: none;
-  }
+  :deep(.q-field__control::after) { display: none !important; }
 
   :deep(.q-field__bottom),
-  :deep(.q-field__marginal) {
-    display: none;
-  }
+  :deep(.q-field__marginal) { display: none; }
 
   :deep(.q-field__control),
   :deep(.q-field__control:before),
@@ -206,7 +321,6 @@ const sendMessage = () => {
     outline: none !important;
   }
 
-  :deep(.q-field--focused .q-field__control),
   :deep(.q-field--focused .q-field__control-container) {
     box-shadow: none !important;
     border-color: rgba(0, 0, 0, 0.12) !important;
@@ -216,10 +330,8 @@ const sendMessage = () => {
 .footer-disclaimer {
   font-family: Inter;
   font-weight: 400;
-  font-style: Regular;
   font-size: 12px;
   line-height: 15px;
-  letter-spacing: 0px;
   text-align: center;
 }
 </style>

+ 2 - 1
src/i18n/locales/en.json

@@ -593,7 +593,8 @@
       "channel_phone_desc": "Talk directly with our team",
       "message_placeholder": "Type your message...",
       "footer_disclaimer": "⚡ Automatic AI responses • Human support available",
-      "coming_soon": "Coming soon"
+      "coming_soon": "Coming soon",
+      "error_message": "Could not process your message. Please try again."
     },
     "privacy_policy": {
       "title": "Privacy Policy",

+ 2 - 1
src/i18n/locales/es.json

@@ -591,7 +591,8 @@
       "channel_phone_desc": "Habla directamente con nuestro equipo",
       "message_placeholder": "Escribe tu mensaje...",
       "footer_disclaimer": "⚡ Respuestas automáticas por IA • Atención humana disponible",
-      "coming_soon": "Próximamente disponible"
+      "coming_soon": "Próximamente disponible",
+      "error_message": "No se pudo procesar tu mensaje. Inténtalo de nuevo."
     },
     "privacy_policy": {
       "title": "Política de Privacidad",

+ 2 - 1
src/i18n/locales/pt.json

@@ -596,7 +596,8 @@
       "channel_phone_desc": "Fale diretamente com nossa equipe",
       "message_placeholder": "Digite sua mensagem...",
       "footer_disclaimer": "⚡ Respostas automáticas por IA • Atendimento humano disponível",
-      "coming_soon": "Em breve disponível"
+      "coming_soon": "Em breve disponível",
+      "error_message": "Não foi possível processar sua mensagem. Tente novamente."
     },
     "privacy": {
       "title": "Privacidade",

+ 92 - 22
src/pages/LoginPage.vue

@@ -377,32 +377,102 @@ const validateCodeInput = async () => {
   }
 };
 
+// const registerUserAndProvider = async () => {
+//   const workingDays = mapWorkingDays();
+
+//   const payload = {
+//     ...stepThreeForm.value,
+//     email: stepThreeForm.value.email || email.value,
+//     phone: stepThreeForm.value.phone || phone.value,
+//     code: code.value,
+//     birth_date: toISODate(stepThreeForm.value.birth_date),
+//     has_complement: !stepThreeForm.value.no_complement,
+//     complement: stepThreeForm.value.no_complement ? null : stepThreeForm.value.complement,
+
+//     selfie_base64: stepFourForm.value.selfie_base64,
+//     document_front_base64: stepFourForm.value.document_front_base64,
+//     document_back_base64: stepFourForm.value.document_back_base64,
+
+//     daily_price_8h: Number(stepFiveForm.value.daily_price_8h),
+//     daily_price_6h: Number(stepFiveForm.value.daily_price_6h),
+//     daily_price_4h: Number(stepFiveForm.value.daily_price_4h),
+//     daily_price_2h: Number(stepFiveForm.value.daily_price_2h),
+//     services_types_ids: stepFiveForm.value.services_types_ids,
+
+//     working_days: workingDays,
+
+//   };
+
+//   form.append('selfie', stepFourForm.value.selfie);
+//   form.append('document_front', stepFourForm.value.document_front);
+//   form.append('document_back', stepFourForm.value.document_back);
+
+//   append('recipient_name', stepThreeForm.value.name);
+//   append('recipient_email', stepThreeForm.value.email || email.value);
+//   append('recipient_document', stepThreeForm.value.document);
+//   append('recipient_type', 'individual');
+//   append('recipient_payment_mode', 'bank_transfer');
+//   append('recipient_default_bank_account[type]', stepSixForm.value.account_type);
+//   append('recipient_default_bank_account[bank]', stepSixForm.value.bank);
+//   append('recipient_default_bank_account[branch_number]', stepSixForm.value.branch_number);
+//   append('recipient_default_bank_account[branch_check_digit]', stepSixForm.value.branch_check_digit);
+//   append('recipient_default_bank_account[account_number]', stepSixForm.value.account_number);
+//   append('recipient_default_bank_account[account_check_digit]', stepSixForm.value.account_check_digit);
+//   append('recipient_default_bank_account[pix_key]', stepSixForm.value.pix_key);
+//   append('recipient_default_bank_account[holder_name]', stepThreeForm.value.name);
+//   append('recipient_default_bank_account[holder_document]', stepThreeForm.value.document);
+//   append('recipient_default_bank_account[holder_type]', 'individual');
+
+//   const response = await createUserAndProvider(form);
+//   if (response.status === 200) {
+//     if (response.data?.payload?.access_token) {
+//       await setAuthDataFromPayload(response.data.payload);
+//       router.push({ name: "DashboardPage" });
+//       return;
+//     }
+//     steps.value = 8;
+//   }
+// };
+
 const registerUserAndProvider = async () => {
   const workingDays = mapWorkingDays();
+  const form = new FormData();
 
-  const payload = {
-    ...stepThreeForm.value,
-    email: stepThreeForm.value.email || email.value,
-    phone: stepThreeForm.value.phone || phone.value,
-    code: code.value,
-    birth_date: toISODate(stepThreeForm.value.birth_date),
-    has_complement: !stepThreeForm.value.no_complement,
-    complement: stepThreeForm.value.no_complement ? null : stepThreeForm.value.complement,
-
-    selfie_base64: stepFourForm.value.selfie_base64,
-    document_front_base64: stepFourForm.value.document_front_base64,
-    document_back_base64: stepFourForm.value.document_back_base64,
-
-    daily_price_8h: Number(stepFiveForm.value.daily_price_8h),
-    daily_price_6h: Number(stepFiveForm.value.daily_price_6h),
-    daily_price_4h: Number(stepFiveForm.value.daily_price_4h),
-    daily_price_2h: Number(stepFiveForm.value.daily_price_2h),
-    services_types_ids: stepFiveForm.value.services_types_ids,
-
-    working_days: workingDays,
-
+  const append = (key, val) => {
+    if (val === null || val === undefined) return;
+    if (typeof val === 'boolean') form.append(key, val ? '1' : '0');
+    else form.append(key, val);
   };
 
+  append('name', stepThreeForm.value.name);
+  append('email', stepThreeForm.value.email || email.value);
+  append('phone', stepThreeForm.value.phone || phone.value);
+  append('code', code.value);
+  append('rg', stepThreeForm.value.rg);
+  append('document', stepThreeForm.value.document);
+  append('birth_date', toISODate(stepThreeForm.value.birth_date));
+  append('zip_code', stepThreeForm.value.zip_code);
+  append('address', stepThreeForm.value.address);
+  append('has_complement', !stepThreeForm.value.no_complement);
+  append('complement', stepThreeForm.value.no_complement ? null : stepThreeForm.value.complement);
+  append('nickname', stepThreeForm.value.nickname);
+  append('instructions', stepThreeForm.value.instructions);
+  append('city', stepThreeForm.value.city);
+  append('state', stepThreeForm.value.state);
+  append('address_type', stepThreeForm.value.address_type);
+
+  append('daily_price_8h', Number(stepFiveForm.value.daily_price_8h));
+  append('daily_price_6h', Number(stepFiveForm.value.daily_price_6h));
+  append('daily_price_4h', Number(stepFiveForm.value.daily_price_4h));
+  append('daily_price_2h', Number(stepFiveForm.value.daily_price_2h));
+
+  (stepFiveForm.value.services_types_ids ?? []).forEach(id => form.append('services_types_ids[]', id));
+
+  workingDays.forEach((wd, i) => {
+    form.append(`working_days[${i}][day]`, wd.day);
+    form.append(`working_days[${i}][period]`, wd.period);
+  });
+
   form.append('selfie', stepFourForm.value.selfie);
   form.append('document_front', stepFourForm.value.document_front);
   form.append('document_back', stepFourForm.value.document_back);
@@ -425,7 +495,7 @@ const registerUserAndProvider = async () => {
 
   const response = await createUserAndProvider(form);
   if (response.status === 200) {
-    if (response.data?.payload?.access_token) {
+      if (response.data?.payload?.access_token) {
       await setAuthDataFromPayload(response.data.payload);
       router.push({ name: "DashboardPage" });
       return;

+ 2 - 2
src/pages/profile/ProfilePage.vue

@@ -139,8 +139,8 @@ import ProfileHelpDialog from 'src/components/profile/ProfileHelpDialog.vue';
 import ProfilePrivacyDialog from 'src/components/profile/ProfilePrivacyDialog.vue';
 import { useRouter } from 'vue-router';
 
-const PRIVACY_POLICY_URL = 'https://https://politicas.softpar.inf.br/politicas/politicaDiaristaPrestador.html';
-const SUPPORT_PAGE_URL   = 'https://https://politicas.softpar.inf.br/suportes/suporteDiaristaPrestador.html';
+const PRIVACY_POLICY_URL = 'https://politicas.softpar.inf.br/politicas/politicaDiaristaPrestador.html';
+const SUPPORT_PAGE_URL   = 'https://politicas.softpar.inf.br/suportes/suporteDiaristaPrestador.html';
 
 const $q = useQuasar();
 

+ 20 - 0
src/stores/chatbot.js

@@ -0,0 +1,20 @@
+import { defineStore } from 'pinia';
+import { ref } from 'vue';
+
+export const chatbotStore = defineStore('chatbot', () => {
+  const messages = ref([]);
+
+  const addMessage = (role, text) => {
+    messages.value.push({
+      role,
+      text,
+      time: new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }),
+    });
+  };
+
+  const clear = () => {
+    messages.value = [];
+  };
+
+  return { messages, addMessage, clear };
+});