Compare commits
49 Commits
3e91269f12
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dc697c8962 | |||
| 667447c389 | |||
| e3f35d808d | |||
| fdfb4606f0 | |||
| e5c18bb6fc | |||
| 6dfdf01830 | |||
| cd6bcf5b3d | |||
| 35e5727e60 | |||
| 96563b30e9 | |||
| 80dbb986f4 | |||
| 6db7b5200b | |||
| 52cda4304b | |||
| a85a6c9b3f | |||
| 2900b9afd9 | |||
| 433773c4b8 | |||
| 6140142bab | |||
| 529036bfe9 | |||
| 52f86cbba5 | |||
| 3eb41133f4 | |||
| e8f15d63ed | |||
| 24786e3512 | |||
| 7bcc582664 | |||
| 4f15851f09 | |||
| 15ce64e786 | |||
| 913e717a21 | |||
| aa275ef538 | |||
| 19f2848fec | |||
| 10e910eb6b | |||
| 9885f59b06 | |||
| d79c9c48a9 | |||
| 7f6c800ab0 | |||
| ec3e439973 | |||
| bfb022866b | |||
| be22792b6d | |||
| cb08c53b40 | |||
| 8c056b0c50 | |||
| b284c4d258 | |||
| 3d36e98185 | |||
| 4e26c13240 | |||
| d15474365c | |||
| e9f47c5b1e | |||
| af8ef2693d | |||
| 5b729b23be | |||
| 41312945d9 | |||
| bb73783e9f | |||
| 57c4abf055 | |||
| f493c55040 | |||
| 0a0d2382d1 | |||
| 47fdcab3b4 |
@@ -0,0 +1,37 @@
|
||||
name: Deploy PROD Avanzato
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy-prod:
|
||||
runs-on: linux_amd64
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Instalar dependências
|
||||
run: npm install
|
||||
|
||||
- name: Build projeto
|
||||
run: npm run build
|
||||
|
||||
- name: Compactar dist
|
||||
run: tar -czf dist.tar.gz dist
|
||||
|
||||
- name: Enviar para servidor PROD
|
||||
run: scp -o StrictHostKeyChecking=no -i /data/avz_hml_deploy dist.tar.gz root@191.252.100.174:/tmp/avz-site-prod-dist.tar.gz
|
||||
|
||||
- name: Publicar PROD
|
||||
run: |
|
||||
ssh -o StrictHostKeyChecking=no -i /data/avz_hml_deploy root@191.252.100.174 '
|
||||
rm -rf /opt/avanzato/site/app/dist_old
|
||||
mv /opt/avanzato/site/app/dist /opt/avanzato/site/app/dist_old
|
||||
mkdir -p /opt/avanzato/site/app/dist
|
||||
tar -xzf /tmp/avz-site-prod-dist.tar.gz -C /opt/avanzato/site/app
|
||||
rm -f /tmp/avz-site-prod-dist.tar.gz
|
||||
docker restart avanzato-site
|
||||
'
|
||||
@@ -40,3 +40,7 @@ Thumbs.db
|
||||
|
||||
# Scripts de deploy local
|
||||
scripts/.env.local
|
||||
|
||||
# ZIPs internos (nunca commite!)
|
||||
*.zip
|
||||
avanzato-*.zip
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# Deploy com Docker - Avanzato
|
||||
|
||||
## O Problema
|
||||
|
||||
O Nginx no Docker nao vem configurado para SPA React. Ao acessar `/avaliacao` diretamente, da 404.
|
||||
|
||||
## A Solucao
|
||||
|
||||
### Opcao 1: Configurar Nginx dentro do container (RECOMENDADO)
|
||||
|
||||
**1. Criar um novo Dockerfile:**
|
||||
|
||||
```dockerfile
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copiar o site
|
||||
COPY dist/ /usr/share/nginx/html/
|
||||
|
||||
# Copiar a configuracao customizada
|
||||
COPY nginx-docker.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Expor porta 80
|
||||
EXPOSE 80
|
||||
|
||||
# Iniciar Nginx
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
**2. Build e run:**
|
||||
|
||||
```bash
|
||||
# Build
|
||||
npm run build
|
||||
bash scripts/build-static-routes.sh dist
|
||||
docker build -t avanzato-site .
|
||||
|
||||
# Run
|
||||
docker run -d -p 80:80 --name avanzato avanzato-site
|
||||
```
|
||||
|
||||
### Opcao 2: Usar docker-compose
|
||||
|
||||
**docker-compose.yml:**
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
avanzato:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./dist:/usr/share/nginx/html:ro
|
||||
- ./nginx-docker.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
container_name: avanzato-site
|
||||
```
|
||||
|
||||
**Comandos:**
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
bash scripts/build-static-routes.sh dist
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Opcao 3: Container ja rodando (hotfix)
|
||||
|
||||
Se o container ja esta rodando:
|
||||
|
||||
```bash
|
||||
# Copiar config para dentro do container
|
||||
docker cp nginx-docker.conf avanzato-site:/etc/nginx/conf.d/default.conf
|
||||
|
||||
# Recarregar Nginx
|
||||
docker exec avanzato-site nginx -s reload
|
||||
```
|
||||
|
||||
## Verificar se funcionou
|
||||
|
||||
```bash
|
||||
# Testar
|
||||
curl -I http://localhost/avaliacao
|
||||
# Deve retornar HTTP 200, nao 404
|
||||
```
|
||||
|
||||
## A regra magica
|
||||
|
||||
A unica coisa que importa e esta linha no Nginx:
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
```
|
||||
|
||||
Isso diz ao Nginx: "se nao encontrar o arquivo, manda para index.html" — onde o React pega a rota.
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
# Guia Rapido - Configurar Nginx (SPA React)
|
||||
|
||||
## O Problema
|
||||
|
||||
URLs como `avanzato.com.br/avaliacao` dao 404 quando acessadas diretamente.
|
||||
Funcionam apenas quando clica no menu (navegacao interna do React).
|
||||
|
||||
## A Solucao
|
||||
|
||||
O Nginx precisa redirecionar todas as rotas para `index.html`.
|
||||
|
||||
## Passo a Passo
|
||||
|
||||
### 1. Copiar a config do projeto para o servidor
|
||||
|
||||
No seu Mac, envie o arquivo `nginx.conf` do projeto para o servidor:
|
||||
|
||||
```bash
|
||||
# Enviar o arquivo nginx.conf do projeto para o servidor
|
||||
scp /Users/avanzato/Projetos/Avanzato/avanzato-site/app/nginx.conf root@SEU_SERVIDOR:/etc/nginx/sites-available/avanzato
|
||||
```
|
||||
|
||||
Ou, se ja estiver logado no servidor:
|
||||
|
||||
```bash
|
||||
# Logar no servidor
|
||||
ssh root@SEU_SERVIDOR
|
||||
|
||||
# Criar o arquivo de config
|
||||
cat > /etc/nginx/sites-available/avanzato << 'EOF'
|
||||
server {
|
||||
listen 80;
|
||||
server_name avanzato.com.br;
|
||||
|
||||
root /var/www/avanzato;
|
||||
index index.html;
|
||||
|
||||
# ESSA LINHA E A CHAVE - redireciona tudo para index.html
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Cache para assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 6M;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
# Seguranca
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
|
||||
# Bloquear arquivos ocultos
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### 2. Ativar o site
|
||||
|
||||
```bash
|
||||
# Criar link simbolico (ativa o site)
|
||||
ln -s /etc/nginx/sites-available/avanzato /etc/nginx/sites-enabled/avanzato
|
||||
|
||||
# Remover o default (se existir)
|
||||
rm -f /etc/nginx/sites-enabled/default
|
||||
```
|
||||
|
||||
### 3. Testar e aplicar
|
||||
|
||||
```bash
|
||||
# Testar a configuracao (mostra erros se houver)
|
||||
nginx -t
|
||||
|
||||
# Se der OK, recarregar o Nginx
|
||||
systemctl reload nginx
|
||||
```
|
||||
|
||||
### 4. Verificar se funcionou
|
||||
|
||||
```bash
|
||||
# Testar uma rota direta
|
||||
curl -I https://avanzato.com.br/avaliacao
|
||||
|
||||
# Deve retornar HTTP 200, nao 404
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comandos Uteis
|
||||
|
||||
| Comando | O que faz |
|
||||
|---------|-----------|
|
||||
| `nginx -t` | Testa a configuracao (sem aplicar) |
|
||||
| `systemctl reload nginx` | Recarrega config sem derrubar o site |
|
||||
| `systemctl restart nginx` | Reinicia o Nginx |
|
||||
| `systemctl status nginx` | Verifica se esta rodando |
|
||||
| `tail -f /var/log/nginx/error.log` | Ver erros em tempo real |
|
||||
|
||||
---
|
||||
|
||||
## Importante
|
||||
|
||||
Se voce usa **Apache** em vez de Nginx, a config esta no arquivo `.htaccess` que ja vai no `dist/`:
|
||||
|
||||
```apache
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^ - [L]
|
||||
RewriteRule ^ index.html [L]
|
||||
```
|
||||
|
||||
Basta garantir que o `.htaccess` esta na raiz do site e o `mod_rewrite` esta ativado.
|
||||
@@ -0,0 +1,95 @@
|
||||
# MAPA DE ATUACAO AVANZATO - Dados Reais
|
||||
|
||||
## Cidades Atendidas (com clientes)
|
||||
|
||||
### 1. Guarulhos (Matriz) - 11 clientes
|
||||
| Cliente | Segmento | Servicos |
|
||||
|---------|----------|----------|
|
||||
| Dellacont | Contabilidade | Suporte + Servidor + Backup |
|
||||
| Madri | Contabilidade | Suporte + Servidor Nuvem + Backup |
|
||||
| Mouradi Naddi | Advocacia | Suporte + Servidor + Backup |
|
||||
| EFLAW | Advocacia | Suporte + Servidor + Backup |
|
||||
| Moraes Advogados | Advocacia | Suporte + Servidor + Backup |
|
||||
| Thalls | Industria | Suporte + Servidor + Backup + VoIP |
|
||||
| Ganiko | BPO | Suporte + Servidor Nuvem + Backup |
|
||||
| DCarvalho | Embalagens | Suporte + Servidor Nuvem + Backup |
|
||||
| Garuppy | Logistica | Suporte + Servidor Nuvem + Backup |
|
||||
| Premier | Monitoramento | Suporte + Servidor + Backup |
|
||||
| Condominio | VoIP | Interligacao aptos a portaria |
|
||||
|
||||
### 2. Mogi das Cruzes - 1 cliente
|
||||
| Cliente | Segmento | Servicos |
|
||||
|---------|----------|----------|
|
||||
| Alamo | Monitoramento | Suporte + Servidor + Backup |
|
||||
|
||||
### 3. Ipiranga (Sao Paulo) - 1 cliente
|
||||
| Cliente | Segmento | Servicos |
|
||||
|---------|----------|----------|
|
||||
| ONG/Terceiro Setor | Institucional | Suporte + Servidor + Backup + Cloud + Seguranca |
|
||||
|
||||
### 4. Cerqueira Cesar (Sao Paulo) - 1 cliente
|
||||
| Cliente | Segmento | Servicos |
|
||||
|---------|----------|----------|
|
||||
| Museu de Faculdade Publica | Acervo Digital | Implantacao Tainacan + ICA-AtoM |
|
||||
|
||||
### 5. Vila Formosa (Sao Paulo) - 1 cliente
|
||||
| Cliente | Segmento | Servicos |
|
||||
|---------|----------|----------|
|
||||
| Apoio 24 Horas | Monitoramento | Servidores + pfSense + VPN + Suporte |
|
||||
|
||||
### 6. Ribeirao Preto - 1 cliente
|
||||
| Cliente | Segmento | Servicos |
|
||||
|---------|----------|----------|
|
||||
| RMA | Portaria Remota | VoIP em Condominios |
|
||||
|
||||
### 7. Bras (Sao Paulo) - 1 cliente
|
||||
| Cliente | Segmento | Servicos |
|
||||
|---------|----------|----------|
|
||||
| Lucena Textil | Textil | Colocation + Rede + VPN + Backup |
|
||||
|
||||
### 8. Santana (Sao Paulo) - 1 cliente
|
||||
| Cliente | Segmento | Servicos |
|
||||
|---------|----------|----------|
|
||||
| Quantica Eletrica | Eventos | Suporte + Servidor Nuvem + Backup |
|
||||
|
||||
### 9. Mongagua - 1 cliente
|
||||
| Cliente | Segmento | Servicos |
|
||||
|---------|----------|----------|
|
||||
| GateOne e Fox | Monitoramento | Servidores + pfSense + VPN + Suporte |
|
||||
|
||||
### 10. Sao Paulo / Cangaiba - 1 cliente
|
||||
| Cliente | Segmento | Servicos |
|
||||
|---------|----------|----------|
|
||||
| RollPrime | VoIP | 2 Ramais VoIP |
|
||||
|
||||
### 11. Sao Paulo / Jurubatuba - 1 cliente
|
||||
| Cliente | Segmento | Servicos |
|
||||
|---------|----------|----------|
|
||||
| Piso.com | Piso de Borracha | Suporte + Servidor Nuvem + Backup (em negociacao) |
|
||||
|
||||
## Total: 20 clientes ativos em 11 localidades
|
||||
|
||||
## Segmentos Atendidos
|
||||
- Contabilidade (2)
|
||||
- Advocacia (3)
|
||||
- Industria (1)
|
||||
- BPO (1)
|
||||
- Embalagens (1)
|
||||
- Logistica (1)
|
||||
- Monitoramento (4)
|
||||
- Institucional (1)
|
||||
- Acervo Digital (1)
|
||||
- Portaria Remota (1)
|
||||
- Textil (1)
|
||||
- Eventos (1)
|
||||
- VoIP (2)
|
||||
- Piso de Borracha (1)
|
||||
|
||||
## Servicos Mais Vendidos (ranking)
|
||||
1. Suporte a Computadores (100% dos clientes)
|
||||
2. Administracao de Servidor (~80%)
|
||||
3. Backup (~80%)
|
||||
4. VPN (~40%)
|
||||
5. VoIP (~25%)
|
||||
6. Cloud (~15%)
|
||||
7. Seguranca/Colocation (~10%)
|
||||
Vendored
BIN
Binary file not shown.
Vendored
-246
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
+42
-11
@@ -5,6 +5,36 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
||||
<!-- DETECAO DE AMBIENTE - Inline (executa antes de tudo) -->
|
||||
<script>
|
||||
(function() {
|
||||
var h = location.hostname;
|
||||
var IS_PROD = h === 'avanzato.com.br' || h === 'www.avanzato.com.br';
|
||||
|
||||
// Torna disponivel para outros scripts
|
||||
window.__IS_PROD__ = IS_PROD;
|
||||
|
||||
// Se NAO for producao, desativa tracking globalmente
|
||||
if (!IS_PROD) {
|
||||
window.__AVANZATO_ENV__ = h === 'hml.avanzato.com.br' ? 'homologation' : 'development';
|
||||
|
||||
// Bloqueia gtag
|
||||
window.dataLayer = { push: function(){} };
|
||||
window.gtag = function(){};
|
||||
|
||||
// Bloqueia fbq (Facebook Pixel)
|
||||
window.fbq = function(){};
|
||||
|
||||
// Bloqueia Mautic
|
||||
window.MauticSDKLoaded = true;
|
||||
|
||||
console.log('[Avanzato] Ambiente: ' + window.__AVANZATO_ENV__ + ' | Tracking bloqueado');
|
||||
} else {
|
||||
window.__AVANZATO_ENV__ = 'production';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Google tag (gtag.js) - Ads + Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-16714813566"></script>
|
||||
<script>
|
||||
@@ -70,6 +100,7 @@
|
||||
<title>Avanzato Tecnologia | TI Gerenciada, Cloud e Seguranca em Guarulhos/SP</title>
|
||||
<meta name="title" content="Avanzato Tecnologia | TI Gerenciada, Cloud e Seguranca em Guarulhos/SP" />
|
||||
<meta name="description" content="Solucoes em TI Gerenciada, Cloud Computing e Seguranca da Informacao. +23 anos de experiencia, +500 clientes atendidos. Suporte 24/7 em Guarulhos e regiao." />
|
||||
<meta name="app-version" content="1.0.0" />
|
||||
<meta name="keywords" content="TI Gerenciada, Cloud Computing, Seguranca da Informacao, Suporte Tecnico, Tecnologia da Informacao, Guarulhos, Sao Paulo, Infraestrutura de TI, Backup, Firewall, Consultoria TI" />
|
||||
<meta name="author" content="Avanzato Tecnologia" />
|
||||
<meta name="robots" content="index, follow" id="meta-robots" />
|
||||
@@ -78,9 +109,9 @@
|
||||
<!-- BLOQUEAR INDEXACAO EM HOMOLOGACAO -->
|
||||
<script>
|
||||
(function() {
|
||||
var hostname = window.location.hostname;
|
||||
if (hostname !== 'avanzato.com.br') {
|
||||
// Homologacao: bloquear indexacao
|
||||
// Usa IS_PROD ja definido no inicio do documento
|
||||
if (!window.__IS_PROD__) {
|
||||
// Homologacao/Desenvolvimento: bloquear indexacao
|
||||
document.getElementById('meta-robots').setAttribute('content', 'noindex, nofollow');
|
||||
document.getElementById('meta-googlebot').setAttribute('content', 'noindex, nofollow');
|
||||
// Adicionar tambem X-Robots-Tag via meta http-equiv
|
||||
@@ -88,7 +119,9 @@
|
||||
meta.httpEquiv = 'X-Robots-Tag';
|
||||
meta.content = 'noindex, nofollow';
|
||||
document.head.appendChild(meta);
|
||||
console.log('%c[SEO] Homologacao detectada - indexacao BLOQUEADA (noindex, nofollow)', 'background:#dc2626;color:#fff;font-weight:bold;padding:2px 6px;border-radius:3px');
|
||||
console.log('%c[SEO] ' + (window.__AVANZATO_ENV__ || 'nao-producao') + ' - indexacao BLOQUEADA', 'background:#dc2626;color:#fff;font-weight:bold;padding:2px 6px;border-radius:3px');
|
||||
} else {
|
||||
console.log('%c[SEO] Producao - indexacao PERMITIDA (index, follow)', 'background:#16a34a;color:#fff;font-weight:bold;padding:2px 6px;border-radius:3px');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -113,11 +146,9 @@
|
||||
<meta property="twitter:image" content="https://avanzato.com.br/og-image.jpg" />
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="./favicon.svg" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="./site.webmanifest" />
|
||||
<link rel="icon" type="image/png" href="https://avanzato.com.br/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/png" href="https://avanzato.com.br/favicon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<!-- Theme Color -->
|
||||
<meta name="theme-color" content="#cbf400" />
|
||||
@@ -208,8 +239,8 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module" crossorigin src="./assets/index-C1GgsDvQ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-Cg4oF5jQ.css">
|
||||
<script type="module" crossorigin src="/assets/index-DLMsdH5y.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-8ILJAHKZ.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
Vendored
+10
-23
@@ -1,26 +1,13 @@
|
||||
# =============================================================================
|
||||
# ROBOTS.TXT - VERSAO HOMOLOGACAO/DESENVOLVIMENTO
|
||||
# =============================================================================
|
||||
# Este arquivo BLOQUEIA TODOS os crawlers.
|
||||
#
|
||||
# Para PRODUCAO (avanzato.com.br), substitua por:
|
||||
# User-agent: *
|
||||
# Allow: /
|
||||
# Sitemap: https://avanzato.com.br/sitemap.xml
|
||||
# =============================================================================
|
||||
# ============================================================================
|
||||
# AVANZATO TECNOLOGIA - ROBOTS.TXT (GENERICO)
|
||||
# ============================================================================
|
||||
# O controle real de indexacao e feito pelo <meta name="robots"> no HTML:
|
||||
# - Producao (avanzato.com.br): index, follow
|
||||
# - Homologacao (hml.avanzato.com.br): noindex, nofollow
|
||||
# - Desenvolvimento (localhost): noindex, nofollow
|
||||
# ============================================================================
|
||||
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
Allow: /
|
||||
|
||||
# Bloqueio explicito de todos os bots
|
||||
User-agent: Googlebot
|
||||
Disallow: /
|
||||
|
||||
User-agent: Bingbot
|
||||
Disallow: /
|
||||
|
||||
User-agent: Slurp
|
||||
Disallow: /
|
||||
|
||||
# Sitemap comentado (descomente em producao)
|
||||
# Sitemap: https://avanzato.com.br/sitemap.xml
|
||||
Sitemap: https://avanzato.com.br/sitemap.xml
|
||||
|
||||
Vendored
+116
@@ -165,4 +165,120 @@
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
|
||||
<!-- SEO Local - Onde Atuamos -->
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/onde-atuamos</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-guarulhos</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-mogi-das-cruzes</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-ipiranga</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-vila-formosa</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-cerqueira-cesar</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-santana</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-bras</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-ribeirao-preto</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
|
||||
<!-- Nichos adicionais -->
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-industria</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-portaria-remota</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-clinicas</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-imobiliarias</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-recrutamento-selecao</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
|
||||
<!-- Avanzato Tools -->
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/spf-checker</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/dkim-checker</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/dmarc-checker</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/mx-lookup</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/ssl-checker</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/email-security-score</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/microsoft-365-analyzer</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
|
||||
+45
-12
@@ -5,6 +5,36 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
||||
<!-- DETECAO DE AMBIENTE - Inline (executa antes de tudo) -->
|
||||
<script>
|
||||
(function() {
|
||||
var h = location.hostname;
|
||||
var IS_PROD = h === 'avanzato.com.br' || h === 'www.avanzato.com.br';
|
||||
|
||||
// Torna disponivel para outros scripts
|
||||
window.__IS_PROD__ = IS_PROD;
|
||||
|
||||
// Se NAO for producao, desativa tracking globalmente
|
||||
if (!IS_PROD) {
|
||||
window.__AVANZATO_ENV__ = h === 'hml.avanzato.com.br' ? 'homologation' : 'development';
|
||||
|
||||
// Bloqueia gtag
|
||||
window.dataLayer = { push: function(){} };
|
||||
window.gtag = function(){};
|
||||
|
||||
// Bloqueia fbq (Facebook Pixel)
|
||||
window.fbq = function(){};
|
||||
|
||||
// Bloqueia Mautic
|
||||
window.MauticSDKLoaded = true;
|
||||
|
||||
console.log('[Avanzato] Ambiente: ' + window.__AVANZATO_ENV__ + ' | Tracking bloqueado');
|
||||
} else {
|
||||
window.__AVANZATO_ENV__ = 'production';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Google tag (gtag.js) - Ads + Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-16714813566"></script>
|
||||
<script>
|
||||
@@ -49,10 +79,6 @@
|
||||
fbq('init', '1223271852059785');
|
||||
fbq('track', 'PageView');
|
||||
</script>
|
||||
<noscript>
|
||||
<img height="1" width="1" style="display:none"
|
||||
src="https://www.facebook.com/tr?id=1223271852059785&ev=PageView&noscript=1"/>
|
||||
</noscript>
|
||||
|
||||
<!-- Mautic Tracking -->
|
||||
<script>
|
||||
@@ -74,6 +100,7 @@
|
||||
<title>Avanzato Tecnologia | TI Gerenciada, Cloud e Seguranca em Guarulhos/SP</title>
|
||||
<meta name="title" content="Avanzato Tecnologia | TI Gerenciada, Cloud e Seguranca em Guarulhos/SP" />
|
||||
<meta name="description" content="Solucoes em TI Gerenciada, Cloud Computing e Seguranca da Informacao. +23 anos de experiencia, +500 clientes atendidos. Suporte 24/7 em Guarulhos e regiao." />
|
||||
<meta name="app-version" content="1.0.0" />
|
||||
<meta name="keywords" content="TI Gerenciada, Cloud Computing, Seguranca da Informacao, Suporte Tecnico, Tecnologia da Informacao, Guarulhos, Sao Paulo, Infraestrutura de TI, Backup, Firewall, Consultoria TI" />
|
||||
<meta name="author" content="Avanzato Tecnologia" />
|
||||
<meta name="robots" content="index, follow" id="meta-robots" />
|
||||
@@ -82,9 +109,9 @@
|
||||
<!-- BLOQUEAR INDEXACAO EM HOMOLOGACAO -->
|
||||
<script>
|
||||
(function() {
|
||||
var hostname = window.location.hostname;
|
||||
if (hostname !== 'avanzato.com.br') {
|
||||
// Homologacao: bloquear indexacao
|
||||
// Usa IS_PROD ja definido no inicio do documento
|
||||
if (!window.__IS_PROD__) {
|
||||
// Homologacao/Desenvolvimento: bloquear indexacao
|
||||
document.getElementById('meta-robots').setAttribute('content', 'noindex, nofollow');
|
||||
document.getElementById('meta-googlebot').setAttribute('content', 'noindex, nofollow');
|
||||
// Adicionar tambem X-Robots-Tag via meta http-equiv
|
||||
@@ -92,7 +119,9 @@
|
||||
meta.httpEquiv = 'X-Robots-Tag';
|
||||
meta.content = 'noindex, nofollow';
|
||||
document.head.appendChild(meta);
|
||||
console.log('%c[SEO] Homologacao detectada - indexacao BLOQUEADA (noindex, nofollow)', 'background:#dc2626;color:#fff;font-weight:bold;padding:2px 6px;border-radius:3px');
|
||||
console.log('%c[SEO] ' + (window.__AVANZATO_ENV__ || 'nao-producao') + ' - indexacao BLOQUEADA', 'background:#dc2626;color:#fff;font-weight:bold;padding:2px 6px;border-radius:3px');
|
||||
} else {
|
||||
console.log('%c[SEO] Producao - indexacao PERMITIDA (index, follow)', 'background:#16a34a;color:#fff;font-weight:bold;padding:2px 6px;border-radius:3px');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -117,10 +146,8 @@
|
||||
<meta property="twitter:image" content="https://avanzato.com.br/og-image.jpg" />
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="icon" type="image/png" href="https://avanzato.com.br/favicon.png" />
|
||||
<link rel="shortcut icon" type="image/png" href="https://avanzato.com.br/favicon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<!-- Theme Color -->
|
||||
@@ -217,6 +244,12 @@
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
|
||||
<!-- Facebook Pixel noscript (moved from head to body to fix parse5 warning) -->
|
||||
<noscript>
|
||||
<img height="1" width="1" style="display:none"
|
||||
src="https://www.facebook.com/tr?id=1223271852059785&ev=PageView&noscript=1"/>
|
||||
</noscript>
|
||||
|
||||
<!-- Noscript content for SEO -->
|
||||
<noscript>
|
||||
<div style="padding: 20px; text-align: center; font-family: Urbanist, Arial, sans-serif; background: #0e0e0e; color: white; min-height: 100vh;">
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Release PROD
|
||||
|
||||
Data: sáb 30 mai 2026 19:30:29 -03
|
||||
|
||||
Ambiente:
|
||||
PROD
|
||||
|
||||
Descrição:
|
||||
Atualização e correção de diretorios
|
||||
|
||||
Branch:
|
||||
main
|
||||
|
||||
Commit:
|
||||
bb73783
|
||||
|
||||
Último commit develop:
|
||||
146652a
|
||||
|
||||
Arquivos alterados no último commit:
|
||||
|
||||
|
||||
Status:
|
||||
Preparado para publicação em produção
|
||||
@@ -0,0 +1,24 @@
|
||||
# Release PROD
|
||||
|
||||
Data: sáb 30 mai 2026 20:36:09 -03
|
||||
|
||||
Ambiente:
|
||||
PROD
|
||||
|
||||
Descrição:
|
||||
Favivon - Correção
|
||||
|
||||
Branch:
|
||||
main
|
||||
|
||||
Commit:
|
||||
5b729b23
|
||||
|
||||
Último commit develop:
|
||||
643f628a
|
||||
|
||||
Arquivos alterados no último commit:
|
||||
|
||||
|
||||
Status:
|
||||
Preparado para publicação em produção
|
||||
@@ -0,0 +1,24 @@
|
||||
# Release PROD
|
||||
|
||||
Data: sáb 30 mai 2026 23:15:11 -03
|
||||
|
||||
Ambiente:
|
||||
PROD
|
||||
|
||||
Descrição:
|
||||
Favivon - Correção
|
||||
|
||||
Branch:
|
||||
main
|
||||
|
||||
Commit:
|
||||
e9f47c5b
|
||||
|
||||
Último commit develop:
|
||||
2dc62486
|
||||
|
||||
Arquivos alterados no último commit:
|
||||
|
||||
|
||||
Status:
|
||||
Preparado para publicação em produção
|
||||
@@ -1,230 +0,0 @@
|
||||
# Release HML
|
||||
|
||||
Data: sáb 30 mai 2026 23:52:07 -03
|
||||
|
||||
Ambiente:
|
||||
HML
|
||||
|
||||
Descrição:
|
||||
Revisão logo
|
||||
|
||||
Branch:
|
||||
develop
|
||||
|
||||
Arquivos alterados antes do commit:
|
||||
D DEPLOY-DOCKER.md
|
||||
D GUIA-NGINX.md
|
||||
M index.html
|
||||
D infra/releases/2026-05-30_19-57-59_HML.md
|
||||
D infra/releases/2026-05-30_23-14-47_HML.md
|
||||
D nginx-docker.conf
|
||||
M node_modules/.package-lock.json
|
||||
M node_modules/.vite/deps/@radix-ui_react-dialog.js
|
||||
M node_modules/.vite/deps/@radix-ui_react-label.js
|
||||
M node_modules/.vite/deps/@radix-ui_react-radio-group.js
|
||||
M node_modules/.vite/deps/@radix-ui_react-select.js
|
||||
M node_modules/.vite/deps/_metadata.json
|
||||
D node_modules/.vite/deps/chunk-D4MYACAJ.js
|
||||
D node_modules/.vite/deps/chunk-D4MYACAJ.js.map
|
||||
D node_modules/.vite/deps/chunk-KKEH7IGY.js
|
||||
D node_modules/.vite/deps/chunk-KKEH7IGY.js.map
|
||||
D node_modules/.vite/deps/chunk-QF76HVLY.js
|
||||
D node_modules/.vite/deps/chunk-QF76HVLY.js.map
|
||||
D node_modules/.vite/deps/chunk-X34PLQQA.js
|
||||
D node_modules/.vite/deps/chunk-X34PLQQA.js.map
|
||||
D node_modules/.vite/deps/react-helmet-async.js
|
||||
D node_modules/.vite/deps/react-helmet-async.js.map
|
||||
D node_modules/invariant/CHANGELOG.md
|
||||
D node_modules/invariant/LICENSE
|
||||
D node_modules/invariant/README.md
|
||||
D node_modules/invariant/browser.js
|
||||
D node_modules/invariant/invariant.js
|
||||
D node_modules/invariant/invariant.js.flow
|
||||
D node_modules/invariant/package.json
|
||||
D node_modules/react-fast-compare/LICENSE
|
||||
D node_modules/react-fast-compare/README.md
|
||||
D node_modules/react-fast-compare/index.d.ts
|
||||
D node_modules/react-fast-compare/index.js
|
||||
D node_modules/react-fast-compare/package.json
|
||||
D node_modules/react-helmet-async/LICENSE
|
||||
D node_modules/react-helmet-async/README.md
|
||||
D node_modules/react-helmet-async/lib/Dispatcher.d.ts
|
||||
D node_modules/react-helmet-async/lib/HelmetData.d.ts
|
||||
D node_modules/react-helmet-async/lib/Provider.d.ts
|
||||
D node_modules/react-helmet-async/lib/React19Dispatcher.d.ts
|
||||
D node_modules/react-helmet-async/lib/client.d.ts
|
||||
D node_modules/react-helmet-async/lib/constants.d.ts
|
||||
D node_modules/react-helmet-async/lib/index.d.ts
|
||||
D node_modules/react-helmet-async/lib/index.esm.js
|
||||
D node_modules/react-helmet-async/lib/index.js
|
||||
D node_modules/react-helmet-async/lib/reactVersion.d.ts
|
||||
D node_modules/react-helmet-async/lib/server.d.ts
|
||||
D node_modules/react-helmet-async/lib/types.d.ts
|
||||
D node_modules/react-helmet-async/lib/utils.d.ts
|
||||
D node_modules/react-helmet-async/package.json
|
||||
D node_modules/shallowequal/LICENSE
|
||||
D node_modules/shallowequal/README.md
|
||||
D node_modules/shallowequal/index.js
|
||||
D node_modules/shallowequal/index.js.flow
|
||||
D node_modules/shallowequal/index.original.js
|
||||
D node_modules/shallowequal/package.json
|
||||
M package-lock.json
|
||||
M package.json
|
||||
M public/sitemap.xml
|
||||
D public/version.json
|
||||
D scripts/build-static-routes.sh
|
||||
M scripts/dev.sh
|
||||
M src/App.tsx
|
||||
D src/components/SEO.tsx
|
||||
M src/config/site.ts
|
||||
M src/main.tsx
|
||||
M src/pages/Blog.tsx
|
||||
M src/pages/Cases.tsx
|
||||
M src/pages/Condominios.tsx
|
||||
M src/pages/Privacidade.tsx
|
||||
M src/pages/Sobre.tsx
|
||||
M src/pages/Tecnologias.tsx
|
||||
M src/pages/Termos.tsx
|
||||
M src/pages/nichos/Advocacia.tsx
|
||||
M src/pages/nichos/BackupCorporativo.tsx
|
||||
M src/pages/nichos/Contabilidade.tsx
|
||||
M src/pages/nichos/FirewallPfSense.tsx
|
||||
M src/pages/nichos/Monitoramento.tsx
|
||||
M src/pages/nichos/VoipEmpresarial.tsx
|
||||
M src/pages/services/Backup.tsx
|
||||
M src/pages/services/CloudComputing.tsx
|
||||
M src/pages/services/Consultoria.tsx
|
||||
M src/pages/services/Noc.tsx
|
||||
M src/pages/services/Pabx.tsx
|
||||
M src/pages/services/Redes.tsx
|
||||
M src/pages/services/Seguranca.tsx
|
||||
M src/pages/services/Servidores.tsx
|
||||
M src/pages/services/Suporte.tsx
|
||||
M src/pages/services/TiGerenciada.tsx
|
||||
M src/pages/tech/Cisco.tsx
|
||||
M src/pages/tech/Linux.tsx
|
||||
M src/pages/tech/PfSense.tsx
|
||||
M src/pages/tech/Proxmox.tsx
|
||||
M src/pages/tech/Sharepoint.tsx
|
||||
M src/pages/tech/Vmware.tsx
|
||||
M src/pages/tech/WindowsServer.tsx
|
||||
M src/sections/Footer.tsx
|
||||
M src/sections/Navigation.tsx
|
||||
M vite.config.ts
|
||||
?? .github/
|
||||
?? .gitignore
|
||||
?? infra/releases/2026-05-30_23-52-07_HML.md
|
||||
|
||||
Status:
|
||||
Preparado para publicação
|
||||
|
||||
Commit:
|
||||
b3b8917a
|
||||
|
||||
Arquivos no commit:
|
||||
.github/workflows/README-CI-CD.md
|
||||
.github/workflows/deploy.yml
|
||||
.gitignore
|
||||
DEPLOY-DOCKER.md
|
||||
GUIA-NGINX.md
|
||||
index.html
|
||||
infra/releases/2026-05-30_19-57-59_HML.md
|
||||
infra/releases/2026-05-30_23-14-47_HML.md
|
||||
infra/releases/2026-05-30_23-52-07_HML.md
|
||||
nginx-docker.conf
|
||||
node_modules/.package-lock.json
|
||||
node_modules/.vite/deps/@radix-ui_react-dialog.js
|
||||
node_modules/.vite/deps/@radix-ui_react-label.js
|
||||
node_modules/.vite/deps/@radix-ui_react-radio-group.js
|
||||
node_modules/.vite/deps/@radix-ui_react-select.js
|
||||
node_modules/.vite/deps/_metadata.json
|
||||
node_modules/.vite/deps/chunk-D4MYACAJ.js
|
||||
node_modules/.vite/deps/chunk-D4MYACAJ.js.map
|
||||
node_modules/.vite/deps/chunk-KKEH7IGY.js
|
||||
node_modules/.vite/deps/chunk-KKEH7IGY.js.map
|
||||
node_modules/.vite/deps/chunk-QF76HVLY.js
|
||||
node_modules/.vite/deps/chunk-QF76HVLY.js.map
|
||||
node_modules/.vite/deps/chunk-X34PLQQA.js
|
||||
node_modules/.vite/deps/chunk-X34PLQQA.js.map
|
||||
node_modules/.vite/deps/react-helmet-async.js
|
||||
node_modules/.vite/deps/react-helmet-async.js.map
|
||||
node_modules/invariant/CHANGELOG.md
|
||||
node_modules/invariant/LICENSE
|
||||
node_modules/invariant/README.md
|
||||
node_modules/invariant/browser.js
|
||||
node_modules/invariant/invariant.js
|
||||
node_modules/invariant/invariant.js.flow
|
||||
node_modules/invariant/package.json
|
||||
node_modules/react-fast-compare/LICENSE
|
||||
node_modules/react-fast-compare/README.md
|
||||
node_modules/react-fast-compare/index.d.ts
|
||||
node_modules/react-fast-compare/index.js
|
||||
node_modules/react-fast-compare/package.json
|
||||
node_modules/react-helmet-async/LICENSE
|
||||
node_modules/react-helmet-async/README.md
|
||||
node_modules/react-helmet-async/lib/Dispatcher.d.ts
|
||||
node_modules/react-helmet-async/lib/HelmetData.d.ts
|
||||
node_modules/react-helmet-async/lib/Provider.d.ts
|
||||
node_modules/react-helmet-async/lib/React19Dispatcher.d.ts
|
||||
node_modules/react-helmet-async/lib/client.d.ts
|
||||
node_modules/react-helmet-async/lib/constants.d.ts
|
||||
node_modules/react-helmet-async/lib/index.d.ts
|
||||
node_modules/react-helmet-async/lib/index.esm.js
|
||||
node_modules/react-helmet-async/lib/index.js
|
||||
node_modules/react-helmet-async/lib/reactVersion.d.ts
|
||||
node_modules/react-helmet-async/lib/server.d.ts
|
||||
node_modules/react-helmet-async/lib/types.d.ts
|
||||
node_modules/react-helmet-async/lib/utils.d.ts
|
||||
node_modules/react-helmet-async/package.json
|
||||
node_modules/shallowequal/LICENSE
|
||||
node_modules/shallowequal/README.md
|
||||
node_modules/shallowequal/index.js
|
||||
node_modules/shallowequal/index.js.flow
|
||||
node_modules/shallowequal/index.original.js
|
||||
node_modules/shallowequal/package.json
|
||||
package-lock.json
|
||||
package.json
|
||||
public/sitemap.xml
|
||||
public/version.json
|
||||
scripts/build-static-routes.sh
|
||||
scripts/dev.sh
|
||||
src/App.tsx
|
||||
src/components/SEO.tsx
|
||||
src/config/site.ts
|
||||
src/main.tsx
|
||||
src/pages/Blog.tsx
|
||||
src/pages/Cases.tsx
|
||||
src/pages/Condominios.tsx
|
||||
src/pages/Privacidade.tsx
|
||||
src/pages/Sobre.tsx
|
||||
src/pages/Tecnologias.tsx
|
||||
src/pages/Termos.tsx
|
||||
src/pages/nichos/Advocacia.tsx
|
||||
src/pages/nichos/BackupCorporativo.tsx
|
||||
src/pages/nichos/Contabilidade.tsx
|
||||
src/pages/nichos/FirewallPfSense.tsx
|
||||
src/pages/nichos/Monitoramento.tsx
|
||||
src/pages/nichos/VoipEmpresarial.tsx
|
||||
src/pages/services/Backup.tsx
|
||||
src/pages/services/CloudComputing.tsx
|
||||
src/pages/services/Consultoria.tsx
|
||||
src/pages/services/Noc.tsx
|
||||
src/pages/services/Pabx.tsx
|
||||
src/pages/services/Redes.tsx
|
||||
src/pages/services/Seguranca.tsx
|
||||
src/pages/services/Servidores.tsx
|
||||
src/pages/services/Suporte.tsx
|
||||
src/pages/services/TiGerenciada.tsx
|
||||
src/pages/tech/Cisco.tsx
|
||||
src/pages/tech/Linux.tsx
|
||||
src/pages/tech/PfSense.tsx
|
||||
src/pages/tech/Proxmox.tsx
|
||||
src/pages/tech/Sharepoint.tsx
|
||||
src/pages/tech/Vmware.tsx
|
||||
src/pages/tech/WindowsServer.tsx
|
||||
src/sections/Footer.tsx
|
||||
src/sections/Navigation.tsx
|
||||
vite.config.ts
|
||||
|
||||
Status final:
|
||||
Publicado no Gitea / aguardando pipeline HML
|
||||
@@ -0,0 +1,24 @@
|
||||
# Release PROD
|
||||
|
||||
Data: dom 31 mai 2026 00:03:02 -03
|
||||
|
||||
Ambiente:
|
||||
PROD
|
||||
|
||||
Descrição:
|
||||
Revisão Logo, Favicon, Robots
|
||||
|
||||
Branch:
|
||||
main
|
||||
|
||||
Commit:
|
||||
b284c4d2
|
||||
|
||||
Último commit develop:
|
||||
3d36e981
|
||||
|
||||
Arquivos alterados no último commit:
|
||||
|
||||
|
||||
Status:
|
||||
Preparado para publicação em produção
|
||||
@@ -0,0 +1,24 @@
|
||||
# Release PROD
|
||||
|
||||
Data: dom 31 mai 2026 00:29:03 -03
|
||||
|
||||
Ambiente:
|
||||
PROD
|
||||
|
||||
Descrição:
|
||||
Revisão Logo, Favicon, Robots
|
||||
|
||||
Branch:
|
||||
main
|
||||
|
||||
Commit:
|
||||
bfb02286
|
||||
|
||||
Último commit develop:
|
||||
be22792b
|
||||
|
||||
Arquivos alterados no último commit:
|
||||
|
||||
|
||||
Status:
|
||||
Preparado para publicação em produção
|
||||
@@ -0,0 +1,24 @@
|
||||
# Release PROD
|
||||
|
||||
Data: dom 31 mai 2026 00:58:08 -03
|
||||
|
||||
Ambiente:
|
||||
PROD
|
||||
|
||||
Descrição:
|
||||
Revisão Logo, Favicon, Robots
|
||||
|
||||
Branch:
|
||||
main
|
||||
|
||||
Commit:
|
||||
9885f59b
|
||||
|
||||
Último commit develop:
|
||||
d79c9c48
|
||||
|
||||
Arquivos alterados no último commit:
|
||||
|
||||
|
||||
Status:
|
||||
Preparado para publicação em produção
|
||||
@@ -0,0 +1,24 @@
|
||||
# Release PROD
|
||||
|
||||
Data: seg 1 jun 2026 19:44:45 -03
|
||||
|
||||
Ambiente:
|
||||
PROD
|
||||
|
||||
Descrição:
|
||||
Paginas e SEO master
|
||||
|
||||
Branch:
|
||||
main
|
||||
|
||||
Commit:
|
||||
4f15851f
|
||||
|
||||
Último commit develop:
|
||||
15ce64e7
|
||||
|
||||
Arquivos alterados no último commit:
|
||||
|
||||
|
||||
Status:
|
||||
Preparado para publicação em produção
|
||||
@@ -0,0 +1,24 @@
|
||||
# Release PROD
|
||||
|
||||
Data: seg 1 jun 2026 19:56:50 -03
|
||||
|
||||
Ambiente:
|
||||
PROD
|
||||
|
||||
Descrição:
|
||||
Paginas e SEO master
|
||||
|
||||
Branch:
|
||||
main
|
||||
|
||||
Commit:
|
||||
3eb41133
|
||||
|
||||
Último commit develop:
|
||||
e8f15d63
|
||||
|
||||
Arquivos alterados no último commit:
|
||||
|
||||
|
||||
Status:
|
||||
Preparado para publicação em produção
|
||||
@@ -0,0 +1,24 @@
|
||||
# Release PROD
|
||||
|
||||
Data: seg 1 jun 2026 20:28:34 -03
|
||||
|
||||
Ambiente:
|
||||
PROD
|
||||
|
||||
Descrição:
|
||||
Paginas e SEO master
|
||||
|
||||
Branch:
|
||||
main
|
||||
|
||||
Commit:
|
||||
52f86cbb
|
||||
|
||||
Último commit develop:
|
||||
e8f15d63
|
||||
|
||||
Arquivos alterados no último commit:
|
||||
infra/releases/2026-06-01_19-56-50_PROD.md
|
||||
|
||||
Status:
|
||||
Preparado para publicação em produção
|
||||
@@ -0,0 +1,24 @@
|
||||
# Release PROD
|
||||
|
||||
Data: ter 2 jun 2026 14:21:07 -03
|
||||
|
||||
Ambiente:
|
||||
PROD
|
||||
|
||||
Descrição:
|
||||
Paginas e SEO master, correção javascript google
|
||||
|
||||
Branch:
|
||||
main
|
||||
|
||||
Commit:
|
||||
35e5727e
|
||||
|
||||
Último commit develop:
|
||||
96563b30
|
||||
|
||||
Arquivos alterados no último commit:
|
||||
|
||||
|
||||
Status:
|
||||
Preparado para publicação em produção
|
||||
@@ -0,0 +1,57 @@
|
||||
# Release HML
|
||||
|
||||
Data: qua 3 jun 2026 22:07:59 -03
|
||||
|
||||
Ambiente:
|
||||
HML
|
||||
|
||||
Descrição:
|
||||
Aplicativos de Atrair Leads
|
||||
|
||||
Branch:
|
||||
develop
|
||||
|
||||
Arquivos alterados antes do commit:
|
||||
M dist/index.html
|
||||
D infra/releases/2026-06-02_22-34-39_HML.md
|
||||
M src/App.tsx
|
||||
M src/sections/About.tsx
|
||||
M src/sections/CondominiosCTA.tsx
|
||||
M src/sections/Hero.tsx
|
||||
M src/sections/Navigation.tsx
|
||||
M src/sections/Services.tsx
|
||||
?? infra/releases/2026-06-03_22-07-58_HML.md
|
||||
?? nginx/
|
||||
?? public/about-image.webp
|
||||
?? public/hero-dashboard.webp
|
||||
?? public/service-cloud.webp
|
||||
?? public/service-security.webp
|
||||
?? public/service-support.webp
|
||||
?? public/service-ti.webp
|
||||
|
||||
Status:
|
||||
Preparado para publicação
|
||||
|
||||
Commit:
|
||||
fdfb4606
|
||||
|
||||
Arquivos no commit:
|
||||
dist/index.html
|
||||
infra/releases/2026-06-02_22-34-39_HML.md
|
||||
infra/releases/2026-06-03_22-07-58_HML.md
|
||||
nginx/avanzato.conf
|
||||
public/about-image.webp
|
||||
public/hero-dashboard.webp
|
||||
public/service-cloud.webp
|
||||
public/service-security.webp
|
||||
public/service-support.webp
|
||||
public/service-ti.webp
|
||||
src/App.tsx
|
||||
src/sections/About.tsx
|
||||
src/sections/CondominiosCTA.tsx
|
||||
src/sections/Hero.tsx
|
||||
src/sections/Navigation.tsx
|
||||
src/sections/Services.tsx
|
||||
|
||||
Status final:
|
||||
Publicado no Gitea / aguardando pipeline HML
|
||||
@@ -0,0 +1,24 @@
|
||||
# Release PROD
|
||||
|
||||
Data: qua 3 jun 2026 22:19:56 -03
|
||||
|
||||
Ambiente:
|
||||
PROD
|
||||
|
||||
Descrição:
|
||||
Aplicativos de Atrair Leads
|
||||
|
||||
Branch:
|
||||
main
|
||||
|
||||
Commit:
|
||||
667447c3
|
||||
|
||||
Último commit develop:
|
||||
e3f35d80
|
||||
|
||||
Arquivos alterados no último commit:
|
||||
|
||||
|
||||
Status:
|
||||
Preparado para publicação em produção
|
||||
@@ -0,0 +1,30 @@
|
||||
# ============================================================================
|
||||
# AVANZATO - Configuracao Nginx para Docker
|
||||
# ============================================================================
|
||||
# Coloque este arquivo em: /etc/nginx/conf.d/default.conf
|
||||
# dentro do container Docker
|
||||
# ============================================================================
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# === CORRECAO CRITICA: SPA React ===
|
||||
# Redireciona TODAS as rotas para index.html
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Cache para assets estaticos
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
|
||||
expires 6M;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Seguranca
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
# ============================================================================
|
||||
# Configuracao Nginx - Avanzato Tecnologia
|
||||
# ============================================================================
|
||||
# Inclui: CSP, HSTS, X-Frame-Options, cache headers, gzip, brotli
|
||||
# Copiar para: /etc/nginx/sites-available/avanzato.com.br
|
||||
# Ativar: ln -s /etc/nginx/sites-available/avanzato.com.br /etc/nginx/sites-enabled/
|
||||
# ============================================================================
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name avanzato.com.br www.avanzato.com.br;
|
||||
|
||||
# Redirecionar HTTP para HTTPS
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name avanzato.com.br www.avanzato.com.br;
|
||||
|
||||
# SSL (certbot gerencia automaticamente)
|
||||
ssl_certificate /etc/letsencrypt/live/avanzato.com.br/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/avanzato.com.br/privkey.pem;
|
||||
ssl_trusted_certificate /etc/letsencrypt/live/avanzato.com.br/chain.pem;
|
||||
|
||||
# SSL hardening
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_tickets off;
|
||||
|
||||
# HSTS - HTTP Strict Transport Security
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
|
||||
# Root do projeto
|
||||
root /var/www/avanzato/dist;
|
||||
index index.html;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types
|
||||
text/plain
|
||||
text/css
|
||||
text/xml
|
||||
text/javascript
|
||||
application/json
|
||||
application/javascript
|
||||
application/xml+rss
|
||||
application/atom+xml
|
||||
image/svg+xml
|
||||
font/woff2;
|
||||
|
||||
# ============================================================================
|
||||
# CACHE HEADERS - Assets estaticos
|
||||
# ============================================================================
|
||||
|
||||
# Imagens, fontes, videos - 1 mes
|
||||
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|woff|woff2|ttf|eot|mp4|webm)$ {
|
||||
expires 1M;
|
||||
add_header Cache-Control "public, immutable" always;
|
||||
add_header Vary "Accept-Encoding" always;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# CSS e JS - 1 ano (tem hash no nome)
|
||||
location ~* \.(css|js)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable" always;
|
||||
add_header Vary "Accept-Encoding" always;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# HTML - nao cachear
|
||||
location ~* \.html$ {
|
||||
expires -1;
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
|
||||
add_header Pragma "no-cache" always;
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# SECURITY HEADERS
|
||||
# ============================================================================
|
||||
|
||||
# Content Security Policy
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://connect.facebook.net https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://dns.google https://crt.sh https://mkt.avanzato.com.br https://www.google-analytics.com https://www.facebook.com; frame-src 'self' https://mkt.avanzato.com.br; media-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests;" always;
|
||||
|
||||
# X-Frame-Options - protecao contra clickjacking
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
|
||||
# X-Content-Type-Options
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
|
||||
# Referrer Policy
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# Permissions Policy
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=()" always;
|
||||
|
||||
# Cross-Origin Opener Policy
|
||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||
|
||||
# Cross-Origin Resource Policy
|
||||
add_header Cross-Origin-Resource-Policy "cross-origin" always;
|
||||
|
||||
# ============================================================================
|
||||
# REACT ROUTER - SPA fallback
|
||||
# ============================================================================
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
|
||||
}
|
||||
|
||||
# Sitemap e robots - acessiveis
|
||||
location = /sitemap.xml {
|
||||
try_files $uri =404;
|
||||
add_header Content-Type "application/xml" always;
|
||||
}
|
||||
|
||||
location = /robots.txt {
|
||||
try_files $uri =404;
|
||||
add_header Content-Type "text/plain" always;
|
||||
}
|
||||
}
|
||||
+35
@@ -5904,6 +5904,15 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/invariant/-/invariant-2.2.4.tgz",
|
||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@@ -6828,6 +6837,26 @@
|
||||
"react": "^19.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-fast-compare": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
|
||||
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-helmet-async": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/react-helmet-async/-/react-helmet-async-3.0.0.tgz",
|
||||
"integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"invariant": "^2.2.4",
|
||||
"react-fast-compare": "^3.2.2",
|
||||
"shallowequal": "^1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.70.0",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.70.0.tgz",
|
||||
@@ -7291,6 +7320,12 @@
|
||||
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shallowequal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+3
-3
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
import {
|
||||
Presence
|
||||
} from "./chunk-3Q7Z2X7T.js";
|
||||
} from "./chunk-INCCO7S4.js";
|
||||
import {
|
||||
Combination_default,
|
||||
DismissableLayer,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
Portal,
|
||||
hideOthers,
|
||||
useFocusGuards
|
||||
} from "./chunk-26QBR6Z3.js";
|
||||
} from "./chunk-LIWCVBQK.js";
|
||||
import {
|
||||
Primitive,
|
||||
composeEventHandlers,
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
createContextScope,
|
||||
useControllableState,
|
||||
useId
|
||||
} from "./chunk-2ZZW3GAI.js";
|
||||
} from "./chunk-MOFDO34C.js";
|
||||
import {
|
||||
composeRefs,
|
||||
useComposedRefs
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
"use client";
|
||||
import {
|
||||
Presence
|
||||
} from "./chunk-3Q7Z2X7T.js";
|
||||
} from "./chunk-INCCO7S4.js";
|
||||
import {
|
||||
createCollection,
|
||||
useDirection,
|
||||
usePrevious,
|
||||
useSize
|
||||
} from "./chunk-5BI4FO63.js";
|
||||
} from "./chunk-L2OGOWTU.js";
|
||||
import {
|
||||
Primitive,
|
||||
composeEventHandlers,
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
useCallbackRef,
|
||||
useControllableState,
|
||||
useId
|
||||
} from "./chunk-2ZZW3GAI.js";
|
||||
} from "./chunk-MOFDO34C.js";
|
||||
import {
|
||||
useComposedRefs
|
||||
} from "./chunk-2VUH7NEY.js";
|
||||
|
||||
+8
-8
@@ -1,10 +1,4 @@
|
||||
"use client";
|
||||
import {
|
||||
createCollection,
|
||||
useDirection,
|
||||
usePrevious,
|
||||
useSize
|
||||
} from "./chunk-5BI4FO63.js";
|
||||
import {
|
||||
Combination_default,
|
||||
DismissableLayer,
|
||||
@@ -12,7 +6,13 @@ import {
|
||||
Portal,
|
||||
hideOthers,
|
||||
useFocusGuards
|
||||
} from "./chunk-26QBR6Z3.js";
|
||||
} from "./chunk-LIWCVBQK.js";
|
||||
import {
|
||||
createCollection,
|
||||
useDirection,
|
||||
usePrevious,
|
||||
useSize
|
||||
} from "./chunk-L2OGOWTU.js";
|
||||
import {
|
||||
Primitive,
|
||||
composeEventHandlers,
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
useControllableState,
|
||||
useId,
|
||||
useLayoutEffect2
|
||||
} from "./chunk-2ZZW3GAI.js";
|
||||
} from "./chunk-MOFDO34C.js";
|
||||
import {
|
||||
composeRefs,
|
||||
useComposedRefs
|
||||
|
||||
+34
-28
@@ -1,109 +1,115 @@
|
||||
{
|
||||
"hash": "baa9bd3c",
|
||||
"hash": "bef2a67d",
|
||||
"configHash": "eccf13b4",
|
||||
"lockfileHash": "f25e87f1",
|
||||
"browserHash": "4d672fc5",
|
||||
"lockfileHash": "c4c8a51c",
|
||||
"browserHash": "e7109be1",
|
||||
"optimized": {
|
||||
"react": {
|
||||
"src": "../../react/index.js",
|
||||
"file": "react.js",
|
||||
"fileHash": "2adf36c9",
|
||||
"fileHash": "dd97ceba",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react-dom": {
|
||||
"src": "../../react-dom/index.js",
|
||||
"file": "react-dom.js",
|
||||
"fileHash": "91ce8077",
|
||||
"fileHash": "8becf687",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react/jsx-dev-runtime": {
|
||||
"src": "../../react/jsx-dev-runtime.js",
|
||||
"file": "react_jsx-dev-runtime.js",
|
||||
"fileHash": "051319e1",
|
||||
"fileHash": "2c7217d2",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react/jsx-runtime": {
|
||||
"src": "../../react/jsx-runtime.js",
|
||||
"file": "react_jsx-runtime.js",
|
||||
"fileHash": "1945fc54",
|
||||
"fileHash": "e28ec614",
|
||||
"needsInterop": true
|
||||
},
|
||||
"@radix-ui/react-dialog": {
|
||||
"src": "../../@radix-ui/react-dialog/dist/index.mjs",
|
||||
"file": "@radix-ui_react-dialog.js",
|
||||
"fileHash": "8be1d102",
|
||||
"fileHash": "22041c72",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@radix-ui/react-label": {
|
||||
"src": "../../@radix-ui/react-label/dist/index.mjs",
|
||||
"file": "@radix-ui_react-label.js",
|
||||
"fileHash": "7c8329ad",
|
||||
"fileHash": "d33ac0ed",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@radix-ui/react-radio-group": {
|
||||
"src": "../../@radix-ui/react-radio-group/dist/index.mjs",
|
||||
"file": "@radix-ui_react-radio-group.js",
|
||||
"fileHash": "c1cf0673",
|
||||
"fileHash": "2be9d4b7",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@radix-ui/react-select": {
|
||||
"src": "../../@radix-ui/react-select/dist/index.mjs",
|
||||
"file": "@radix-ui_react-select.js",
|
||||
"fileHash": "e8a77682",
|
||||
"fileHash": "51dd47da",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@radix-ui/react-slot": {
|
||||
"src": "../../@radix-ui/react-slot/dist/index.mjs",
|
||||
"file": "@radix-ui_react-slot.js",
|
||||
"fileHash": "4581ea7a",
|
||||
"fileHash": "32dfa860",
|
||||
"needsInterop": false
|
||||
},
|
||||
"class-variance-authority": {
|
||||
"src": "../../class-variance-authority/dist/index.mjs",
|
||||
"file": "class-variance-authority.js",
|
||||
"fileHash": "e840c683",
|
||||
"fileHash": "f4a8c6f4",
|
||||
"needsInterop": false
|
||||
},
|
||||
"clsx": {
|
||||
"src": "../../clsx/dist/clsx.mjs",
|
||||
"file": "clsx.js",
|
||||
"fileHash": "79cbbdef",
|
||||
"fileHash": "e5a299ae",
|
||||
"needsInterop": false
|
||||
},
|
||||
"gsap": {
|
||||
"src": "../../gsap/index.js",
|
||||
"file": "gsap.js",
|
||||
"fileHash": "096a1c72",
|
||||
"fileHash": "76791308",
|
||||
"needsInterop": false
|
||||
},
|
||||
"gsap/ScrollTrigger": {
|
||||
"src": "../../gsap/ScrollTrigger.js",
|
||||
"file": "gsap_ScrollTrigger.js",
|
||||
"fileHash": "05527b31",
|
||||
"fileHash": "187ec0aa",
|
||||
"needsInterop": false
|
||||
},
|
||||
"lucide-react": {
|
||||
"src": "../../lucide-react/dist/esm/lucide-react.js",
|
||||
"file": "lucide-react.js",
|
||||
"fileHash": "4c9ad860",
|
||||
"fileHash": "58304198",
|
||||
"needsInterop": false
|
||||
},
|
||||
"react-dom/client": {
|
||||
"src": "../../react-dom/client.js",
|
||||
"file": "react-dom_client.js",
|
||||
"fileHash": "93025179",
|
||||
"fileHash": "b30625c8",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react-helmet-async": {
|
||||
"src": "../../react-helmet-async/lib/index.esm.js",
|
||||
"file": "react-helmet-async.js",
|
||||
"fileHash": "79a82144",
|
||||
"needsInterop": false
|
||||
},
|
||||
"react-router-dom": {
|
||||
"src": "../../react-router-dom/dist/index.mjs",
|
||||
"file": "react-router-dom.js",
|
||||
"fileHash": "a7f80be8",
|
||||
"fileHash": "4427109e",
|
||||
"needsInterop": false
|
||||
},
|
||||
"tailwind-merge": {
|
||||
"src": "../../tailwind-merge/dist/bundle-mjs.mjs",
|
||||
"file": "tailwind-merge.js",
|
||||
"fileHash": "e87c9277",
|
||||
"fileHash": "9511be49",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
@@ -114,17 +120,17 @@
|
||||
"chunk-YWBEB5PG": {
|
||||
"file": "chunk-YWBEB5PG.js"
|
||||
},
|
||||
"chunk-3Q7Z2X7T": {
|
||||
"file": "chunk-3Q7Z2X7T.js"
|
||||
"chunk-INCCO7S4": {
|
||||
"file": "chunk-INCCO7S4.js"
|
||||
},
|
||||
"chunk-5BI4FO63": {
|
||||
"file": "chunk-5BI4FO63.js"
|
||||
"chunk-LIWCVBQK": {
|
||||
"file": "chunk-LIWCVBQK.js"
|
||||
},
|
||||
"chunk-26QBR6Z3": {
|
||||
"file": "chunk-26QBR6Z3.js"
|
||||
"chunk-L2OGOWTU": {
|
||||
"file": "chunk-L2OGOWTU.js"
|
||||
},
|
||||
"chunk-2ZZW3GAI": {
|
||||
"file": "chunk-2ZZW3GAI.js"
|
||||
"chunk-MOFDO34C": {
|
||||
"file": "chunk-MOFDO34C.js"
|
||||
},
|
||||
"chunk-2VUH7NEY": {
|
||||
"file": "chunk-2VUH7NEY.js"
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
import {
|
||||
useLayoutEffect2
|
||||
} from "./chunk-MOFDO34C.js";
|
||||
import {
|
||||
useComposedRefs
|
||||
} from "./chunk-2VUH7NEY.js";
|
||||
import {
|
||||
require_react
|
||||
} from "./chunk-WUR7D6NS.js";
|
||||
import {
|
||||
__toESM
|
||||
} from "./chunk-G3PMV62Z.js";
|
||||
|
||||
// node_modules/@radix-ui/react-presence/dist/index.mjs
|
||||
var React2 = __toESM(require_react(), 1);
|
||||
var React = __toESM(require_react(), 1);
|
||||
function useStateMachine(initialState, machine) {
|
||||
return React.useReducer((state, event) => {
|
||||
const nextState = machine[state][event];
|
||||
return nextState ?? state;
|
||||
}, initialState);
|
||||
}
|
||||
var Presence = (props) => {
|
||||
const { present, children } = props;
|
||||
const presence = usePresence(present);
|
||||
const child = typeof children === "function" ? children({ present: presence.isPresent }) : React2.Children.only(children);
|
||||
const ref = useComposedRefs(presence.ref, getElementRef(child));
|
||||
const forceMount = typeof children === "function";
|
||||
return forceMount || presence.isPresent ? React2.cloneElement(child, { ref }) : null;
|
||||
};
|
||||
Presence.displayName = "Presence";
|
||||
function usePresence(present) {
|
||||
const [node, setNode] = React2.useState();
|
||||
const stylesRef = React2.useRef(null);
|
||||
const prevPresentRef = React2.useRef(present);
|
||||
const prevAnimationNameRef = React2.useRef("none");
|
||||
const initialState = present ? "mounted" : "unmounted";
|
||||
const [state, send] = useStateMachine(initialState, {
|
||||
mounted: {
|
||||
UNMOUNT: "unmounted",
|
||||
ANIMATION_OUT: "unmountSuspended"
|
||||
},
|
||||
unmountSuspended: {
|
||||
MOUNT: "mounted",
|
||||
ANIMATION_END: "unmounted"
|
||||
},
|
||||
unmounted: {
|
||||
MOUNT: "mounted"
|
||||
}
|
||||
});
|
||||
React2.useEffect(() => {
|
||||
const currentAnimationName = getAnimationName(stylesRef.current);
|
||||
prevAnimationNameRef.current = state === "mounted" ? currentAnimationName : "none";
|
||||
}, [state]);
|
||||
useLayoutEffect2(() => {
|
||||
const styles = stylesRef.current;
|
||||
const wasPresent = prevPresentRef.current;
|
||||
const hasPresentChanged = wasPresent !== present;
|
||||
if (hasPresentChanged) {
|
||||
const prevAnimationName = prevAnimationNameRef.current;
|
||||
const currentAnimationName = getAnimationName(styles);
|
||||
if (present) {
|
||||
send("MOUNT");
|
||||
} else if (currentAnimationName === "none" || styles?.display === "none") {
|
||||
send("UNMOUNT");
|
||||
} else {
|
||||
const isAnimating = prevAnimationName !== currentAnimationName;
|
||||
if (wasPresent && isAnimating) {
|
||||
send("ANIMATION_OUT");
|
||||
} else {
|
||||
send("UNMOUNT");
|
||||
}
|
||||
}
|
||||
prevPresentRef.current = present;
|
||||
}
|
||||
}, [present, send]);
|
||||
useLayoutEffect2(() => {
|
||||
if (node) {
|
||||
let timeoutId;
|
||||
const ownerWindow = node.ownerDocument.defaultView ?? window;
|
||||
const handleAnimationEnd = (event) => {
|
||||
const currentAnimationName = getAnimationName(stylesRef.current);
|
||||
const isCurrentAnimation = currentAnimationName.includes(CSS.escape(event.animationName));
|
||||
if (event.target === node && isCurrentAnimation) {
|
||||
send("ANIMATION_END");
|
||||
if (!prevPresentRef.current) {
|
||||
const currentFillMode = node.style.animationFillMode;
|
||||
node.style.animationFillMode = "forwards";
|
||||
timeoutId = ownerWindow.setTimeout(() => {
|
||||
if (node.style.animationFillMode === "forwards") {
|
||||
node.style.animationFillMode = currentFillMode;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleAnimationStart = (event) => {
|
||||
if (event.target === node) {
|
||||
prevAnimationNameRef.current = getAnimationName(stylesRef.current);
|
||||
}
|
||||
};
|
||||
node.addEventListener("animationstart", handleAnimationStart);
|
||||
node.addEventListener("animationcancel", handleAnimationEnd);
|
||||
node.addEventListener("animationend", handleAnimationEnd);
|
||||
return () => {
|
||||
ownerWindow.clearTimeout(timeoutId);
|
||||
node.removeEventListener("animationstart", handleAnimationStart);
|
||||
node.removeEventListener("animationcancel", handleAnimationEnd);
|
||||
node.removeEventListener("animationend", handleAnimationEnd);
|
||||
};
|
||||
} else {
|
||||
send("ANIMATION_END");
|
||||
}
|
||||
}, [node, send]);
|
||||
return {
|
||||
isPresent: ["mounted", "unmountSuspended"].includes(state),
|
||||
ref: React2.useCallback((node2) => {
|
||||
stylesRef.current = node2 ? getComputedStyle(node2) : null;
|
||||
setNode(node2);
|
||||
}, [])
|
||||
};
|
||||
}
|
||||
function getAnimationName(styles) {
|
||||
return styles?.animationName || "none";
|
||||
}
|
||||
function getElementRef(element) {
|
||||
let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
|
||||
let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
||||
if (mayWarn) {
|
||||
return element.ref;
|
||||
}
|
||||
getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
|
||||
mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
||||
if (mayWarn) {
|
||||
return element.props.ref;
|
||||
}
|
||||
return element.props.ref || element.ref;
|
||||
}
|
||||
|
||||
export {
|
||||
Presence
|
||||
};
|
||||
//# sourceMappingURL=chunk-INCCO7S4.js.map
|
||||
+7
File diff suppressed because one or more lines are too long
+248
@@ -0,0 +1,248 @@
|
||||
import {
|
||||
createContextScope,
|
||||
useLayoutEffect2
|
||||
} from "./chunk-MOFDO34C.js";
|
||||
import {
|
||||
composeRefs,
|
||||
useComposedRefs
|
||||
} from "./chunk-2VUH7NEY.js";
|
||||
import {
|
||||
require_jsx_runtime
|
||||
} from "./chunk-2YVA4HRZ.js";
|
||||
import {
|
||||
require_react
|
||||
} from "./chunk-WUR7D6NS.js";
|
||||
import {
|
||||
__toESM
|
||||
} from "./chunk-G3PMV62Z.js";
|
||||
|
||||
// node_modules/@radix-ui/react-collection/dist/index.mjs
|
||||
var import_react = __toESM(require_react(), 1);
|
||||
|
||||
// node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot/dist/index.mjs
|
||||
var React = __toESM(require_react(), 1);
|
||||
var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
|
||||
function createSlot(ownerName) {
|
||||
const SlotClone = createSlotClone(ownerName);
|
||||
const Slot2 = React.forwardRef((props, forwardedRef) => {
|
||||
const { children, ...slotProps } = props;
|
||||
const childrenArray = React.Children.toArray(children);
|
||||
const slottable = childrenArray.find(isSlottable);
|
||||
if (slottable) {
|
||||
const newElement = slottable.props.children;
|
||||
const newChildren = childrenArray.map((child) => {
|
||||
if (child === slottable) {
|
||||
if (React.Children.count(newElement) > 1) return React.Children.only(null);
|
||||
return React.isValidElement(newElement) ? newElement.props.children : null;
|
||||
} else {
|
||||
return child;
|
||||
}
|
||||
});
|
||||
return (0, import_jsx_runtime.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children: React.isValidElement(newElement) ? React.cloneElement(newElement, void 0, newChildren) : null });
|
||||
}
|
||||
return (0, import_jsx_runtime.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children });
|
||||
});
|
||||
Slot2.displayName = `${ownerName}.Slot`;
|
||||
return Slot2;
|
||||
}
|
||||
var Slot = createSlot("Slot");
|
||||
function createSlotClone(ownerName) {
|
||||
const SlotClone = React.forwardRef((props, forwardedRef) => {
|
||||
const { children, ...slotProps } = props;
|
||||
if (React.isValidElement(children)) {
|
||||
const childrenRef = getElementRef(children);
|
||||
const props2 = mergeProps(slotProps, children.props);
|
||||
if (children.type !== React.Fragment) {
|
||||
props2.ref = forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef;
|
||||
}
|
||||
return React.cloneElement(children, props2);
|
||||
}
|
||||
return React.Children.count(children) > 1 ? React.Children.only(null) : null;
|
||||
});
|
||||
SlotClone.displayName = `${ownerName}.SlotClone`;
|
||||
return SlotClone;
|
||||
}
|
||||
var SLOTTABLE_IDENTIFIER = /* @__PURE__ */ Symbol("radix.slottable");
|
||||
function createSlottable(ownerName) {
|
||||
const Slottable2 = ({ children }) => {
|
||||
return (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
|
||||
};
|
||||
Slottable2.displayName = `${ownerName}.Slottable`;
|
||||
Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
|
||||
return Slottable2;
|
||||
}
|
||||
var Slottable = createSlottable("Slottable");
|
||||
function isSlottable(child) {
|
||||
return React.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
|
||||
}
|
||||
function mergeProps(slotProps, childProps) {
|
||||
const overrideProps = { ...childProps };
|
||||
for (const propName in childProps) {
|
||||
const slotPropValue = slotProps[propName];
|
||||
const childPropValue = childProps[propName];
|
||||
const isHandler = /^on[A-Z]/.test(propName);
|
||||
if (isHandler) {
|
||||
if (slotPropValue && childPropValue) {
|
||||
overrideProps[propName] = (...args) => {
|
||||
const result = childPropValue(...args);
|
||||
slotPropValue(...args);
|
||||
return result;
|
||||
};
|
||||
} else if (slotPropValue) {
|
||||
overrideProps[propName] = slotPropValue;
|
||||
}
|
||||
} else if (propName === "style") {
|
||||
overrideProps[propName] = { ...slotPropValue, ...childPropValue };
|
||||
} else if (propName === "className") {
|
||||
overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
|
||||
}
|
||||
}
|
||||
return { ...slotProps, ...overrideProps };
|
||||
}
|
||||
function getElementRef(element) {
|
||||
let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
|
||||
let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
||||
if (mayWarn) {
|
||||
return element.ref;
|
||||
}
|
||||
getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
|
||||
mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
||||
if (mayWarn) {
|
||||
return element.props.ref;
|
||||
}
|
||||
return element.props.ref || element.ref;
|
||||
}
|
||||
|
||||
// node_modules/@radix-ui/react-collection/dist/index.mjs
|
||||
var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
|
||||
var import_react2 = __toESM(require_react(), 1);
|
||||
var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
|
||||
function createCollection(name) {
|
||||
const PROVIDER_NAME = name + "CollectionProvider";
|
||||
const [createCollectionContext, createCollectionScope] = createContextScope(PROVIDER_NAME);
|
||||
const [CollectionProviderImpl, useCollectionContext] = createCollectionContext(
|
||||
PROVIDER_NAME,
|
||||
{ collectionRef: { current: null }, itemMap: /* @__PURE__ */ new Map() }
|
||||
);
|
||||
const CollectionProvider = (props) => {
|
||||
const { scope, children } = props;
|
||||
const ref = import_react.default.useRef(null);
|
||||
const itemMap = import_react.default.useRef(/* @__PURE__ */ new Map()).current;
|
||||
return (0, import_jsx_runtime2.jsx)(CollectionProviderImpl, { scope, itemMap, collectionRef: ref, children });
|
||||
};
|
||||
CollectionProvider.displayName = PROVIDER_NAME;
|
||||
const COLLECTION_SLOT_NAME = name + "CollectionSlot";
|
||||
const CollectionSlotImpl = createSlot(COLLECTION_SLOT_NAME);
|
||||
const CollectionSlot = import_react.default.forwardRef(
|
||||
(props, forwardedRef) => {
|
||||
const { scope, children } = props;
|
||||
const context = useCollectionContext(COLLECTION_SLOT_NAME, scope);
|
||||
const composedRefs = useComposedRefs(forwardedRef, context.collectionRef);
|
||||
return (0, import_jsx_runtime2.jsx)(CollectionSlotImpl, { ref: composedRefs, children });
|
||||
}
|
||||
);
|
||||
CollectionSlot.displayName = COLLECTION_SLOT_NAME;
|
||||
const ITEM_SLOT_NAME = name + "CollectionItemSlot";
|
||||
const ITEM_DATA_ATTR = "data-radix-collection-item";
|
||||
const CollectionItemSlotImpl = createSlot(ITEM_SLOT_NAME);
|
||||
const CollectionItemSlot = import_react.default.forwardRef(
|
||||
(props, forwardedRef) => {
|
||||
const { scope, children, ...itemData } = props;
|
||||
const ref = import_react.default.useRef(null);
|
||||
const composedRefs = useComposedRefs(forwardedRef, ref);
|
||||
const context = useCollectionContext(ITEM_SLOT_NAME, scope);
|
||||
import_react.default.useEffect(() => {
|
||||
context.itemMap.set(ref, { ref, ...itemData });
|
||||
return () => void context.itemMap.delete(ref);
|
||||
});
|
||||
return (0, import_jsx_runtime2.jsx)(CollectionItemSlotImpl, { ...{ [ITEM_DATA_ATTR]: "" }, ref: composedRefs, children });
|
||||
}
|
||||
);
|
||||
CollectionItemSlot.displayName = ITEM_SLOT_NAME;
|
||||
function useCollection(scope) {
|
||||
const context = useCollectionContext(name + "CollectionConsumer", scope);
|
||||
const getItems = import_react.default.useCallback(() => {
|
||||
const collectionNode = context.collectionRef.current;
|
||||
if (!collectionNode) return [];
|
||||
const orderedNodes = Array.from(collectionNode.querySelectorAll(`[${ITEM_DATA_ATTR}]`));
|
||||
const items = Array.from(context.itemMap.values());
|
||||
const orderedItems = items.sort(
|
||||
(a, b) => orderedNodes.indexOf(a.ref.current) - orderedNodes.indexOf(b.ref.current)
|
||||
);
|
||||
return orderedItems;
|
||||
}, [context.collectionRef, context.itemMap]);
|
||||
return getItems;
|
||||
}
|
||||
return [
|
||||
{ Provider: CollectionProvider, Slot: CollectionSlot, ItemSlot: CollectionItemSlot },
|
||||
useCollection,
|
||||
createCollectionScope
|
||||
];
|
||||
}
|
||||
|
||||
// node_modules/@radix-ui/react-direction/dist/index.mjs
|
||||
var React3 = __toESM(require_react(), 1);
|
||||
var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
|
||||
var DirectionContext = React3.createContext(void 0);
|
||||
function useDirection(localDir) {
|
||||
const globalDir = React3.useContext(DirectionContext);
|
||||
return localDir || globalDir || "ltr";
|
||||
}
|
||||
|
||||
// node_modules/@radix-ui/react-use-size/dist/index.mjs
|
||||
var React4 = __toESM(require_react(), 1);
|
||||
function useSize(element) {
|
||||
const [size, setSize] = React4.useState(void 0);
|
||||
useLayoutEffect2(() => {
|
||||
if (element) {
|
||||
setSize({ width: element.offsetWidth, height: element.offsetHeight });
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
if (!Array.isArray(entries)) {
|
||||
return;
|
||||
}
|
||||
if (!entries.length) {
|
||||
return;
|
||||
}
|
||||
const entry = entries[0];
|
||||
let width;
|
||||
let height;
|
||||
if ("borderBoxSize" in entry) {
|
||||
const borderSizeEntry = entry["borderBoxSize"];
|
||||
const borderSize = Array.isArray(borderSizeEntry) ? borderSizeEntry[0] : borderSizeEntry;
|
||||
width = borderSize["inlineSize"];
|
||||
height = borderSize["blockSize"];
|
||||
} else {
|
||||
width = element.offsetWidth;
|
||||
height = element.offsetHeight;
|
||||
}
|
||||
setSize({ width, height });
|
||||
});
|
||||
resizeObserver.observe(element, { box: "border-box" });
|
||||
return () => resizeObserver.unobserve(element);
|
||||
} else {
|
||||
setSize(void 0);
|
||||
}
|
||||
}, [element]);
|
||||
return size;
|
||||
}
|
||||
|
||||
// node_modules/@radix-ui/react-use-previous/dist/index.mjs
|
||||
var React5 = __toESM(require_react(), 1);
|
||||
function usePrevious(value) {
|
||||
const ref = React5.useRef({ value, previous: value });
|
||||
return React5.useMemo(() => {
|
||||
if (ref.current.value !== value) {
|
||||
ref.current.previous = ref.current.value;
|
||||
ref.current.value = value;
|
||||
}
|
||||
return ref.current.previous;
|
||||
}, [value]);
|
||||
}
|
||||
|
||||
export {
|
||||
createCollection,
|
||||
useDirection,
|
||||
useSize,
|
||||
usePrevious
|
||||
};
|
||||
//# sourceMappingURL=chunk-L2OGOWTU.js.map
|
||||
+7
File diff suppressed because one or more lines are too long
+1352
File diff suppressed because it is too large
Load Diff
+7
File diff suppressed because one or more lines are too long
+354
@@ -0,0 +1,354 @@
|
||||
import {
|
||||
composeRefs
|
||||
} from "./chunk-2VUH7NEY.js";
|
||||
import {
|
||||
require_react_dom
|
||||
} from "./chunk-YF4B4G2L.js";
|
||||
import {
|
||||
require_jsx_runtime
|
||||
} from "./chunk-2YVA4HRZ.js";
|
||||
import {
|
||||
require_react
|
||||
} from "./chunk-WUR7D6NS.js";
|
||||
import {
|
||||
__toESM
|
||||
} from "./chunk-G3PMV62Z.js";
|
||||
|
||||
// node_modules/@radix-ui/primitive/dist/index.mjs
|
||||
var canUseDOM = !!(typeof window !== "undefined" && window.document && window.document.createElement);
|
||||
function composeEventHandlers(originalEventHandler, ourEventHandler, { checkForDefaultPrevented = true } = {}) {
|
||||
return function handleEvent(event) {
|
||||
originalEventHandler?.(event);
|
||||
if (checkForDefaultPrevented === false || !event.defaultPrevented) {
|
||||
return ourEventHandler?.(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// node_modules/@radix-ui/react-context/dist/index.mjs
|
||||
var React = __toESM(require_react(), 1);
|
||||
var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
|
||||
function createContext2(rootComponentName, defaultContext) {
|
||||
const Context = React.createContext(defaultContext);
|
||||
const Provider = (props) => {
|
||||
const { children, ...context } = props;
|
||||
const value = React.useMemo(() => context, Object.values(context));
|
||||
return (0, import_jsx_runtime.jsx)(Context.Provider, { value, children });
|
||||
};
|
||||
Provider.displayName = rootComponentName + "Provider";
|
||||
function useContext2(consumerName) {
|
||||
const context = React.useContext(Context);
|
||||
if (context) return context;
|
||||
if (defaultContext !== void 0) return defaultContext;
|
||||
throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``);
|
||||
}
|
||||
return [Provider, useContext2];
|
||||
}
|
||||
function createContextScope(scopeName, createContextScopeDeps = []) {
|
||||
let defaultContexts = [];
|
||||
function createContext3(rootComponentName, defaultContext) {
|
||||
const BaseContext = React.createContext(defaultContext);
|
||||
const index = defaultContexts.length;
|
||||
defaultContexts = [...defaultContexts, defaultContext];
|
||||
const Provider = (props) => {
|
||||
const { scope, children, ...context } = props;
|
||||
const Context = scope?.[scopeName]?.[index] || BaseContext;
|
||||
const value = React.useMemo(() => context, Object.values(context));
|
||||
return (0, import_jsx_runtime.jsx)(Context.Provider, { value, children });
|
||||
};
|
||||
Provider.displayName = rootComponentName + "Provider";
|
||||
function useContext2(consumerName, scope) {
|
||||
const Context = scope?.[scopeName]?.[index] || BaseContext;
|
||||
const context = React.useContext(Context);
|
||||
if (context) return context;
|
||||
if (defaultContext !== void 0) return defaultContext;
|
||||
throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``);
|
||||
}
|
||||
return [Provider, useContext2];
|
||||
}
|
||||
const createScope = () => {
|
||||
const scopeContexts = defaultContexts.map((defaultContext) => {
|
||||
return React.createContext(defaultContext);
|
||||
});
|
||||
return function useScope(scope) {
|
||||
const contexts = scope?.[scopeName] || scopeContexts;
|
||||
return React.useMemo(
|
||||
() => ({ [`__scope${scopeName}`]: { ...scope, [scopeName]: contexts } }),
|
||||
[scope, contexts]
|
||||
);
|
||||
};
|
||||
};
|
||||
createScope.scopeName = scopeName;
|
||||
return [createContext3, composeContextScopes(createScope, ...createContextScopeDeps)];
|
||||
}
|
||||
function composeContextScopes(...scopes) {
|
||||
const baseScope = scopes[0];
|
||||
if (scopes.length === 1) return baseScope;
|
||||
const createScope = () => {
|
||||
const scopeHooks = scopes.map((createScope2) => ({
|
||||
useScope: createScope2(),
|
||||
scopeName: createScope2.scopeName
|
||||
}));
|
||||
return function useComposedScopes(overrideScopes) {
|
||||
const nextScopes = scopeHooks.reduce((nextScopes2, { useScope, scopeName }) => {
|
||||
const scopeProps = useScope(overrideScopes);
|
||||
const currentScope = scopeProps[`__scope${scopeName}`];
|
||||
return { ...nextScopes2, ...currentScope };
|
||||
}, {});
|
||||
return React.useMemo(() => ({ [`__scope${baseScope.scopeName}`]: nextScopes }), [nextScopes]);
|
||||
};
|
||||
};
|
||||
createScope.scopeName = baseScope.scopeName;
|
||||
return createScope;
|
||||
}
|
||||
|
||||
// node_modules/@radix-ui/react-use-layout-effect/dist/index.mjs
|
||||
var React2 = __toESM(require_react(), 1);
|
||||
var useLayoutEffect2 = globalThis?.document ? React2.useLayoutEffect : () => {
|
||||
};
|
||||
|
||||
// node_modules/@radix-ui/react-id/dist/index.mjs
|
||||
var React3 = __toESM(require_react(), 1);
|
||||
var useReactId = React3[" useId ".trim().toString()] || (() => void 0);
|
||||
var count = 0;
|
||||
function useId(deterministicId) {
|
||||
const [id, setId] = React3.useState(useReactId());
|
||||
useLayoutEffect2(() => {
|
||||
if (!deterministicId) setId((reactId) => reactId ?? String(count++));
|
||||
}, [deterministicId]);
|
||||
return deterministicId || (id ? `radix-${id}` : "");
|
||||
}
|
||||
|
||||
// node_modules/@radix-ui/react-use-controllable-state/dist/index.mjs
|
||||
var React5 = __toESM(require_react(), 1);
|
||||
var React22 = __toESM(require_react(), 1);
|
||||
|
||||
// node_modules/@radix-ui/react-use-effect-event/dist/index.mjs
|
||||
var React4 = __toESM(require_react(), 1);
|
||||
var useReactEffectEvent = React4[" useEffectEvent ".trim().toString()];
|
||||
var useReactInsertionEffect = React4[" useInsertionEffect ".trim().toString()];
|
||||
|
||||
// node_modules/@radix-ui/react-use-controllable-state/dist/index.mjs
|
||||
var useInsertionEffect = React5[" useInsertionEffect ".trim().toString()] || useLayoutEffect2;
|
||||
function useControllableState({
|
||||
prop,
|
||||
defaultProp,
|
||||
onChange = () => {
|
||||
},
|
||||
caller
|
||||
}) {
|
||||
const [uncontrolledProp, setUncontrolledProp, onChangeRef] = useUncontrolledState({
|
||||
defaultProp,
|
||||
onChange
|
||||
});
|
||||
const isControlled = prop !== void 0;
|
||||
const value = isControlled ? prop : uncontrolledProp;
|
||||
if (true) {
|
||||
const isControlledRef = React5.useRef(prop !== void 0);
|
||||
React5.useEffect(() => {
|
||||
const wasControlled = isControlledRef.current;
|
||||
if (wasControlled !== isControlled) {
|
||||
const from = wasControlled ? "controlled" : "uncontrolled";
|
||||
const to = isControlled ? "controlled" : "uncontrolled";
|
||||
console.warn(
|
||||
`${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`
|
||||
);
|
||||
}
|
||||
isControlledRef.current = isControlled;
|
||||
}, [isControlled, caller]);
|
||||
}
|
||||
const setValue = React5.useCallback(
|
||||
(nextValue) => {
|
||||
if (isControlled) {
|
||||
const value2 = isFunction(nextValue) ? nextValue(prop) : nextValue;
|
||||
if (value2 !== prop) {
|
||||
onChangeRef.current?.(value2);
|
||||
}
|
||||
} else {
|
||||
setUncontrolledProp(nextValue);
|
||||
}
|
||||
},
|
||||
[isControlled, prop, setUncontrolledProp, onChangeRef]
|
||||
);
|
||||
return [value, setValue];
|
||||
}
|
||||
function useUncontrolledState({
|
||||
defaultProp,
|
||||
onChange
|
||||
}) {
|
||||
const [value, setValue] = React5.useState(defaultProp);
|
||||
const prevValueRef = React5.useRef(value);
|
||||
const onChangeRef = React5.useRef(onChange);
|
||||
useInsertionEffect(() => {
|
||||
onChangeRef.current = onChange;
|
||||
}, [onChange]);
|
||||
React5.useEffect(() => {
|
||||
if (prevValueRef.current !== value) {
|
||||
onChangeRef.current?.(value);
|
||||
prevValueRef.current = value;
|
||||
}
|
||||
}, [value, prevValueRef]);
|
||||
return [value, setValue, onChangeRef];
|
||||
}
|
||||
function isFunction(value) {
|
||||
return typeof value === "function";
|
||||
}
|
||||
|
||||
// node_modules/@radix-ui/react-primitive/dist/index.mjs
|
||||
var React7 = __toESM(require_react(), 1);
|
||||
var ReactDOM = __toESM(require_react_dom(), 1);
|
||||
|
||||
// node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs
|
||||
var React6 = __toESM(require_react(), 1);
|
||||
var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
|
||||
function createSlot(ownerName) {
|
||||
const SlotClone = createSlotClone(ownerName);
|
||||
const Slot2 = React6.forwardRef((props, forwardedRef) => {
|
||||
const { children, ...slotProps } = props;
|
||||
const childrenArray = React6.Children.toArray(children);
|
||||
const slottable = childrenArray.find(isSlottable);
|
||||
if (slottable) {
|
||||
const newElement = slottable.props.children;
|
||||
const newChildren = childrenArray.map((child) => {
|
||||
if (child === slottable) {
|
||||
if (React6.Children.count(newElement) > 1) return React6.Children.only(null);
|
||||
return React6.isValidElement(newElement) ? newElement.props.children : null;
|
||||
} else {
|
||||
return child;
|
||||
}
|
||||
});
|
||||
return (0, import_jsx_runtime2.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children: React6.isValidElement(newElement) ? React6.cloneElement(newElement, void 0, newChildren) : null });
|
||||
}
|
||||
return (0, import_jsx_runtime2.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children });
|
||||
});
|
||||
Slot2.displayName = `${ownerName}.Slot`;
|
||||
return Slot2;
|
||||
}
|
||||
var Slot = createSlot("Slot");
|
||||
function createSlotClone(ownerName) {
|
||||
const SlotClone = React6.forwardRef((props, forwardedRef) => {
|
||||
const { children, ...slotProps } = props;
|
||||
if (React6.isValidElement(children)) {
|
||||
const childrenRef = getElementRef(children);
|
||||
const props2 = mergeProps(slotProps, children.props);
|
||||
if (children.type !== React6.Fragment) {
|
||||
props2.ref = forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef;
|
||||
}
|
||||
return React6.cloneElement(children, props2);
|
||||
}
|
||||
return React6.Children.count(children) > 1 ? React6.Children.only(null) : null;
|
||||
});
|
||||
SlotClone.displayName = `${ownerName}.SlotClone`;
|
||||
return SlotClone;
|
||||
}
|
||||
var SLOTTABLE_IDENTIFIER = /* @__PURE__ */ Symbol("radix.slottable");
|
||||
function createSlottable(ownerName) {
|
||||
const Slottable2 = ({ children }) => {
|
||||
return (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
||||
};
|
||||
Slottable2.displayName = `${ownerName}.Slottable`;
|
||||
Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
|
||||
return Slottable2;
|
||||
}
|
||||
var Slottable = createSlottable("Slottable");
|
||||
function isSlottable(child) {
|
||||
return React6.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
|
||||
}
|
||||
function mergeProps(slotProps, childProps) {
|
||||
const overrideProps = { ...childProps };
|
||||
for (const propName in childProps) {
|
||||
const slotPropValue = slotProps[propName];
|
||||
const childPropValue = childProps[propName];
|
||||
const isHandler = /^on[A-Z]/.test(propName);
|
||||
if (isHandler) {
|
||||
if (slotPropValue && childPropValue) {
|
||||
overrideProps[propName] = (...args) => {
|
||||
const result = childPropValue(...args);
|
||||
slotPropValue(...args);
|
||||
return result;
|
||||
};
|
||||
} else if (slotPropValue) {
|
||||
overrideProps[propName] = slotPropValue;
|
||||
}
|
||||
} else if (propName === "style") {
|
||||
overrideProps[propName] = { ...slotPropValue, ...childPropValue };
|
||||
} else if (propName === "className") {
|
||||
overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
|
||||
}
|
||||
}
|
||||
return { ...slotProps, ...overrideProps };
|
||||
}
|
||||
function getElementRef(element) {
|
||||
let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
|
||||
let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
||||
if (mayWarn) {
|
||||
return element.ref;
|
||||
}
|
||||
getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
|
||||
mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
||||
if (mayWarn) {
|
||||
return element.props.ref;
|
||||
}
|
||||
return element.props.ref || element.ref;
|
||||
}
|
||||
|
||||
// node_modules/@radix-ui/react-primitive/dist/index.mjs
|
||||
var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
|
||||
var NODES = [
|
||||
"a",
|
||||
"button",
|
||||
"div",
|
||||
"form",
|
||||
"h2",
|
||||
"h3",
|
||||
"img",
|
||||
"input",
|
||||
"label",
|
||||
"li",
|
||||
"nav",
|
||||
"ol",
|
||||
"p",
|
||||
"select",
|
||||
"span",
|
||||
"svg",
|
||||
"ul"
|
||||
];
|
||||
var Primitive = NODES.reduce((primitive, node) => {
|
||||
const Slot2 = createSlot(`Primitive.${node}`);
|
||||
const Node = React7.forwardRef((props, forwardedRef) => {
|
||||
const { asChild, ...primitiveProps } = props;
|
||||
const Comp = asChild ? Slot2 : node;
|
||||
if (typeof window !== "undefined") {
|
||||
window[/* @__PURE__ */ Symbol.for("radix-ui")] = true;
|
||||
}
|
||||
return (0, import_jsx_runtime3.jsx)(Comp, { ...primitiveProps, ref: forwardedRef });
|
||||
});
|
||||
Node.displayName = `Primitive.${node}`;
|
||||
return { ...primitive, [node]: Node };
|
||||
}, {});
|
||||
function dispatchDiscreteCustomEvent(target, event) {
|
||||
if (target) ReactDOM.flushSync(() => target.dispatchEvent(event));
|
||||
}
|
||||
|
||||
// node_modules/@radix-ui/react-use-callback-ref/dist/index.mjs
|
||||
var React8 = __toESM(require_react(), 1);
|
||||
function useCallbackRef(callback) {
|
||||
const callbackRef = React8.useRef(callback);
|
||||
React8.useEffect(() => {
|
||||
callbackRef.current = callback;
|
||||
});
|
||||
return React8.useMemo(() => (...args) => callbackRef.current?.(...args), []);
|
||||
}
|
||||
|
||||
export {
|
||||
composeEventHandlers,
|
||||
createContext2,
|
||||
createContextScope,
|
||||
useLayoutEffect2,
|
||||
useId,
|
||||
useControllableState,
|
||||
Primitive,
|
||||
dispatchDiscreteCustomEvent,
|
||||
useCallbackRef
|
||||
};
|
||||
//# sourceMappingURL=chunk-MOFDO34C.js.map
|
||||
+7
File diff suppressed because one or more lines are too long
+1150
File diff suppressed because it is too large
Load Diff
+7
File diff suppressed because one or more lines are too long
+69
@@ -0,0 +1,69 @@
|
||||
2.2.4 / 2018-03-13
|
||||
==================
|
||||
|
||||
* Use flow strict mode (i.e. `@flow strict`).
|
||||
|
||||
2.2.3 / 2018-02-19
|
||||
==================
|
||||
|
||||
* Change license from BSD+Patents to MIT.
|
||||
|
||||
2.2.2 / 2016-11-15
|
||||
==================
|
||||
|
||||
* Add LICENSE file.
|
||||
* Misc housekeeping.
|
||||
|
||||
2.2.1 / 2016-03-09
|
||||
==================
|
||||
|
||||
* Use `NODE_ENV` variable instead of `__DEV__` to cache `process.env.NODE_ENV`.
|
||||
|
||||
2.2.0 / 2015-11-17
|
||||
==================
|
||||
|
||||
* Use `error.name` instead of `Invariant Violation`.
|
||||
|
||||
2.1.3 / 2015-11-17
|
||||
==================
|
||||
|
||||
* Remove `@provideModule` pragma.
|
||||
|
||||
2.1.2 / 2015-10-27
|
||||
==================
|
||||
|
||||
* Fix license.
|
||||
|
||||
2.1.1 / 2015-09-20
|
||||
==================
|
||||
|
||||
* Use correct SPDX license.
|
||||
* Test "browser.js" using browserify.
|
||||
* Switch from "envify" to "loose-envify".
|
||||
|
||||
2.1.0 / 2015-06-03
|
||||
==================
|
||||
|
||||
* Add "envify" as a dependency.
|
||||
* Fixed license field in "package.json".
|
||||
|
||||
2.0.0 / 2015-02-21
|
||||
==================
|
||||
|
||||
* Switch to using the "browser" field. There are now browser and server versions that respect the "format" in production.
|
||||
|
||||
1.0.2 / 2014-09-24
|
||||
==================
|
||||
|
||||
* Added tests, npmignore and gitignore.
|
||||
* Clarifications in README.
|
||||
|
||||
1.0.1 / 2014-09-24
|
||||
==================
|
||||
|
||||
* Actually include 'invariant.js'.
|
||||
|
||||
1.0.0 / 2014-09-24
|
||||
==================
|
||||
|
||||
* Initial release.
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2013-present, Facebook, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# invariant
|
||||
|
||||
[](https://travis-ci.org/zertosh/invariant)
|
||||
|
||||
A mirror of Facebook's `invariant` (e.g. [React](https://github.com/facebook/react/blob/v0.13.3/src/vendor/core/invariant.js), [flux](https://github.com/facebook/flux/blob/2.0.2/src/invariant.js)).
|
||||
|
||||
A way to provide descriptive errors in development but generic errors in production.
|
||||
|
||||
## Install
|
||||
|
||||
With [npm](http://npmjs.org) do:
|
||||
|
||||
```sh
|
||||
npm install invariant
|
||||
```
|
||||
|
||||
## `invariant(condition, message)`
|
||||
|
||||
```js
|
||||
var invariant = require('invariant');
|
||||
|
||||
invariant(someTruthyVal, 'This will not throw');
|
||||
// No errors
|
||||
|
||||
invariant(someFalseyVal, 'This will throw an error with this message');
|
||||
// Error: Invariant Violation: This will throw an error with this message
|
||||
```
|
||||
|
||||
**Note:** When `process.env.NODE_ENV` is not `production`, the message is required. If omitted, `invariant` will throw regardless of the truthiness of the condition. When `process.env.NODE_ENV` is `production`, the message is optional – so they can be minified away.
|
||||
|
||||
### Browser
|
||||
|
||||
When used with [browserify](https://github.com/substack/node-browserify), it'll use `browser.js` (instead of `invariant.js`) and the [envify](https://github.com/hughsk/envify) transform will inline the value of `process.env.NODE_ENV`.
|
||||
|
||||
### Node
|
||||
|
||||
The node version is optimized around the performance implications of accessing `process.env`. The value of `process.env.NODE_ENV` is cached, and repeatedly used instead of reading `process.env`. See [Server rendering is slower with npm react #812](https://github.com/facebook/react/issues/812)
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Use invariant() to assert state which your program assumes to be true.
|
||||
*
|
||||
* Provide sprintf-style format (only %s is supported) and arguments
|
||||
* to provide information about what broke and what you were
|
||||
* expecting.
|
||||
*
|
||||
* The invariant message will be stripped in production, but the invariant
|
||||
* will remain to ensure logic does not differ in production.
|
||||
*/
|
||||
|
||||
var invariant = function(condition, format, a, b, c, d, e, f) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (format === undefined) {
|
||||
throw new Error('invariant requires an error message argument');
|
||||
}
|
||||
}
|
||||
|
||||
if (!condition) {
|
||||
var error;
|
||||
if (format === undefined) {
|
||||
error = new Error(
|
||||
'Minified exception occurred; use the non-minified dev environment ' +
|
||||
'for the full error message and additional helpful warnings.'
|
||||
);
|
||||
} else {
|
||||
var args = [a, b, c, d, e, f];
|
||||
var argIndex = 0;
|
||||
error = new Error(
|
||||
format.replace(/%s/g, function() { return args[argIndex++]; })
|
||||
);
|
||||
error.name = 'Invariant Violation';
|
||||
}
|
||||
|
||||
error.framesToPop = 1; // we don't care about invariant's own frame
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = invariant;
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Use invariant() to assert state which your program assumes to be true.
|
||||
*
|
||||
* Provide sprintf-style format (only %s is supported) and arguments
|
||||
* to provide information about what broke and what you were
|
||||
* expecting.
|
||||
*
|
||||
* The invariant message will be stripped in production, but the invariant
|
||||
* will remain to ensure logic does not differ in production.
|
||||
*/
|
||||
|
||||
var NODE_ENV = process.env.NODE_ENV;
|
||||
|
||||
var invariant = function(condition, format, a, b, c, d, e, f) {
|
||||
if (NODE_ENV !== 'production') {
|
||||
if (format === undefined) {
|
||||
throw new Error('invariant requires an error message argument');
|
||||
}
|
||||
}
|
||||
|
||||
if (!condition) {
|
||||
var error;
|
||||
if (format === undefined) {
|
||||
error = new Error(
|
||||
'Minified exception occurred; use the non-minified dev environment ' +
|
||||
'for the full error message and additional helpful warnings.'
|
||||
);
|
||||
} else {
|
||||
var args = [a, b, c, d, e, f];
|
||||
var argIndex = 0;
|
||||
error = new Error(
|
||||
format.replace(/%s/g, function() { return args[argIndex++]; })
|
||||
);
|
||||
error.name = 'Invariant Violation';
|
||||
}
|
||||
|
||||
error.framesToPop = 1; // we don't care about invariant's own frame
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = invariant;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/* @flow strict */
|
||||
|
||||
declare module.exports: (
|
||||
condition: any,
|
||||
format?: string,
|
||||
...args: Array<any>
|
||||
) => void;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "invariant",
|
||||
"version": "2.2.4",
|
||||
"description": "invariant",
|
||||
"keywords": [
|
||||
"test",
|
||||
"invariant"
|
||||
],
|
||||
"license": "MIT",
|
||||
"author": "Andres Suarez <zertosh@gmail.com>",
|
||||
"files": [
|
||||
"browser.js",
|
||||
"invariant.js",
|
||||
"invariant.js.flow"
|
||||
],
|
||||
"repository": "https://github.com/zertosh/invariant",
|
||||
"scripts": {
|
||||
"test": "NODE_ENV=production tap test/*.js && NODE_ENV=development tap test/*.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^11.0.1",
|
||||
"flow-bin": "^0.67.1",
|
||||
"tap": "^1.4.0"
|
||||
},
|
||||
"main": "invariant.js",
|
||||
"browser": "browser.js",
|
||||
"browserify": {
|
||||
"transform": [
|
||||
"loose-envify"
|
||||
]
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Formidable Labs
|
||||
Copyright (c) 2017 Evgeny Poberezkin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
[](https://formidable.com/open-source/)
|
||||
|
||||
[![Downloads][downloads_img]][npm_site]
|
||||
[![Bundle Size][bundle_img]](#bundle-size)
|
||||
[![GH Actions Status][actions_img]][actions_site]
|
||||
[![Coverage Status][cov_img]][cov_site]
|
||||
[![npm version][npm_img]][npm_site]
|
||||
[![Maintenance Status][maintenance_img]](#maintenance-status)
|
||||
|
||||
The fastest deep equal comparison for React. Very quick general-purpose deep
|
||||
comparison, too. Great for `React.memo` and `shouldComponentUpdate`.
|
||||
|
||||
This is a fork of the brilliant
|
||||
[fast-deep-equal](https://github.com/epoberezkin/fast-deep-equal) with some
|
||||
extra handling for React.
|
||||
|
||||

|
||||
|
||||
(Check out the [benchmarking details](#benchmarking-this-library).)
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
$ yarn add react-fast-compare
|
||||
# or
|
||||
$ npm install react-fast-compare
|
||||
```
|
||||
|
||||
## Highlights
|
||||
|
||||
- ES5 compatible; works in node.js (0.10+) and browsers (IE9+)
|
||||
- deeply compares any value (besides objects with circular references)
|
||||
- handles React-specific circular references, like elements
|
||||
- checks equality Date and RegExp objects
|
||||
- should as fast as [fast-deep-equal](https://github.com/epoberezkin/fast-deep-equal) via a single unified library, and with added guardrails for circular references.
|
||||
- small: under 660 bytes minified+gzipped
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
const isEqual = require("react-fast-compare");
|
||||
|
||||
// general usage
|
||||
console.log(isEqual({ foo: "bar" }, { foo: "bar" })); // true
|
||||
|
||||
// React.memo
|
||||
// only re-render ExpensiveComponent when the props have deeply changed
|
||||
const DeepMemoComponent = React.memo(ExpensiveComponent, isEqual);
|
||||
|
||||
// React.Component shouldComponentUpdate
|
||||
// only re-render AnotherExpensiveComponent when the props have deeply changed
|
||||
class AnotherExpensiveComponent extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !isEqual(this.props, nextProps);
|
||||
}
|
||||
render() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Do I Need `React.memo` (or `shouldComponentUpdate`)?
|
||||
|
||||
> What's faster than a really fast deep comparison? No deep comparison at all.
|
||||
|
||||
—This Readme
|
||||
|
||||
Deep checks in `React.memo` or a `shouldComponentUpdate` should not be used blindly.
|
||||
First, see if the default
|
||||
[React.memo](https://reactjs.org/docs/react-api.html#reactmemo) or
|
||||
[PureComponent](https://reactjs.org/docs/react-api.html#reactpurecomponent)
|
||||
will work for you. If it won't (if you need deep checks), it's wise to make
|
||||
sure you've correctly indentified the bottleneck in your application by
|
||||
[profiling the performance](https://reactjs.org/docs/optimizing-performance.html#profiling-components-with-the-chrome-performance-tab).
|
||||
After you've determined that you _do_ need deep equality checks and you've
|
||||
identified the minimum number of places to apply them, then this library may
|
||||
be for you!
|
||||
|
||||
## Benchmarking this Library
|
||||
|
||||
The absolute values are much less important than the relative differences
|
||||
between packages.
|
||||
|
||||
Benchmarking source can be found
|
||||
[here](https://github.com/FormidableLabs/react-fast-compare/blob/master/benchmark/index.js).
|
||||
Each "operation" consists of running all relevant tests. The React benchmark
|
||||
uses both the generic tests and the react tests; these runs will be slower
|
||||
simply because there are more tests in each operation.
|
||||
|
||||
The results below are from a local test on a laptop _(stats last updated 6/2/2020)_:
|
||||
|
||||
### Generic Data
|
||||
|
||||
```
|
||||
react-fast-compare x 177,600 ops/sec ±1.73% (92 runs sampled)
|
||||
fast-deep-equal x 184,211 ops/sec ±0.65% (87 runs sampled)
|
||||
lodash.isEqual x 39,826 ops/sec ±1.32% (86 runs sampled)
|
||||
nano-equal x 176,023 ops/sec ±0.89% (92 runs sampled)
|
||||
shallow-equal-fuzzy x 146,355 ops/sec ±0.64% (89 runs sampled)
|
||||
fastest: fast-deep-equal
|
||||
```
|
||||
|
||||
`react-fast-compare` and `fast-deep-equal` should be the same speed for these
|
||||
tests; any difference is just noise. `react-fast-compare` won't be faster than
|
||||
`fast-deep-equal`, because it's based on it.
|
||||
|
||||
### React and Generic Data
|
||||
|
||||
```
|
||||
react-fast-compare x 86,392 ops/sec ±0.70% (93 runs sampled)
|
||||
fast-deep-equal x 85,567 ops/sec ±0.95% (92 runs sampled)
|
||||
lodash.isEqual x 7,369 ops/sec ±1.78% (84 runs sampled)
|
||||
fastest: react-fast-compare,fast-deep-equal
|
||||
```
|
||||
|
||||
Two of these packages cannot handle comparing React elements, because they
|
||||
contain circular reference: `nano-equal` and `shallow-equal-fuzzy`.
|
||||
|
||||
### Running Benchmarks
|
||||
|
||||
```sh
|
||||
$ yarn install
|
||||
$ yarn run benchmark
|
||||
```
|
||||
|
||||
## Differences between this library and `fast-deep-equal`
|
||||
|
||||
`react-fast-compare` is based on `fast-deep-equal`, with some additions:
|
||||
|
||||
- `react-fast-compare` has `try`/`catch` guardrails for stack overflows from undetected (non-React) circular references.
|
||||
- `react-fast-compare` has a _single_ unified entry point for all uses. No matter what your target application is, `import equal from 'react-fast-compare'` just works. `fast-deep-equal` has multiple entry points for different use cases.
|
||||
|
||||
This version of `react-fast-compare` tracks `fast-deep-equal@3.1.1`.
|
||||
|
||||
## Bundle Size
|
||||
|
||||
There are a variety of ways to calculate bundle size for JavaScript code.
|
||||
You can see our size test code in the `compress` script in
|
||||
[`package.json`](https://github.com/FormidableLabs/react-fast-compare/blob/master/package.json).
|
||||
[Bundlephobia's calculation](https://bundlephobia.com/result?p=react-fast-compare) is slightly higher,
|
||||
as they [do not mangle during minification](https://github.com/pastelsky/package-build-stats/blob/v6.1.1/src/getDependencySizeTree.js#L139).
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/FormidableLabs/react-fast-compare/blob/readme/LICENSE)
|
||||
|
||||
## Contributing
|
||||
|
||||
Please see our [contributions guide](./CONTRIBUTING.md).
|
||||
|
||||
## Maintenance Status
|
||||
|
||||
**Active:** Formidable is actively working on this project, and we expect to continue for work for the foreseeable future. Bug reports, feature requests and pull requests are welcome.
|
||||
|
||||
[actions_img]: https://github.com/FormidableLabs/react-fast-compare/actions/workflows/ci.yml/badge.svg
|
||||
[actions_site]: https://github.com/formidablelabs/react-fast-compare/actions/workflows/ci.yml
|
||||
[cov_img]: https://codecov.io/gh/FormidableLabs/react-fast-compare/branch/master/graph/badge.svg
|
||||
[cov_site]: https://codecov.io/gh/FormidableLabs/react-fast-compare
|
||||
[npm_img]: https://badge.fury.io/js/react-fast-compare.svg
|
||||
[npm_site]: http://badge.fury.io/js/react-fast-compare
|
||||
[appveyor_img]: https://ci.appveyor.com/api/projects/status/github/formidablelabs/react-fast-compare?branch=master&svg=true
|
||||
[appveyor_site]: https://ci.appveyor.com/project/FormidableLabs/react-fast-compare
|
||||
[bundle_img]: https://img.shields.io/badge/minzipped%20size-656%20B-flatgreen.svg
|
||||
[downloads_img]: https://img.shields.io/npm/dm/react-fast-compare.svg
|
||||
[maintenance_img]: https://img.shields.io/badge/maintenance-active-flatgreen.svg
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
declare function isEqual<A = any, B = any>(a: A, b: B): boolean;
|
||||
export = isEqual;
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
/* global Map:readonly, Set:readonly, ArrayBuffer:readonly */
|
||||
|
||||
var hasElementType = typeof Element !== 'undefined';
|
||||
var hasMap = typeof Map === 'function';
|
||||
var hasSet = typeof Set === 'function';
|
||||
var hasArrayBuffer = typeof ArrayBuffer === 'function' && !!ArrayBuffer.isView;
|
||||
|
||||
// Note: We **don't** need `envHasBigInt64Array` in fde es6/index.js
|
||||
|
||||
function equal(a, b) {
|
||||
// START: fast-deep-equal es6/index.js 3.1.3
|
||||
if (a === b) return true;
|
||||
|
||||
if (a && b && typeof a == 'object' && typeof b == 'object') {
|
||||
if (a.constructor !== b.constructor) return false;
|
||||
|
||||
var length, i, keys;
|
||||
if (Array.isArray(a)) {
|
||||
length = a.length;
|
||||
if (length != b.length) return false;
|
||||
for (i = length; i-- !== 0;)
|
||||
if (!equal(a[i], b[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// START: Modifications:
|
||||
// 1. Extra `has<Type> &&` helpers in initial condition allow es6 code
|
||||
// to co-exist with es5.
|
||||
// 2. Replace `for of` with es5 compliant iteration using `for`.
|
||||
// Basically, take:
|
||||
//
|
||||
// ```js
|
||||
// for (i of a.entries())
|
||||
// if (!b.has(i[0])) return false;
|
||||
// ```
|
||||
//
|
||||
// ... and convert to:
|
||||
//
|
||||
// ```js
|
||||
// it = a.entries();
|
||||
// while (!(i = it.next()).done)
|
||||
// if (!b.has(i.value[0])) return false;
|
||||
// ```
|
||||
//
|
||||
// **Note**: `i` access switches to `i.value`.
|
||||
var it;
|
||||
if (hasMap && (a instanceof Map) && (b instanceof Map)) {
|
||||
if (a.size !== b.size) return false;
|
||||
it = a.entries();
|
||||
while (!(i = it.next()).done)
|
||||
if (!b.has(i.value[0])) return false;
|
||||
it = a.entries();
|
||||
while (!(i = it.next()).done)
|
||||
if (!equal(i.value[1], b.get(i.value[0]))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasSet && (a instanceof Set) && (b instanceof Set)) {
|
||||
if (a.size !== b.size) return false;
|
||||
it = a.entries();
|
||||
while (!(i = it.next()).done)
|
||||
if (!b.has(i.value[0])) return false;
|
||||
return true;
|
||||
}
|
||||
// END: Modifications
|
||||
|
||||
if (hasArrayBuffer && ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
|
||||
length = a.length;
|
||||
if (length != b.length) return false;
|
||||
for (i = length; i-- !== 0;)
|
||||
if (a[i] !== b[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
|
||||
// START: Modifications:
|
||||
// Apply guards for `Object.create(null)` handling. See:
|
||||
// - https://github.com/FormidableLabs/react-fast-compare/issues/64
|
||||
// - https://github.com/epoberezkin/fast-deep-equal/issues/49
|
||||
if (a.valueOf !== Object.prototype.valueOf && typeof a.valueOf === 'function' && typeof b.valueOf === 'function') return a.valueOf() === b.valueOf();
|
||||
if (a.toString !== Object.prototype.toString && typeof a.toString === 'function' && typeof b.toString === 'function') return a.toString() === b.toString();
|
||||
// END: Modifications
|
||||
|
||||
keys = Object.keys(a);
|
||||
length = keys.length;
|
||||
if (length !== Object.keys(b).length) return false;
|
||||
|
||||
for (i = length; i-- !== 0;)
|
||||
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
|
||||
// END: fast-deep-equal
|
||||
|
||||
// START: react-fast-compare
|
||||
// custom handling for DOM elements
|
||||
if (hasElementType && a instanceof Element) return false;
|
||||
|
||||
// custom handling for React/Preact
|
||||
for (i = length; i-- !== 0;) {
|
||||
if ((keys[i] === '_owner' || keys[i] === '__v' || keys[i] === '__o') && a.$$typeof) {
|
||||
// React-specific: avoid traversing React elements' _owner
|
||||
// Preact-specific: avoid traversing Preact elements' __v and __o
|
||||
// __v = $_original / $_vnode
|
||||
// __o = $_owner
|
||||
// These properties contain circular references and are not needed when
|
||||
// comparing the actual elements (and not their owners)
|
||||
// .$$typeof and ._store on just reasonable markers of elements
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// all other properties should be traversed as usual
|
||||
if (!equal(a[keys[i]], b[keys[i]])) return false;
|
||||
}
|
||||
// END: react-fast-compare
|
||||
|
||||
// START: fast-deep-equal
|
||||
return true;
|
||||
}
|
||||
|
||||
return a !== a && b !== b;
|
||||
}
|
||||
// end fast-deep-equal
|
||||
|
||||
module.exports = function isEqual(a, b) {
|
||||
try {
|
||||
return equal(a, b);
|
||||
} catch (error) {
|
||||
if (((error.message || '').match(/stack|recursion/i))) {
|
||||
// warn on circular references, don't crash
|
||||
// browsers give this different errors name and messages:
|
||||
// chrome/safari: "RangeError", "Maximum call stack size exceeded"
|
||||
// firefox: "InternalError", too much recursion"
|
||||
// edge: "Error", "Out of stack space"
|
||||
console.warn('react-fast-compare cannot handle circular refs');
|
||||
return false;
|
||||
}
|
||||
// some other error. we should definitely know about these
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"name": "react-fast-compare",
|
||||
"version": "3.2.2",
|
||||
"description": "Fastest deep equal comparison for React. Great for React.memo & shouldComponentUpdate. Also really fast general-purpose deep comparison.",
|
||||
"main": "index.js",
|
||||
"typings": "index.d.ts",
|
||||
"scripts": {
|
||||
"preversion": "yarn test",
|
||||
"benchmark": "node benchmark",
|
||||
"eslint": "eslint \"*.js\" benchmark test",
|
||||
"tslint": "eslint test/typescript/*.tsx",
|
||||
"test-browser": "karma start test/browser/karma.conf.js",
|
||||
"test-node": "mocha \"test/node/*.spec.js\"",
|
||||
"test-node-cov": "nyc mocha \"test/node/*.spec.js\"",
|
||||
"test-ts-usage": "tsc --esModuleInterop --jsx react --noEmit test/typescript/sample-react-redux-usage.tsx test/typescript/sample-usage.tsx",
|
||||
"test-ts-defs": "tsc --target ES5 index.d.ts",
|
||||
"test": "builder concurrent --buffer eslint tslint test-ts-usage test-ts-defs test-node-cov test-browser",
|
||||
"compress": "terser --compress --mangle=\"toplevel:true\" -- index.js",
|
||||
"size-min-gz": "yarn -s compress | gzip -9 | wc -c"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/FormidableLabs/react-fast-compare"
|
||||
},
|
||||
"keywords": [
|
||||
"fast",
|
||||
"equal",
|
||||
"react",
|
||||
"compare",
|
||||
"shouldComponentUpdate",
|
||||
"deep-equal"
|
||||
],
|
||||
"author": "Chris Bolin",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/FormidableLabs/react-fast-compare/issues"
|
||||
},
|
||||
"homepage": "https://github.com/FormidableLabs/react-fast-compare",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.0",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@changesets/cli": "^2.26.1",
|
||||
"@svitejs/changesets-changelog-github-compact": "^0.1.1",
|
||||
"@testing-library/dom": "^9.0.1",
|
||||
"@testing-library/preact": "^3.2.3",
|
||||
"@types/node": "^18.15.0",
|
||||
"@types/react": "^16.9.35",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-redux": "^7.1.25",
|
||||
"@typescript-eslint/parser": "^5.54.1",
|
||||
"assert": "^2.0.0",
|
||||
"babel-loader": "^9.1.2",
|
||||
"benchmark": "^2.1.4",
|
||||
"builder": "^5.0.0",
|
||||
"codecov": "^3.8.3",
|
||||
"core-js": "^3.29.0",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"fast-deep-equal-git": "epoberezkin/fast-deep-equal#v3.1.3",
|
||||
"jsdom": "^21.1.0",
|
||||
"jsdom-global": "^3.0.2",
|
||||
"karma": "^6.4.1",
|
||||
"karma-chrome-launcher": "^3.1.1",
|
||||
"karma-firefox-launcher": "^2.1.2",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-safari-launcher": "^1.0.0",
|
||||
"karma-webpack": "^5.0.0",
|
||||
"lodash": "^4.17.10",
|
||||
"mocha": "^10.2.0",
|
||||
"nano-equal": "^2.0.2",
|
||||
"nyc": "^15.1.0",
|
||||
"preact": "^10.13.1",
|
||||
"process": "^0.11.10",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-test-renderer": "^18.2.0",
|
||||
"redux": "^4.2.1",
|
||||
"shallow-equal-fuzzy": "0.0.2",
|
||||
"sinon": "^15.0.1",
|
||||
"terser": "^5.16.6",
|
||||
"typescript": "^4.9.5",
|
||||
"webpack": "^5.76.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"provenance": true
|
||||
},
|
||||
"nyc": {
|
||||
"exclude": [
|
||||
"**/test/**",
|
||||
"node_modules"
|
||||
],
|
||||
"reporter": [
|
||||
"lcov",
|
||||
"text-summary"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts"
|
||||
],
|
||||
"types": "index.d.ts"
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2018 The New York Times Company
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
# react-helmet-async
|
||||
|
||||
[](https://github.com/staylor/react-helmet-async/actions/workflows/ci.yml)
|
||||
|
||||
[Announcement post on Times Open blog](https://open.nytimes.com/the-future-of-meta-tag-management-for-modern-react-development-ec26a7dc9183)
|
||||
|
||||
This package is a fork of [React Helmet](https://github.com/nfl/react-helmet).
|
||||
`<Helmet>` usage is synonymous, but server and client now requires `<HelmetProvider>` to encapsulate state per request.
|
||||
|
||||
`react-helmet` relies on `react-side-effect`, which is not thread-safe. If you are doing anything asynchronous on the server, you need Helmet to encapsulate data on a per-request basis, this package does just that.
|
||||
|
||||
## React 19
|
||||
|
||||
React 19 has built-in support for hoisting `<title>`, `<meta>`, `<link>`, `<style>`, and `<script>` elements to `<head>`. Starting with version 3.0.0, this package detects the React version at runtime:
|
||||
|
||||
- **React 19+**: `<Helmet>` renders actual DOM elements and lets React handle hoisting them to `<head>`. `<HelmetProvider>` becomes a transparent passthrough. The existing API is fully compatible — you do not need to change any code.
|
||||
- **React 16–18**: The existing behavior is preserved. `<Helmet>` collects all instances, deduplicates tags, and applies changes to the DOM via manual manipulation (client) or serializes them for the response (server).
|
||||
|
||||
> **Note:** `htmlAttributes` and `bodyAttributes` do not have a React 19 equivalent, so they are still applied via direct DOM manipulation on both code paths.
|
||||
|
||||
If you are starting a new React 19 project and do not need `htmlAttributes`/`bodyAttributes`, SSR `context` serialization, `onChangeClientState`, `prioritizeSeoTags`, or `titleTemplate` support, you may not need this package at all — React 19's built-in metadata handling may be sufficient.
|
||||
|
||||
## Usage
|
||||
|
||||
**New in 1.0.0:** No more default export! `import { Helmet } from 'react-helmet-async'`
|
||||
|
||||
The main way that this package differs from `react-helmet` is that it requires using a Provider to encapsulate Helmet state for your React tree. If you use libraries like Redux or Apollo, you are already familiar with this paradigm:
|
||||
|
||||
```javascript
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { Helmet, HelmetProvider } from 'react-helmet-async';
|
||||
|
||||
const app = (
|
||||
<HelmetProvider>
|
||||
<App>
|
||||
<Helmet>
|
||||
<title>Hello World</title>
|
||||
<link rel="canonical" href="https://www.tacobell.com/" />
|
||||
</Helmet>
|
||||
<h1>Hello World</h1>
|
||||
</App>
|
||||
</HelmetProvider>
|
||||
);
|
||||
|
||||
createRoot(document.getElementById('app')).render(app);
|
||||
```
|
||||
|
||||
On the server, we will no longer use static methods to extract state. `react-side-effect`
|
||||
exposed a `.rewind()` method, which Helmet used when calling `Helmet.renderStatic()`. Instead, we are going
|
||||
to pass a `context` prop to `HelmetProvider`, which will hold our state specific to each request.
|
||||
|
||||
```javascript
|
||||
import React from 'react';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { Helmet, HelmetProvider } from 'react-helmet-async';
|
||||
|
||||
const helmetContext = {};
|
||||
|
||||
const app = (
|
||||
<HelmetProvider context={helmetContext}>
|
||||
<App>
|
||||
<Helmet>
|
||||
<title>Hello World</title>
|
||||
<link rel="canonical" href="https://www.tacobell.com/" />
|
||||
</Helmet>
|
||||
<h1>Hello World</h1>
|
||||
</App>
|
||||
</HelmetProvider>
|
||||
);
|
||||
|
||||
const html = renderToString(app);
|
||||
|
||||
const { helmet } = helmetContext;
|
||||
|
||||
// helmet.title.toString() etc…
|
||||
```
|
||||
|
||||
> **React 19 SSR note:** When using React 19, `<title>`, `<meta>`, and `<link>` tags rendered inside `<Helmet>` are included directly in the React render output and hoisted to `<head>` by React itself. The `context` object will not be populated with helmet state on React 19. If you rely on the `context` for server rendering, you can render these tags directly in your component tree instead and let React 19 handle them natively.
|
||||
|
||||
## Streams
|
||||
|
||||
This package only works with streaming if your `<head>` data is output outside of `renderToNodeStream()`.
|
||||
This is possible if your data hydration method already parses your React tree. Example:
|
||||
|
||||
```javascript
|
||||
import through from 'through';
|
||||
import { renderToNodeStream } from 'react-dom/server';
|
||||
import { getDataFromTree } from 'react-apollo';
|
||||
import { Helmet, HelmetProvider } from 'react-helmet-async';
|
||||
import template from 'server/template';
|
||||
|
||||
const helmetContext = {};
|
||||
|
||||
const app = (
|
||||
<HelmetProvider context={helmetContext}>
|
||||
<App>
|
||||
<Helmet>
|
||||
<title>Hello World</title>
|
||||
<link rel="canonical" href="https://www.tacobell.com/" />
|
||||
</Helmet>
|
||||
<h1>Hello World</h1>
|
||||
</App>
|
||||
</HelmetProvider>
|
||||
);
|
||||
|
||||
await getDataFromTree(app);
|
||||
|
||||
const [header, footer] = template({
|
||||
helmet: helmetContext.helmet,
|
||||
});
|
||||
|
||||
res.status(200);
|
||||
res.write(header);
|
||||
renderToNodeStream(app)
|
||||
.pipe(
|
||||
through(
|
||||
function write(data) {
|
||||
this.queue(data);
|
||||
},
|
||||
function end() {
|
||||
this.queue(footer);
|
||||
this.queue(null);
|
||||
}
|
||||
)
|
||||
)
|
||||
.pipe(res);
|
||||
```
|
||||
|
||||
> **React 19:** React 19's `renderToReadableStream` natively handles `<title>`, `<meta>`, and `<link>` hoisting during streaming, so the manual context extraction shown above is not necessary.
|
||||
|
||||
## Usage in Jest
|
||||
While testing in using jest, if there is a need to emulate SSR, the following string is required to have the test behave the way they are expected to.
|
||||
|
||||
```javascript
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
|
||||
HelmetProvider.canUseDOM = false;
|
||||
```
|
||||
|
||||
> This is only relevant for React 16–18. On React 19, `HelmetProvider` is a passthrough and `canUseDOM` has no effect.
|
||||
|
||||
## Prioritizing tags for SEO
|
||||
|
||||
It is understood that in some cases for SEO, certain tags should appear earlier in the HEAD. Using the `prioritizeSeoTags` flag on any `<Helmet>` component allows the server render of react-helmet-async to expose a method for prioritizing relevant SEO tags.
|
||||
|
||||
In the component:
|
||||
```javascript
|
||||
<Helmet prioritizeSeoTags>
|
||||
<title>A fancy webpage</title>
|
||||
<link rel="notImportant" href="https://www.chipotle.com" />
|
||||
<meta name="whatever" value="notImportant" />
|
||||
<link rel="canonical" href="https://www.tacobell.com" />
|
||||
<meta property="og:title" content="A very important title"/>
|
||||
</Helmet>
|
||||
```
|
||||
|
||||
In your server template:
|
||||
|
||||
```javascript
|
||||
<html>
|
||||
<head>
|
||||
${helmet.title.toString()}
|
||||
${helmet.priority.toString()}
|
||||
${helmet.meta.toString()}
|
||||
${helmet.link.toString()}
|
||||
${helmet.script.toString()}
|
||||
</head>
|
||||
...
|
||||
</html>
|
||||
```
|
||||
|
||||
Will result in:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>A fancy webpage</title>
|
||||
<meta property="og:title" content="A very important title"/>
|
||||
<link rel="canonical" href="https://www.tacobell.com" />
|
||||
<meta name="whatever" value="notImportant" />
|
||||
<link rel="notImportant" href="https://www.chipotle.com" />
|
||||
</head>
|
||||
...
|
||||
</html>
|
||||
```
|
||||
|
||||
A list of prioritized tags and attributes can be found in [constants.ts](./src/constants.ts).
|
||||
|
||||
> **React 19:** The `prioritizeSeoTags` flag has no effect on React 19, since tags are rendered as regular JSX elements and their order in `<head>` is determined by React's rendering order.
|
||||
|
||||
## Usage without Context
|
||||
You can optionally use `<Helmet>` outside a context by manually creating a stateful `HelmetData` instance, and passing that stateful object to each `<Helmet>` instance:
|
||||
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { Helmet, HelmetData } from 'react-helmet-async';
|
||||
|
||||
const helmetData = new HelmetData({});
|
||||
|
||||
const app = (
|
||||
<App>
|
||||
<Helmet helmetData={helmetData}>
|
||||
<title>Hello World</title>
|
||||
<link rel="canonical" href="https://www.tacobell.com/" />
|
||||
</Helmet>
|
||||
<h1>Hello World</h1>
|
||||
</App>
|
||||
);
|
||||
|
||||
const html = renderToString(app);
|
||||
|
||||
const { helmet } = helmetData.context;
|
||||
```
|
||||
|
||||
> **React 19:** The `helmetData` prop is ignored on React 19, since `<Helmet>` renders elements directly without the need for external state management.
|
||||
|
||||
## Compatibility
|
||||
|
||||
| React Version | Behavior |
|
||||
|---|---|
|
||||
| 16.6+ | Full support via `HelmetProvider` context and manual DOM updates |
|
||||
| 17.x | Full support via `HelmetProvider` context and manual DOM updates |
|
||||
| 18.x | Full support via `HelmetProvider` context and manual DOM updates |
|
||||
| 19.x+ | Renders native JSX elements; React handles `<head>` hoisting |
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm test # unit tests
|
||||
pnpm run test:e2e # server + browser e2e tests
|
||||
pnpm run test:all # everything
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the Apache 2.0 License, Copyright © 2018 Scott Taylor
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { Component } from 'react';
|
||||
import type { HelmetServerState } from './types';
|
||||
export interface DispatcherContextProp {
|
||||
setHelmet: (newState: HelmetServerState | null) => void;
|
||||
helmetInstances: {
|
||||
get: () => HelmetDispatcher[];
|
||||
add: (helmet: HelmetDispatcher) => void;
|
||||
remove: (helmet: HelmetDispatcher) => void;
|
||||
};
|
||||
}
|
||||
interface DispatcherProps {
|
||||
context: DispatcherContextProp;
|
||||
}
|
||||
export default class HelmetDispatcher extends Component<DispatcherProps> {
|
||||
rendered: boolean;
|
||||
shouldComponentUpdate(nextProps: DispatcherProps): boolean;
|
||||
componentDidUpdate(): void;
|
||||
componentWillUnmount(): void;
|
||||
emitChange(): void;
|
||||
init(): void;
|
||||
render(): null;
|
||||
}
|
||||
export {};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import type HelmetDispatcher from './Dispatcher';
|
||||
import type { HelmetServerState } from './types';
|
||||
export declare function clearInstances(): void;
|
||||
export interface HelmetDataType {
|
||||
instances: HelmetDispatcher[];
|
||||
context: HelmetDataContext;
|
||||
}
|
||||
interface HelmetDataContext {
|
||||
helmet: HelmetServerState | null;
|
||||
}
|
||||
export declare const isDocument: boolean;
|
||||
export default class HelmetData implements HelmetDataType {
|
||||
instances: never[];
|
||||
canUseDOM: boolean;
|
||||
context: HelmetDataContext;
|
||||
value: {
|
||||
setHelmet: (serverState: HelmetServerState | null) => void;
|
||||
helmetInstances: {
|
||||
get: () => HelmetDispatcher[];
|
||||
add: (instance: HelmetDispatcher) => void;
|
||||
remove: (instance: HelmetDispatcher) => void;
|
||||
};
|
||||
};
|
||||
constructor(context: any, canUseDOM?: boolean);
|
||||
}
|
||||
export {};
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import HelmetData from './HelmetData';
|
||||
import type { HelmetServerState } from './types';
|
||||
export declare const Context: React.Context<{}>;
|
||||
interface ProviderProps {
|
||||
context?: {
|
||||
helmet?: HelmetServerState | null;
|
||||
};
|
||||
}
|
||||
export default class HelmetProvider extends Component<PropsWithChildren<ProviderProps>> {
|
||||
static canUseDOM: boolean;
|
||||
helmetData: HelmetData | null;
|
||||
constructor(props: PropsWithChildren<ProviderProps>);
|
||||
render(): React.JSX.Element;
|
||||
}
|
||||
export {};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import React, { Component } from 'react';
|
||||
import type { HelmetProps } from './types';
|
||||
interface React19DispatcherProps extends HelmetProps {
|
||||
/**
|
||||
* The processed props including mapped children. These come from Helmet's
|
||||
* mapChildrenToProps or the raw API props.
|
||||
*/
|
||||
[key: string]: any;
|
||||
}
|
||||
/**
|
||||
* React 19+ Dispatcher: Instead of manual DOM manipulation, this component
|
||||
* renders actual JSX elements. React 19 automatically hoists <title>, <meta>,
|
||||
* <link>, <style>, and <script async> to <head>.
|
||||
*
|
||||
* For htmlAttributes and bodyAttributes, we still apply via direct DOM
|
||||
* manipulation since React 19 doesn't handle those.
|
||||
*/
|
||||
export default class React19Dispatcher extends Component<React19DispatcherProps> {
|
||||
componentDidMount(): void;
|
||||
componentDidUpdate(): void;
|
||||
componentWillUnmount(): void;
|
||||
resolveTitle(): string | undefined;
|
||||
renderTitle(): React.DetailedReactHTMLElement<{
|
||||
[key: string]: any;
|
||||
}, HTMLElement> | null;
|
||||
renderBase(): React.DetailedReactHTMLElement<{
|
||||
[key: string]: any;
|
||||
}, HTMLElement> | null;
|
||||
renderMeta(): React.DetailedReactHTMLElement<React.HTMLAttributes<HTMLElement>, HTMLElement>[] | null;
|
||||
renderLink(): React.DetailedReactHTMLElement<React.HTMLAttributes<HTMLElement>, HTMLElement>[] | null;
|
||||
renderScript(): React.DetailedReactHTMLElement<React.HTMLAttributes<HTMLElement>, HTMLElement>[] | null;
|
||||
renderStyle(): React.DetailedReactHTMLElement<React.HTMLAttributes<HTMLElement>, HTMLElement>[] | null;
|
||||
renderNoscript(): React.DetailedReactHTMLElement<React.HTMLAttributes<HTMLElement>, HTMLElement>[] | null;
|
||||
render(): React.FunctionComponentElement<{
|
||||
children?: React.ReactNode;
|
||||
}>;
|
||||
}
|
||||
export {};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import type { StateUpdate } from './types';
|
||||
declare const handleStateChangeOnClient: (newState: StateUpdate) => void;
|
||||
export default handleStateChangeOnClient;
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
export declare enum TAG_PROPERTIES {
|
||||
CHARSET = "charset",
|
||||
CSS_TEXT = "cssText",
|
||||
HREF = "href",
|
||||
HTTPEQUIV = "http-equiv",
|
||||
INNER_HTML = "innerHTML",
|
||||
ITEM_PROP = "itemprop",
|
||||
NAME = "name",
|
||||
PROPERTY = "property",
|
||||
REL = "rel",
|
||||
SRC = "src"
|
||||
}
|
||||
export declare enum ATTRIBUTE_NAMES {
|
||||
BODY = "bodyAttributes",
|
||||
HTML = "htmlAttributes",
|
||||
TITLE = "titleAttributes"
|
||||
}
|
||||
export declare enum TAG_NAMES {
|
||||
BASE = "base",
|
||||
BODY = "body",
|
||||
HEAD = "head",
|
||||
HTML = "html",
|
||||
LINK = "link",
|
||||
META = "meta",
|
||||
NOSCRIPT = "noscript",
|
||||
SCRIPT = "script",
|
||||
STYLE = "style",
|
||||
TITLE = "title",
|
||||
FRAGMENT = "Symbol(react.fragment)"
|
||||
}
|
||||
export declare const SEO_PRIORITY_TAGS: {
|
||||
link: {
|
||||
rel: string[];
|
||||
};
|
||||
script: {
|
||||
type: string[];
|
||||
};
|
||||
meta: {
|
||||
charset: string;
|
||||
name: string[];
|
||||
property: string[];
|
||||
};
|
||||
};
|
||||
export declare const VALID_TAG_NAMES: TAG_NAMES[];
|
||||
export declare const REACT_TAG_MAP: {
|
||||
accesskey: string;
|
||||
charset: string;
|
||||
class: string;
|
||||
contenteditable: string;
|
||||
contextmenu: string;
|
||||
'http-equiv': string;
|
||||
itemprop: string;
|
||||
tabindex: string;
|
||||
};
|
||||
export declare const HTML_TAG_MAP: {
|
||||
[key: string]: string;
|
||||
};
|
||||
export declare const HELMET_ATTRIBUTE = "data-rh";
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import type { HelmetProps } from './types';
|
||||
export type { Attributes, BodyProps, HelmetDatum, HelmetHTMLBodyDatum, HelmetHTMLElementDatum, HelmetProps, HelmetServerState, HelmetTags, HtmlProps, LinkProps, MetaProps, StateUpdate, TagList, TitleProps, } from './types';
|
||||
export { default as HelmetData } from './HelmetData';
|
||||
export { default as HelmetProvider } from './Provider';
|
||||
export declare class Helmet extends Component<PropsWithChildren<HelmetProps>> {
|
||||
static defaultProps: {
|
||||
defer: boolean;
|
||||
encodeSpecialCharacters: boolean;
|
||||
prioritizeSeoTags: boolean;
|
||||
};
|
||||
shouldComponentUpdate(nextProps: HelmetProps): boolean;
|
||||
private mapNestedChildrenToProps;
|
||||
private flattenArrayTypeChildren;
|
||||
private mapObjectTypeChildren;
|
||||
private mapArrayTypeChildrenToProps;
|
||||
private warnOnInvalidChildren;
|
||||
private mapChildrenToProps;
|
||||
render(): React.JSX.Element;
|
||||
}
|
||||
+983
@@ -0,0 +1,983 @@
|
||||
// src/index.tsx
|
||||
import React5, { Component as Component4 } from "react";
|
||||
import fastCompare from "react-fast-compare";
|
||||
import invariant from "invariant";
|
||||
|
||||
// src/Provider.tsx
|
||||
import React3, { Component } from "react";
|
||||
|
||||
// src/server.ts
|
||||
import React from "react";
|
||||
|
||||
// src/constants.ts
|
||||
var TAG_NAMES = /* @__PURE__ */ ((TAG_NAMES2) => {
|
||||
TAG_NAMES2["BASE"] = "base";
|
||||
TAG_NAMES2["BODY"] = "body";
|
||||
TAG_NAMES2["HEAD"] = "head";
|
||||
TAG_NAMES2["HTML"] = "html";
|
||||
TAG_NAMES2["LINK"] = "link";
|
||||
TAG_NAMES2["META"] = "meta";
|
||||
TAG_NAMES2["NOSCRIPT"] = "noscript";
|
||||
TAG_NAMES2["SCRIPT"] = "script";
|
||||
TAG_NAMES2["STYLE"] = "style";
|
||||
TAG_NAMES2["TITLE"] = "title";
|
||||
TAG_NAMES2["FRAGMENT"] = "Symbol(react.fragment)";
|
||||
return TAG_NAMES2;
|
||||
})(TAG_NAMES || {});
|
||||
var SEO_PRIORITY_TAGS = {
|
||||
link: { rel: ["amphtml", "canonical", "alternate"] },
|
||||
script: { type: ["application/ld+json"] },
|
||||
meta: {
|
||||
charset: "",
|
||||
name: ["generator", "robots", "description"],
|
||||
property: [
|
||||
"og:type",
|
||||
"og:title",
|
||||
"og:url",
|
||||
"og:image",
|
||||
"og:image:alt",
|
||||
"og:description",
|
||||
"twitter:url",
|
||||
"twitter:title",
|
||||
"twitter:description",
|
||||
"twitter:image",
|
||||
"twitter:image:alt",
|
||||
"twitter:card",
|
||||
"twitter:site"
|
||||
]
|
||||
}
|
||||
};
|
||||
var VALID_TAG_NAMES = Object.values(TAG_NAMES);
|
||||
var REACT_TAG_MAP = {
|
||||
accesskey: "accessKey",
|
||||
charset: "charSet",
|
||||
class: "className",
|
||||
contenteditable: "contentEditable",
|
||||
contextmenu: "contextMenu",
|
||||
"http-equiv": "httpEquiv",
|
||||
itemprop: "itemProp",
|
||||
tabindex: "tabIndex"
|
||||
};
|
||||
var HTML_TAG_MAP = Object.entries(REACT_TAG_MAP).reduce(
|
||||
(carry, [key, value]) => {
|
||||
carry[value] = key;
|
||||
return carry;
|
||||
},
|
||||
{}
|
||||
);
|
||||
var HELMET_ATTRIBUTE = "data-rh";
|
||||
|
||||
// src/utils.ts
|
||||
var HELMET_PROPS = {
|
||||
DEFAULT_TITLE: "defaultTitle",
|
||||
DEFER: "defer",
|
||||
ENCODE_SPECIAL_CHARACTERS: "encodeSpecialCharacters",
|
||||
ON_CHANGE_CLIENT_STATE: "onChangeClientState",
|
||||
TITLE_TEMPLATE: "titleTemplate",
|
||||
PRIORITIZE_SEO_TAGS: "prioritizeSeoTags"
|
||||
};
|
||||
var getInnermostProperty = (propsList, property) => {
|
||||
for (let i = propsList.length - 1; i >= 0; i -= 1) {
|
||||
const props = propsList[i];
|
||||
if (Object.prototype.hasOwnProperty.call(props, property)) {
|
||||
return props[property];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
var getTitleFromPropsList = (propsList) => {
|
||||
let innermostTitle = getInnermostProperty(propsList, "title" /* TITLE */);
|
||||
const innermostTemplate = getInnermostProperty(propsList, HELMET_PROPS.TITLE_TEMPLATE);
|
||||
if (Array.isArray(innermostTitle)) {
|
||||
innermostTitle = innermostTitle.join("");
|
||||
}
|
||||
if (innermostTemplate && innermostTitle) {
|
||||
return innermostTemplate.replace(/%s/g, () => innermostTitle);
|
||||
}
|
||||
const innermostDefaultTitle = getInnermostProperty(propsList, HELMET_PROPS.DEFAULT_TITLE);
|
||||
return innermostTitle || innermostDefaultTitle || void 0;
|
||||
};
|
||||
var getOnChangeClientState = (propsList) => getInnermostProperty(propsList, HELMET_PROPS.ON_CHANGE_CLIENT_STATE) || (() => {
|
||||
});
|
||||
var getAttributesFromPropsList = (tagType, propsList) => propsList.filter((props) => typeof props[tagType] !== "undefined").map((props) => props[tagType]).reduce((tagAttrs, current) => ({ ...tagAttrs, ...current }), {});
|
||||
var getBaseTagFromPropsList = (primaryAttributes, propsList) => propsList.filter((props) => typeof props["base" /* BASE */] !== "undefined").map((props) => props["base" /* BASE */]).reverse().reduce((innermostBaseTag, tag) => {
|
||||
if (!innermostBaseTag.length) {
|
||||
const keys = Object.keys(tag);
|
||||
for (let i = 0; i < keys.length; i += 1) {
|
||||
const attributeKey = keys[i];
|
||||
const lowerCaseAttributeKey = attributeKey.toLowerCase();
|
||||
if (primaryAttributes.indexOf(lowerCaseAttributeKey) !== -1 && tag[lowerCaseAttributeKey]) {
|
||||
return innermostBaseTag.concat(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
return innermostBaseTag;
|
||||
}, []);
|
||||
var warn = (msg) => console && typeof console.warn === "function" && console.warn(msg);
|
||||
var getTagsFromPropsList = (tagName, primaryAttributes, propsList) => {
|
||||
const approvedSeenTags = {};
|
||||
return propsList.filter((props) => {
|
||||
if (Array.isArray(props[tagName])) {
|
||||
return true;
|
||||
}
|
||||
if (typeof props[tagName] !== "undefined") {
|
||||
warn(
|
||||
`Helmet: ${tagName} should be of type "Array". Instead found type "${typeof props[tagName]}"`
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}).map((props) => props[tagName]).reverse().reduce((approvedTags, instanceTags) => {
|
||||
const instanceSeenTags = {};
|
||||
instanceTags.filter((tag) => {
|
||||
let primaryAttributeKey;
|
||||
const keys2 = Object.keys(tag);
|
||||
for (let i = 0; i < keys2.length; i += 1) {
|
||||
const attributeKey = keys2[i];
|
||||
const lowerCaseAttributeKey = attributeKey.toLowerCase();
|
||||
if (primaryAttributes.indexOf(lowerCaseAttributeKey) !== -1 && !(primaryAttributeKey === "rel" /* REL */ && tag[primaryAttributeKey].toLowerCase() === "canonical") && !(lowerCaseAttributeKey === "rel" /* REL */ && tag[lowerCaseAttributeKey].toLowerCase() === "stylesheet")) {
|
||||
primaryAttributeKey = lowerCaseAttributeKey;
|
||||
}
|
||||
if (primaryAttributes.indexOf(attributeKey) !== -1 && (attributeKey === "innerHTML" /* INNER_HTML */ || attributeKey === "cssText" /* CSS_TEXT */ || attributeKey === "itemprop" /* ITEM_PROP */)) {
|
||||
primaryAttributeKey = attributeKey;
|
||||
}
|
||||
}
|
||||
if (!primaryAttributeKey || !tag[primaryAttributeKey]) {
|
||||
return false;
|
||||
}
|
||||
const value = tag[primaryAttributeKey].toLowerCase();
|
||||
if (!approvedSeenTags[primaryAttributeKey]) {
|
||||
approvedSeenTags[primaryAttributeKey] = {};
|
||||
}
|
||||
if (!instanceSeenTags[primaryAttributeKey]) {
|
||||
instanceSeenTags[primaryAttributeKey] = {};
|
||||
}
|
||||
if (!approvedSeenTags[primaryAttributeKey][value]) {
|
||||
instanceSeenTags[primaryAttributeKey][value] = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}).reverse().forEach((tag) => approvedTags.push(tag));
|
||||
const keys = Object.keys(instanceSeenTags);
|
||||
for (let i = 0; i < keys.length; i += 1) {
|
||||
const attributeKey = keys[i];
|
||||
const tagUnion = {
|
||||
...approvedSeenTags[attributeKey],
|
||||
...instanceSeenTags[attributeKey]
|
||||
};
|
||||
approvedSeenTags[attributeKey] = tagUnion;
|
||||
}
|
||||
return approvedTags;
|
||||
}, []).reverse();
|
||||
};
|
||||
var getAnyTrueFromPropsList = (propsList, checkedTag) => {
|
||||
if (Array.isArray(propsList) && propsList.length) {
|
||||
for (let index = 0; index < propsList.length; index += 1) {
|
||||
const prop = propsList[index];
|
||||
if (prop[checkedTag]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
var reducePropsToState = (propsList) => ({
|
||||
baseTag: getBaseTagFromPropsList(["href" /* HREF */], propsList),
|
||||
bodyAttributes: getAttributesFromPropsList("bodyAttributes" /* BODY */, propsList),
|
||||
defer: getInnermostProperty(propsList, HELMET_PROPS.DEFER),
|
||||
encode: getInnermostProperty(propsList, HELMET_PROPS.ENCODE_SPECIAL_CHARACTERS),
|
||||
htmlAttributes: getAttributesFromPropsList("htmlAttributes" /* HTML */, propsList),
|
||||
linkTags: getTagsFromPropsList(
|
||||
"link" /* LINK */,
|
||||
["rel" /* REL */, "href" /* HREF */],
|
||||
propsList
|
||||
),
|
||||
metaTags: getTagsFromPropsList(
|
||||
"meta" /* META */,
|
||||
[
|
||||
"name" /* NAME */,
|
||||
"charset" /* CHARSET */,
|
||||
"http-equiv" /* HTTPEQUIV */,
|
||||
"property" /* PROPERTY */,
|
||||
"itemprop" /* ITEM_PROP */
|
||||
],
|
||||
propsList
|
||||
),
|
||||
noscriptTags: getTagsFromPropsList("noscript" /* NOSCRIPT */, ["innerHTML" /* INNER_HTML */], propsList),
|
||||
onChangeClientState: getOnChangeClientState(propsList),
|
||||
scriptTags: getTagsFromPropsList(
|
||||
"script" /* SCRIPT */,
|
||||
["src" /* SRC */, "innerHTML" /* INNER_HTML */],
|
||||
propsList
|
||||
),
|
||||
styleTags: getTagsFromPropsList("style" /* STYLE */, ["cssText" /* CSS_TEXT */], propsList),
|
||||
title: getTitleFromPropsList(propsList),
|
||||
titleAttributes: getAttributesFromPropsList("titleAttributes" /* TITLE */, propsList),
|
||||
prioritizeSeoTags: getAnyTrueFromPropsList(propsList, HELMET_PROPS.PRIORITIZE_SEO_TAGS)
|
||||
});
|
||||
var flattenArray = (possibleArray) => Array.isArray(possibleArray) ? possibleArray.join("") : possibleArray;
|
||||
var checkIfPropsMatch = (props, toMatch) => {
|
||||
const keys = Object.keys(props);
|
||||
for (let i = 0; i < keys.length; i += 1) {
|
||||
if (toMatch[keys[i]] && toMatch[keys[i]].includes(props[keys[i]])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
var prioritizer = (elementsList, propsToMatch) => {
|
||||
if (Array.isArray(elementsList)) {
|
||||
return elementsList.reduce(
|
||||
(acc, elementAttrs) => {
|
||||
if (checkIfPropsMatch(elementAttrs, propsToMatch)) {
|
||||
acc.priority.push(elementAttrs);
|
||||
} else {
|
||||
acc.default.push(elementAttrs);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ priority: [], default: [] }
|
||||
);
|
||||
}
|
||||
return { default: elementsList, priority: [] };
|
||||
};
|
||||
var without = (obj, key) => {
|
||||
return {
|
||||
...obj,
|
||||
[key]: void 0
|
||||
};
|
||||
};
|
||||
|
||||
// src/server.ts
|
||||
var SELF_CLOSING_TAGS = ["noscript" /* NOSCRIPT */, "script" /* SCRIPT */, "style" /* STYLE */];
|
||||
var encodeSpecialCharacters = (str, encode = true) => {
|
||||
if (encode === false) {
|
||||
return String(str);
|
||||
}
|
||||
return String(str).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
||||
};
|
||||
var generateElementAttributesAsString = (attributes) => Object.keys(attributes).reduce((str, key) => {
|
||||
const attr = typeof attributes[key] !== "undefined" ? `${key}="${attributes[key]}"` : `${key}`;
|
||||
return str ? `${str} ${attr}` : attr;
|
||||
}, "");
|
||||
var generateTitleAsString = (type, title, attributes, encode) => {
|
||||
const attributeString = generateElementAttributesAsString(attributes);
|
||||
const flattenedTitle = flattenArray(title);
|
||||
return attributeString ? `<${type} ${HELMET_ATTRIBUTE}="true" ${attributeString}>${encodeSpecialCharacters(
|
||||
flattenedTitle,
|
||||
encode
|
||||
)}</${type}>` : `<${type} ${HELMET_ATTRIBUTE}="true">${encodeSpecialCharacters(
|
||||
flattenedTitle,
|
||||
encode
|
||||
)}</${type}>`;
|
||||
};
|
||||
var generateTagsAsString = (type, tags, encode = true) => tags.reduce((str, t) => {
|
||||
const tag = t;
|
||||
const attributeHtml = Object.keys(tag).filter(
|
||||
(attribute) => !(attribute === "innerHTML" /* INNER_HTML */ || attribute === "cssText" /* CSS_TEXT */)
|
||||
).reduce((string, attribute) => {
|
||||
const attr = typeof tag[attribute] === "undefined" ? attribute : `${attribute}="${encodeSpecialCharacters(tag[attribute], encode)}"`;
|
||||
return string ? `${string} ${attr}` : attr;
|
||||
}, "");
|
||||
const tagContent = tag.innerHTML || tag.cssText || "";
|
||||
const isSelfClosing = SELF_CLOSING_TAGS.indexOf(type) === -1;
|
||||
return `${str}<${type} ${HELMET_ATTRIBUTE}="true" ${attributeHtml}${isSelfClosing ? `/>` : `>${tagContent}</${type}>`}`;
|
||||
}, "");
|
||||
var convertElementAttributesToReactProps = (attributes, initProps = {}) => Object.keys(attributes).reduce((obj, key) => {
|
||||
const mapped = REACT_TAG_MAP[key];
|
||||
obj[mapped || key] = attributes[key];
|
||||
return obj;
|
||||
}, initProps);
|
||||
var generateTitleAsReactComponent = (_type, title, attributes) => {
|
||||
const initProps = {
|
||||
key: title,
|
||||
[HELMET_ATTRIBUTE]: true
|
||||
};
|
||||
const props = convertElementAttributesToReactProps(attributes, initProps);
|
||||
return [React.createElement("title" /* TITLE */, props, title)];
|
||||
};
|
||||
var generateTagsAsReactComponent = (type, tags) => tags.map((tag, i) => {
|
||||
const mappedTag = {
|
||||
key: i,
|
||||
[HELMET_ATTRIBUTE]: true
|
||||
};
|
||||
Object.keys(tag).forEach((attribute) => {
|
||||
const mapped = REACT_TAG_MAP[attribute];
|
||||
const mappedAttribute = mapped || attribute;
|
||||
if (mappedAttribute === "innerHTML" /* INNER_HTML */ || mappedAttribute === "cssText" /* CSS_TEXT */) {
|
||||
const content = tag.innerHTML || tag.cssText;
|
||||
mappedTag.dangerouslySetInnerHTML = { __html: content };
|
||||
} else {
|
||||
mappedTag[mappedAttribute] = tag[attribute];
|
||||
}
|
||||
});
|
||||
return React.createElement(type, mappedTag);
|
||||
});
|
||||
var getMethodsForTag = (type, tags, encode = true) => {
|
||||
switch (type) {
|
||||
case "title" /* TITLE */:
|
||||
return {
|
||||
toComponent: () => generateTitleAsReactComponent(type, tags.title, tags.titleAttributes),
|
||||
toString: () => generateTitleAsString(type, tags.title, tags.titleAttributes, encode)
|
||||
};
|
||||
case "bodyAttributes" /* BODY */:
|
||||
case "htmlAttributes" /* HTML */:
|
||||
return {
|
||||
toComponent: () => convertElementAttributesToReactProps(tags),
|
||||
toString: () => generateElementAttributesAsString(tags)
|
||||
};
|
||||
default:
|
||||
return {
|
||||
toComponent: () => generateTagsAsReactComponent(type, tags),
|
||||
toString: () => generateTagsAsString(type, tags, encode)
|
||||
};
|
||||
}
|
||||
};
|
||||
var getPriorityMethods = ({ metaTags, linkTags, scriptTags, encode }) => {
|
||||
const meta = prioritizer(metaTags, SEO_PRIORITY_TAGS.meta);
|
||||
const link = prioritizer(linkTags, SEO_PRIORITY_TAGS.link);
|
||||
const script = prioritizer(scriptTags, SEO_PRIORITY_TAGS.script);
|
||||
const priorityMethods = {
|
||||
toComponent: () => [
|
||||
...generateTagsAsReactComponent("meta" /* META */, meta.priority),
|
||||
...generateTagsAsReactComponent("link" /* LINK */, link.priority),
|
||||
...generateTagsAsReactComponent("script" /* SCRIPT */, script.priority)
|
||||
],
|
||||
toString: () => (
|
||||
// generate all the tags as strings and concatenate them
|
||||
`${getMethodsForTag("meta" /* META */, meta.priority, encode)} ${getMethodsForTag(
|
||||
"link" /* LINK */,
|
||||
link.priority,
|
||||
encode
|
||||
)} ${getMethodsForTag("script" /* SCRIPT */, script.priority, encode)}`
|
||||
)
|
||||
};
|
||||
return {
|
||||
priorityMethods,
|
||||
metaTags: meta.default,
|
||||
linkTags: link.default,
|
||||
scriptTags: script.default
|
||||
};
|
||||
};
|
||||
var mapStateOnServer = (props) => {
|
||||
const {
|
||||
baseTag,
|
||||
bodyAttributes,
|
||||
encode = true,
|
||||
htmlAttributes,
|
||||
noscriptTags,
|
||||
styleTags,
|
||||
title = "",
|
||||
titleAttributes,
|
||||
prioritizeSeoTags
|
||||
} = props;
|
||||
let { linkTags, metaTags, scriptTags } = props;
|
||||
let priorityMethods = {
|
||||
toComponent: () => [],
|
||||
toString: () => ""
|
||||
};
|
||||
if (prioritizeSeoTags) {
|
||||
({ priorityMethods, linkTags, metaTags, scriptTags } = getPriorityMethods(props));
|
||||
}
|
||||
return {
|
||||
priority: priorityMethods,
|
||||
base: getMethodsForTag("base" /* BASE */, baseTag, encode),
|
||||
bodyAttributes: getMethodsForTag("bodyAttributes" /* BODY */, bodyAttributes, encode),
|
||||
htmlAttributes: getMethodsForTag("htmlAttributes" /* HTML */, htmlAttributes, encode),
|
||||
link: getMethodsForTag("link" /* LINK */, linkTags, encode),
|
||||
meta: getMethodsForTag("meta" /* META */, metaTags, encode),
|
||||
noscript: getMethodsForTag("noscript" /* NOSCRIPT */, noscriptTags, encode),
|
||||
script: getMethodsForTag("script" /* SCRIPT */, scriptTags, encode),
|
||||
style: getMethodsForTag("style" /* STYLE */, styleTags, encode),
|
||||
title: getMethodsForTag("title" /* TITLE */, { title, titleAttributes }, encode)
|
||||
};
|
||||
};
|
||||
var server_default = mapStateOnServer;
|
||||
|
||||
// src/HelmetData.ts
|
||||
var instances = [];
|
||||
var isDocument = !!(typeof window !== "undefined" && window.document && window.document.createElement);
|
||||
var HelmetData = class {
|
||||
instances = [];
|
||||
canUseDOM = isDocument;
|
||||
context;
|
||||
value = {
|
||||
setHelmet: (serverState) => {
|
||||
this.context.helmet = serverState;
|
||||
},
|
||||
helmetInstances: {
|
||||
get: () => this.canUseDOM ? instances : this.instances,
|
||||
add: (instance) => {
|
||||
(this.canUseDOM ? instances : this.instances).push(instance);
|
||||
},
|
||||
remove: (instance) => {
|
||||
const index = (this.canUseDOM ? instances : this.instances).indexOf(instance);
|
||||
(this.canUseDOM ? instances : this.instances).splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
constructor(context, canUseDOM) {
|
||||
this.context = context;
|
||||
this.canUseDOM = canUseDOM || false;
|
||||
if (!canUseDOM) {
|
||||
context.helmet = server_default({
|
||||
baseTag: [],
|
||||
bodyAttributes: {},
|
||||
encodeSpecialCharacters: true,
|
||||
htmlAttributes: {},
|
||||
linkTags: [],
|
||||
metaTags: [],
|
||||
noscriptTags: [],
|
||||
scriptTags: [],
|
||||
styleTags: [],
|
||||
title: "",
|
||||
titleAttributes: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// src/reactVersion.ts
|
||||
import React2 from "react";
|
||||
var major = parseInt(React2.version.split(".")[0], 10);
|
||||
var isReact19 = major >= 19;
|
||||
|
||||
// src/Provider.tsx
|
||||
var defaultValue = {};
|
||||
var Context = React3.createContext(defaultValue);
|
||||
var HelmetProvider = class _HelmetProvider extends Component {
|
||||
static canUseDOM = isDocument;
|
||||
helmetData;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (isReact19) {
|
||||
this.helmetData = null;
|
||||
} else {
|
||||
this.helmetData = new HelmetData(this.props.context || {}, _HelmetProvider.canUseDOM);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
if (isReact19) {
|
||||
return /* @__PURE__ */ React3.createElement(React3.Fragment, null, this.props.children);
|
||||
}
|
||||
return /* @__PURE__ */ React3.createElement(Context.Provider, { value: this.helmetData.value }, this.props.children);
|
||||
}
|
||||
};
|
||||
|
||||
// src/Dispatcher.tsx
|
||||
import { Component as Component2 } from "react";
|
||||
import shallowEqual from "shallowequal";
|
||||
|
||||
// src/client.ts
|
||||
var updateTags = (type, tags) => {
|
||||
const headElement = document.head || document.querySelector("head" /* HEAD */);
|
||||
const tagNodes = headElement.querySelectorAll(`${type}[${HELMET_ATTRIBUTE}]`);
|
||||
const oldTags = [].slice.call(tagNodes);
|
||||
const newTags = [];
|
||||
let indexToDelete;
|
||||
if (tags && tags.length) {
|
||||
tags.forEach((tag) => {
|
||||
const newElement = document.createElement(type);
|
||||
for (const attribute in tag) {
|
||||
if (Object.prototype.hasOwnProperty.call(tag, attribute)) {
|
||||
if (attribute === "innerHTML" /* INNER_HTML */) {
|
||||
newElement.innerHTML = tag.innerHTML;
|
||||
} else if (attribute === "cssText" /* CSS_TEXT */) {
|
||||
const cssText = tag.cssText;
|
||||
newElement.appendChild(document.createTextNode(cssText));
|
||||
} else {
|
||||
const attr = attribute;
|
||||
const value = typeof tag[attr] === "undefined" ? "" : tag[attr];
|
||||
newElement.setAttribute(attribute, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
newElement.setAttribute(HELMET_ATTRIBUTE, "true");
|
||||
if (oldTags.some((existingTag, index) => {
|
||||
indexToDelete = index;
|
||||
return newElement.isEqualNode(existingTag);
|
||||
})) {
|
||||
oldTags.splice(indexToDelete, 1);
|
||||
} else {
|
||||
newTags.push(newElement);
|
||||
}
|
||||
});
|
||||
}
|
||||
oldTags.forEach((tag) => tag.parentNode?.removeChild(tag));
|
||||
newTags.forEach((tag) => headElement.appendChild(tag));
|
||||
return {
|
||||
oldTags,
|
||||
newTags
|
||||
};
|
||||
};
|
||||
var updateAttributes = (tagName, attributes) => {
|
||||
const elementTag = document.getElementsByTagName(tagName)[0];
|
||||
if (!elementTag) {
|
||||
return;
|
||||
}
|
||||
const helmetAttributeString = elementTag.getAttribute(HELMET_ATTRIBUTE);
|
||||
const helmetAttributes = helmetAttributeString ? helmetAttributeString.split(",") : [];
|
||||
const attributesToRemove = [...helmetAttributes];
|
||||
const attributeKeys = Object.keys(attributes);
|
||||
for (const attribute of attributeKeys) {
|
||||
const value = attributes[attribute] || "";
|
||||
if (elementTag.getAttribute(attribute) !== value) {
|
||||
elementTag.setAttribute(attribute, value);
|
||||
}
|
||||
if (helmetAttributes.indexOf(attribute) === -1) {
|
||||
helmetAttributes.push(attribute);
|
||||
}
|
||||
const indexToSave = attributesToRemove.indexOf(attribute);
|
||||
if (indexToSave !== -1) {
|
||||
attributesToRemove.splice(indexToSave, 1);
|
||||
}
|
||||
}
|
||||
for (let i = attributesToRemove.length - 1; i >= 0; i -= 1) {
|
||||
elementTag.removeAttribute(attributesToRemove[i]);
|
||||
}
|
||||
if (helmetAttributes.length === attributesToRemove.length) {
|
||||
elementTag.removeAttribute(HELMET_ATTRIBUTE);
|
||||
} else if (elementTag.getAttribute(HELMET_ATTRIBUTE) !== attributeKeys.join(",")) {
|
||||
elementTag.setAttribute(HELMET_ATTRIBUTE, attributeKeys.join(","));
|
||||
}
|
||||
};
|
||||
var updateTitle = (title, attributes) => {
|
||||
if (typeof title !== "undefined" && document.title !== title) {
|
||||
document.title = flattenArray(title);
|
||||
}
|
||||
updateAttributes("title" /* TITLE */, attributes);
|
||||
};
|
||||
var commitTagChanges = (newState, cb) => {
|
||||
const {
|
||||
baseTag,
|
||||
bodyAttributes,
|
||||
htmlAttributes,
|
||||
linkTags,
|
||||
metaTags,
|
||||
noscriptTags,
|
||||
onChangeClientState,
|
||||
scriptTags,
|
||||
styleTags,
|
||||
title,
|
||||
titleAttributes
|
||||
} = newState;
|
||||
updateAttributes("body" /* BODY */, bodyAttributes);
|
||||
updateAttributes("html" /* HTML */, htmlAttributes);
|
||||
updateTitle(title, titleAttributes);
|
||||
const tagUpdates = {
|
||||
baseTag: updateTags("base" /* BASE */, baseTag),
|
||||
linkTags: updateTags("link" /* LINK */, linkTags),
|
||||
metaTags: updateTags("meta" /* META */, metaTags),
|
||||
noscriptTags: updateTags("noscript" /* NOSCRIPT */, noscriptTags),
|
||||
scriptTags: updateTags("script" /* SCRIPT */, scriptTags),
|
||||
styleTags: updateTags("style" /* STYLE */, styleTags)
|
||||
};
|
||||
const addedTags = {};
|
||||
const removedTags = {};
|
||||
Object.keys(tagUpdates).forEach((tagType) => {
|
||||
const { newTags, oldTags } = tagUpdates[tagType];
|
||||
if (newTags.length) {
|
||||
addedTags[tagType] = newTags;
|
||||
}
|
||||
if (oldTags.length) {
|
||||
removedTags[tagType] = tagUpdates[tagType].oldTags;
|
||||
}
|
||||
});
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
onChangeClientState(newState, addedTags, removedTags);
|
||||
};
|
||||
var _helmetCallback = null;
|
||||
var handleStateChangeOnClient = (newState) => {
|
||||
if (_helmetCallback) {
|
||||
cancelAnimationFrame(_helmetCallback);
|
||||
}
|
||||
if (newState.defer) {
|
||||
_helmetCallback = requestAnimationFrame(() => {
|
||||
commitTagChanges(newState, () => {
|
||||
_helmetCallback = null;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
commitTagChanges(newState);
|
||||
_helmetCallback = null;
|
||||
}
|
||||
};
|
||||
var client_default = handleStateChangeOnClient;
|
||||
|
||||
// src/Dispatcher.tsx
|
||||
var HelmetDispatcher = class extends Component2 {
|
||||
rendered = false;
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !shallowEqual(nextProps, this.props);
|
||||
}
|
||||
componentDidUpdate() {
|
||||
this.emitChange();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
const { helmetInstances } = this.props.context;
|
||||
helmetInstances.remove(this);
|
||||
this.emitChange();
|
||||
}
|
||||
emitChange() {
|
||||
const { helmetInstances, setHelmet } = this.props.context;
|
||||
let serverState = null;
|
||||
const state = reducePropsToState(
|
||||
helmetInstances.get().map((instance) => {
|
||||
const { context: _context, ...props } = instance.props;
|
||||
return props;
|
||||
})
|
||||
);
|
||||
if (HelmetProvider.canUseDOM) {
|
||||
client_default(state);
|
||||
} else if (server_default) {
|
||||
serverState = server_default(state);
|
||||
}
|
||||
setHelmet(serverState);
|
||||
}
|
||||
// componentWillMount will be deprecated
|
||||
// for SSR, initialize on first render
|
||||
// constructor is also unsafe in StrictMode
|
||||
init() {
|
||||
if (this.rendered) {
|
||||
return;
|
||||
}
|
||||
this.rendered = true;
|
||||
const { helmetInstances } = this.props.context;
|
||||
helmetInstances.add(this);
|
||||
this.emitChange();
|
||||
}
|
||||
render() {
|
||||
this.init();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// src/React19Dispatcher.tsx
|
||||
import React4, { Component as Component3 } from "react";
|
||||
var react19Instances = [];
|
||||
var toHtmlAttributes = (props) => {
|
||||
const result = {};
|
||||
for (const key of Object.keys(props)) {
|
||||
result[HTML_TAG_MAP[key] || key] = props[key];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
var toReactProps = (attrs) => {
|
||||
const result = {};
|
||||
for (const key of Object.keys(attrs)) {
|
||||
const mapped = REACT_TAG_MAP[key];
|
||||
result[mapped || key] = attrs[key];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
var applyAttributes = (tagName, attributes) => {
|
||||
if (!isDocument)
|
||||
return;
|
||||
const el = document.getElementsByTagName(tagName)[0];
|
||||
if (!el)
|
||||
return;
|
||||
const managedAttr = "data-rh-managed";
|
||||
const prev = el.getAttribute(managedAttr);
|
||||
const prevKeys = prev ? prev.split(",") : [];
|
||||
const nextKeys = Object.keys(attributes);
|
||||
for (const key of prevKeys) {
|
||||
if (!nextKeys.includes(key)) {
|
||||
el.removeAttribute(key);
|
||||
}
|
||||
}
|
||||
for (const key of nextKeys) {
|
||||
const value = attributes[key];
|
||||
if (value === void 0 || value === null || value === false) {
|
||||
el.removeAttribute(key);
|
||||
} else if (value === true) {
|
||||
el.setAttribute(key, "");
|
||||
} else {
|
||||
el.setAttribute(key, String(value));
|
||||
}
|
||||
}
|
||||
if (nextKeys.length > 0) {
|
||||
el.setAttribute(managedAttr, nextKeys.join(","));
|
||||
} else {
|
||||
el.removeAttribute(managedAttr);
|
||||
}
|
||||
};
|
||||
var syncAllAttributes = () => {
|
||||
const htmlAttrs = {};
|
||||
const bodyAttrs = {};
|
||||
for (const instance of react19Instances) {
|
||||
const { htmlAttributes, bodyAttributes } = instance.props;
|
||||
if (htmlAttributes) {
|
||||
Object.assign(htmlAttrs, toHtmlAttributes(htmlAttributes));
|
||||
}
|
||||
if (bodyAttributes) {
|
||||
Object.assign(bodyAttrs, toHtmlAttributes(bodyAttributes));
|
||||
}
|
||||
}
|
||||
applyAttributes("html" /* HTML */, htmlAttrs);
|
||||
applyAttributes("body" /* BODY */, bodyAttrs);
|
||||
};
|
||||
var React19Dispatcher = class extends Component3 {
|
||||
componentDidMount() {
|
||||
react19Instances.push(this);
|
||||
syncAllAttributes();
|
||||
}
|
||||
componentDidUpdate() {
|
||||
syncAllAttributes();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
const index = react19Instances.indexOf(this);
|
||||
if (index !== -1) {
|
||||
react19Instances.splice(index, 1);
|
||||
}
|
||||
syncAllAttributes();
|
||||
}
|
||||
resolveTitle() {
|
||||
const { title, titleTemplate, defaultTitle } = this.props;
|
||||
if (title && titleTemplate) {
|
||||
return titleTemplate.replace(/%s/g, () => Array.isArray(title) ? title.join("") : title);
|
||||
}
|
||||
return title || defaultTitle || void 0;
|
||||
}
|
||||
renderTitle() {
|
||||
const title = this.resolveTitle();
|
||||
if (title === void 0)
|
||||
return null;
|
||||
const titleAttributes = this.props.titleAttributes || {};
|
||||
return React4.createElement("title" /* TITLE */, toReactProps(titleAttributes), title);
|
||||
}
|
||||
renderBase() {
|
||||
const { base } = this.props;
|
||||
if (!base)
|
||||
return null;
|
||||
return React4.createElement("base" /* BASE */, toReactProps(base));
|
||||
}
|
||||
renderMeta() {
|
||||
const { meta } = this.props;
|
||||
if (!meta || !Array.isArray(meta))
|
||||
return null;
|
||||
return meta.map(
|
||||
(attrs, i) => React4.createElement("meta" /* META */, {
|
||||
key: i,
|
||||
...toReactProps(attrs)
|
||||
})
|
||||
);
|
||||
}
|
||||
renderLink() {
|
||||
const { link } = this.props;
|
||||
if (!link || !Array.isArray(link))
|
||||
return null;
|
||||
return link.map(
|
||||
(attrs, i) => React4.createElement("link" /* LINK */, {
|
||||
key: i,
|
||||
...toReactProps(attrs)
|
||||
})
|
||||
);
|
||||
}
|
||||
renderScript() {
|
||||
const { script } = this.props;
|
||||
if (!script || !Array.isArray(script))
|
||||
return null;
|
||||
return script.map((attrs, i) => {
|
||||
const { innerHTML, ...rest } = attrs;
|
||||
const props = toReactProps(rest);
|
||||
if (innerHTML) {
|
||||
props.dangerouslySetInnerHTML = { __html: innerHTML };
|
||||
}
|
||||
return React4.createElement("script" /* SCRIPT */, { key: i, ...props });
|
||||
});
|
||||
}
|
||||
renderStyle() {
|
||||
const { style } = this.props;
|
||||
if (!style || !Array.isArray(style))
|
||||
return null;
|
||||
return style.map((attrs, i) => {
|
||||
const { cssText, ...rest } = attrs;
|
||||
const props = toReactProps(rest);
|
||||
if (cssText) {
|
||||
props.dangerouslySetInnerHTML = { __html: cssText };
|
||||
}
|
||||
return React4.createElement("style" /* STYLE */, { key: i, ...props });
|
||||
});
|
||||
}
|
||||
renderNoscript() {
|
||||
const { noscript } = this.props;
|
||||
if (!noscript || !Array.isArray(noscript))
|
||||
return null;
|
||||
return noscript.map((attrs, i) => {
|
||||
const { innerHTML, ...rest } = attrs;
|
||||
const props = toReactProps(rest);
|
||||
if (innerHTML) {
|
||||
props.dangerouslySetInnerHTML = { __html: innerHTML };
|
||||
}
|
||||
return React4.createElement("noscript" /* NOSCRIPT */, { key: i, ...props });
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return React4.createElement(
|
||||
React4.Fragment,
|
||||
null,
|
||||
this.renderTitle(),
|
||||
this.renderBase(),
|
||||
this.renderMeta(),
|
||||
this.renderLink(),
|
||||
this.renderScript(),
|
||||
this.renderStyle(),
|
||||
this.renderNoscript()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// src/index.tsx
|
||||
var Helmet = class extends Component4 {
|
||||
static defaultProps = {
|
||||
defer: true,
|
||||
encodeSpecialCharacters: true,
|
||||
prioritizeSeoTags: false
|
||||
};
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !fastCompare(without(this.props, "helmetData"), without(nextProps, "helmetData"));
|
||||
}
|
||||
mapNestedChildrenToProps(child, nestedChildren) {
|
||||
if (!nestedChildren) {
|
||||
return null;
|
||||
}
|
||||
switch (child.type) {
|
||||
case "script" /* SCRIPT */:
|
||||
case "noscript" /* NOSCRIPT */:
|
||||
return {
|
||||
innerHTML: nestedChildren
|
||||
};
|
||||
case "style" /* STYLE */:
|
||||
return {
|
||||
cssText: nestedChildren
|
||||
};
|
||||
default:
|
||||
throw new Error(
|
||||
`<${child.type} /> elements are self-closing and can not contain children. Refer to our API for more information.`
|
||||
);
|
||||
}
|
||||
}
|
||||
flattenArrayTypeChildren(child, arrayTypeChildren, newChildProps, nestedChildren) {
|
||||
return {
|
||||
...arrayTypeChildren,
|
||||
[child.type]: [
|
||||
...arrayTypeChildren[child.type] || [],
|
||||
{
|
||||
...newChildProps,
|
||||
...this.mapNestedChildrenToProps(child, nestedChildren)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
mapObjectTypeChildren(child, newProps, newChildProps, nestedChildren) {
|
||||
switch (child.type) {
|
||||
case "title" /* TITLE */:
|
||||
return {
|
||||
...newProps,
|
||||
[child.type]: nestedChildren,
|
||||
titleAttributes: { ...newChildProps }
|
||||
};
|
||||
case "body" /* BODY */:
|
||||
return {
|
||||
...newProps,
|
||||
bodyAttributes: { ...newChildProps }
|
||||
};
|
||||
case "html" /* HTML */:
|
||||
return {
|
||||
...newProps,
|
||||
htmlAttributes: { ...newChildProps }
|
||||
};
|
||||
default:
|
||||
return {
|
||||
...newProps,
|
||||
[child.type]: { ...newChildProps }
|
||||
};
|
||||
}
|
||||
}
|
||||
mapArrayTypeChildrenToProps(arrayTypeChildren, newProps) {
|
||||
let newFlattenedProps = { ...newProps };
|
||||
Object.keys(arrayTypeChildren).forEach((arrayChildName) => {
|
||||
newFlattenedProps = {
|
||||
...newFlattenedProps,
|
||||
[arrayChildName]: arrayTypeChildren[arrayChildName]
|
||||
};
|
||||
});
|
||||
return newFlattenedProps;
|
||||
}
|
||||
warnOnInvalidChildren(child, nestedChildren) {
|
||||
invariant(
|
||||
VALID_TAG_NAMES.some((name) => child.type === name),
|
||||
typeof child.type === "function" ? `You may be attempting to nest <Helmet> components within each other, which is not allowed. Refer to our API for more information.` : `Only elements types ${VALID_TAG_NAMES.join(
|
||||
", "
|
||||
)} are allowed. Helmet does not support rendering <${child.type}> elements. Refer to our API for more information.`
|
||||
);
|
||||
invariant(
|
||||
!nestedChildren || typeof nestedChildren === "string" || Array.isArray(nestedChildren) && !nestedChildren.some((nestedChild) => typeof nestedChild !== "string"),
|
||||
`Helmet expects a string as a child of <${child.type}>. Did you forget to wrap your children in braces? ( <${child.type}>{\`\`}</${child.type}> ) Refer to our API for more information.`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
mapChildrenToProps(children, newProps) {
|
||||
let arrayTypeChildren = {};
|
||||
React5.Children.forEach(children, (child) => {
|
||||
if (!child || !child.props) {
|
||||
return;
|
||||
}
|
||||
const { children: nestedChildren, ...childProps } = child.props;
|
||||
const newChildProps = Object.keys(childProps).reduce((obj, key) => {
|
||||
obj[HTML_TAG_MAP[key] || key] = childProps[key];
|
||||
return obj;
|
||||
}, {});
|
||||
let { type } = child;
|
||||
if (typeof type === "symbol") {
|
||||
type = type.toString();
|
||||
} else {
|
||||
this.warnOnInvalidChildren(child, nestedChildren);
|
||||
}
|
||||
switch (type) {
|
||||
case "Symbol(react.fragment)" /* FRAGMENT */:
|
||||
newProps = this.mapChildrenToProps(nestedChildren, newProps);
|
||||
break;
|
||||
case "link" /* LINK */:
|
||||
case "meta" /* META */:
|
||||
case "noscript" /* NOSCRIPT */:
|
||||
case "script" /* SCRIPT */:
|
||||
case "style" /* STYLE */:
|
||||
arrayTypeChildren = this.flattenArrayTypeChildren(
|
||||
child,
|
||||
arrayTypeChildren,
|
||||
newChildProps,
|
||||
nestedChildren
|
||||
);
|
||||
break;
|
||||
default:
|
||||
newProps = this.mapObjectTypeChildren(child, newProps, newChildProps, nestedChildren);
|
||||
break;
|
||||
}
|
||||
});
|
||||
return this.mapArrayTypeChildrenToProps(arrayTypeChildren, newProps);
|
||||
}
|
||||
render() {
|
||||
const { children, ...props } = this.props;
|
||||
let newProps = { ...props };
|
||||
let { helmetData } = props;
|
||||
if (children) {
|
||||
newProps = this.mapChildrenToProps(children, newProps);
|
||||
}
|
||||
if (helmetData && !(helmetData instanceof HelmetData)) {
|
||||
const data = helmetData;
|
||||
helmetData = new HelmetData(data.context, true);
|
||||
delete newProps.helmetData;
|
||||
}
|
||||
if (isReact19) {
|
||||
return /* @__PURE__ */ React5.createElement(React19Dispatcher, { ...newProps });
|
||||
}
|
||||
return helmetData ? /* @__PURE__ */ React5.createElement(HelmetDispatcher, { ...newProps, context: helmetData.value }) : /* @__PURE__ */ React5.createElement(Context.Consumer, null, (context) => /* @__PURE__ */ React5.createElement(HelmetDispatcher, { ...newProps, context }));
|
||||
}
|
||||
};
|
||||
export {
|
||||
Helmet,
|
||||
HelmetData,
|
||||
HelmetProvider
|
||||
};
|
||||
+1014
File diff suppressed because it is too large
Load Diff
+1
@@ -0,0 +1 @@
|
||||
export declare const isReact19: boolean;
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import type { MappedServerState } from './types';
|
||||
declare const mapStateOnServer: (props: MappedServerState) => {
|
||||
priority: {
|
||||
toComponent: () => React.ReactElement[];
|
||||
toString: () => string;
|
||||
};
|
||||
base: {
|
||||
toComponent: () => {};
|
||||
toString: () => string;
|
||||
};
|
||||
bodyAttributes: {
|
||||
toComponent: () => {};
|
||||
toString: () => string;
|
||||
};
|
||||
htmlAttributes: {
|
||||
toComponent: () => {};
|
||||
toString: () => string;
|
||||
};
|
||||
link: {
|
||||
toComponent: () => {};
|
||||
toString: () => string;
|
||||
};
|
||||
meta: {
|
||||
toComponent: () => {};
|
||||
toString: () => string;
|
||||
};
|
||||
noscript: {
|
||||
toComponent: () => {};
|
||||
toString: () => string;
|
||||
};
|
||||
script: {
|
||||
toComponent: () => {};
|
||||
toString: () => string;
|
||||
};
|
||||
style: {
|
||||
toComponent: () => {};
|
||||
toString: () => string;
|
||||
};
|
||||
title: {
|
||||
toComponent: () => {};
|
||||
toString: () => string;
|
||||
};
|
||||
};
|
||||
export default mapStateOnServer;
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
import type { HTMLAttributes, JSX } from 'react';
|
||||
import type HelmetData from './HelmetData';
|
||||
export type Attributes = {
|
||||
[key: string]: string;
|
||||
};
|
||||
interface OtherElementAttributes {
|
||||
[key: string]: string | number | boolean | null | undefined;
|
||||
}
|
||||
export type HtmlProps = JSX.IntrinsicElements['html'] & OtherElementAttributes;
|
||||
export type BodyProps = JSX.IntrinsicElements['body'] & OtherElementAttributes;
|
||||
export type LinkProps = JSX.IntrinsicElements['link'];
|
||||
export type MetaProps = JSX.IntrinsicElements['meta'] & {
|
||||
charset?: string | undefined;
|
||||
'http-equiv'?: string | undefined;
|
||||
itemprop?: string | undefined;
|
||||
};
|
||||
export type TitleProps = HTMLAttributes<HTMLTitleElement>;
|
||||
export interface HelmetTags {
|
||||
baseTag: HTMLBaseElement[];
|
||||
linkTags: HTMLLinkElement[];
|
||||
metaTags: HTMLMetaElement[];
|
||||
noscriptTags: HTMLElement[];
|
||||
scriptTags: HTMLScriptElement[];
|
||||
styleTags: HTMLStyleElement[];
|
||||
}
|
||||
export interface HelmetDatum {
|
||||
toString(): string;
|
||||
toComponent(): React.ReactElement[];
|
||||
}
|
||||
export interface HelmetHTMLBodyDatum {
|
||||
toString(): string;
|
||||
toComponent(): React.HTMLAttributes<HTMLBodyElement>;
|
||||
}
|
||||
export interface HelmetHTMLElementDatum {
|
||||
toString(): string;
|
||||
toComponent(): React.HTMLAttributes<HTMLHtmlElement>;
|
||||
}
|
||||
export interface HelmetServerState {
|
||||
base: HelmetDatum;
|
||||
bodyAttributes: HelmetHTMLBodyDatum;
|
||||
htmlAttributes: HelmetHTMLElementDatum;
|
||||
link: HelmetDatum;
|
||||
meta: HelmetDatum;
|
||||
noscript: HelmetDatum;
|
||||
script: HelmetDatum;
|
||||
style: HelmetDatum;
|
||||
title: HelmetDatum;
|
||||
priority: HelmetDatum;
|
||||
}
|
||||
export type MappedServerState = HelmetProps & HelmetTags & {
|
||||
encode?: boolean;
|
||||
};
|
||||
export interface TagList {
|
||||
[key: string]: HTMLElement[];
|
||||
}
|
||||
export interface StateUpdate extends HelmetTags {
|
||||
bodyAttributes: BodyProps;
|
||||
defer: boolean;
|
||||
htmlAttributes: HtmlProps;
|
||||
onChangeClientState: (newState: StateUpdate, addedTags: TagList, removedTags: TagList) => void;
|
||||
title: string;
|
||||
titleAttributes: TitleProps;
|
||||
}
|
||||
export interface HelmetProps {
|
||||
async?: boolean;
|
||||
base?: Attributes;
|
||||
bodyAttributes?: BodyProps;
|
||||
defaultTitle?: string;
|
||||
defer?: boolean;
|
||||
encodeSpecialCharacters?: boolean;
|
||||
helmetData?: HelmetData;
|
||||
htmlAttributes?: HtmlProps;
|
||||
onChangeClientState?: (newState: StateUpdate, addedTags: HelmetTags, removedTags: HelmetTags) => void;
|
||||
link?: LinkProps[];
|
||||
meta?: MetaProps[];
|
||||
noscript?: Attributes[];
|
||||
script?: Attributes[];
|
||||
style?: Attributes[];
|
||||
title?: string;
|
||||
titleAttributes?: Attributes;
|
||||
titleTemplate?: string;
|
||||
prioritizeSeoTags?: boolean;
|
||||
}
|
||||
export {};
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
interface PropList {
|
||||
[key: string]: any;
|
||||
}
|
||||
type PropsList = PropList[];
|
||||
type AttributeList = string[];
|
||||
interface MatchProps {
|
||||
[key: string]: string | AttributeList;
|
||||
}
|
||||
declare const reducePropsToState: (propsList: PropsList) => {
|
||||
baseTag: any;
|
||||
bodyAttributes: any;
|
||||
defer: any;
|
||||
encode: any;
|
||||
htmlAttributes: any;
|
||||
linkTags: any;
|
||||
metaTags: any;
|
||||
noscriptTags: any;
|
||||
onChangeClientState: any;
|
||||
scriptTags: any;
|
||||
styleTags: any;
|
||||
title: any;
|
||||
titleAttributes: any;
|
||||
prioritizeSeoTags: boolean;
|
||||
};
|
||||
export declare const flattenArray: (possibleArray: string[] | string) => string;
|
||||
export { reducePropsToState };
|
||||
export declare const prioritizer: (elementsList: HTMLElement[], propsToMatch: MatchProps) => {
|
||||
priority: HTMLElement[];
|
||||
default: HTMLElement[];
|
||||
};
|
||||
export declare const without: (obj: PropList, key: string) => {
|
||||
[x: string]: any;
|
||||
};
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"name": "react-helmet-async",
|
||||
"version": "3.0.0",
|
||||
"description": "Thread-safe Helmet for React 16–18, with native support for React 19+",
|
||||
"sideEffects": false,
|
||||
"main": "./lib/index.js",
|
||||
"module": "./lib/index.esm.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./lib/index.d.ts",
|
||||
"default": "./lib/index.esm.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./lib/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": "http://github.com/staylor/react-helmet-async",
|
||||
"author": "Scott Taylor <scott.c.taylor@mac.com>",
|
||||
"license": "Apache-2.0",
|
||||
"files": [
|
||||
"lib/"
|
||||
],
|
||||
"dependencies": {
|
||||
"invariant": "^2.2.4",
|
||||
"react-fast-compare": "^3.2.2",
|
||||
"shallowequal": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "18.4.3",
|
||||
"@commitlint/config-conventional": "18.4.3",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@remix-run/eslint-config": "2.3.1",
|
||||
"@testing-library/jest-dom": "6.1.5",
|
||||
"@testing-library/react": "14.1.2",
|
||||
"@types/eslint": "8.44.8",
|
||||
"@types/invariant": "2.2.37",
|
||||
"@types/jsdom": "21.1.6",
|
||||
"@types/react": "18.2.39",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@types/shallowequal": "1.1.5",
|
||||
"@vitejs/plugin-react": "4.2.0",
|
||||
"esbuild": "0.19.8",
|
||||
"eslint": "8.54.0",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
"eslint-plugin-prettier": "5.0.1",
|
||||
"husky": "8.0.3",
|
||||
"jsdom": "22.1.0",
|
||||
"playwright": "^1.58.2",
|
||||
"prettier": "3.1.0",
|
||||
"raf": "3.4.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"rimraf": "5.0.5",
|
||||
"tsx": "4.6.1",
|
||||
"typescript": "5.2.2",
|
||||
"vite": "4.5.0",
|
||||
"vitest": "0.34.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf lib",
|
||||
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint --report-unused-disable-directives .",
|
||||
"lint-fix": "pnpm lint --fix",
|
||||
"test": "vitest run",
|
||||
"test:e2e:server": "vitest run --config e2e/vitest.config.ts",
|
||||
"test:e2e:browser": "playwright test --config e2e/playwright.config.ts",
|
||||
"test:e2e": "pnpm run test:e2e:server && pnpm run test:e2e:browser",
|
||||
"test:all": "pnpm test && pnpm run test:e2e",
|
||||
"test-watch": "pnpm test -- --watch",
|
||||
"test-update": "pnpm test -- -u",
|
||||
"compile": "pnpm run clean && NODE_ENV=production tsx build.ts && pnpm run types",
|
||||
"prepare": "pnpm run compile && husky install",
|
||||
"types": "tsc --project tsconfig.build.json"
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Alberto Leal <mailforalberto@gmail.com> (github.com/dashed)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
# shallowequal [](https://travis-ci.org/dashed/shallowequal) [](https://npmjs.com/shallowequal) [](https://www.npmjs.com/package/shallowequal)
|
||||
|
||||
[](https://greenkeeper.io/)
|
||||
|
||||
> `shallowequal` is like lodash's [`isEqualWith`](https://lodash.com/docs/4.17.4#isEqualWith) but for shallow (strict) equal.
|
||||
|
||||
`shallowequal(value, other, [customizer], [thisArg])`
|
||||
|
||||
Performs a ***shallow equality*** comparison between two values (i.e. `value` and `other`) to determine if they are equivalent.
|
||||
|
||||
The equality is performed by iterating through keys on the given `value`, and returning `false` whenever any key has values which are not **strictly equal** between `value` and `other`. Otherwise, return `true` whenever the values of all keys are strictly equal.
|
||||
|
||||
If `customizer` (expected to be a function) is provided it is invoked to compare values. If `customizer` returns `undefined` (i.e. `void 0`), then comparisons are handled by the `shallowequal` function instead.
|
||||
|
||||
The `customizer` is bound to `thisArg` and invoked with three arguments: `(value, other, key)`.
|
||||
|
||||
**NOTE:** Docs are (shamelessly) adapted from [lodash's v3.x docs](https://lodash.com/docs/3.10.1#isEqualWith)
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
$ yarn add shallowequal
|
||||
# npm v5+
|
||||
$ npm install shallowequal
|
||||
# before npm v5
|
||||
$ npm install --save shallowequal
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
const shallowequal = require('shallowequal');
|
||||
|
||||
const object = { 'user': 'fred' };
|
||||
const other = { 'user': 'fred' };
|
||||
|
||||
object == other;
|
||||
// → false
|
||||
|
||||
shallowequal(object, other);
|
||||
// → true
|
||||
```
|
||||
|
||||
## Credit
|
||||
|
||||
Code for `shallowEqual` originated from https://github.com/gaearon/react-pure-render/ and has since been refactored to have the exact same API as `lodash.isEqualWith` (as of `v4.17.4`).
|
||||
|
||||
## Development
|
||||
|
||||
- `node.js` and `npm`. See: https://github.com/creationix/nvm#installation
|
||||
- `yarn`. See: https://yarnpkg.com/en/docs/install
|
||||
- `npm` dependencies. Run: `yarn install`
|
||||
|
||||
### Chores
|
||||
|
||||
- Lint: `yarn lint`
|
||||
- Test: `yarn test`
|
||||
- Pretty: `yarn pretty`
|
||||
- Pre-publish: `yarn prepublish`
|
||||
|
||||
## License
|
||||
|
||||
MIT.
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
//
|
||||
|
||||
module.exports = function shallowEqual(objA, objB, compare, compareContext) {
|
||||
var ret = compare ? compare.call(compareContext, objA, objB) : void 0;
|
||||
|
||||
if (ret !== void 0) {
|
||||
return !!ret;
|
||||
}
|
||||
|
||||
if (objA === objB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof objA !== "object" || !objA || typeof objB !== "object" || !objB) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var keysA = Object.keys(objA);
|
||||
var keysB = Object.keys(objB);
|
||||
|
||||
if (keysA.length !== keysB.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB);
|
||||
|
||||
// Test for A's keys different from B.
|
||||
for (var idx = 0; idx < keysA.length; idx++) {
|
||||
var key = keysA[idx];
|
||||
|
||||
if (!bHasOwnProperty(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var valueA = objA[key];
|
||||
var valueB = objB[key];
|
||||
|
||||
ret = compare ? compare.call(compareContext, valueA, valueB, key) : void 0;
|
||||
|
||||
if (ret === false || (ret === void 0 && valueA !== valueB)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// @flow
|
||||
|
||||
declare module.exports: <T, U>(
|
||||
objA?: ?T,
|
||||
objB?: ?U,
|
||||
compare?: ?(objValue: any, otherValue: any, key?: string) => boolean | void,
|
||||
compareContext?: ?any
|
||||
) => boolean;
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// @flow
|
||||
|
||||
module.exports = function shallowEqual<T, U>(
|
||||
objA?: ?T,
|
||||
objB?: ?U,
|
||||
compare?: ?(objValue: any, otherValue: any, key?: string) => boolean | void,
|
||||
compareContext?: ?any
|
||||
): boolean {
|
||||
var ret = compare ? compare.call(compareContext, objA, objB) : void 0;
|
||||
|
||||
if (ret !== void 0) {
|
||||
return !!ret;
|
||||
}
|
||||
|
||||
if (objA === objB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof objA !== "object" || !objA || typeof objB !== "object" || !objB) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var keysA = Object.keys(objA);
|
||||
var keysB = Object.keys(objB);
|
||||
|
||||
if (keysA.length !== keysB.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB);
|
||||
|
||||
// Test for A's keys different from B.
|
||||
for (var idx = 0; idx < keysA.length; idx++) {
|
||||
var key = keysA[idx];
|
||||
|
||||
if (!bHasOwnProperty(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var valueA = objA[key];
|
||||
var valueB = objB[key];
|
||||
|
||||
ret = compare ? compare.call(compareContext, valueA, valueB, key) : void 0;
|
||||
|
||||
if (ret === false || (ret === void 0 && valueA !== valueB)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "shallowequal",
|
||||
"version": "1.1.0",
|
||||
"description": "Like lodash isEqualWith but for shallow equal.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"lint": "eslint index.js test",
|
||||
"test": "mocha --require babel-register",
|
||||
"build:strip-flow":
|
||||
"flow-remove-types --pretty index.original.js > index.js",
|
||||
"build:gen-flow": "flow gen-flow-files index.original.js > index.js.flow",
|
||||
"build": "npm run build:strip-flow && npm run build:gen-flow",
|
||||
"prepublish":
|
||||
"npm run build && npm run pretty && npm run lint && npm run test",
|
||||
"travis": "npm run lint && npm run test",
|
||||
"pretty": "prettier --write --tab-width 2 'test/**/*.js' '*.{js,js.flow}'",
|
||||
"precommit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,json,css,js.flow}": ["prettier --write", "git add"]
|
||||
},
|
||||
"author": {
|
||||
"name": "Alberto Leal",
|
||||
"email": "mailforalberto@gmail.com",
|
||||
"url": "github.com/dashed"
|
||||
},
|
||||
"repository": "dashed/shallowequal",
|
||||
"license": "MIT",
|
||||
"files": ["index.js", "index.js.flow", "index.original.js"],
|
||||
"keywords": [
|
||||
"shallowequal",
|
||||
"shallow",
|
||||
"equal",
|
||||
"isequal",
|
||||
"compare",
|
||||
"isequalwith"
|
||||
],
|
||||
"eslintConfig": {
|
||||
"parser": "babel-eslint",
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"mocha": true
|
||||
},
|
||||
"extends": ["eslint:recommended"]
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^8.0.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-register": "^6.24.1",
|
||||
"chai": "^4.0.0",
|
||||
"eslint": "^4.7.1",
|
||||
"flow-bin": "^0.75.0",
|
||||
"flow-remove-types": "^1.2.3",
|
||||
"husky": "^0.14.3",
|
||||
"lint-staged": "^6.0.0",
|
||||
"mocha": "^5.0.0",
|
||||
"prettier": "^1.9.2"
|
||||
}
|
||||
}
|
||||
Generated
+36
@@ -51,6 +51,7 @@
|
||||
"react": "^19.2.0",
|
||||
"react-day-picker": "^9.13.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-helmet-async": "^3.0.0",
|
||||
"react-hook-form": "^7.70.0",
|
||||
"react-resizable-panels": "^4.2.2",
|
||||
"react-router-dom": "^7.14.0",
|
||||
@@ -6744,6 +6745,15 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/invariant/-/invariant-2.2.4.tgz",
|
||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@@ -7668,6 +7678,26 @@
|
||||
"react": "^19.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-fast-compare": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
|
||||
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-helmet-async": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/react-helmet-async/-/react-helmet-async-3.0.0.tgz",
|
||||
"integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"invariant": "^2.2.4",
|
||||
"react-fast-compare": "^3.2.2",
|
||||
"shallowequal": "^1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.70.0",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.70.0.tgz",
|
||||
@@ -8131,6 +8161,12 @@
|
||||
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shallowequal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"react": "^19.2.0",
|
||||
"react-day-picker": "^9.13.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-helmet-async": "^3.0.0",
|
||||
"react-hook-form": "^7.70.0",
|
||||
"react-resizable-panels": "^4.2.2",
|
||||
"react-router-dom": "^7.14.0",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
+10
-23
@@ -1,26 +1,13 @@
|
||||
# =============================================================================
|
||||
# ROBOTS.TXT - VERSAO HOMOLOGACAO/DESENVOLVIMENTO
|
||||
# =============================================================================
|
||||
# Este arquivo BLOQUEIA TODOS os crawlers.
|
||||
#
|
||||
# Para PRODUCAO (avanzato.com.br), substitua por:
|
||||
# User-agent: *
|
||||
# Allow: /
|
||||
# Sitemap: https://avanzato.com.br/sitemap.xml
|
||||
# =============================================================================
|
||||
# ============================================================================
|
||||
# AVANZATO TECNOLOGIA - ROBOTS.TXT (GENERICO)
|
||||
# ============================================================================
|
||||
# O controle real de indexacao e feito pelo <meta name="robots"> no HTML:
|
||||
# - Producao (avanzato.com.br): index, follow
|
||||
# - Homologacao (hml.avanzato.com.br): noindex, nofollow
|
||||
# - Desenvolvimento (localhost): noindex, nofollow
|
||||
# ============================================================================
|
||||
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
Allow: /
|
||||
|
||||
# Bloqueio explicito de todos os bots
|
||||
User-agent: Googlebot
|
||||
Disallow: /
|
||||
|
||||
User-agent: Bingbot
|
||||
Disallow: /
|
||||
|
||||
User-agent: Slurp
|
||||
Disallow: /
|
||||
|
||||
# Sitemap comentado (descomente em producao)
|
||||
# Sitemap: https://avanzato.com.br/sitemap.xml
|
||||
Sitemap: https://avanzato.com.br/sitemap.xml
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -135,4 +135,150 @@
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ti-para-contabilidades</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ti-para-advocacias</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/monitoramento-empresarial</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/firewall-pfsense-empresarial</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/backup-corporativo</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/voip-empresarial</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
|
||||
<!-- SEO Local - Onde Atuamos -->
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/onde-atuamos</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-guarulhos</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-mogi-das-cruzes</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-ipiranga</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-vila-formosa</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-cerqueira-cesar</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-santana</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-bras</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-ribeirao-preto</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
|
||||
<!-- Nichos adicionais -->
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-industria</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-portaria-remota</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-clinicas</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-imobiliarias</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/suporte-ti-recrutamento-selecao</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
|
||||
<!-- Avanzato Tools -->
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/spf-checker</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/dkim-checker</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/dmarc-checker</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/mx-lookup</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/ssl-checker</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/email-security-score</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://avanzato.com.br/ferramentas/microsoft-365-analyzer</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": "1.4.0",
|
||||
"date": "2026-06-02",
|
||||
"environment": "auto-detect"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"current": "1.4.0",
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.4.0",
|
||||
"date": "2026-06-02",
|
||||
"title": "Avanzato Tools - Fase 1",
|
||||
"changes": [
|
||||
"7 ferramentas gratuitas: SPF Checker, DKIM Checker, DMARC Checker, MX Lookup, SSL Checker, Email Security Score, Microsoft 365 Analyzer",
|
||||
"Pagina principal /ferramentas com grid de ferramentas",
|
||||
"Sistema de 2 consultas gratuitas + cadastro para ilimitado",
|
||||
"Captura de 'dor de TI' e urgencia para scoring de leads",
|
||||
"Integracao com avaliacao gratuita e contato",
|
||||
"Prerender de todas as 8 paginas de ferramentas para SEO",
|
||||
"Sitemap atualizado com 8 novas URLs",
|
||||
"Schema.org FAQPage nas paginas de ferramentas"
|
||||
],
|
||||
"file": "avanzato-site-v1.4.0.zip"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# Script Post-Build - Gera paginas estaticas para rotas React
|
||||
# ============================================================================
|
||||
# Isso permite que URLs como /avaliacao funcionem diretamente,
|
||||
# mesmo em servidores sem try_files configurado.
|
||||
#
|
||||
# Uso: bash scripts/build-static-routes.sh (executa automaticamente apos build)
|
||||
# ============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo -e "${BLUE}[Post-Build]${NC} Gerando paginas estaticas para rotas..."
|
||||
|
||||
DIST_DIR="${1:-dist}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
if [ ! -d "$DIST_DIR" ]; then
|
||||
echo -e "${YELLOW}Pasta dist/ nao encontrada. Execute npm run build primeiro.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Lista de todas as rotas do site
|
||||
ROUTES=(
|
||||
"sobre"
|
||||
"contato"
|
||||
"cases"
|
||||
"blog"
|
||||
"avaliacao"
|
||||
"tecnologias"
|
||||
"tecnologia-para-condominios"
|
||||
"ti-para-contabilidades"
|
||||
"ti-para-advocacias"
|
||||
"monitoramento-empresarial"
|
||||
"firewall-pfsense-empresarial"
|
||||
"backup-corporativo"
|
||||
"voip-empresarial"
|
||||
"servicos/ti-gerenciada"
|
||||
"servicos/cloud-computing"
|
||||
"servicos/seguranca"
|
||||
"servicos/suporte"
|
||||
"servicos/redes"
|
||||
"servicos/pabx"
|
||||
"servicos/backup"
|
||||
"servicos/servidores"
|
||||
"servicos/consultoria"
|
||||
"servicos/noc"
|
||||
"suporte-windows-server"
|
||||
"suporte-linux-empresas"
|
||||
"suporte-pfsense"
|
||||
"suporte-proxmox"
|
||||
"suporte-vmware"
|
||||
"suporte-sharepoint"
|
||||
"suporte-redes-cisco"
|
||||
"onde-atuamos"
|
||||
"suporte-ti-guarulhos"
|
||||
"suporte-ti-mogi-das-cruzes"
|
||||
"suporte-ti-ipiranga"
|
||||
"suporte-ti-vila-formosa"
|
||||
"suporte-ti-cerqueira-cesar"
|
||||
"suporte-ti-santana"
|
||||
"suporte-ti-bras"
|
||||
"suporte-ti-ribeirao-preto"
|
||||
"suporte-ti-industria"
|
||||
"suporte-ti-portaria-remota"
|
||||
"suporte-ti-clinicas"
|
||||
"suporte-ti-imobiliarias"
|
||||
"suporte-ti-recrutamento-selecao"
|
||||
"privacidade"
|
||||
"termos"
|
||||
"ferramentas"
|
||||
"ferramentas/spf-checker"
|
||||
"ferramentas/dkim-checker"
|
||||
"ferramentas/dmarc-checker"
|
||||
"ferramentas/mx-lookup"
|
||||
"ferramentas/ssl-checker"
|
||||
"ferramentas/email-security-score"
|
||||
"ferramentas/microsoft-365-analyzer"
|
||||
)
|
||||
|
||||
# Copiar index.html como base
|
||||
cp "$DIST_DIR/index.html" "$DIST_DIR/index.html.bak"
|
||||
|
||||
# Gerar pagina para cada rota
|
||||
for route in "${ROUTES[@]}"; do
|
||||
mkdir -p "$DIST_DIR/$route"
|
||||
cp "$DIST_DIR/index.html" "$DIST_DIR/$route/index.html"
|
||||
done
|
||||
|
||||
echo -e "${GREEN}OK${NC} ${#ROUTES[@]} rotas estaticas criadas"
|
||||
|
||||
# Criar 404.html (copia do index.html para fallback)
|
||||
cp "$DIST_DIR/index.html" "$DIST_DIR/404.html"
|
||||
echo -e "${GREEN}OK${NC} 404.html criado"
|
||||
|
||||
# Limpar backup
|
||||
rm -f "$DIST_DIR/index.html.bak"
|
||||
|
||||
echo ""
|
||||
# Gerar HTML prerenderizado com conteudo real (SEO)
|
||||
echo ""
|
||||
echo -e "${BLUE}[Prerender]${NC} Gerando HTML com conteudo real para SEO..."
|
||||
node "${SCRIPT_DIR}/prerender.cjs"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Post-build concluido!${NC}"
|
||||
echo ""
|
||||
echo "Estrutura gerada:"
|
||||
echo " dist/index.html (home)"
|
||||
echo " dist/404.html (pagina nao encontrada)"
|
||||
echo " dist/{rota}/index.html (${#ROUTES[@]} paginas estaticas)"
|
||||
echo " dist/{rota}/index.html (HTML com conteudo real para SEO)"
|
||||
echo ""
|
||||
@@ -47,6 +47,13 @@ show_banner() {
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Post-build: gerar paginas estaticas para rotas
|
||||
run_post_build() {
|
||||
local dist_dir="${1:-dist}"
|
||||
echo -e "${BLUE}[Post-Build]${NC} Gerando paginas estaticas para rotas..."
|
||||
bash "${SCRIPT_DIR}/build-static-routes.sh" "$dist_dir"
|
||||
}
|
||||
|
||||
# Verificar dependencias
|
||||
check_deps() {
|
||||
if ! command -v node &> /dev/null; then
|
||||
@@ -86,6 +93,10 @@ cmd_build() {
|
||||
echo -e "${BLUE}[2/2]${NC} Configurando robots.txt para producao..."
|
||||
cp "${PROJECT_DIR}/public/robots-producao.txt" "${DIST_DIR}/robots.txt"
|
||||
echo -e "${GREEN}OK${NC} - Build de producao pronto em dist/"
|
||||
|
||||
# Gerar paginas estaticas para rotas
|
||||
run_post_build "$DIST_DIR"
|
||||
|
||||
echo ""
|
||||
echo -e "Para preview: ${CYAN}bash scripts/dev.sh preview${NC}"
|
||||
}
|
||||
@@ -101,6 +112,10 @@ cmd_build_hom() {
|
||||
echo -e "${YELLOW}[2/2]${NC} Configurando anti-indexacao..."
|
||||
# robots.txt ja esta com noindex por padrao
|
||||
echo -e "${GREEN}OK${NC} - Build de homologacao pronto em dist/"
|
||||
|
||||
# Gerar paginas estaticas para rotas
|
||||
run_post_build "$DIST_DIR"
|
||||
|
||||
echo ""
|
||||
echo -e "Para deploy: ${CYAN}bash scripts/dev.sh deploy-hom${NC}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
// ============================================================================
|
||||
// PRERENDER - Gera HTML estatico com conteudo real para cada rota
|
||||
// ============================================================================
|
||||
// Executa apos npm run build
|
||||
// Gera index.html com conteudo HTML real (nao vazio) para cada rota
|
||||
// Isso resolve o problema de SEO: Google recebe conteudo no 1o request
|
||||
// ============================================================================
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const DIST_DIR = path.resolve(__dirname, '../dist');
|
||||
const INDEX_HTML = path.join(DIST_DIR, 'index.html');
|
||||
|
||||
// Verificar se dist existe
|
||||
if (!fs.existsSync(DIST_DIR)) {
|
||||
console.error('ERRO: pasta dist/ nao encontrada. Execute npm run build primeiro.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Ler o index.html base
|
||||
let baseHtml = fs.readFileSync(INDEX_HTML, 'utf-8');
|
||||
|
||||
// Rotas para prerenderizar
|
||||
const ROUTES = [
|
||||
{ path: '/', title: 'Avanzato Tecnologia | TI Gerenciada, Cloud e Seguranca em Guarulhos/SP', desc: 'Solucoes em TI Gerenciada, Cloud Computing e Seguranca da Informacao. +23 anos de experiencia, +500 clientes atendidos. Suporte 24/7.' },
|
||||
{ path: '/sobre', title: 'Sobre a Avanzato Tecnologia | +23 Anos de Experiencia em TI', desc: 'Conheca a Avanzato Tecnologia. +23 anos de experiencia em TI Gerenciada, Cloud e Seguranca. +500 clientes satisfeitos em Sao Paulo.' },
|
||||
{ path: '/cases', title: 'Cases de Sucesso - Avanzato Tecnologia | Projetos de TI', desc: 'Conheca nossos cases de sucesso em TI. Empresas de contabilidade, advocacia, industria, monitoramento e mais. +23 anos de experiencia.' },
|
||||
{ path: '/onde-atuamos', title: 'Onde Atuamos - Suporte de TI em Sao Paulo e Regiao', desc: 'Avanzato Tecnologia atende em Guarulhos, Mogi das Cruzes, Ipiranga, Zona Leste, Zona Norte, Centro e Ribeirao Preto. Suporte presencial e remoto.' },
|
||||
{ path: '/avaliacao', title: 'Avaliacao Gratuita de TI - Avanzato Tecnologia', desc: 'Avalie gratuitamente a maturidade da TI da sua empresa. Descubra o nivel e receba recomendacoes personalizadas.' },
|
||||
{ path: '/contato', title: 'Contato - Avanzato Tecnologia | TI em Guarulhos/SP', desc: 'Entre em contato com a Avanzato Tecnologia. Telefone (11) 4810-1704, WhatsApp, email ou formulario. Atendimento 24/7.' },
|
||||
{ path: '/servicos/ti-gerenciada', title: 'TI Gerenciada para Empresas - Avanzato Tecnologia', desc: 'TI Gerenciada completa para empresas em Guarulhos e Sao Paulo. Suporte 24/7, monitoramento em tempo real e equipe especializada.' },
|
||||
{ path: '/servicos/cloud-computing', title: 'Cloud Computing e Virtualizacao - Avanzato', desc: 'Solucoes em Cloud Computing: AWS, Azure, Google Cloud. Migracao, gestao e otimizacao de infraestrutura em nuvem.' },
|
||||
{ path: '/servicos/seguranca', title: 'Seguranca da Informacao - Avanzato Tecnologia', desc: 'Protecao completa para sua empresa: firewall, antivirus, backup, LGPD. Seguranca de rede e dados com suporte 24/7.' },
|
||||
{ path: '/servicos/suporte', title: 'Suporte Tecnico 24/7 - Avanzato Tecnologia', desc: 'Suporte tecnico empresarial 24 horas. Help desk, atendimento remoto e presencial em Guarulhos e regiao.' },
|
||||
{ path: '/ti-para-contabilidades', title: 'TI para Escritorios de Contabilidade - Avanzato', desc: 'Solucoes de TI especializadas para contabilidades: seguranca de dados fiscais, backup de obrigacoes, NF-e. SPED e ECD.' },
|
||||
{ path: '/ti-para-advocacias', title: 'TI para Escritorios de Advocacia - Avanzato', desc: 'TI para advocacias: seguranca de processos, LGPD para clientes, backup de documentos juridicos. PJe e e-SAJ.' },
|
||||
{ path: '/monitoramento-empresarial', title: 'Monitoramento Empresarial com CFTV - Avanzato', desc: 'Sistemas de CFTV e monitoramento por camera para empresas. IP cameras, NVR, DVR, acesso remoto via celular.' },
|
||||
{ path: '/firewall-pfsense-empresarial', title: 'Firewall pfSense Empresarial - Avanzato', desc: 'Implementacao e configuracao de firewall pfSense e OPNsense. Seguranca de rede, VPN, filtro de conteudo.' },
|
||||
{ path: '/backup-corporativo', title: 'Backup Corporativo e Disaster Recovery - Avanzato', desc: 'Backup automatizado em nuvem e local. Disaster Recovery, recuperacao de dados. Protecao contra ransomware.' },
|
||||
{ path: '/voip-empresarial', title: 'VoIP Empresarial e PABX IP - Avanzato Tecnologia', desc: 'Telefonia VoIP para empresas: PABX IP, chamadas ilimitadas, integracao com CRM. Reducao de 70% na conta telefonica.' },
|
||||
{ path: '/suporte-ti-guarulhos', title: 'Suporte de TI em Guarulhos - Matriz Avanzato', desc: 'Suporte de TI empresarial em Guarulhos. Atendemos contabilidades, advocacias, industrias e mais. Matriz da Avanzato. +23 anos.' },
|
||||
{ path: '/suporte-ti-mogi-das-cruzes', title: 'Suporte de TI em Mogi das Cruzes - Avanzato', desc: 'Suporte de TI empresarial em Mogi das Cruzes. Atendimento presencial e remoto para empresas de todos os portes.' },
|
||||
{ path: '/suporte-ti-ipiranga', title: 'Suporte de TI em Ipiranga - Avanzato Tecnologia', desc: 'Suporte de TI empresarial em Ipiranga, Sao Paulo. Atendimento a clinicas, escolas e comercios do bairro.' },
|
||||
{ path: '/suporte-ti-industria', title: 'TI para Industria - Avanzato Tecnologia', desc: 'Solucoes de TI especializadas para industrias. Suporte tecnico, seguranca, cloud e comunicacao. +23 anos de experiencia.' },
|
||||
{ path: '/suporte-ti-portaria-remota', title: 'TI para Portaria Remota - Avanzato Tecnologia', desc: 'Solucoes de TI para portaria remota: interfornia IP, controle de acesso, cameras. Condominios, empresas, industrias.' },
|
||||
{ path: '/suporte-ti-clinicas', title: 'TI para Clinicas - Avanzato Tecnologia', desc: 'Solucoes de TI para clinicas: odontologia, fisioterapia, medica, veterinaria. LGPD, prontuario digital, telemedicina.' },
|
||||
{ path: '/suporte-ti-imobiliarias', title: 'TI para Imobiliarias - Avanzato Tecnologia', desc: 'Solucoes de TI para imobiliarias: sistemas de gestao, CRM, site de imoveis. Residencial, comercial, industrial.' },
|
||||
{ path: '/tecnologias', title: 'Tecnologias que Trabalhamos - Avanzato Tecnologia', desc: 'Conheca as tecnologias que a Avanzato domina: Cisco, VMware, Microsoft, Linux, pfSense, Proxmox e muito mais.' },
|
||||
// Avanzato Tools
|
||||
{ path: '/ferramentas', title: 'Ferramentas Gratuitas de TI - Avanzato Tools', desc: 'Ferramentas gratuitas para diagnosticar seguranca de e-mail, SSL, Microsoft 365 e infraestrutura de TI. SPF Checker, DKIM Checker, DMARC Checker, SSL Checker e mais.' },
|
||||
{ path: '/ferramentas/spf-checker', title: 'SPF Checker - Verifique sua Protecao de E-mail | Avanzato', desc: 'Verifique se o registro SPF do seu dominio esta configurado corretamente. Proteja contra spoofing e phishing. Ferramenta gratuita.' },
|
||||
{ path: '/ferramentas/dkim-checker', title: 'DKIM Checker - Valide Assinatura Digital de E-mail | Avanzato', desc: 'Valide a assinatura digital DKIM do seu dominio. Garanta que seus e-mails sejam autenticados e nao vao para spam.' },
|
||||
{ path: '/ferramentas/dmarc-checker', title: 'DMARC Checker - Analise sua Politica DMARC | Avanzato', desc: 'Analise a politica DMARC do seu dominio. Descubra se voce esta protegido contra phishing e spoofing de e-mail.' },
|
||||
{ path: '/ferramentas/mx-lookup', title: 'MX Lookup - Consulte seus Servidores de E-mail | Avanzato', desc: 'Consulte os registros MX do seu dominio. Verifique a infraestrutura de recebimento de e-mails e o provedor.' },
|
||||
{ path: '/ferramentas/ssl-checker', title: 'SSL Checker - Verifique seu Certificado SSL | Avanzato', desc: 'Verifique a validade e configuracao do certificado SSL do seu site. Garanta seguranca HTTPS para seus visitantes.' },
|
||||
{ path: '/ferramentas/email-security-score', title: 'Email Security Score - Avaliacao Completa | Avanzato', desc: 'Avalie a seguranca completa do e-mail do seu dominio. Score de 0 a 100 com SPF, DKIM, DMARC, MX e recomendacoes.' },
|
||||
{ path: '/ferramentas/microsoft-365-analyzer', title: 'Microsoft 365 Analyzer - Diagnostico M365 | Avanzato', desc: 'Analise a configuracao do Microsoft 365 do seu dominio. Descubra oportunidades de melhoria e seguranca.' },
|
||||
];
|
||||
|
||||
// Conteudo HTML prerenderizado para cada tipo de pagina
|
||||
function generateContent(route) {
|
||||
const isHome = route.path === '/';
|
||||
const isLocal = route.path.includes('suporte-ti-');
|
||||
const isService = route.path.includes('servicos/');
|
||||
const isNicho = route.path.includes('ti-para-') || route.path.includes('suporte-ti-') && !isLocal;
|
||||
|
||||
let content = '';
|
||||
|
||||
if (isHome) {
|
||||
content = `
|
||||
<section class="hero">
|
||||
<h1>Avanzato Tecnologia - TI Gerenciada, Cloud e Seguranca</h1>
|
||||
<p>+23 anos de experiencia em TI para empresas. +500 clientes atendidos em Guarulhos, Sao Paulo e regiao.</p>
|
||||
<p>Servicos: Suporte Tecnico 24/7, Cloud Computing, Seguranca da Informacao, Backup Corporativo, VoIP, Firewall, Monitoramento.</p>
|
||||
<div><a href="/avaliacao">Avaliacao Gratuita</a> <a href="/contato">Falar com Especialista</a></div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Por que escolher a Avanzato?</h2>
|
||||
<ul>
|
||||
<li>Suporte 24/7 com equipe especializada</li>
|
||||
<li>+23 anos de experiencia em TI empresarial</li>
|
||||
<li>+500 clientes atendidos em diversos segmentos</li>
|
||||
<li>Atendimento em Guarulhos, Grande SP e interior</li>
|
||||
<li>Relatorios tecnicos mensais detalhados</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Segmentos que Atendemos</h2>
|
||||
<p>Contabilidades, Advocacias, Industrias, BPO, Clinicas, Imobiliarias, Condominios, Logistica, Monitoramento.</p>
|
||||
<p><a href="/onde-atuamos">Ver onde atuamos</a></p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Onde Atuamos</h2>
|
||||
<p>Guarulhos (matriz), Mogi das Cruzes, Ipiranga, Vila Formosa, Cerqueira Cesar, Santana, Bras, Ribeirao Preto.</p>
|
||||
<p><a href="/onde-atuamos">Ver todas as localidades</a></p>
|
||||
</section>`;
|
||||
} else if (isLocal) {
|
||||
const city = route.title.split(' em ')[1]?.split(' - ')[0] || '';
|
||||
content = `
|
||||
<article>
|
||||
<h1>Suporte de TI em ${city}</h1>
|
||||
<p>Empresa de suporte de TI em ${city} com atendimento presencial e remoto. +23 anos de experiencia.</p>
|
||||
<h2>Servicos em ${city}</h2>
|
||||
<ul>
|
||||
<li>Suporte tecnico 24/7</li>
|
||||
<li>Administracao de servidores</li>
|
||||
<li>Backup corporativo</li>
|
||||
<li>Firewall e seguranca</li>
|
||||
<li>Microsoft 365</li>
|
||||
<li>Cloud Computing</li>
|
||||
<li>VoIP e PABX IP</li>
|
||||
<li>Monitoramento CFTV</li>
|
||||
</ul>
|
||||
<h2>Segmentos Atendidos em ${city}</h2>
|
||||
<p>Contabilidades, Advocacias, Industrias, Clinicas, Imobiliarias, Condominios, Logistica, Monitoramento.</p>
|
||||
<div><a href="/avaliacao">Solicitar Avaliacao Gratuita</a></div>
|
||||
</article>`;
|
||||
} else if (route.path === '/onde-atuamos') {
|
||||
content = `
|
||||
<article>
|
||||
<h1>Onde a Avanzato Atua</h1>
|
||||
<p>Presente em toda a regiao metropolitana de Sao Paulo e interior. Atendimento presencial e remoto.</p>
|
||||
<h2>Cidades Atendidas</h2>
|
||||
<ul>
|
||||
<li><a href="/suporte-ti-guarulhos">Guarulhos</a> - Matriz Avanzato</li>
|
||||
<li><a href="/suporte-ti-mogi-das-cruzes">Mogi das Cruzes</a></li>
|
||||
<li><a href="/suporte-ti-ipiranga">Ipiranga</a></li>
|
||||
<li><a href="/suporte-ti-vila-formosa">Vila Formosa</a></li>
|
||||
<li><a href="/suporte-ti-cerqueira-cesar">Cerqueira Cesar</a></li>
|
||||
<li><a href="/suporte-ti-santana">Santana</a></li>
|
||||
<li><a href="/suporte-ti-bras">Bras</a></li>
|
||||
<li><a href="/suporte-ti-ribeirao-preto">Ribeirao Preto</a></li>
|
||||
</ul>
|
||||
<div><a href="/contato">Falar com Especialista</a></div>
|
||||
</article>`;
|
||||
} else if (route.path === '/ferramentas') {
|
||||
content = `
|
||||
<article>
|
||||
<h1>Avanzato Tools - Ferramentas Gratuitas de TI</h1>
|
||||
<p>Ferramentas gratuitas de diagnostico tecnico para empresas. Verifique a seguranca do seu e-mail, SSL, Microsoft 365 e infraestrutura de TI.</p>
|
||||
<h2>Ferramentas Disponiveis</h2>
|
||||
<ul>
|
||||
<li><a href="/ferramentas/spf-checker">SPF Checker</a> - Verifique sua protecao contra spoofing</li>
|
||||
<li><a href="/ferramentas/dkim-checker">DKIM Checker</a> - Valide assinatura digital de e-mail</li>
|
||||
<li><a href="/ferramentas/dmarc-checker">DMARC Checker</a> - Analise sua politica anti-phishing</li>
|
||||
<li><a href="/ferramentas/mx-lookup">MX Lookup</a> - Consulte servidores de e-mail</li>
|
||||
<li><a href="/ferramentas/ssl-checker">SSL Checker</a> - Verifique certificado SSL</li>
|
||||
<li><a href="/ferramentas/email-security-score">Email Security Score</a> - Avaliacao completa</li>
|
||||
<li><a href="/ferramentas/microsoft-365-analyzer">Microsoft 365 Analyzer</a> - Diagnostico M365</li>
|
||||
</ul>
|
||||
<p>2 consultas gratuitas por ferramenta. Cadastro rapido para consultas ilimitadas.</p>
|
||||
<div><a href="/avaliacao">Avaliacao Gratuita de TI</a> <a href="/contato">Falar com Especialista</a></div>
|
||||
</article>`;
|
||||
} else if (route.path.startsWith('/ferramentas/')) {
|
||||
const toolName = route.title.split(' - ')[0];
|
||||
content = `
|
||||
<article>
|
||||
<h1>${toolName}</h1>
|
||||
<p>${route.desc}</p>
|
||||
<h2>Como usar</h2>
|
||||
<ol>
|
||||
<li>Digite o dominio que deseja verificar (ex: avanzato.com.br)</li>
|
||||
<li>Clique em "Verificar"</li>
|
||||
<li>Receba o resultado em segundos com recomendacoes</li>
|
||||
</ol>
|
||||
<h2>Perguntas Frequentes</h2>
|
||||
<p><strong>Preciso cadastrar?</strong> Nao nas primeiras 2 consultas. Apos isso, cadastro rapido gratuito.</p>
|
||||
<p><strong>Os resultados sao confiaveis?</strong> Sim, consultamos servidores DNS publicos em tempo real.</p>
|
||||
<div><a href="/ferramentas">Ver todas as ferramentas</a> <a href="/avaliacao">Avaliacao Gratuita</a></div>
|
||||
</article>`;
|
||||
} else if (route.path === '/cases') {
|
||||
content = `
|
||||
<article>
|
||||
<h1>Cases de Sucesso - Avanzato Tecnologia</h1>
|
||||
<p>Conheca nossos cases de sucesso em TI. Empresas de diversos segmentos que confiam na Avanzato.</p>
|
||||
<h2>Cases Recentes</h2>
|
||||
<ul>
|
||||
<li><strong>Apoio 24 Horas</strong> - Monitoramento - Servidores, pfSense, VPN</li>
|
||||
<li><strong>ONG/Terceiro Setor</strong> - Institucional - Suporte, Cloud, Seguranca</li>
|
||||
<li><strong>Museu de Faculdade Publica</strong> - Acervo Digital - Tainacan, ICA-AtoM</li>
|
||||
<li><strong>Thalls</strong> - Industria - Suporte, VoIP</li>
|
||||
<li><strong>Dellacont</strong> - Contabilidade - Suporte, Servidor, Backup</li>
|
||||
<li><strong>Mourad Naddi Advogados</strong> - Advocacia - Suporte, Servidor, Backup</li>
|
||||
<li><strong>GateOne e Fox</strong> - Monitoramento - Servidores, VPN</li>
|
||||
<li><strong>Lucena Textil</strong> - Textil - Colocation, VPN, Backup</li>
|
||||
</ul>
|
||||
<div><a href="/avaliacao">Quero um case de sucesso tambem</a></div>
|
||||
</article>`;
|
||||
} else if (isService || isNicho) {
|
||||
const serviceName = route.title.split(' - ')[0];
|
||||
content = `
|
||||
<article>
|
||||
<h1>${serviceName}</h1>
|
||||
<p>${route.desc}</p>
|
||||
<h2>O que oferecemos</h2>
|
||||
<ul>
|
||||
<li>Diagnostico inicial gratuito</li>
|
||||
<li>Projeto personalizado</li>
|
||||
<li>Implantacao completa</li>
|
||||
<li>Suporte continuo</li>
|
||||
<li>Relatorios periodicos</li>
|
||||
</ul>
|
||||
<h2>Perguntas Frequentes</h2>
|
||||
<p><strong>Qual o tempo de resposta?</strong> Nosso SLA garante atendimento em ate 4 horas para chamados criticos.</p>
|
||||
<p><strong>Atendem empresas de qual porte?</strong> Atendemos de pequenas a grandes empresas, com planos flexiveis.</p>
|
||||
<div><a href="/avaliacao">Solicitar Avaliacao Gratuita</a> <a href="/contato">Falar com Especialista</a></div>
|
||||
</article>`;
|
||||
} else {
|
||||
content = `
|
||||
<article>
|
||||
<h1>${route.title.split(' - ')[0]}</h1>
|
||||
<p>${route.desc}</p>
|
||||
<div><a href="/contato">Entrar em Contato</a> <a href="/avaliacao">Avaliacao Gratuita</a></div>
|
||||
</article>`;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
// Gerar HTML prerenderizado para cada rota
|
||||
console.log('[Prerender] Gerando HTML estatico com conteudo real...\n');
|
||||
|
||||
let count = 0;
|
||||
|
||||
for (const route of ROUTES) {
|
||||
// Atualizar title e meta description
|
||||
let html = baseHtml
|
||||
.replace(/<title>.*?<\/title>/, `<title>${route.title}</title>`)
|
||||
.replace(/<meta name="title" content=".*?" \/>/, `<meta name="title" content="${route.title}" />`)
|
||||
.replace(/<meta name="description" content=".*?" \/>/, `<meta name="description" content="${route.desc}" />`)
|
||||
.replace(/<meta name="app-version" content=".*?" \/>/, `<meta name="app-version" content="${require('../public/version.json').version}" />`);
|
||||
|
||||
// Gerar conteudo HTML
|
||||
const content = generateContent(route);
|
||||
|
||||
// Inserir conteudo no div#root (substituir <div id="root"></div>)
|
||||
html = html.replace(
|
||||
'<div id="root"></div>',
|
||||
`<div id="root">${content}</div>`
|
||||
);
|
||||
|
||||
// Determinar caminho de saida
|
||||
let outputPath;
|
||||
if (route.path === '/') {
|
||||
outputPath = path.join(DIST_DIR, 'index.html');
|
||||
} else {
|
||||
// Criar pasta e salvar index.html
|
||||
const dir = path.join(DIST_DIR, route.path.replace(/^\//, ''));
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
outputPath = path.join(dir, 'index.html');
|
||||
}
|
||||
|
||||
fs.writeFileSync(outputPath, html);
|
||||
count++;
|
||||
console.log(` OK: ${route.path} → ${outputPath.replace(DIST_DIR + '/', '')}`);
|
||||
}
|
||||
|
||||
console.log(`\n[Prerender] ${count} paginas geradas com conteudo HTML real!`);
|
||||
console.log('[Prerender] Agora o Google recebe conteudo no 1o request, sem precisar de JavaScript.\n');
|
||||
+169
-102
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { HashRouter as Router, Routes, Route, useLocation } from 'react-router-dom';
|
||||
import { useEffect, useState, lazy, Suspense } from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route, useLocation } from 'react-router-dom';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
import Navigation from './sections/Navigation';
|
||||
@@ -8,50 +8,80 @@ import WhatsAppWidget from './components/WhatsAppWidget';
|
||||
import CookieConsent from './components/CookieConsent';
|
||||
import HomologationBanner from './components/HomologationBanner';
|
||||
|
||||
// Páginas
|
||||
// Página inicial carrega direto (mais acessada)
|
||||
import Home from './pages/Home';
|
||||
import Sobre from './pages/Sobre';
|
||||
import Blog from './pages/Blog';
|
||||
import BlogPost from './pages/BlogPost';
|
||||
import Cases from './pages/Cases';
|
||||
import Contato from './pages/Contato';
|
||||
import Privacidade from './pages/Privacidade';
|
||||
import Termos from './pages/Termos';
|
||||
import NotFound from './pages/NotFound';
|
||||
|
||||
// Todas as outras páginas com lazy loading para reduzir bundle inicial
|
||||
const Sobre = lazy(() => import('./pages/Sobre'));
|
||||
const Blog = lazy(() => import('./pages/Blog'));
|
||||
const BlogPost = lazy(() => import('./pages/BlogPost'));
|
||||
const Cases = lazy(() => import('./pages/Cases'));
|
||||
const Contato = lazy(() => import('./pages/Contato'));
|
||||
const Privacidade = lazy(() => import('./pages/Privacidade'));
|
||||
const Termos = lazy(() => import('./pages/Termos'));
|
||||
|
||||
// Páginas de Serviços
|
||||
import TiGerenciada from './pages/services/TiGerenciada';
|
||||
import CloudComputing from './pages/services/CloudComputing';
|
||||
import Seguranca from './pages/services/Seguranca';
|
||||
import Suporte from './pages/services/Suporte';
|
||||
import Redes from './pages/services/Redes';
|
||||
import Pabx from './pages/services/Pabx';
|
||||
import Backup from './pages/services/Backup';
|
||||
import Servidores from './pages/services/Servidores';
|
||||
import Consultoria from './pages/services/Consultoria';
|
||||
import Noc from './pages/services/Noc';
|
||||
const TiGerenciada = lazy(() => import('./pages/services/TiGerenciada'));
|
||||
const CloudComputing = lazy(() => import('./pages/services/CloudComputing'));
|
||||
const Seguranca = lazy(() => import('./pages/services/Seguranca'));
|
||||
const Suporte = lazy(() => import('./pages/services/Suporte'));
|
||||
const Redes = lazy(() => import('./pages/services/Redes'));
|
||||
const Pabx = lazy(() => import('./pages/services/Pabx'));
|
||||
const Backup = lazy(() => import('./pages/services/Backup'));
|
||||
const Servidores = lazy(() => import('./pages/services/Servidores'));
|
||||
const Consultoria = lazy(() => import('./pages/services/Consultoria'));
|
||||
const Noc = lazy(() => import('./pages/services/Noc'));
|
||||
|
||||
// Páginas de Tecnologias (SEO)
|
||||
import Tecnologias from './pages/Tecnologias';
|
||||
import WindowsServer from './pages/tech/WindowsServer';
|
||||
import Linux from './pages/tech/Linux';
|
||||
import PfSense from './pages/tech/PfSense';
|
||||
import Proxmox from './pages/tech/Proxmox';
|
||||
import Vmware from './pages/tech/Vmware';
|
||||
import Sharepoint from './pages/tech/Sharepoint';
|
||||
import Cisco from './pages/tech/Cisco';
|
||||
const Tecnologias = lazy(() => import('./pages/Tecnologias'));
|
||||
const WindowsServer = lazy(() => import('./pages/tech/WindowsServer'));
|
||||
const Linux = lazy(() => import('./pages/tech/Linux'));
|
||||
const PfSense = lazy(() => import('./pages/tech/PfSense'));
|
||||
const Proxmox = lazy(() => import('./pages/tech/Proxmox'));
|
||||
const Vmware = lazy(() => import('./pages/tech/Vmware'));
|
||||
const Sharepoint = lazy(() => import('./pages/tech/Sharepoint'));
|
||||
const Cisco = lazy(() => import('./pages/tech/Cisco'));
|
||||
|
||||
// Avaliador de Maturidade Tecnológica
|
||||
import Avaliador from './pages/Avaliador';
|
||||
import Condominios from './pages/Condominios';
|
||||
const Avaliador = lazy(() => import('./pages/Avaliador'));
|
||||
const Condominios = lazy(() => import('./pages/Condominios'));
|
||||
|
||||
// Landing Pages por Nicho
|
||||
import Contabilidade from './pages/nichos/Contabilidade';
|
||||
import Advocacia from './pages/nichos/Advocacia';
|
||||
import Monitoramento from './pages/nichos/Monitoramento';
|
||||
import FirewallPfSense from './pages/nichos/FirewallPfSense';
|
||||
import BackupCorporativo from './pages/nichos/BackupCorporativo';
|
||||
import VoipEmpresarial from './pages/nichos/VoipEmpresarial';
|
||||
const Contabilidade = lazy(() => import('./pages/nichos/Contabilidade'));
|
||||
const Advocacia = lazy(() => import('./pages/nichos/Advocacia'));
|
||||
const Monitoramento = lazy(() => import('./pages/nichos/Monitoramento'));
|
||||
const FirewallPfSense = lazy(() => import('./pages/nichos/FirewallPfSense'));
|
||||
const BackupCorporativo = lazy(() => import('./pages/nichos/BackupCorporativo'));
|
||||
const VoipEmpresarial = lazy(() => import('./pages/nichos/VoipEmpresarial'));
|
||||
|
||||
// SEO Local - Onde Atuamos
|
||||
const OndeAtuamos = lazy(() => import('./pages/OndeAtuamos'));
|
||||
const LocalGuarulhos = lazy(() => import('./pages/local-guarulhos'));
|
||||
const LocalMogi = lazy(() => import('./pages/local-mogi-das-cruzes'));
|
||||
const LocalIpiranga = lazy(() => import('./pages/local-ipiranga'));
|
||||
const LocalVilaFormosa = lazy(() => import('./pages/local-vila-formosa'));
|
||||
const LocalCerqueiraCesar = lazy(() => import('./pages/local-cerqueira-cesar'));
|
||||
const LocalSantana = lazy(() => import('./pages/local-santana'));
|
||||
const LocalBras = lazy(() => import('./pages/local-bras'));
|
||||
const LocalRibeiraoPreto = lazy(() => import('./pages/local-ribeirao-preto'));
|
||||
|
||||
// Nichos adicionais
|
||||
const NichoIndustria = lazy(() => import('./pages/nicho-industria'));
|
||||
const NichoPortaria = lazy(() => import('./pages/nicho-portaria-remota'));
|
||||
const NichoClinicas = lazy(() => import('./pages/nicho-clinicas'));
|
||||
const NichoImobiliarias = lazy(() => import('./pages/nicho-imobiliarias'));
|
||||
const NichoRecrutamento = lazy(() => import('./pages/nicho-recrutamento-selecao'));
|
||||
|
||||
// Avanzato Tools
|
||||
const Ferramentas = lazy(() => import('./pages/ferramentas'));
|
||||
const SpfChecker = lazy(() => import('./pages/ferramentas/SpfChecker'));
|
||||
const DkimChecker = lazy(() => import('./pages/ferramentas/DkimChecker'));
|
||||
const DmarcChecker = lazy(() => import('./pages/ferramentas/DmarcChecker'));
|
||||
const MxLookup = lazy(() => import('./pages/ferramentas/MxLookup'));
|
||||
const SslChecker = lazy(() => import('./pages/ferramentas/SslChecker'));
|
||||
const EmailSecurityScore = lazy(() => import('./pages/ferramentas/EmailSecurityScore'));
|
||||
const Microsoft365Analyzer = lazy(() => import('./pages/ferramentas/Microsoft365Analyzer'));
|
||||
|
||||
import './App.css';
|
||||
|
||||
@@ -60,20 +90,20 @@ gsap.registerPlugin(ScrollTrigger);
|
||||
// Componente para scroll ao topo ao mudar de página
|
||||
function ScrollToTop() {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
// Refresh ScrollTrigger ao mudar de página
|
||||
ScrollTrigger.refresh();
|
||||
}, [pathname]);
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Componente de Progress Bar
|
||||
function ProgressBar() {
|
||||
const [scrollProgress, setScrollProgress] = useState(0);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const scrollTop = window.scrollY;
|
||||
@@ -85,7 +115,7 @@ function ProgressBar() {
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="progress-bar"
|
||||
@@ -94,6 +124,13 @@ function ProgressBar() {
|
||||
);
|
||||
}
|
||||
|
||||
// Loading simples para lazy load
|
||||
const PageLoader = () => (
|
||||
<div className="min-h-screen bg-[#0e0e0e] flex items-center justify-center">
|
||||
<div className="w-8 h-8 border-2 border-[#cbf400] border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
@@ -101,70 +138,100 @@ function App() {
|
||||
<div className="relative bg-[#0e0e0e] min-h-screen">
|
||||
<ProgressBar />
|
||||
<Navigation />
|
||||
|
||||
|
||||
<main>
|
||||
<Routes>
|
||||
{/* Página Inicial */}
|
||||
<Route path="/" element={<Home />} />
|
||||
|
||||
{/* Sobre */}
|
||||
<Route path="/sobre" element={<Sobre />} />
|
||||
|
||||
{/* Serviços */}
|
||||
<Route path="/servicos/ti-gerenciada" element={<TiGerenciada />} />
|
||||
<Route path="/servicos/cloud-computing" element={<CloudComputing />} />
|
||||
<Route path="/servicos/seguranca" element={<Seguranca />} />
|
||||
<Route path="/servicos/suporte" element={<Suporte />} />
|
||||
<Route path="/servicos/redes" element={<Redes />} />
|
||||
<Route path="/servicos/pabx" element={<Pabx />} />
|
||||
<Route path="/servicos/backup" element={<Backup />} />
|
||||
<Route path="/servicos/servidores" element={<Servidores />} />
|
||||
<Route path="/servicos/consultoria" element={<Consultoria />} />
|
||||
<Route path="/servicos/noc" element={<Noc />} />
|
||||
|
||||
{/* Blog */}
|
||||
<Route path="/blog" element={<Blog />} />
|
||||
<Route path="/blog/:slug" element={<BlogPost />} />
|
||||
|
||||
{/* Cases */}
|
||||
<Route path="/cases" element={<Cases />} />
|
||||
|
||||
{/* Tecnologias (SEO) */}
|
||||
<Route path="/tecnologias" element={<Tecnologias />} />
|
||||
<Route path="/suporte-windows-server" element={<WindowsServer />} />
|
||||
<Route path="/suporte-linux-empresas" element={<Linux />} />
|
||||
<Route path="/suporte-pfsense" element={<PfSense />} />
|
||||
<Route path="/suporte-proxmox" element={<Proxmox />} />
|
||||
<Route path="/suporte-vmware" element={<Vmware />} />
|
||||
<Route path="/suporte-sharepoint" element={<Sharepoint />} />
|
||||
<Route path="/suporte-redes-cisco" element={<Cisco />} />
|
||||
|
||||
{/* Avaliador de Maturidade */}
|
||||
<Route path="/avaliacao" element={<Avaliador />} />
|
||||
|
||||
{/* Tecnologia para Condomínios */}
|
||||
<Route path="/tecnologia-para-condominios" element={<Condominios />} />
|
||||
|
||||
{/* Landing Pages por Nicho */}
|
||||
<Route path="/ti-para-contabilidades" element={<Contabilidade />} />
|
||||
<Route path="/ti-para-advocacias" element={<Advocacia />} />
|
||||
<Route path="/monitoramento-empresarial" element={<Monitoramento />} />
|
||||
<Route path="/firewall-pfsense-empresarial" element={<FirewallPfSense />} />
|
||||
<Route path="/backup-corporativo" element={<BackupCorporativo />} />
|
||||
<Route path="/voip-empresarial" element={<VoipEmpresarial />} />
|
||||
|
||||
{/* Contato */}
|
||||
<Route path="/contato" element={<Contato />} />
|
||||
|
||||
{/* Páginas Legais */}
|
||||
<Route path="/privacidade" element={<Privacidade />} />
|
||||
<Route path="/termos" element={<Termos />} />
|
||||
|
||||
{/* 404 */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
<Suspense fallback={<PageLoader />}>
|
||||
<Routes>
|
||||
{/* Página Inicial - carrega direto */}
|
||||
<Route path="/" element={<Home />} />
|
||||
|
||||
{/* Sobre */}
|
||||
<Route path="/sobre" element={<Sobre />} />
|
||||
|
||||
{/* Serviços */}
|
||||
<Route path="/servicos/ti-gerenciada" element={<TiGerenciada />} />
|
||||
<Route path="/servicos/cloud-computing" element={<CloudComputing />} />
|
||||
<Route path="/servicos/seguranca" element={<Seguranca />} />
|
||||
<Route path="/servicos/suporte" element={<Suporte />} />
|
||||
<Route path="/servicos/redes" element={<Redes />} />
|
||||
<Route path="/servicos/pabx" element={<Pabx />} />
|
||||
<Route path="/servicos/backup" element={<Backup />} />
|
||||
<Route path="/servicos/servidores" element={<Servidores />} />
|
||||
<Route path="/servicos/consultoria" element={<Consultoria />} />
|
||||
<Route path="/servicos/noc" element={<Noc />} />
|
||||
|
||||
{/* Blog */}
|
||||
<Route path="/blog" element={<Blog />} />
|
||||
<Route path="/blog/:slug" element={<BlogPost />} />
|
||||
|
||||
{/* Cases */}
|
||||
<Route path="/cases" element={<Cases />} />
|
||||
|
||||
{/* Tecnologias (SEO) */}
|
||||
<Route path="/tecnologias" element={<Tecnologias />} />
|
||||
<Route path="/suporte-windows-server" element={<WindowsServer />} />
|
||||
<Route path="/suporte-linux-empresas" element={<Linux />} />
|
||||
<Route path="/suporte-pfsense" element={<PfSense />} />
|
||||
<Route path="/suporte-proxmox" element={<Proxmox />} />
|
||||
<Route path="/suporte-vmware" element={<Vmware />} />
|
||||
<Route path="/suporte-sharepoint" element={<Sharepoint />} />
|
||||
<Route path="/suporte-redes-cisco" element={<Cisco />} />
|
||||
|
||||
{/* Avaliador de Maturidade */}
|
||||
<Route path="/avaliacao" element={<Avaliador />} />
|
||||
|
||||
{/* Tecnologia para Condomínios */}
|
||||
<Route path="/tecnologia-para-condominios" element={<Condominios />} />
|
||||
|
||||
{/* Landing Pages por Nicho */}
|
||||
<Route path="/ti-para-contabilidades" element={<Contabilidade />} />
|
||||
<Route path="/ti-para-advocacias" element={<Advocacia />} />
|
||||
<Route path="/monitoramento-empresarial" element={<Monitoramento />} />
|
||||
<Route path="/firewall-pfsense-empresarial" element={<FirewallPfSense />} />
|
||||
<Route path="/backup-corporativo" element={<BackupCorporativo />} />
|
||||
<Route path="/voip-empresarial" element={<VoipEmpresarial />} />
|
||||
|
||||
{/* SEO Local - Onde Atuamos */}
|
||||
<Route path="/onde-atuamos" element={<OndeAtuamos />} />
|
||||
<Route path="/suporte-ti-guarulhos" element={<LocalGuarulhos />} />
|
||||
<Route path="/suporte-ti-mogi-das-cruzes" element={<LocalMogi />} />
|
||||
<Route path="/suporte-ti-ipiranga" element={<LocalIpiranga />} />
|
||||
<Route path="/suporte-ti-vila-formosa" element={<LocalVilaFormosa />} />
|
||||
<Route path="/suporte-ti-cerqueira-cesar" element={<LocalCerqueiraCesar />} />
|
||||
<Route path="/suporte-ti-santana" element={<LocalSantana />} />
|
||||
<Route path="/suporte-ti-bras" element={<LocalBras />} />
|
||||
<Route path="/suporte-ti-ribeirao-preto" element={<LocalRibeiraoPreto />} />
|
||||
|
||||
{/* Nichos adicionais */}
|
||||
<Route path="/suporte-ti-industria" element={<NichoIndustria />} />
|
||||
<Route path="/suporte-ti-portaria-remota" element={<NichoPortaria />} />
|
||||
<Route path="/suporte-ti-clinicas" element={<NichoClinicas />} />
|
||||
<Route path="/suporte-ti-imobiliarias" element={<NichoImobiliarias />} />
|
||||
<Route path="/suporte-ti-recrutamento-selecao" element={<NichoRecrutamento />} />
|
||||
|
||||
{/* Contato */}
|
||||
<Route path="/contato" element={<Contato />} />
|
||||
|
||||
{/* Páginas Legais */}
|
||||
<Route path="/privacidade" element={<Privacidade />} />
|
||||
<Route path="/termos" element={<Termos />} />
|
||||
|
||||
{/* Avanzato Tools */}
|
||||
<Route path="/ferramentas" element={<Ferramentas />} />
|
||||
<Route path="/ferramentas/spf-checker" element={<SpfChecker />} />
|
||||
<Route path="/ferramentas/dkim-checker" element={<DkimChecker />} />
|
||||
<Route path="/ferramentas/dmarc-checker" element={<DmarcChecker />} />
|
||||
<Route path="/ferramentas/mx-lookup" element={<MxLookup />} />
|
||||
<Route path="/ferramentas/ssl-checker" element={<SslChecker />} />
|
||||
<Route path="/ferramentas/email-security-score" element={<EmailSecurityScore />} />
|
||||
<Route path="/ferramentas/microsoft-365-analyzer" element={<Microsoft365Analyzer />} />
|
||||
|
||||
{/* 404 */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</main>
|
||||
|
||||
|
||||
<Footer />
|
||||
<WhatsAppWidget />
|
||||
<CookieConsent />
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
import { useState } from 'react';
|
||||
import { AlertTriangle, X } from 'lucide-react';
|
||||
import { IS_HOMOLOGATION } from '@/config/mautic';
|
||||
import { ENV_CONFIG, IS_PRODUCTION } from '../config/environment';
|
||||
|
||||
/**
|
||||
* Banner visual que aparece em ambiente de homologacao
|
||||
* Avisa que Mautic, Google Ads e Facebook Pixel estao em modo simulacao
|
||||
* Banner visual que aparece em ambiente NAO-producao
|
||||
* Avisa qual ambiente esta rodando
|
||||
*/
|
||||
export default function HomologationBanner() {
|
||||
export default function AmbientBanner() {
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
|
||||
if (!IS_HOMOLOGATION || dismissed) return null;
|
||||
// So mostra em producao
|
||||
if (IS_PRODUCTION || dismissed) return null;
|
||||
|
||||
const { bannerText, bannerColor } = ENV_CONFIG;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-4 right-4 z-[100] max-w-md">
|
||||
<div className="bg-amber-500 text-[#0e0e0e] rounded-xl shadow-2xl p-4 border-2 border-amber-400">
|
||||
<div className={`${bannerColor} text-[#0e0e0e] rounded-xl shadow-2xl p-4 border-2 border-white/20`}>
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="w-5 h-5 shrink-0 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<p className="font-bold text-sm">Ambiente de Homologacao</p>
|
||||
<p className="font-bold text-sm">Ambiente: {bannerText}</p>
|
||||
<p className="text-xs mt-1 opacity-90">
|
||||
Mautic, Google Ads e Facebook Pixel estao em modo <strong>simulacao</strong>.
|
||||
Nenhum dado real e enviado. Verifique o console (F12) para ver o que seria enviado.
|
||||
Tracking desativado | SEO: noindex |
|
||||
Nenhum dado enviado a Mautic, GA4, Ads ou Pixel.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setDismissed(true)}
|
||||
className="shrink-0 p-1 hover:bg-amber-600 rounded-lg transition-colors"
|
||||
className="shrink-0 p-1 hover:bg-black/20 rounded-lg transition-colors"
|
||||
aria-label="Fechar"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// ============================================================================
|
||||
// SEO - Meta tags dinamicas por pagina
|
||||
// ============================================================================
|
||||
// Uso: <SEO title="..." description="..." />
|
||||
//
|
||||
// Cada pagina deve usar este componente com suas proprias tags.
|
||||
// O canonical e gerado automaticamente a partir da URL atual.
|
||||
// ============================================================================
|
||||
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { ENV_CONFIG } from '../config/environment';
|
||||
|
||||
interface SEOProps {
|
||||
title?: string;
|
||||
description?: string;
|
||||
keywords?: string;
|
||||
ogImage?: string;
|
||||
ogType?: string;
|
||||
noindex?: boolean; // Se true, forca noindex mesmo em producao
|
||||
schema?: Record<string, any>;
|
||||
}
|
||||
|
||||
const SITE_URL = 'https://avanzato.com.br';
|
||||
const DEFAULT_TITLE = 'Avanzato Tecnologia | TI Gerenciada, Cloud e Seguranca';
|
||||
const DEFAULT_DESC = 'Solucoes em TI Gerenciada, Cloud Computing e Seguranca da Informacao. +23 anos de experiencia, +500 clientes atendidos. Suporte 24/7 em Guarulhos e regiao.';
|
||||
const DEFAULT_OG_IMAGE = 'https://avanzato.com.br/og-image.jpg';
|
||||
|
||||
export default function SEO({
|
||||
title,
|
||||
description,
|
||||
keywords,
|
||||
ogImage,
|
||||
ogType = 'website',
|
||||
noindex = false,
|
||||
schema,
|
||||
}: SEOProps) {
|
||||
const location = useLocation();
|
||||
const pathname = location.pathname;
|
||||
const canonicalUrl = `${SITE_URL}${pathname}`;
|
||||
const fullTitle = title ? `${title} | Avanzato` : DEFAULT_TITLE;
|
||||
|
||||
return (
|
||||
<Helmet>
|
||||
{/* Title */}
|
||||
<title>{fullTitle}</title>
|
||||
<meta name="title" content={fullTitle} />
|
||||
|
||||
{/* Description */}
|
||||
{description && <meta name="description" content={description} />}
|
||||
{!description && <meta name="description" content={DEFAULT_DESC} />}
|
||||
|
||||
{/* Keywords */}
|
||||
{keywords && <meta name="keywords" content={keywords} />}
|
||||
|
||||
{/* Canonical - DINAMICO por pagina */}
|
||||
<link rel="canonical" href={canonicalUrl} />
|
||||
|
||||
{/* Robots - automatico por ambiente, override com prop noindex */}
|
||||
{noindex || !ENV_CONFIG.allowIndexing ? (
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
) : (
|
||||
<meta name="robots" content="index, follow" />
|
||||
)}
|
||||
|
||||
{/* Open Graph */}
|
||||
<meta property="og:type" content={ogType} />
|
||||
<meta property="og:url" content={canonicalUrl} />
|
||||
<meta property="og:title" content={fullTitle} />
|
||||
<meta property="og:description" content={description || DEFAULT_DESC} />
|
||||
<meta property="og:image" content={ogImage || DEFAULT_OG_IMAGE} />
|
||||
<meta property="og:locale" content="pt_BR" />
|
||||
<meta property="og:site_name" content="Avanzato Tecnologia" />
|
||||
|
||||
{/* Twitter */}
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={canonicalUrl} />
|
||||
<meta property="twitter:title" content={fullTitle} />
|
||||
<meta property="twitter:description" content={description || DEFAULT_DESC} />
|
||||
<meta property="twitter:image" content={ogImage || DEFAULT_OG_IMAGE} />
|
||||
|
||||
{/* Schema.org */}
|
||||
{schema && (
|
||||
<script type="application/ld+json">
|
||||
{JSON.stringify(schema)}
|
||||
</script>
|
||||
)}
|
||||
</Helmet>
|
||||
);
|
||||
}
|
||||
|
||||
// Schema helpers
|
||||
export const createServiceSchema = (name: string, description: string, urlPath: string) => ({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Service',
|
||||
name,
|
||||
description,
|
||||
provider: {
|
||||
'@type': 'Organization',
|
||||
name: 'Avanzato Tecnologia',
|
||||
url: SITE_URL,
|
||||
},
|
||||
url: `${SITE_URL}${urlPath}`,
|
||||
areaServed: {
|
||||
'@type': 'City',
|
||||
name: 'Guarulhos',
|
||||
},
|
||||
});
|
||||
|
||||
export const createFAQSchema = (questions: Array<{q: string; a: string}>) => ({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: questions.map(({q, a}) => ({
|
||||
'@type': 'Question',
|
||||
name: q,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: a,
|
||||
},
|
||||
})),
|
||||
});
|
||||
@@ -0,0 +1,368 @@
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ArrowLeft, ArrowRight, CheckCircle, AlertTriangle, XCircle, Info, Loader2 } from 'lucide-react';
|
||||
|
||||
export interface ResultadoFerramenta {
|
||||
status: 'success' | 'warning' | 'error' | 'info';
|
||||
titulo: string;
|
||||
mensagem: string;
|
||||
detalhes?: Record<string, string>;
|
||||
recomendacoes?: string[];
|
||||
score?: number;
|
||||
}
|
||||
|
||||
interface FerramentaLayoutProps {
|
||||
nome: string;
|
||||
descricao: string;
|
||||
icone: React.ReactNode;
|
||||
placeholder: string;
|
||||
explicacao: string;
|
||||
comoFunciona: string[];
|
||||
perguntasUteis: { q: string; a: string }[];
|
||||
onConsultar: (dominio: string) => Promise<ResultadoFerramenta>;
|
||||
schema?: Record<string, any>;
|
||||
}
|
||||
|
||||
const contagemKey = (slug: string) => `avanzato-tools-count-${slug}`;
|
||||
const cadastroKey = 'avanzato-tools-cadastro';
|
||||
|
||||
const FerramentaLayout = ({
|
||||
nome,
|
||||
descricao,
|
||||
icone,
|
||||
placeholder,
|
||||
explicacao,
|
||||
comoFunciona,
|
||||
perguntasUteis,
|
||||
onConsultar,
|
||||
}: FerramentaLayoutProps) => {
|
||||
const [dominio, setDominio] = useState('');
|
||||
const [resultado, setResultado] = useState<ResultadoFerramenta | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [erro, setErro] = useState('');
|
||||
const [mostrarCadastro, setMostrarCadastro] = useState(false);
|
||||
const [cadastrado, setCadastrado] = useState(false);
|
||||
const [consultas, setConsultas] = useState(() => {
|
||||
const slug = window.location.pathname.split('/').pop() || '';
|
||||
return parseInt(localStorage.getItem(contagemKey(slug)) || '0', 10);
|
||||
});
|
||||
const [feedbackUtil, setFeedbackUtil] = useState<'sim' | 'nao' | null>(null);
|
||||
const [feedbackTexto, setFeedbackTexto] = useState('');
|
||||
const [mostrarDor, setMostrarDor] = useState(false);
|
||||
const [dorTI, setDorTI] = useState('');
|
||||
const [urgencia, setUrgencia] = useState('');
|
||||
|
||||
const slug = window.location.pathname.split('/').pop() || '';
|
||||
|
||||
const podeConsultar = () => {
|
||||
if (cadastrado) return true;
|
||||
if (consultas < 2) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleConsultar = async () => {
|
||||
if (!dominio.trim()) {
|
||||
setErro('Digite um dominio valido (ex: avanzato.com.br)');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!podeConsultar()) {
|
||||
setMostrarCadastro(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setErro('');
|
||||
setLoading(true);
|
||||
setResultado(null);
|
||||
|
||||
try {
|
||||
const res = await onConsultar(dominio.trim());
|
||||
setResultado(res);
|
||||
|
||||
// Incrementar contagem
|
||||
const novaContagem = consultas + 1;
|
||||
setConsultas(novaContagem);
|
||||
localStorage.setItem(contagemKey(slug), String(novaContagem));
|
||||
|
||||
// Se atingiu limite, mostrar cadastro
|
||||
if (novaContagem >= 2 && !cadastrado) {
|
||||
setTimeout(() => setMostrarDor(true), 2000);
|
||||
}
|
||||
} catch (err) {
|
||||
setErro('Erro ao consultar. Verifique o dominio e tente novamente.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCadastro = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setCadastrado(true);
|
||||
localStorage.setItem(cadastroKey, 'true');
|
||||
setMostrarCadastro(false);
|
||||
};
|
||||
|
||||
const statusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case 'success': return <CheckCircle className="w-8 h-8 text-green-400" />;
|
||||
case 'warning': return <AlertTriangle className="w-8 h-8 text-yellow-400" />;
|
||||
case 'error': return <XCircle className="w-8 h-8 text-red-400" />;
|
||||
default: return <Info className="w-8 h-8 text-blue-400" />;
|
||||
}
|
||||
};
|
||||
|
||||
const doresTI = [
|
||||
'Seguranca da informacao',
|
||||
'Backup',
|
||||
'Microsoft 365',
|
||||
'Firewall',
|
||||
'Servidores',
|
||||
'Lentidao da rede',
|
||||
'VPN / Home Office',
|
||||
'Suporte aos usuarios',
|
||||
'Custos de TI',
|
||||
'LGPD',
|
||||
'Ransomware',
|
||||
'Outro',
|
||||
];
|
||||
|
||||
const urgencias = [
|
||||
'Apenas pesquisando',
|
||||
'Quero melhorar nos proximos meses',
|
||||
'Preciso resolver este mes',
|
||||
'Preciso resolver imediatamente',
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<Link to="/ferramentas" className="inline-flex items-center gap-2 text-gray-400 hover:text-[#cbf400] transition-colors mb-4">
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
Todas as ferramentas
|
||||
</Link>
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className="p-3 bg-[#cbf400]/10 rounded-xl text-[#cbf400]">{icone}</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-white">{nome}</h1>
|
||||
<p className="text-gray-300">{descricao}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Formulario */}
|
||||
<div className="bg-[#1a1a1a] rounded-2xl p-6 border border-[#2a2a2a] mb-8">
|
||||
<div className="flex gap-4">
|
||||
<input
|
||||
type="text"
|
||||
value={dominio}
|
||||
onChange={(e) => setDominio(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleConsultar()}
|
||||
placeholder={placeholder}
|
||||
className="flex-1 bg-[#0e0e0e] border border-[#2a2a2a] rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:border-[#cbf400] focus:outline-none"
|
||||
/>
|
||||
<button
|
||||
onClick={handleConsultar}
|
||||
disabled={loading}
|
||||
className="bg-[#cbf400] text-[#0e0e0e] px-6 py-3 rounded-xl font-semibold hover:bg-[#b8e000] transition-colors disabled:opacity-50 flex items-center gap-2"
|
||||
>
|
||||
{loading && <Loader2 className="w-4 h-4 animate-spin" />}
|
||||
{loading ? 'Analisando...' : 'Verificar'}
|
||||
</button>
|
||||
</div>
|
||||
{erro && <p className="text-red-400 mt-3 text-sm">{erro}</p>}
|
||||
|
||||
{!cadastrado && consultas >= 2 && (
|
||||
<p className="text-yellow-400 mt-3 text-sm">
|
||||
Voce usou {consultas} consultas. <button onClick={() => setMostrarCadastro(true)} className="underline hover:text-[#cbf400]">Cadastre-se gratuitamente</button> para consultas ilimitadas.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Resultado */}
|
||||
{resultado && (
|
||||
<div className={`bg-[#1a1a1a] rounded-2xl p-6 border mb-8 ${
|
||||
resultado.status === 'success' ? 'border-green-500/30' :
|
||||
resultado.status === 'warning' ? 'border-yellow-500/30' :
|
||||
resultado.status === 'error' ? 'border-red-500/30' : 'border-blue-500/30'
|
||||
}`}>
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
{statusIcon(resultado.status)}
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-white">{resultado.titulo}</h2>
|
||||
{resultado.score !== undefined && (
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<div className="w-32 h-2 bg-gray-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full ${
|
||||
resultado.score >= 80 ? 'bg-green-400' :
|
||||
resultado.score >= 50 ? 'bg-yellow-400' : 'bg-red-400'
|
||||
}`}
|
||||
style={{ width: `${resultado.score}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-white">{resultado.score}/100</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-300 mb-4">{resultado.mensagem}</p>
|
||||
|
||||
{resultado.detalhes && Object.entries(resultado.detalhes).length > 0 && (
|
||||
<div className="bg-[#0e0e0e] rounded-xl p-4 mb-4">
|
||||
{Object.entries(resultado.detalhes).map(([key, value]) => (
|
||||
<div key={key} className="flex justify-between py-2 border-b border-[#2a2a2a] last:border-0">
|
||||
<span className="text-gray-400">{key}</span>
|
||||
<span className="text-white font-mono text-sm">{value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{resultado.recomendacoes && resultado.recomendacoes.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-[#cbf400] mb-2">Recomendacoes</h3>
|
||||
<ul className="space-y-2">
|
||||
{resultado.recomendacoes.map((r, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<CheckCircle className="w-4 h-4 text-[#cbf400] mt-0.5 shrink-0" />
|
||||
{r}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Cadastro */}
|
||||
{mostrarCadastro && !cadastrado && (
|
||||
<div className="bg-[#1a1a1a] rounded-2xl p-6 border border-[#cbf400]/30 mb-8">
|
||||
<h2 className="text-xl font-semibold text-white mb-2">Cadastro Rapido Gratuito</h2>
|
||||
<p className="text-gray-300 text-sm mb-4">
|
||||
Voce ja usou 2 consultas gratuitas. Cadastre-se para consultas ilimitadas e relatorios completos.
|
||||
</p>
|
||||
<form onSubmit={handleCadastro} className="space-y-3">
|
||||
<input type="text" placeholder="Nome" required className="w-full bg-[#0e0e0e] border border-[#2a2a2a] rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:border-[#cbf400] focus:outline-none" />
|
||||
<input type="email" placeholder="E-mail corporativo" required className="w-full bg-[#0e0e0e] border border-[#2a2a2a] rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:border-[#cbf400] focus:outline-none" />
|
||||
<input type="text" placeholder="Empresa" required className="w-full bg-[#0e0e0e] border border-[#2a2a2a] rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:border-[#cbf400] focus:outline-none" />
|
||||
<p className="text-gray-500 text-xs">
|
||||
Autorizo a Avanzato Tecnologia a enviar conteudos tecnicos, diagnosticos e informacoes sobre solucoes de TI, seguranca e Microsoft 365.
|
||||
</p>
|
||||
<button type="submit" className="bg-[#cbf400] text-[#0e0e0e] px-6 py-3 rounded-xl font-semibold hover:bg-[#b8e000] transition-colors">
|
||||
Cadastrar e Continuar
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Dor de TI */}
|
||||
{mostrarDor && !cadastrado && (
|
||||
<div className="bg-[#1a1a1a] rounded-2xl p-6 border border-yellow-500/30 mb-8">
|
||||
<h2 className="text-lg font-semibold text-white mb-4">Qual e hoje sua maior dor em TI?</h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-2 mb-4">
|
||||
{doresTI.map(d => (
|
||||
<button key={d} onClick={() => setDorTI(d)} className={`p-2 rounded-lg text-sm transition-colors ${dorTI === d ? 'bg-[#cbf400] text-[#0e0e0e]' : 'bg-[#0e0e0e] text-gray-300 hover:bg-[#2a2a2a]'}`}>
|
||||
{d}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{dorTI && (
|
||||
<div className="mt-4">
|
||||
<h3 className="text-sm font-semibold text-white mb-2">Qual a urgencia?</h3>
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
{urgencias.map(u => (
|
||||
<button key={u} onClick={() => setUrgencia(u)} className={`px-3 py-2 rounded-lg text-sm transition-colors ${urgencia === u ? 'bg-[#cbf400] text-[#0e0e0e]' : 'bg-[#0e0e0e] text-gray-300 hover:bg-[#2a2a2a]'}`}>
|
||||
{u}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{dorTI && urgencia && (
|
||||
<div className="flex gap-4 mt-4">
|
||||
<Link to="/avaliacao" className="bg-[#cbf400] text-[#0e0e0e] px-6 py-3 rounded-xl font-semibold hover:bg-[#b8e000] transition-colors inline-flex items-center gap-2">
|
||||
Solicitar Avaliacao Gratuita <ArrowRight className="w-4 h-4" />
|
||||
</Link>
|
||||
<button onClick={() => setMostrarCadastro(true)} className="border border-[#cbf400] text-[#cbf400] px-6 py-3 rounded-xl font-semibold hover:bg-[#cbf400]/10 transition-colors">
|
||||
Cadastrar para Relatorio Completo
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Feedback */}
|
||||
{resultado && (
|
||||
<div className="bg-[#1a1a1a] rounded-2xl p-6 border border-[#2a2a2a] mb-8">
|
||||
<h3 className="text-lg font-semibold text-white mb-3">Esta ferramenta foi util?</h3>
|
||||
{feedbackUtil === null ? (
|
||||
<div className="flex gap-4">
|
||||
<button onClick={() => setFeedbackUtil('sim')} className="bg-green-500/20 text-green-400 px-6 py-2 rounded-lg hover:bg-green-500/30 transition-colors">
|
||||
Sim, foi util
|
||||
</button>
|
||||
<button onClick={() => setFeedbackUtil('nao')} className="bg-red-500/20 text-red-400 px-6 py-2 rounded-lg hover:bg-red-500/30 transition-colors">
|
||||
Nao, faltou algo
|
||||
</button>
|
||||
</div>
|
||||
) : feedbackUtil === 'sim' ? (
|
||||
<div>
|
||||
<p className="text-green-400 mb-4">Que otimo! A Avanzato investe em ferramentas gratuitas para ajudar empresas a proteger sua infraestrutura de TI.</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Link to="/avaliacao" className="bg-[#cbf400] text-[#0e0e0e] px-4 py-2 rounded-lg font-medium hover:bg-[#b8e000] transition-colors inline-flex items-center gap-2">
|
||||
Solicitar Avaliacao Gratuita <ArrowRight className="w-4 h-4" />
|
||||
</Link>
|
||||
<button onClick={() => setMostrarCadastro(true)} className="border border-[#cbf400] text-[#cbf400] px-4 py-2 rounded-lg hover:bg-[#cbf400]/10 transition-colors">
|
||||
Receber Relatorio Completo
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<p className="text-gray-300 mb-2">O que faltou ou o que podemos melhorar?</p>
|
||||
<textarea
|
||||
value={feedbackTexto}
|
||||
onChange={(e) => setFeedbackTexto(e.target.value)}
|
||||
className="w-full bg-[#0e0e0e] border border-[#2a2a2a] rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:border-[#cbf400] focus:outline-none h-24"
|
||||
placeholder="Conte-nos como podemos melhorar..."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Conteudo explicativo */}
|
||||
<div className="bg-[#1a1a1a] rounded-2xl p-6 border border-[#2a2a2a] mb-8">
|
||||
<h2 className="text-xl font-semibold text-white mb-4">O que e {nome.split(' ')[0]}?</h2>
|
||||
<p className="text-gray-300 mb-6">{explicacao}</p>
|
||||
|
||||
<h3 className="text-lg font-semibold text-white mb-3">Como funciona</h3>
|
||||
<ol className="space-y-2 mb-6">
|
||||
{comoFunciona.map((passo, i) => (
|
||||
<li key={i} className="flex items-start gap-3 text-gray-300">
|
||||
<span className="w-6 h-6 bg-[#cbf400]/20 text-[#cbf400] rounded-full flex items-center justify-center text-sm font-medium shrink-0">{i + 1}</span>
|
||||
{passo}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
{/* FAQ da ferramenta */}
|
||||
{perguntasUteis.length > 0 && (
|
||||
<div className="bg-[#1a1a1a] rounded-2xl p-6 border border-[#2a2a2a] mb-8">
|
||||
<h2 className="text-xl font-semibold text-white mb-4">Perguntas Frequentes sobre {nome.split(' ')[0]}</h2>
|
||||
<div className="space-y-4">
|
||||
{perguntasUteis.map((faq, i) => (
|
||||
<div key={i} className="border-b border-[#2a2a2a] pb-4 last:border-0">
|
||||
<h3 className="text-[#cbf400] font-medium mb-1">{faq.q}</h3>
|
||||
<p className="text-gray-300 text-sm">{faq.a}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FerramentaLayout;
|
||||
@@ -0,0 +1,373 @@
|
||||
// ============================================================================
|
||||
// DNS/SSL API - Consultas via APIs publicas
|
||||
// ============================================================================
|
||||
// Usa APIs gratuitas para consultar registros DNS, SSL, MX, SPF, DKIM, DMARC
|
||||
// ============================================================================
|
||||
|
||||
import type { ResultadoFerramenta } from './FerramentaLayout';
|
||||
|
||||
// Helper para chamar a API do Google DNS
|
||||
async function queryDNS(name: string, type: string): Promise<any> {
|
||||
try {
|
||||
const res = await fetch(`https://dns.google/resolve?name=${encodeURIComponent(name)}&type=${type}`);
|
||||
return await res.json();
|
||||
} catch {
|
||||
return { Status: 2, Answer: [] };
|
||||
}
|
||||
}
|
||||
|
||||
// Verificar SPF
|
||||
export async function checkSPF(dominio: string): Promise<ResultadoFerramenta> {
|
||||
const data = await queryDNS(dominio, 'TXT');
|
||||
const records = (data.Answer || []).filter((r: any) => r.data && r.data.includes('v=spf1'));
|
||||
|
||||
if (records.length === 0) {
|
||||
return {
|
||||
status: 'error',
|
||||
titulo: 'SPF Nao Encontrado',
|
||||
mensagem: `O dominio ${dominio} nao possui um registro SPF configurado. Sem SPF, qualquer pessoa pode enviar e-mails em nome do seu dominio, o que facilita ataques de phishing e spoofing.`,
|
||||
detalhes: { Dominio: dominio, Registro: 'Nao encontrado' },
|
||||
recomendacoes: [
|
||||
'Crie um registro TXT no DNS com a politica SPF',
|
||||
'Exemplo: v=spf1 include:_spf.google.com ~all (para Microsoft 365/Google Workspace)',
|
||||
'Use o mecanismo ~all (softfail) inicialmente para testes',
|
||||
'Monitore os logs antes de usar -all (hardfail)',
|
||||
'Consulte a Avanzato para configuracao personalizada',
|
||||
],
|
||||
score: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const spf = records[0].data.replace(/"/g, '');
|
||||
const hasAll = spf.includes('-all') || spf.includes('~all') || spf.includes('?all');
|
||||
const isHardFail = spf.includes('-all');
|
||||
const includes = spf.match(/include:[^\s]+/g) || [];
|
||||
const ip4 = spf.match(/ip4:[^\s]+/g) || [];
|
||||
const ip6 = spf.match(/ip6:[^\s]+/g) || [];
|
||||
|
||||
let status: 'success' | 'warning' | 'error' = 'warning';
|
||||
let score = 60;
|
||||
let recomendacoes: string[] = [];
|
||||
|
||||
if (isHardFail) { status = 'success'; score = 90; }
|
||||
else if (spf.includes('~all')) { status = 'warning'; score = 70; recomendacoes.push('Considere mudar ~all para -all para protecao mais forte'); }
|
||||
else if (!hasAll) { status = 'error'; score = 30; recomendacoes.push('Adicione ~all ou -all ao final do seu SPF'); }
|
||||
|
||||
if (includes.length > 10) { score -= 20; recomendacoes.push('Voce tem muitos includes. Mais de 10 lookups pode quebrar o SPF'); }
|
||||
|
||||
return {
|
||||
status,
|
||||
titulo: status === 'success' ? 'SPF Configurado Corretamente' : 'SPF Configurado com Ressalvas',
|
||||
mensagem: `O dominio ${dominio} possui um registro SPF ${isHardFail ? 'com protecao rigida (-all)' : hasAll ? 'mas sem protecao rigida' : 'mas sem mecanismo de falha'}.`,
|
||||
detalhes: {
|
||||
Dominio: dominio,
|
||||
Registro: spf.substring(0, 100),
|
||||
Mecanismo: isHardFail ? '-all (hardfail)' : spf.includes('~all') ? '~all (softfail)' : 'Sem all',
|
||||
Includes: String(includes.length),
|
||||
'IPs v4': String(ip4.length),
|
||||
'IPs v6': String(ip6.length),
|
||||
},
|
||||
recomendacoes: recomendacoes.length > 0 ? recomendacoes : ['Seu SPF esta bem configurado!', 'Monitore regularmente para garantir que todos os servidores legitimos estejam incluidos'],
|
||||
score,
|
||||
};
|
||||
}
|
||||
|
||||
// Verificar DKIM
|
||||
export async function checkDKIM(dominio: string): Promise<ResultadoFerramenta> {
|
||||
const selectors = ['default', 'google', 'selector1', 'selector2', 'mail', 'dkim'];
|
||||
let found = false;
|
||||
let validSelector = '';
|
||||
let dkimRecord = '';
|
||||
|
||||
for (const selector of selectors) {
|
||||
const data = await queryDNS(`${selector}._domainkey.${dominio}`, 'TXT');
|
||||
if (data.Answer && data.Answer.length > 0) {
|
||||
const record = data.Answer[0].data;
|
||||
if (record.includes('v=DKIM1') || record.includes('k=rsa')) {
|
||||
found = true;
|
||||
validSelector = selector;
|
||||
dkimRecord = record.replace(/"/g, '').substring(0, 80);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return {
|
||||
status: 'warning',
|
||||
titulo: 'DKIM Nao Encontrado',
|
||||
mensagem: `Nao foi possivel encontrar um registro DKIM valido para ${dominio} com os selectors comuns. Isso nao significa que nao existe, mas pode indicar que nao esta configurado ou usa um selector personalizado.`,
|
||||
detalhes: { Dominio: dominio, 'Selectors testados': selectors.join(', ') },
|
||||
recomendacoes: [
|
||||
'Verifique qual selector seu servidor de e-mail usa',
|
||||
'Para Microsoft 365: selector1 e selector2',
|
||||
'Para Google Workspace: google',
|
||||
'Entre em contato com seu administrador de e-mail',
|
||||
'A Avanzato pode configurar DKIM para seu dominio',
|
||||
],
|
||||
score: 30,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
titulo: 'DKIM Configurado',
|
||||
mensagem: `DKIM encontrado com o selector "${validSelector}". Seu dominio possui assinatura digital para autenticacao de e-mails.`,
|
||||
detalhes: {
|
||||
Dominio: dominio,
|
||||
Selector: validSelector,
|
||||
Registro: dkimRecord + '...',
|
||||
},
|
||||
recomendacoes: [
|
||||
'DKIM esta configurado corretamente!',
|
||||
'Certifique-se de que todos os servidores de e-mail assinam com DKIM',
|
||||
'Renove as chaves DKIM periodicamente',
|
||||
],
|
||||
score: 85,
|
||||
};
|
||||
}
|
||||
|
||||
// Verificar DMARC
|
||||
export async function checkDMARC(dominio: string): Promise<ResultadoFerramenta> {
|
||||
const data = await queryDNS(`_dmarc.${dominio}`, 'TXT');
|
||||
const records = (data.Answer || []).filter((r: any) => r.data && r.data.includes('v=DMARC1'));
|
||||
|
||||
if (records.length === 0) {
|
||||
return {
|
||||
status: 'error',
|
||||
titulo: 'DMARC Nao Encontrado',
|
||||
mensagem: `O dominio ${dominio} nao possui DMARC configurado. Sem DMARC, voce nao recebe relatorios sobre falhas de autenticacao e nao pode instruir provedores a rejeitar e-mails falsos.`,
|
||||
detalhes: { Dominio: dominio, Registro: 'Nao encontrado' },
|
||||
recomendacoes: [
|
||||
'Crie um registro TXT em _dmarc.seudominio.com',
|
||||
'Exemplo: v=DMARC1; p=quarantine; rua=mailto:dmarc@seudominio.com',
|
||||
'Comece com p=none para monitorar, depois mude para quarantine ou reject',
|
||||
'Configure rua para receber relatorios agregados',
|
||||
'Consulte a Avanzato para configuracao completa',
|
||||
],
|
||||
score: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const dmarc = records[0].data.replace(/"/g, '');
|
||||
const policy = dmarc.match(/p=(\w+)/)?.[1] || 'none';
|
||||
const rua = dmarc.match(/rua=([^;]+)/)?.[1] || 'Nao configurado';
|
||||
|
||||
let status: 'success' | 'warning' | 'error' = 'warning';
|
||||
let score = 50;
|
||||
|
||||
if (policy === 'reject') { status = 'success'; score = 100; }
|
||||
else if (policy === 'quarantine') { status = 'warning'; score = 75; }
|
||||
else { status = 'error'; score = 40; }
|
||||
|
||||
return {
|
||||
status,
|
||||
titulo: policy === 'reject' ? 'DMARC com Protecao Maxima' : 'DMARC Configurado',
|
||||
mensagem: `DMARC encontrado com politica "${policy}". ${policy === 'reject' ? 'E-mails que falham na autenticacao serao rejeitados.' : policy === 'quarantine' ? 'E-mails suspeitos vao para spam.' : 'Apenas monitorando, sem acao sobre e-mails falsos.'}`,
|
||||
detalhes: {
|
||||
Dominio: dominio,
|
||||
Politica: policy,
|
||||
'Relatorios (rua)': rua,
|
||||
Registro: dmarc.substring(0, 80) + '...',
|
||||
},
|
||||
recomendacoes: policy === 'reject'
|
||||
? ['Excelente! Sua protecao DMARC esta no nivel maximo']
|
||||
: [`Considere mudar p=${policy} para p=reject para protecao total`, 'Monitore os relatorios rua antes de mudar para reject'],
|
||||
score,
|
||||
};
|
||||
}
|
||||
|
||||
// MX Lookup
|
||||
export async function checkMX(dominio: string): Promise<ResultadoFerramenta> {
|
||||
const data = await queryDNS(dominio, 'MX');
|
||||
const records = (data.Answer || []).sort((a: any, b: any) => a.preference - b.preference);
|
||||
|
||||
if (records.length === 0) {
|
||||
return {
|
||||
status: 'error',
|
||||
titulo: 'Registros MX Nao Encontrados',
|
||||
mensagem: `O dominio ${dominio} nao possui registros MX. Sem MX, o dominio nao pode receber e-mails.`,
|
||||
detalhes: { Dominio: dominio, Status: 'Sem MX' },
|
||||
recomendacoes: [
|
||||
'Configure registros MX no DNS do seu dominio',
|
||||
'Para Microsoft 365: siga o assistente de configuracao',
|
||||
'Para Google Workspace: adicione os MX do Google',
|
||||
],
|
||||
score: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const mxProviders = records.map((r: any) => r.data).join(', ');
|
||||
const isMicrosoft = mxProviders.toLowerCase().includes('microsoft') || mxProviders.toLowerCase().includes('outlook') || mxProviders.toLowerCase().includes('protection');
|
||||
const isGoogle = mxProviders.toLowerCase().includes('google') || mxProviders.toLowerCase().includes('aspmx');
|
||||
const provider = isMicrosoft ? 'Microsoft 365 / Exchange Online' : isGoogle ? 'Google Workspace' : 'Outro / Servidor Proprio';
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
titulo: `${records.length} Servidor(es) MX Encontrado(s)`,
|
||||
mensagem: `O dominio ${dominio} esta configurado para receber e-mails via ${provider}.`,
|
||||
detalhes: {
|
||||
Dominio: dominio,
|
||||
'Servidores MX': String(records.length),
|
||||
Provedor: provider,
|
||||
'Prioridade mais alta': records[0].data,
|
||||
},
|
||||
recomendacoes: [
|
||||
'Sua infraestrutura de e-mail esta configurada',
|
||||
`Voce esta usando: ${provider}`,
|
||||
'Mantenha pelo menos 2 servidores MX para redundancia',
|
||||
],
|
||||
score: 90,
|
||||
};
|
||||
}
|
||||
|
||||
// SSL Checker
|
||||
export async function checkSSL(dominio: string): Promise<ResultadoFerramenta> {
|
||||
try {
|
||||
// Usar a API do crt.sh para verificar certificados
|
||||
const res = await fetch(`https://crt.sh/?q=${encodeURIComponent(dominio)}&output=json`);
|
||||
const data = await res.json();
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
return {
|
||||
status: 'warning',
|
||||
titulo: 'Certificado SSL Nao Encontrado',
|
||||
mensagem: `Nao foi possivel encontrar informacoes de certificado SSL para ${dominio} no banco de dados publico. Isso pode significar que o site nao usa HTTPS ou o certificado nao e publico.`,
|
||||
score: 30,
|
||||
};
|
||||
}
|
||||
|
||||
const cert = data[0];
|
||||
const issuer = cert.issuer_name || 'Desconhecido';
|
||||
const notAfter = cert.not_after ? new Date(cert.not_after) : null;
|
||||
const diasRestantes = notAfter ? Math.floor((notAfter.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : 0;
|
||||
const isExpired = diasRestantes < 0;
|
||||
const isExpiringSoon = diasRestantes < 30 && diasRestantes > 0;
|
||||
|
||||
let status: 'success' | 'warning' | 'error' = 'success';
|
||||
let score = 90;
|
||||
|
||||
if (isExpired) { status = 'error'; score = 0; }
|
||||
else if (isExpiringSoon) { status = 'warning'; score = 50; }
|
||||
|
||||
return {
|
||||
status,
|
||||
titulo: isExpired ? 'Certificado SSL Expirado!' : isExpiringSoon ? 'Certificado SSL Vence em Breve' : 'Certificado SSL Valido',
|
||||
mensagem: isExpired
|
||||
? `O certificado SSL de ${dominio} EXPIROU em ${notAfter?.toLocaleDateString('pt-BR')}. Seu site esta inseguro!`
|
||||
: `Certificado SSL de ${dominio} emitido por ${issuer.split('=').pop()}. ${isExpiringSoon ? `Vence em ${diasRestantes} dias.` : `Valido ate ${notAfter?.toLocaleDateString('pt-BR')}.`}`,
|
||||
detalhes: {
|
||||
Dominio: dominio,
|
||||
Emissor: issuer.split('=').pop() || issuer,
|
||||
'Valido ate': notAfter?.toLocaleDateString('pt-BR') || 'Desconhecido',
|
||||
'Dias restantes': String(diasRestantes),
|
||||
},
|
||||
recomendacoes: isExpired
|
||||
? ['RENOVE IMEDIATAMENTE seu certificado SSL', 'Use Let\'s Encrypt (gratuito) ou contrate um certificado pago', 'Entre em contato com a Avanzato para renovacao urgente']
|
||||
: isExpiringSoon
|
||||
? [`Renove seu certificado nos proximos ${diasRestantes} dias`, 'Configure renovacao automatica', 'Considere usar Let\'s Encrypt com auto-renew']
|
||||
: ['Seu certificado SSL esta valido!', 'Configure renovacao automatica para evitar expiracao', 'Monitore a data de vencimento'],
|
||||
score,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
status: 'info',
|
||||
titulo: 'Verificacao de SSL',
|
||||
mensagem: `Nao foi possivel verificar o SSL de ${dominio} externamente. Verifique diretamente no navegador clicando no cadeado ao lado da URL.`,
|
||||
score: 50,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Email Security Score
|
||||
export async function checkEmailSecurity(dominio: string): Promise<ResultadoFerramenta> {
|
||||
const [spf, dkim, dmarc, mx] = await Promise.all([
|
||||
checkSPF(dominio),
|
||||
checkDKIM(dominio),
|
||||
checkDMARC(dominio),
|
||||
checkMX(dominio),
|
||||
]);
|
||||
|
||||
const scores = [spf.score || 0, dkim.score || 0, dmarc.score || 0, mx.score || 0];
|
||||
const media = Math.floor(scores.reduce((a, b) => a + b, 0) / scores.length);
|
||||
|
||||
let status: 'success' | 'warning' | 'error' = 'success';
|
||||
if (media < 50) status = 'error';
|
||||
else if (media < 80) status = 'warning';
|
||||
|
||||
return {
|
||||
status,
|
||||
titulo: `Email Security Score: ${media}/100`,
|
||||
mensagem: media >= 80
|
||||
? `A seguranca de e-mail de ${dominio} esta boa! Score: ${media}/100.`
|
||||
: media >= 50
|
||||
? `A seguranca de e-mail de ${dominio} precisa de atencao. Score: ${media}/100.`
|
||||
: `A seguranca de e-mail de ${dominio} esta critica! Score: ${media}/100.`,
|
||||
detalhes: {
|
||||
Dominio: dominio,
|
||||
'Score Total': `${media}/100`,
|
||||
'SPF': `${spf.score}/100 (${spf.status === 'success' ? 'OK' : 'Problema'})`,
|
||||
'DKIM': `${dkim.score}/100 (${dkim.status === 'success' ? 'OK' : 'Atenção'})`,
|
||||
'DMARC': `${dmarc.score}/100 (${dmarc.status === 'success' ? 'OK' : 'Problema'})`,
|
||||
'MX': `${mx.score}/100 (${mx.status === 'success' ? 'OK' : 'Problema'})`,
|
||||
},
|
||||
recomendacoes: [
|
||||
...spf.recomendacoes || [],
|
||||
...dmarc.recomendacoes || [],
|
||||
...(dkim.score && dkim.score < 80 ? dkim.recomendacoes || [] : []),
|
||||
].filter((v, i, a) => a.indexOf(v) === i).slice(0, 6),
|
||||
score: media,
|
||||
};
|
||||
}
|
||||
|
||||
// Microsoft 365 Analyzer
|
||||
export async function checkMicrosoft365(dominio: string): Promise<ResultadoFerramenta> {
|
||||
const [mx, spf, dmarc, dkim] = await Promise.all([
|
||||
checkMX(dominio),
|
||||
checkSPF(dominio),
|
||||
checkDMARC(dominio),
|
||||
checkDKIM(dominio),
|
||||
]);
|
||||
|
||||
const isMicrosoft365 = mx.detalhes?.Provedor?.includes('Microsoft');
|
||||
const m365Score = isMicrosoft365 ? 40 : 0;
|
||||
const spfScore = spf.score || 0;
|
||||
const dmarcScore = dmarc.score || 0;
|
||||
const dkimScore = dkim.score || 0;
|
||||
const total = Math.min(100, m365Score + Math.floor((spfScore + dmarcScore + dkimScore) / 3 * 0.6));
|
||||
|
||||
let status: 'success' | 'warning' | 'error' = 'success';
|
||||
if (total < 40) status = 'error';
|
||||
else if (total < 70) status = 'warning';
|
||||
|
||||
return {
|
||||
status,
|
||||
titulo: `Microsoft 365 Analyzer: ${total}/100`,
|
||||
mensagem: isMicrosoft365
|
||||
? `O dominio ${dominio} esta configurado com Microsoft 365. Score de seguranca: ${total}/100.`
|
||||
: `O dominio ${dominio} nao parece usar Microsoft 365 como servidor de e-mail principal. Score de seguranca: ${total}/100.`,
|
||||
detalhes: {
|
||||
Dominio: dominio,
|
||||
'Usa Microsoft 365': isMicrosoft365 ? 'Sim' : 'Nao detectado',
|
||||
'Score Total': `${total}/100`,
|
||||
'SPF': `${spfScore}/100`,
|
||||
'DKIM': `${dkimScore}/100`,
|
||||
'DMARC': `${dmarcScore}/100`,
|
||||
},
|
||||
recomendacoes: isMicrosoft365
|
||||
? [
|
||||
...(total < 80 ? ['Configure DKIM corretamente para Microsoft 365'] : []),
|
||||
...(dmarcScore < 80 ? ['Fortaleca a politica DMARC'] : []),
|
||||
'Habilite ATP (Advanced Threat Protection) no Microsoft 365',
|
||||
'Configure regras de anti-spam e anti-malware',
|
||||
'Ative a autenticacao de dois fatores para todos os usuarios',
|
||||
]
|
||||
: [
|
||||
'Se voce usa Microsoft 365, verifique se os registros MX estao corretos',
|
||||
'Configure SPF com include:_spf.microsoft.com',
|
||||
'Ative DKIM no portal do Microsoft 365',
|
||||
'Configure DMARC para monitoramento',
|
||||
],
|
||||
score: total,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// ============================================================================
|
||||
// DETECAO DE AMBIENTE - Automatico por hostname
|
||||
// ============================================================================
|
||||
// Um unico build serve todos os ambientes:
|
||||
// - PRODUCAO (avanzato.com.br): index, tracking ativo
|
||||
// - HOMOLOGACAO (hml.avanzato.com.br): noindex, tracking desativado
|
||||
// - DESENVOLVIMENTO (localhost): noindex, tracking desativado
|
||||
// ============================================================================
|
||||
|
||||
const hostname = typeof window !== 'undefined' ? window.location.hostname : '';
|
||||
|
||||
export type Environment = 'production' | 'homologation' | 'development';
|
||||
|
||||
function detectEnvironment(): Environment {
|
||||
// Producao: domínio principal
|
||||
if (hostname === 'avanzato.com.br' || hostname === 'www.avanzato.com.br') {
|
||||
return 'production';
|
||||
}
|
||||
|
||||
// Homologacao: subdominio hml
|
||||
if (hostname === 'hml.avanzato.com.br') {
|
||||
return 'homologation';
|
||||
}
|
||||
|
||||
// Desenvolvimento: localhost, 127.0.0.1, IPs locais, previews
|
||||
if (
|
||||
hostname === 'localhost' ||
|
||||
hostname === '127.0.0.1' ||
|
||||
hostname.startsWith('192.168.') ||
|
||||
hostname.startsWith('10.') ||
|
||||
hostname.startsWith('172.') ||
|
||||
hostname.includes('kimi.page') || // previews
|
||||
hostname === '' // SSR
|
||||
) {
|
||||
return 'development';
|
||||
}
|
||||
|
||||
// Fallback: se nao reconhece, trata como homologacao (seguro)
|
||||
return 'homologation';
|
||||
}
|
||||
|
||||
export const ENV = detectEnvironment();
|
||||
|
||||
// Helpers
|
||||
export const IS_PRODUCTION = ENV === 'production';
|
||||
export const IS_HOMOLOGATION = ENV === 'homologation';
|
||||
export const IS_DEVELOPMENT = ENV === 'development';
|
||||
|
||||
// Configuracoes por ambiente
|
||||
export const ENV_CONFIG = {
|
||||
// SEO / Indexacao
|
||||
allowIndexing: IS_PRODUCTION, // So producao permite Google indexar
|
||||
robotsMeta: IS_PRODUCTION ? 'index, follow' : 'noindex, nofollow',
|
||||
|
||||
// Tracking / Integracoes
|
||||
mauticActive: IS_PRODUCTION, // So envia leads reais em producao
|
||||
googleAnalytics: IS_PRODUCTION, // So trackeia em producao
|
||||
googleAds: IS_PRODUCTION, // So dispara conversões em producao
|
||||
facebookPixel: IS_PRODUCTION, // So dispara pixel em producao
|
||||
|
||||
// Visual
|
||||
showBanner: !IS_PRODUCTION, // Banner de ambiente em hom/dev
|
||||
bannerText: IS_HOMOLOGATION ? 'HOMOLOGACAO' : 'DESENVOLVIMENTO',
|
||||
bannerColor: IS_HOMOLOGATION ? 'bg-yellow-500' : 'bg-blue-500',
|
||||
} as const;
|
||||
|
||||
// Log para debug
|
||||
if (typeof window !== 'undefined') {
|
||||
console.log(`[Ambiente] ${ENV.toUpperCase()} | Host: ${hostname}`);
|
||||
if (!IS_PRODUCTION) {
|
||||
console.log('[Ambiente] Tracking desativado | SEO: noindex');
|
||||
}
|
||||
}
|
||||
+11
-16
@@ -1,12 +1,7 @@
|
||||
// Configuracao do Mautic - Avanzato
|
||||
// URL: https://mkt.avanzato.com.br
|
||||
|
||||
// --- DETECAO DE AMBIENTE ---
|
||||
// Em homologacao (localhost, IP, ou nao-producao), o Mautic fica em modo simulacao
|
||||
// Apenas loga no console, nao envia dados reais
|
||||
const hostname = typeof window !== 'undefined' ? window.location.hostname : '';
|
||||
const PROD_HOSTS = ['avanzato.com.br'];
|
||||
export const IS_HOMOLOGATION = !PROD_HOSTS.includes(hostname);
|
||||
import { IS_PRODUCTION } from './environment';
|
||||
|
||||
export const MAUTIC_CONFIG = {
|
||||
baseUrl: 'https://mkt.avanzato.com.br',
|
||||
@@ -17,8 +12,8 @@ export const MAUTIC_CONFIG = {
|
||||
contato: { id: '4', enabled: true },
|
||||
},
|
||||
|
||||
// Desativa envio real em homologacao
|
||||
enabled: !IS_HOMOLOGATION,
|
||||
// So envia dados reais em producao
|
||||
enabled: IS_PRODUCTION,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -37,9 +32,9 @@ export const sendLeadToMautic = (
|
||||
return false;
|
||||
}
|
||||
|
||||
// MODO HOMOLOGACAO: simula envio, nao manda dados reais
|
||||
if (IS_HOMOLOGATION) {
|
||||
console.log('%c[HOMOLOGACAO] Mautic em modo simulacao - dados NAO enviados ao servidor real', 'background:#f59e0b;color:#000;font-weight:bold;padding:4px 8px;border-radius:4px');
|
||||
// MODO NAO-PRODUCAO: simula envio, nao manda dados reais
|
||||
if (!IS_PRODUCTION) {
|
||||
console.log('%c[' + (window as any).__AVANZATO_ENV__?.toUpperCase() + '] Mautic em modo simulacao - dados NAO enviados', 'background:#f59e0b;color:#000;font-weight:bold;padding:4px 8px;border-radius:4px');
|
||||
console.log(`[Mautic] Form: ${formType} | Form ID: ${formConfig.id}`);
|
||||
console.log(`[Mautic] Dados que seriam enviados:`, JSON.stringify(formData, null, 2));
|
||||
console.log(`[Mautic] Em producao, enviaria para: ${MAUTIC_CONFIG.baseUrl}/form/submit?formId=${formConfig.id}`);
|
||||
@@ -123,8 +118,8 @@ export const sendLeadViaMailto = (formData: Record<string, string>): void => {
|
||||
* Desativado em homologacao para nao poluir dados
|
||||
*/
|
||||
export const trackConversion = (): void => {
|
||||
if (IS_HOMOLOGATION) {
|
||||
console.log('%c[HOMOLOGACAO] Google Ads conversion NAO disparado (ambiente de teste)', 'color:#f59e0b');
|
||||
if (!IS_PRODUCTION) {
|
||||
console.log('%c[' + (window as any).__AVANZATO_ENV__ + '] Google Ads conversion NAO disparado', 'color:#f59e0b');
|
||||
return;
|
||||
}
|
||||
if (typeof window !== 'undefined' && (window as any).gtag) {
|
||||
@@ -142,12 +137,12 @@ export const trackConversion = (): void => {
|
||||
* Desativado em homologacao
|
||||
*/
|
||||
export const trackFacebookPixel = (eventName: string = 'Lead', params?: Record<string, any>): void => {
|
||||
if (IS_HOMOLOGATION) {
|
||||
console.log(`%c[HOMOLOGACAO] Facebook Pixel "${eventName}" NAO disparado (ambiente de teste)`, 'color:#f59e0b', params);
|
||||
if (!IS_PRODUCTION) {
|
||||
console.log(`%c[${(window as any).__AVANZATO_ENV__}] Facebook Pixel "${eventName}" NAO disparado`, 'color:#f59e0b', params);
|
||||
return;
|
||||
}
|
||||
if (typeof window !== 'undefined' && (window as any).fbq) {
|
||||
(window as any).fbq('track', eventName, params || {});
|
||||
console.log(`[Facebook Pixel] Evento: ${eventName}`, params);
|
||||
}
|
||||
};
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user