|
|
@@ -1,7 +1,7 @@
|
|
|
<script>
|
|
|
import { goto } from '$app/navigation';
|
|
|
import { browser } from '$app/environment';
|
|
|
- import { onMount, onDestroy } from 'svelte';
|
|
|
+ import { onMount, tick } from 'svelte';
|
|
|
import { authToken, companyId as companyIdStore } from '$lib/utils/stores';
|
|
|
|
|
|
const apiUrl = import.meta.env.VITE_API_URL;
|
|
|
@@ -13,6 +13,273 @@
|
|
|
let success = '';
|
|
|
let successTimeout;
|
|
|
|
|
|
+ const kycStatusEndpoint = `${apiUrl}/company/user/kyc/status`;
|
|
|
+ const KYC_POLL_INTERVAL_MS = 10_000;
|
|
|
+ const LOGIN_KYC_PENDING_KEY = 'login_kyc_pending_state';
|
|
|
+ const LOGIN_KYC_PENDING_TTL_MS = 1000 * 60 * 30;
|
|
|
+
|
|
|
+ let kycMode = null;
|
|
|
+ let kycLink = '';
|
|
|
+ let kycNumberToken = '';
|
|
|
+ let kycStatusMessage = '';
|
|
|
+ let kycStatusCode = null;
|
|
|
+ let kycError = '';
|
|
|
+ let qrCodeContainer;
|
|
|
+ let qrCodeInstance = null;
|
|
|
+ let kycPollingId = null;
|
|
|
+ let isCheckingKyc = false;
|
|
|
+
|
|
|
+ function showTimedSuccess(message) {
|
|
|
+ success = message;
|
|
|
+ if (successTimeout) {
|
|
|
+ clearTimeout(successTimeout);
|
|
|
+ }
|
|
|
+ successTimeout = setTimeout(() => {
|
|
|
+ success = '';
|
|
|
+ }, 4000);
|
|
|
+ }
|
|
|
+
|
|
|
+ function savePendingState(state) {
|
|
|
+ if (!browser) return;
|
|
|
+ try {
|
|
|
+ const payload = { ...state, savedAt: Date.now() };
|
|
|
+ localStorage.setItem(LOGIN_KYC_PENDING_KEY, JSON.stringify(payload));
|
|
|
+ } catch (err) {
|
|
|
+ console.warn('Não foi possível salvar o estado de pendência de KYC:', err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function loadPendingState() {
|
|
|
+ if (!browser) return null;
|
|
|
+ try {
|
|
|
+ const raw = localStorage.getItem(LOGIN_KYC_PENDING_KEY);
|
|
|
+ if (!raw) return null;
|
|
|
+ const data = JSON.parse(raw);
|
|
|
+ if (data?.savedAt && Date.now() - data.savedAt > LOGIN_KYC_PENDING_TTL_MS) {
|
|
|
+ localStorage.removeItem(LOGIN_KYC_PENDING_KEY);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return data;
|
|
|
+ } catch (err) {
|
|
|
+ console.warn('Não foi possível restaurar o estado de pendência de KYC:', err);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function clearPendingState() {
|
|
|
+ if (!browser) return;
|
|
|
+ try {
|
|
|
+ localStorage.removeItem(LOGIN_KYC_PENDING_KEY);
|
|
|
+ } catch (err) {
|
|
|
+ console.warn('Não foi possível limpar o estado de pendência de KYC:', err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function extractKycLinkData(data = {}) {
|
|
|
+ return {
|
|
|
+ link: data?.link ?? data?.tshield?.link,
|
|
|
+ numberToken: data?.numberToken ?? data?.tshield?.numberToken ?? data?.tshield?.number
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ async function handleLoginErrorResponse(status, payload) {
|
|
|
+ const code = payload?.code;
|
|
|
+ const message = payload?.msg ?? payload?.message;
|
|
|
+
|
|
|
+ if (status === 401 || code === 'E_VALIDATE') {
|
|
|
+ error = message ?? 'Credenciais inválidas.';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (status === 403 && code === 'E_KYC') {
|
|
|
+ const reason = payload?.data?.reason;
|
|
|
+ if (reason === 'KYC_PJ_PENDING') {
|
|
|
+ enterPjPending(message ?? 'Necessário finalizar análise PJ ou contatar o suporte.');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const { link, numberToken } = extractKycLinkData(payload?.data ?? {});
|
|
|
+ if (link && numberToken) {
|
|
|
+ await enterPfPending(link, numberToken, message ?? 'KYC pendente. Conclua pelo link disponibilizado.');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (reason === 'KYC_PF_MISSING_DOCUMENT') {
|
|
|
+ error = message ?? 'CPF não cadastrado. Contate o suporte para concluir a verificação.';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ error = message ?? 'KYC pendente. Entre em contato com o suporte.';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (status === 502 || code === 'E_EXTERNAL') {
|
|
|
+ error = message ?? 'Não foi possível gerar o link de verificação. Tente novamente.';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (code === 'E_VALIDATE') {
|
|
|
+ error = message ?? 'Credenciais inválidas.';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ error = message ?? 'Falha ao autenticar. Tente novamente.';
|
|
|
+ }
|
|
|
+
|
|
|
+ function stopKycPolling() {
|
|
|
+ if (kycPollingId) {
|
|
|
+ clearInterval(kycPollingId);
|
|
|
+ kycPollingId = null;
|
|
|
+ }
|
|
|
+ isCheckingKyc = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ function resetKycState() {
|
|
|
+ stopKycPolling();
|
|
|
+ kycMode = null;
|
|
|
+ kycLink = '';
|
|
|
+ kycNumberToken = '';
|
|
|
+ kycStatusMessage = '';
|
|
|
+ kycStatusCode = null;
|
|
|
+ kycError = '';
|
|
|
+ if (qrCodeContainer) {
|
|
|
+ qrCodeContainer.innerHTML = '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async function renderQrCode(link) {
|
|
|
+ if (!browser || !link) return;
|
|
|
+ if (!qrCodeInstance) {
|
|
|
+ const module = await import('qr-code-styling');
|
|
|
+ const QRCodeStyles = module.default ?? module;
|
|
|
+ qrCodeInstance = new QRCodeStyles({
|
|
|
+ width: 240,
|
|
|
+ height: 240,
|
|
|
+ type: 'svg',
|
|
|
+ data: link,
|
|
|
+ dotsOptions: {
|
|
|
+ color: '#0f172a',
|
|
|
+ type: 'rounded'
|
|
|
+ },
|
|
|
+ backgroundOptions: {
|
|
|
+ color: '#ffffff'
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ qrCodeInstance.update({ data: link });
|
|
|
+ }
|
|
|
+ await tick();
|
|
|
+ if (qrCodeContainer) {
|
|
|
+ qrCodeContainer.innerHTML = '';
|
|
|
+ qrCodeInstance.append(qrCodeContainer);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function startKycPolling() {
|
|
|
+ stopKycPolling();
|
|
|
+ checkKycStatus();
|
|
|
+ kycPollingId = setInterval(checkKycStatus, KYC_POLL_INTERVAL_MS);
|
|
|
+ }
|
|
|
+
|
|
|
+ async function checkKycStatus() {
|
|
|
+ if (!kycNumberToken || isCheckingKyc) return;
|
|
|
+ isCheckingKyc = true;
|
|
|
+ try {
|
|
|
+ const res = await fetch(kycStatusEndpoint, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: { 'content-type': 'application/json' },
|
|
|
+ body: JSON.stringify({ numberToken: kycNumberToken })
|
|
|
+ });
|
|
|
+ const raw = await res.text();
|
|
|
+ let body = null;
|
|
|
+ if (raw) {
|
|
|
+ try {
|
|
|
+ body = JSON.parse(raw);
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Resposta inválida do endpoint de status do KYC:', err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!res.ok) {
|
|
|
+ const message = body?.message ?? body?.msg ?? 'Falha ao consultar status do KYC.';
|
|
|
+ throw new Error(message);
|
|
|
+ }
|
|
|
+
|
|
|
+ const resolvedStatus = Number(body?.status);
|
|
|
+ if (!Number.isNaN(resolvedStatus)) {
|
|
|
+ kycStatusCode = resolvedStatus;
|
|
|
+ if (resolvedStatus === 1) {
|
|
|
+ kycStatusMessage = 'KYC concluído com sucesso. Faça login novamente.';
|
|
|
+ kycError = '';
|
|
|
+ clearPendingState();
|
|
|
+ stopKycPolling();
|
|
|
+ } else if (resolvedStatus === 2) {
|
|
|
+ kycStatusMessage = 'Falha na validação do KYC. Entre em contato com o suporte.';
|
|
|
+ clearPendingState();
|
|
|
+ stopKycPolling();
|
|
|
+ } else {
|
|
|
+ kycStatusMessage = 'Aguardando validação do KYC...';
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ kycStatusMessage = 'Aguardando validação do KYC...';
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Erro ao verificar status do KYC:', err);
|
|
|
+ kycError = err?.message ?? 'Não foi possível verificar o status do KYC.';
|
|
|
+ } finally {
|
|
|
+ isCheckingKyc = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async function enterPfPending(link, numberToken, message, options = {}) {
|
|
|
+ const { persist = true } = options;
|
|
|
+ resetKycState();
|
|
|
+ kycMode = 'PF_PENDING';
|
|
|
+ kycLink = link;
|
|
|
+ kycNumberToken = numberToken;
|
|
|
+ kycStatusMessage = message ?? 'KYC pendente. Conclua pelo QR Code.';
|
|
|
+ kycStatusCode = null;
|
|
|
+ kycError = '';
|
|
|
+ error = '';
|
|
|
+ success = '';
|
|
|
+ if (persist) {
|
|
|
+ savePendingState({
|
|
|
+ mode: 'PF_PENDING',
|
|
|
+ link,
|
|
|
+ numberToken,
|
|
|
+ message: kycStatusMessage
|
|
|
+ });
|
|
|
+ }
|
|
|
+ await renderQrCode(link);
|
|
|
+ startKycPolling();
|
|
|
+ }
|
|
|
+
|
|
|
+ function enterPjPending(message, options = {}) {
|
|
|
+ const { persist = true } = options;
|
|
|
+ resetKycState();
|
|
|
+ kycMode = 'PJ_PENDING';
|
|
|
+ kycStatusMessage = message ?? 'Necessário finalizar análise PJ ou contatar o suporte.';
|
|
|
+ error = '';
|
|
|
+ success = '';
|
|
|
+ if (persist) {
|
|
|
+ savePendingState({
|
|
|
+ mode: 'PJ_PENDING',
|
|
|
+ message: kycStatusMessage
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleBackToLogin(message = '') {
|
|
|
+ resetKycState();
|
|
|
+ clearPendingState();
|
|
|
+ if (message) {
|
|
|
+ showTimedSuccess(message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleCancelKycFlow() {
|
|
|
+ handleBackToLogin('');
|
|
|
+ }
|
|
|
+
|
|
|
function setDark(enabled) {
|
|
|
if (enabled) document.documentElement.classList.add('dark');
|
|
|
else document.documentElement.classList.remove('dark');
|
|
|
@@ -34,29 +301,42 @@
|
|
|
const onStorage = (e) => {
|
|
|
if (e.key === 'darkmode') applyDarkModeFromStorage();
|
|
|
};
|
|
|
- window.addEventListener('storage', onStorage);
|
|
|
+ if (browser) window.addEventListener('storage', onStorage);
|
|
|
try {
|
|
|
const stored = localStorage.getItem('registrationSuccessMessage');
|
|
|
if (stored) {
|
|
|
- success = stored;
|
|
|
localStorage.removeItem('registrationSuccessMessage');
|
|
|
- successTimeout = setTimeout(() => success = '', 4000);
|
|
|
+ showTimedSuccess(stored);
|
|
|
}
|
|
|
} catch (err) {
|
|
|
console.warn('Falha ao recuperar mensagem de sucesso:', err);
|
|
|
}
|
|
|
- onDestroy(() => {
|
|
|
- window.removeEventListener('storage', onStorage);
|
|
|
+
|
|
|
+ const restorePendingState = async () => {
|
|
|
+ const pending = loadPendingState();
|
|
|
+ if (!pending) return;
|
|
|
+ if (pending.mode === 'PF_PENDING' && pending.link && pending.numberToken) {
|
|
|
+ await enterPfPending(pending.link, pending.numberToken, pending.message, { persist: false });
|
|
|
+ } else if (pending.mode === 'PJ_PENDING') {
|
|
|
+ enterPjPending(pending.message, { persist: false });
|
|
|
+ }
|
|
|
+ };
|
|
|
+ void restorePendingState();
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ if (browser) window.removeEventListener('storage', onStorage);
|
|
|
if (successTimeout) {
|
|
|
clearTimeout(successTimeout);
|
|
|
}
|
|
|
- });
|
|
|
+ };
|
|
|
});
|
|
|
|
|
|
async function onSubmit(e) {
|
|
|
e.preventDefault();
|
|
|
error = '';
|
|
|
+ success = '';
|
|
|
loading = true;
|
|
|
+ resetKycState();
|
|
|
try {
|
|
|
if (!email || !password) {
|
|
|
throw new Error('Preencha e-mail e senha.');
|
|
|
@@ -72,19 +352,25 @@
|
|
|
body: JSON.stringify({ email, password })
|
|
|
});
|
|
|
|
|
|
- if (!res.ok) {
|
|
|
- let msg = 'Credenciais inválidas.';
|
|
|
+ const raw = await res.text();
|
|
|
+ let payload = null;
|
|
|
+ if (raw) {
|
|
|
try {
|
|
|
- const err = await res.json();
|
|
|
- if (err?.message) msg = err.message;
|
|
|
- } catch {}
|
|
|
- throw new Error(msg);
|
|
|
+ payload = JSON.parse(raw);
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Resposta inválida do login:', err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!res.ok) {
|
|
|
+ await handleLoginErrorResponse(res.status, payload);
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- const payload = await res.json();
|
|
|
const statusOk = payload?.status === 'ok' && payload?.code === 'S_OK';
|
|
|
if (!statusOk) {
|
|
|
- throw new Error(payload?.msg ?? 'Falha ao autenticar.');
|
|
|
+ await handleLoginErrorResponse(res.status, payload);
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
const token = payload?.data?.token;
|
|
|
@@ -106,6 +392,8 @@
|
|
|
}
|
|
|
authToken.set(token);
|
|
|
|
|
|
+ clearPendingState();
|
|
|
+
|
|
|
if (browser) {
|
|
|
try {
|
|
|
localStorage.removeItem('registrationSuccessMessage');
|
|
|
@@ -125,68 +413,165 @@
|
|
|
|
|
|
<div class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 px-4">
|
|
|
<div class="w-full max-w-md">
|
|
|
- <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm p-8">
|
|
|
- <h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100 mb-2">Entrar</h1>
|
|
|
- <p class="text-sm text-gray-600 dark:text-gray-400 mb-6">Acesse sua conta para continuar.</p>
|
|
|
-
|
|
|
- {#if error}
|
|
|
- <div class="mb-4 rounded border border-red-300 bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 px-3 py-2 text-sm">{error}</div>
|
|
|
- {/if}
|
|
|
- {#if success}
|
|
|
- <div class="mb-4 rounded border border-green-300 bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 px-3 py-2 text-sm">{success}</div>
|
|
|
- {/if}
|
|
|
-
|
|
|
- <form on:submit|preventDefault={onSubmit} class="space-y-4">
|
|
|
- <div>
|
|
|
- <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="email">E-mail</label>
|
|
|
- <input
|
|
|
- id="email"
|
|
|
- name="email"
|
|
|
- type="email"
|
|
|
- bind:value={email}
|
|
|
- class="w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
|
- placeholder="seu@email.com"
|
|
|
- autocomplete="email"
|
|
|
- required
|
|
|
- />
|
|
|
+ {#if kycMode === 'PF_PENDING'}
|
|
|
+ <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm p-8 space-y-6">
|
|
|
+ <div class="text-center">
|
|
|
+ <h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100">Validação necessária</h1>
|
|
|
+ <p class="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
|
|
+ Escaneie o QR Code com o celular e conclua a validação facial no link da TShield.
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="space-y-3 text-sm text-gray-600 dark:text-gray-300">
|
|
|
+ <p>1. Abra a câmera do celular e aponte para o QR Code.</p>
|
|
|
+ <p>2. Siga as instruções para a verificação facial.</p>
|
|
|
+ <p>3. Aguarde alguns minutos — verificamos o status automaticamente.</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex justify-center">
|
|
|
+ <div class="p-4 border border-dashed border-gray-300 dark:border-gray-600 rounded-xl bg-gray-50 dark:bg-gray-800/50">
|
|
|
+ <div
|
|
|
+ class="w-56 h-56 flex items-center justify-center text-gray-500 dark:text-gray-400"
|
|
|
+ bind:this={qrCodeContainer}
|
|
|
+ >
|
|
|
+ {#if !kycLink}
|
|
|
+ Gerando QR Code...
|
|
|
+ {/if}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="rounded border border-blue-100 dark:border-blue-900/40 bg-blue-50 dark:bg-blue-900/20 px-3 py-2 text-sm text-blue-800 dark:text-blue-100">
|
|
|
+ {kycStatusMessage || 'Aguardando validação do KYC...'}
|
|
|
</div>
|
|
|
|
|
|
- <div>
|
|
|
- <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="password">Senha</label>
|
|
|
- <input
|
|
|
- id="password"
|
|
|
- name="password"
|
|
|
- type="password"
|
|
|
- bind:value={password}
|
|
|
- class="w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
|
- placeholder="••••••••"
|
|
|
- autocomplete="current-password"
|
|
|
- required
|
|
|
- />
|
|
|
+ {#if kycError}
|
|
|
+ <div class="rounded border border-red-200 bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 px-3 py-2 text-sm">{kycError}</div>
|
|
|
+ {/if}
|
|
|
+
|
|
|
+ <div class="space-y-2 text-sm text-gray-600 dark:text-gray-300">
|
|
|
+ <p>Se preferir, abra o link diretamente:</p>
|
|
|
+ <a
|
|
|
+ class="inline-flex items-center gap-2 text-blue-600 hover:text-blue-500 text-sm"
|
|
|
+ href={kycLink}
|
|
|
+ target="_blank"
|
|
|
+ rel="noreferrer"
|
|
|
+ >
|
|
|
+ <span>📱 Abrir link de verificação</span>
|
|
|
+ </a>
|
|
|
</div>
|
|
|
|
|
|
- <button
|
|
|
- class="w-full inline-flex items-center justify-center rounded bg-blue-600 hover:bg-blue-700 text-white font-medium px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-60"
|
|
|
- disabled={loading}
|
|
|
- type="submit"
|
|
|
- >
|
|
|
- {#if loading}
|
|
|
- Entrando...
|
|
|
- {:else}
|
|
|
- Entrar
|
|
|
+ <div class="flex flex-col sm:flex-row gap-3">
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="w-full inline-flex items-center justify-center rounded border border-gray-300 dark:border-gray-600 text-gray-800 dark:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-800 font-medium px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
|
+ on:click={handleCancelKycFlow}
|
|
|
+ >
|
|
|
+ Voltar ao login
|
|
|
+ </button>
|
|
|
+ {#if kycStatusCode === 1}
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="w-full inline-flex items-center justify-center rounded bg-green-600 hover:bg-green-700 text-white font-medium px-4 py-2 focus:outline-none focus:ring-2 focus:ring-green-500"
|
|
|
+ on:click={() => handleBackToLogin('KYC concluído com sucesso. Faça login novamente.')}
|
|
|
+ >
|
|
|
+ Concluir
|
|
|
+ </button>
|
|
|
{/if}
|
|
|
- </button>
|
|
|
- </form>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p class="mt-6 text-center text-sm text-gray-600 dark:text-gray-400">
|
|
|
- Esqueceu a senha? <a href="#" class="text-blue-600 hover:text-blue-500">Fale com o suporte</a>
|
|
|
- </p>
|
|
|
- <div class="mt-3">
|
|
|
- <a href="/register" class="block w-full text-center rounded border border-blue-300 dark:border-blue-700 text-blue-700 dark:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/20 font-medium px-4 py-2">
|
|
|
- Ainda não tem conta? Crie agora
|
|
|
- </a>
|
|
|
- </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ {:else if kycMode === 'PJ_PENDING'}
|
|
|
+ <div class="bg-white dark:bg-gray-800 border border-amber-300 dark:border-amber-500/50 rounded-lg shadow-sm p-8 space-y-6">
|
|
|
+ <div class="text-center space-y-2">
|
|
|
+ <h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100">Análise PJ em andamento</h1>
|
|
|
+ <p class="text-sm text-gray-700 dark:text-gray-300">
|
|
|
+ {kycStatusMessage || 'Necessário finalizar análise PJ ou contatar o suporte.'}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="rounded border border-amber-200 bg-amber-50 dark:border-amber-500/40 dark:bg-amber-500/10 px-3 py-2 text-sm text-amber-900 dark:text-amber-200">
|
|
|
+ Nossa equipe está validando os documentos. Assim que concluir, tente acessar novamente ou fale com o suporte.
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="space-y-3">
|
|
|
+ <a href="mailto:suporte@toeasy.com" class="inline-flex items-center gap-2 text-blue-600 hover:text-blue-500 text-sm">
|
|
|
+ <span>💬 Contactar suporte</span>
|
|
|
+ </a>
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="w-full inline-flex items-center justify-center rounded bg-blue-600 hover:bg-blue-700 text-white font-medium px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
|
+ on:click={handleCancelKycFlow}
|
|
|
+ >
|
|
|
+ Voltar ao login
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ {:else}
|
|
|
+ <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm p-8">
|
|
|
+ <h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100 mb-2">Entrar</h1>
|
|
|
+ <p class="text-sm text-gray-600 dark:text-gray-400 mb-6">Acesse sua conta para continuar.</p>
|
|
|
+
|
|
|
+ {#if error}
|
|
|
+ <div class="mb-4 rounded border border-red-300 bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 px-3 py-2 text-sm">{error}</div>
|
|
|
+ {/if}
|
|
|
+ {#if success}
|
|
|
+ <div class="mb-4 rounded border border-yellow-300 bg-yellow-50 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-200 px-3 py-2 text-sm">
|
|
|
+ {success}
|
|
|
+ </div>
|
|
|
+ {/if}
|
|
|
+
|
|
|
+ <form on:submit|preventDefault={onSubmit} class="space-y-4">
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="email">E-mail</label>
|
|
|
+ <input
|
|
|
+ id="email"
|
|
|
+ name="email"
|
|
|
+ type="email"
|
|
|
+ bind:value={email}
|
|
|
+ class="w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
|
+ placeholder="seu@email.com"
|
|
|
+ autocomplete="email"
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="password">Senha</label>
|
|
|
+ <input
|
|
|
+ id="password"
|
|
|
+ name="password"
|
|
|
+ type="password"
|
|
|
+ bind:value={password}
|
|
|
+ class="w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
|
+ placeholder="••••••••"
|
|
|
+ autocomplete="current-password"
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <button
|
|
|
+ class="w-full inline-flex items-center justify-center rounded bg-blue-600 hover:bg-blue-700 text-white font-medium px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-60"
|
|
|
+ disabled={loading}
|
|
|
+ type="submit"
|
|
|
+ >
|
|
|
+ {#if loading}
|
|
|
+ Entrando...
|
|
|
+ {:else}
|
|
|
+ Entrar
|
|
|
+ {/if}
|
|
|
+ </button>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <p class="mt-6 text-center text-sm text-gray-600 dark:text-gray-400">
|
|
|
+ Esqueceu a senha? <a href="#" class="text-blue-600 hover:text-blue-500">Fale com o suporte</a>
|
|
|
+ </p>
|
|
|
+ <div class="mt-3">
|
|
|
+ <a href="/register" class="block w-full text-center rounded border border-blue-300 dark:border-blue-700 text-blue-700 dark:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/20 font-medium px-4 py-2">
|
|
|
+ Ainda não tem conta? Crie agora
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ {/if}
|
|
|
</div>
|
|
|
|
|
|
</div>
|