672c3b01e5
Provedor de Prótese SEM conta (doc/MODO1-LINK-SEGURO-PROTESE): - Token por OS: tabela protese_os_link (aleatório 24B base64url, expira 60 dias, revogável, conta acessos). POST /protese/os/:id/link (gera/recupera) + POST .../link/revogar. - Leitura pública GET /api/p/:token (sem auth): payload curado com a mesma blindagem soLab — SEM paciente_id (CPF) e SEM valor cobrado ao paciente; mostra trabalho, etapas, componentes, fotos, custódia, ocorrências, histórico e custo do lab. - Página pública ProtesePublicView (mobile-first 320px+), interceptada no index.tsx antes do app autenticado; CTA "Criar conta grátis". Botão Gerar link / WhatsApp / Copiar na aba Monitoramento (modo clínica). - Validado em DEV: leitura, blindagem (CPF/valor ocultos), token inválido→404, revogação→404. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
60 lines
2.4 KiB
TypeScript
60 lines
2.4 KiB
TypeScript
import React from 'react';
|
|
import { createRoot } from 'react-dom/client';
|
|
import App from './App.tsx';
|
|
import { ProtesePublicView } from './views/ProtesePublicView.tsx';
|
|
import { ErrorBoundary } from './components/ErrorBoundary.tsx';
|
|
import { ToastProvider } from './contexts/ToastContext.tsx';
|
|
import { ConfirmProvider } from './contexts/ConfirmContext.tsx';
|
|
import './index.css';
|
|
|
|
// CSS imports are now handled in index.html via <link> tags for AI Studio compatibility,
|
|
// but we add it here too for Vite dev server robustness.
|
|
|
|
/**
|
|
* AI Studio Compatible Entrypoint
|
|
*
|
|
* This version restores the necessary providers (`ErrorBoundary`, `ToastProvider`)
|
|
* to allow the application to run correctly after the initial bootstrap.
|
|
* It uses the modern `createRoot` API, which is required for React 18 and newer.
|
|
*/
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
|
|
const queryClient = new QueryClient();
|
|
|
|
// Auto-reload em chunk obsoleto pós-deploy (par do watchdog inline no index.html).
|
|
// Timestamp evita loop: só recarrega se a última tentativa foi há mais de 30s.
|
|
window.addEventListener('vite:preloadError', () => {
|
|
try {
|
|
const last = +(sessionStorage.getItem('__sd_stale_reload__') || 0);
|
|
if (Date.now() - last < 30000) return;
|
|
sessionStorage.setItem('__sd_stale_reload__', String(Date.now()));
|
|
} catch { /* ignore */ }
|
|
window.location.reload();
|
|
});
|
|
|
|
const rootElement = document.getElementById('root');
|
|
if (rootElement) {
|
|
const root = createRoot(rootElement);
|
|
// Modo 1 — link seguro: /p/TOKEN é página PÚBLICA (sem login), fora do app autenticado.
|
|
const publicMatch = window.location.pathname.match(/^\/p\/(.+)$/);
|
|
root.render(
|
|
<React.StrictMode>
|
|
<QueryClientProvider client={queryClient}>
|
|
<ErrorBoundary>
|
|
<ToastProvider>
|
|
<ConfirmProvider>
|
|
{publicMatch ? <ProtesePublicView token={decodeURIComponent(publicMatch[1])} /> : <App />}
|
|
</ConfirmProvider>
|
|
</ToastProvider>
|
|
</ErrorBoundary>
|
|
</QueryClientProvider>
|
|
</React.StrictMode>
|
|
);
|
|
} else {
|
|
console.error("CRITICAL: Root element #root not found. App could not be mounted.");
|
|
const errorDiv = document.getElementById('global-error-display');
|
|
if (errorDiv) {
|
|
errorDiv.style.display = 'block';
|
|
errorDiv.innerText = 'ERRO CRÍTICO: O ELEMENTO #ROOT NÃO FOI ENCONTRADO NO HTML.';
|
|
}
|
|
} |