Revisão Logo, Favicon, Robots

This commit is contained in:
2026-05-30 23:54:48 -03:00
parent 3e91269f12
commit 4e26c13240
105 changed files with 8137 additions and 693 deletions
-135
View File
@@ -1,135 +0,0 @@
# CI/CD - Avanzato Tecnologia
## Estrutura do Pipeline
```
Push para main/master
|
v
+------------------+ +------------------+ +------------------+
| BUILD | --> | PRESERVAR ASSETS | --> | DEPLOY |
| | | | | |
| - Checkout | | - Logo | | - SCP para VPS |
| - npm install | | - Imagens | | - Limpar cache |
| - npm run build | | - Favicon | | - Notificar |
+------------------+ +------------------+ +------------------+
```
---
## Configurar Secrets no GitHub
Va em: **Settings > Secrets and variables > Actions > New repository secret**
| Secret | Descricao | Exemplo |
|--------|-----------|---------|
| `SSH_HOST` | IP ou dominio do servidor | `201.45.120.30` ou `avanzato.com.br` |
| `SSH_USER` | Usuario SSH | `root` ou `deploy` |
| `SSH_PRIVATE_KEY` | Chave SSH privada | `-----BEGIN OPENSSH PRIVATE KEY-----...` |
| `SSH_PORT` | Porta SSH (opcional) | `22` |
| `DEPLOY_PATH` | Caminho no servidor | `/var/www/avanzato` |
| `STAGING_PATH` | Caminho staging (opcional) | `/var/www/avanzato-staging` |
### Como gerar a chave SSH:
```bash
# No seu computador (NAO no servidor):
ssh-keygen -t ed25519 -C "github-actions" -f ~/.ssh/github_actions
# Copiar publica para o servidor:
ssh-copy-id -i ~/.ssh/github_actions.pub root@SEU_SERVIDOR
# Copiar privada para o GitHub Secret:
cat ~/.ssh/github_actions
# Cole o conteudo inteiro no secret SSH_PRIVATE_KEY
```
---
## Estrutura de Branches
```
main/master --> Deploy automatico para producao
staging --> Deploy automatico para staging (se existir)
feature/* --> Apenas build (sem deploy)
hotfix/* --> Apenas build (sem deploy)
```
---
## Como Funciona
### Cenario 1: Deploy Automatico
```
1. Voce faz push para main
2. GitHub Actions executa automaticamente
3. Build + Deploy em ~2 minutos
4. Site atualizado!
```
### Cenario 2: Deploy Manual
```
1. Va em Actions > Build e Deploy
2. Clique "Run workflow"
3. Escolha: production ou staging
4. Clique "Run"
```
### Cenario 3: Pull Request
```
1. Cria PR para main
2. CI executa build (testa se compila)
3. NAO faz deploy
4. Merge aprovado? Deploy automatico!
```
---
## Assets Customizados (Logo, Imagens)
Para preservar assets apos o deploy automatico:
### 1. Commitar assets no repositorio
```bash
git add custom-assets/logo/logo.png
git add custom-assets/images/
git commit -m "Adiciona logo e imagens customizadas"
git push origin main
```
### 2. O pipeline faz sozinho
O workflow executa automaticamente:
```bash
bash scripts/preserve-assets.sh dist/
```
---
## Monitore os Deploys
- **GitHub Actions**: https://github.com/SEU_USUARIO/avanzato-site/actions
- **Status**: Check verde = sucesso, X vermelho = falha
- **Logs**: Clique no workflow para ver detalhes
---
## Troubleshooting
### Deploy falhou?
1. **Verifique os secrets**: Settings > Secrets > Actions
2. **Verifique a chave SSH**: `ssh -i ~/.ssh/github_actions root@SEU_SERVIDOR`
3. **Verifique permissoes**: O usuario precisa de acesso de escrita no DEPLOY_PATH
4. **Verifique logs**: Actions > Clique no workflow > Veja o passo que falhou
### Assets nao preservados?
1. Verifique se `custom-assets/` esta no repositorio
2. Verifique se os arquivos estao comitados: `git ls-files custom-assets/`
3. Verifique os logs do passo "Preservar assets customizados"
---
*Configurado em: Maio 2026*
-162
View File
@@ -1,162 +0,0 @@
# ============================================================================
# CI/CD Pipeline - Avanzato Tecnologia
# ============================================================================
# Este workflow automatiza o build e deploy do site.
#
# TRIGGER: Push na branch main ou manual (workflow_dispatch)
# ============================================================================
name: Build e Deploy
on:
# Deploy automatico ao fazer push na main
push:
branches: [ main, master ]
# Deploy manual pelo botao "Run workflow" no GitHub
workflow_dispatch:
inputs:
environment:
description: 'Ambiente'
required: true
default: 'production'
type: choice
options:
- production
- staging
# Pull Request (apenas build, sem deploy)
pull_request:
branches: [ main, master ]
# Permissoes necessarias
permissions:
contents: read
jobs:
# ==========================================================================
# JOB 1: Build
# ==========================================================================
build:
name: Build
runs-on: ubuntu-latest
steps:
# Passo 1: Checkout do codigo
- name: Checkout codigo
uses: actions/checkout@v4
# Passo 2: Setup Node.js
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
# Passo 3: Instalar dependencias
- name: Instalar dependencias
run: npm ci
# Passo 4: Build
- name: Build
run: npm run build
# Passo 5: Upload do artefato (para usar no job de deploy)
- name: Upload artefato
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 1
# ==========================================================================
# JOB 2: Deploy para Servidor (Producao)
# ==========================================================================
deploy-production:
name: Deploy - Producao
needs: build
runs-on: ubuntu-latest
# So executa na branch main/master ou manual com environment=production
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.event.inputs.environment == 'production'
steps:
# Passo 1: Download do artefato buildado
- name: Download artefato
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
# Passo 2: Preservar assets customizados (logo, imagens)
- name: Preservar assets customizados
run: |
if [ -d "custom-assets" ]; then
echo "Preservando assets customizados..."
bash scripts/preserve-assets.sh dist/
else
echo "Nenhum asset customizado encontrado."
fi
# Passo 3: Deploy via SCP (SSH)
- name: Deploy para servidor
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT || '22' }}
source: "dist/*"
target: ${{ secrets.DEPLOY_PATH || '/var/www/avanzato' }}
strip_components: 1
rm: true # Remove arquivos antigos antes de copiar
# Passo 4: Notificar (opcional - webhook Discord/Slack)
- name: Notificar sucesso
if: success()
run: |
echo "Deploy para producao concluido com sucesso!"
echo "Site: https://avanzato.com.br"
# ==========================================================================
# JOB 3: Deploy para Staging (Opcional)
# ==========================================================================
deploy-staging:
name: Deploy - Staging
needs: build
runs-on: ubuntu-latest
# So executa em push para branch staging ou manual
if: github.ref == 'refs/heads/staging' || github.event.inputs.environment == 'staging'
steps:
- name: Download artefato
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Deploy para staging
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT || '22' }}
source: "dist/*"
target: ${{ secrets.STAGING_PATH || '/var/www/avanzato-staging' }}
strip_components: 1
rm: true
# ==========================================================================
# JOB 4: Notificacao de Falha
# ==========================================================================
notify-failure:
name: Notificar Falha
needs: [build, deploy-production]
runs-on: ubuntu-latest
if: failure()
steps:
- name: Notificar falha
run: |
echo "ALERTA: O deploy falhou!"
echo "Verifique os logs em: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
-42
View File
@@ -1,42 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Build
dist
dist-ssr
*.local
# Cache
.cache
.vite
# Dependencias
node_modules
# Ambiente (NUNCA commite!)
.env
.env.local
.env.*.local
# Editor
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# OS
.DS_Store
Thumbs.db
# Scripts de deploy local
scripts/.env.local
+96
View File
@@ -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
View File
@@ -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.
+35 -8
View File
@@ -5,6 +5,33 @@
<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';
// 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 +76,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>
@@ -117,10 +140,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 +238,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;">
-230
View File
@@ -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
+92
View File
@@ -0,0 +1,92 @@
# Release HML
Data: sáb 30 mai 2026 23:54:46 -03
Ambiente:
HML
Descrição:
Revisão Logo, Favicon, Robots
Branch:
develop
Arquivos alterados antes do commit:
D .github/workflows/README-CI-CD.md
D .github/workflows/deploy.yml
D .gitignore
M index.html
D infra/releases/2026-05-30_23-52-07_HML.md
M node_modules/.package-lock.json
M node_modules/.vite/deps/@radix-ui_react-dialog.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
M package-lock.json
M package.json
M public/robots.txt
M public/sitemap.xml
M scripts/dev.sh
M src/App.tsx
M src/components/HomologationBanner.tsx
M src/config/mautic.ts
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
?? DEPLOY-DOCKER.md
?? GUIA-NGINX.md
?? infra/releases/2026-05-30_23-54-46_HML.md
?? nginx-docker.conf
?? node_modules/.vite/deps/chunk-INCCO7S4.js
?? node_modules/.vite/deps/chunk-INCCO7S4.js.map
?? node_modules/.vite/deps/chunk-L2OGOWTU.js
?? node_modules/.vite/deps/chunk-L2OGOWTU.js.map
?? node_modules/.vite/deps/chunk-LIWCVBQK.js
?? node_modules/.vite/deps/chunk-LIWCVBQK.js.map
?? node_modules/.vite/deps/chunk-MOFDO34C.js
?? node_modules/.vite/deps/chunk-MOFDO34C.js.map
?? node_modules/.vite/deps/react-helmet-async.js
?? node_modules/.vite/deps/react-helmet-async.js.map
?? node_modules/invariant/
?? node_modules/react-fast-compare/
?? node_modules/react-helmet-async/
?? node_modules/shallowequal/
?? public/version.json
?? scripts/build-static-routes.sh
?? src/components/SEO.tsx
?? src/config/environment.ts
Status:
Preparado para publicação
+30
View File
@@ -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;
}
+35
View File
@@ -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",
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
+35 -29
View File
@@ -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": "d2a0cc23",
"needsInterop": true
},
"react-dom": {
"src": "../../react-dom/index.js",
"file": "react-dom.js",
"fileHash": "91ce8077",
"fileHash": "e89a827d",
"needsInterop": true
},
"react/jsx-dev-runtime": {
"src": "../../react/jsx-dev-runtime.js",
"file": "react_jsx-dev-runtime.js",
"fileHash": "051319e1",
"fileHash": "9ce7ae60",
"needsInterop": true
},
"react/jsx-runtime": {
"src": "../../react/jsx-runtime.js",
"file": "react_jsx-runtime.js",
"fileHash": "1945fc54",
"fileHash": "8ba5f754",
"needsInterop": true
},
"@radix-ui/react-dialog": {
"src": "../../@radix-ui/react-dialog/dist/index.mjs",
"file": "@radix-ui_react-dialog.js",
"fileHash": "8be1d102",
"fileHash": "312e2a39",
"needsInterop": false
},
"@radix-ui/react-label": {
"src": "../../@radix-ui/react-label/dist/index.mjs",
"file": "@radix-ui_react-label.js",
"fileHash": "7c8329ad",
"fileHash": "3b607856",
"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": "20509c2b",
"needsInterop": false
},
"@radix-ui/react-select": {
"src": "../../@radix-ui/react-select/dist/index.mjs",
"file": "@radix-ui_react-select.js",
"fileHash": "e8a77682",
"fileHash": "91be68ee",
"needsInterop": false
},
"@radix-ui/react-slot": {
"src": "../../@radix-ui/react-slot/dist/index.mjs",
"file": "@radix-ui_react-slot.js",
"fileHash": "4581ea7a",
"fileHash": "ce1344ec",
"needsInterop": false
},
"class-variance-authority": {
"src": "../../class-variance-authority/dist/index.mjs",
"file": "class-variance-authority.js",
"fileHash": "e840c683",
"fileHash": "0e5017ff",
"needsInterop": false
},
"clsx": {
"src": "../../clsx/dist/clsx.mjs",
"file": "clsx.js",
"fileHash": "79cbbdef",
"fileHash": "2dbad761",
"needsInterop": false
},
"gsap": {
"src": "../../gsap/index.js",
"file": "gsap.js",
"fileHash": "096a1c72",
"fileHash": "1393a05a",
"needsInterop": false
},
"gsap/ScrollTrigger": {
"src": "../../gsap/ScrollTrigger.js",
"file": "gsap_ScrollTrigger.js",
"fileHash": "05527b31",
"fileHash": "965be7ec",
"needsInterop": false
},
"lucide-react": {
"src": "../../lucide-react/dist/esm/lucide-react.js",
"file": "lucide-react.js",
"fileHash": "4c9ad860",
"fileHash": "3db69029",
"needsInterop": false
},
"react-dom/client": {
"src": "../../react-dom/client.js",
"file": "react-dom_client.js",
"fileHash": "93025179",
"fileHash": "8798ba07",
"needsInterop": true
},
"react-helmet-async": {
"src": "../../react-helmet-async/lib/index.esm.js",
"file": "react-helmet-async.js",
"fileHash": "60b58734",
"needsInterop": false
},
"react-router-dom": {
"src": "../../react-router-dom/dist/index.mjs",
"file": "react-router-dom.js",
"fileHash": "a7f80be8",
"fileHash": "03035af3",
"needsInterop": false
},
"tailwind-merge": {
"src": "../../tailwind-merge/dist/bundle-mjs.mjs",
"file": "tailwind-merge.js",
"fileHash": "e87c9277",
"fileHash": "afbfe76d",
"needsInterop": false
}
},
@@ -111,20 +117,20 @@
"chunk-U7P2NEEE": {
"file": "chunk-U7P2NEEE.js"
},
"chunk-INCCO7S4": {
"file": "chunk-INCCO7S4.js"
},
"chunk-YWBEB5PG": {
"file": "chunk-YWBEB5PG.js"
},
"chunk-3Q7Z2X7T": {
"file": "chunk-3Q7Z2X7T.js"
"chunk-LIWCVBQK": {
"file": "chunk-LIWCVBQK.js"
},
"chunk-5BI4FO63": {
"file": "chunk-5BI4FO63.js"
"chunk-L2OGOWTU": {
"file": "chunk-L2OGOWTU.js"
},
"chunk-26QBR6Z3": {
"file": "chunk-26QBR6Z3.js"
},
"chunk-2ZZW3GAI": {
"file": "chunk-2ZZW3GAI.js"
"chunk-MOFDO34C": {
"file": "chunk-MOFDO34C.js"
},
"chunk-2VUH7NEY": {
"file": "chunk-2VUH7NEY.js"
+143
View File
@@ -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
File diff suppressed because one or more lines are too long
+248
View File
@@ -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
File diff suppressed because one or more lines are too long
+1352
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+354
View File
@@ -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
File diff suppressed because one or more lines are too long
+1150
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+69
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,37 @@
# invariant
[![Build Status](https://travis-ci.org/zertosh/invariant.svg?branch=master)](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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,7 @@
/* @flow strict */
declare module.exports: (
condition: any,
format?: string,
...args: Array<any>
) => void;
+35
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,165 @@
[![React Fast Compare — Formidable, We build the modern web](https://raw.githubusercontent.com/FormidableLabs/react-fast-compare/master/react-fast-compare-Hero.png)](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.
![benchmark chart](https://raw.githubusercontent.com/FormidableLabs/react-fast-compare/master/assets/benchmarking.png "benchmarking chart")
(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
View File
@@ -0,0 +1,2 @@
declare function isEqual<A = any, B = any>(a: A, b: B): boolean;
export = isEqual;
+139
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,240 @@
# react-helmet-async
[![CI](https://github.com/staylor/react-helmet-async/actions/workflows/ci.yml/badge.svg)](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 1618**: 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 1618. 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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,3 @@
import type { StateUpdate } from './types';
declare const handleStateChangeOnClient: (newState: StateUpdate) => void;
export default handleStateChangeOnClient;
+58
View File
@@ -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
View File
@@ -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
View File
@@ -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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
};
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
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
export declare const isReact19: boolean;
+45
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,81 @@
{
"name": "react-helmet-async",
"version": "3.0.0",
"description": "Thread-safe Helmet for React 1618, 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
View File
@@ -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
View File
@@ -0,0 +1,63 @@
# shallowequal [![Build Status](https://travis-ci.org/dashed/shallowequal.svg)](https://travis-ci.org/dashed/shallowequal) [![Downloads](https://img.shields.io/npm/dm/shallowequal.svg)](https://npmjs.com/shallowequal) [![npm version](https://img.shields.io/npm/v/shallowequal.svg?style=flat)](https://www.npmjs.com/package/shallowequal)
[![Greenkeeper badge](https://badges.greenkeeper.io/dashed/shallowequal.svg)](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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"
}
}
+36
View File
@@ -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",
+1
View File
@@ -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",
+10 -23
View File
@@ -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
+30
View File
@@ -135,4 +135,34 @@
<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>
</urlset>
+5
View File
@@ -0,0 +1,5 @@
{
"version": "1.2.1",
"date": "2026-05-31",
"environment": "auto-detect"
}
+89
View File
@@ -0,0 +1,89 @@
#!/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}"
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"
"privacidade"
"termos"
)
# 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 ""
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 ""
+15
View File
@@ -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}"
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { HashRouter as Router, Routes, Route, useLocation } from 'react-router-dom';
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';
+13 -10
View File
@@ -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" />
+121
View File
@@ -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,
},
})),
});
+73
View File
@@ -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
View File
@@ -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);
}
};
};
+9 -12
View File
@@ -11,20 +11,17 @@
// Opcao 2: URL da imagem (deixe svgInline vazio e preencha imageUrl)
export const LOGO = {
// Se quiser usar SVG inline, cole o codigo aqui entre as crases
// Exemplo: svgInline: `<svg ...>...</svg>`
svgInline: `<svg width="40" height="40" viewBox="0 0 100 100">
<path d="M50 15 L75 75 H60 L55 60 H45 L40 75 H25 L50 15 Z M48 48 H52 L50 40 L48 48 Z"
fill="#cbf400" stroke="#cbf400" stroke-width="4" stroke-linejoin="round" stroke-linecap="round"/>
</svg>`,
// Opcao 1: SVG inline (deixe vazio se for usar imagem)
svgInline: '',
// Se quiser usar imagem (PNG/JPG), coloque o arquivo em public/assets/
// e defina: imageUrl: '/assets/logo.png'
imageUrl: '',
// Opcao 2: URL da imagem
// Use '/logo.png' para carregar da pasta public/ (funciona em qualquer ambiente)
// Use 'https://avanzato.com.br/logo.png' apenas se for carregar de servidor externo
imageUrl: '/logo.png',
// Texto ao lado do logo (deixe vazio se nao quiser texto)
text: 'avanzato',
textHighlight: '.', // O ponto verde apos o texto
// Texto ao lado do logo (deixe vazio se o logo ja tiver texto)
text: '',
textHighlight: '',
// Link do logo (para onde vai quando clica)
href: '/',
+4 -1
View File
@@ -1,10 +1,13 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { HelmetProvider } from 'react-helmet-async'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
<HelmetProvider>
<App />
</HelmetProvider>
</StrictMode>,
)
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../components/SEO';
import { useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
import { gsap } from 'gsap';
@@ -132,6 +133,10 @@ const Blog = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Blog de Tecnologia"
description="Artigos e novidades sobre TI, seguranca da informacao, cloud computing e tecnologia para empresas."
/>
{/* Hero */}
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -199,6 +200,10 @@ const Cases = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Cases de Sucesso"
description="Veja os cases de sucesso da Avanzato Tecnologia. Projetos realizados para empresas de diversos segmentos em Sao Paulo."
/>
{/* Schema.org JSON-LD */}
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }} />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../components/SEO';
import { useEffect, useRef, useState } from 'react';
import { sendLeadToMautic, sendLeadViaMailto, trackConversion, trackFacebookPixel } from '../config/mautic';
import { gsap } from 'gsap';
@@ -174,6 +175,10 @@ const Condominios = () => {
return (
<div className="min-h-screen bg-[#0e0e0e]">
<SEO
title="Tecnologia para Condominios"
description="Solucoes de TI para condominios: CFTV, controle de acesso, interfonia IP, internet, WiFi. Atendimento em Sao Paulo."
/>
{/* SEO Meta Tags - Injected via useEffect */}
<title>Tecnologia para Condomínios | VOIP e Infraestrutura | Avanzato</title>
<meta name="description" content="Reduza custos e tenha independência com VOIP e infraestrutura para condomínios. Diagnóstico gratuito com a Avanzato." />
+6
View File
@@ -1,3 +1,4 @@
import SEO from '../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { Shield, Lock, Eye, FileText, User, Share2, Mail } from 'lucide-react';
@@ -137,6 +138,11 @@ const Privacidade = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Politica de Privacidade"
description="Politica de Privacidade da Avanzato Tecnologia. Conformidade com a LGPD. Protecao de dados pessoais."
noindex
/>
{/* Hero */}
<section className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -178,6 +179,10 @@ const Sobre = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Sobre a Avanzato Tecnologia"
description="Conheca a Avanzato Tecnologia: +23 anos de experiencia em TI Gerenciada, Cloud e Seguranca. +500 clientes satisfeitos."
/>
{/* Hero Section */}
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -105,6 +106,10 @@ const Tecnologias = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Tecnologias que Trabalhamos"
description="Conheca as tecnologias que a Avanzato domina: Cisco, VMware, Microsoft, Linux, pfSense, Proxmox e muito mais."
/>
{/* Schema.org JSON-LD */}
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }} />
+6
View File
@@ -1,3 +1,4 @@
import SEO from '../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { FileText, CheckCircle, AlertCircle, Scale, Gavel, RefreshCw } from 'lucide-react';
@@ -123,6 +124,11 @@ const Termos = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Termos de Uso"
description="Termos de Uso do site da Avanzato Tecnologia. Condicoes de acesso e utilizacao dos servicos."
noindex
/>
{/* Hero */}
<section className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef, useState } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -53,6 +54,10 @@ function FormSection() {
return (
<div id="formulario" className="scroll-mt-20">
<SEO
title="TI para Escritorios de Advocacia"
description="TI para advocacias: seguranca de processos, LGPD para clientes, backup de documentos juridicos. PJe e e-SAJ."
/>
<div className="bg-[#1a1a1a] border border-[#333] rounded-2xl p-8 sticky top-24">
<h3 className="text-2xl font-bold text-white mb-2">Diagnostico gratuito para seu escritorio</h3>
<p className="text-gray-400 mb-6">Avaliamos a seguranca da sua infraestrutura e entregamos um relatorio de conformidade para a OAB.</p>
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef, useState } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -44,6 +45,10 @@ function FormSection() {
return (
<div id="formulario" className="scroll-mt-20">
<SEO
title="Backup Corporativo em Nuvem"
description="Backup empresarial automatizado: local, nuvem e hibrido. Protecao contra ransomware. Recuperacao rapida de dados."
/>
<div className="bg-[#1a1a1a] border border-[#333] rounded-2xl p-8 sticky top-24">
<h3 className="text-2xl font-bold text-white mb-2">Diagnostico de backup gratuito</h3>
<p className="text-gray-400 mb-6">Avaliamos sua estrategia atual e identificamos falhas antes que seja tarde demais.</p>
+5 -2
View File
@@ -1,4 +1,5 @@
import { useEffect, useRef, useState } from 'react';
import SEO from '../../components/SEO';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import {
@@ -205,8 +206,10 @@ export default function ContabilidadePage() {
return (
<div ref={pageRef} className="min-h-screen bg-[#0e0e0e]">
<title>TI para Escritorios Contabeis | Avanzato Tecnologia</title>
<meta name="description" content="TI especializada para escritorios contabeis. Seguranca para SPED, backup de dados, servidor otimizado e suporte tecnico especializado." />
<SEO
title="TI para Escritorios de Contabilidade"
description="Solucoes de TI especializadas para contabilidades: seguranca de dados fiscais, backup de obrigacoes, NF-e. SPED e ECD."
/>
<section className="relative min-h-[90vh] flex items-center bg-[#0e0e0e]">
<div className="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1554224155-8d04cb21cd6c?w=1920&q=80')] bg-cover bg-center opacity-10" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef, useState } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -44,6 +45,10 @@ function FormSection() {
return (
<div id="formulario" className="scroll-mt-20">
<SEO
title="Firewall pfSense Empresarial"
description="Implementacao e configuracao de firewall pfSense e OPNsense. Seguranca de rede, VPN, filtro de conteudo."
/>
<div className="bg-[#1a1a1a] border border-[#333] rounded-2xl p-8 sticky top-24">
<h3 className="text-2xl font-bold text-white mb-2">Avaliacao gratuita de firewall</h3>
<p className="text-gray-400 mb-6">Analisamos sua rede e indicamos a melhor configuracao de firewall para sua empresa.</p>
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef, useState } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -46,6 +47,10 @@ function FormSection() {
return (
<div id="formulario" className="scroll-mt-20">
<SEO
title="Monitoramento Empresarial com CFTV"
description="Sistemas de CFTV e monitoramento por camera para empresas. IP cameras, NVR, DVR, acesso remoto via celular."
/>
<div className="bg-[#1a1a1a] border border-[#333] rounded-2xl p-8 sticky top-24">
<h3 className="text-2xl font-bold text-white mb-2">Diagnostico gratuito de CFTV</h3>
<p className="text-gray-400 mb-6">Avaliamos sua estrutura e indicamos a melhor solucao de cameras e monitoramento para seu espaco.</p>
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef, useState } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -46,6 +47,10 @@ function FormSection() {
return (
<div id="formulario" className="scroll-mt-20">
<SEO
title="VoIP Empresarial e PABX IP"
description="Telefonia VoIP para empresas: PABX IP, chamadas ilimitadas, integracao com CRM. Reducao de 70% na conta telefonica."
/>
<div className="bg-[#1a1a1a] border border-[#333] rounded-2xl p-8 sticky top-24">
<h3 className="text-2xl font-bold text-white mb-2">Orcamento de PABX VOIP</h3>
<p className="text-gray-400 mb-6">Preencha os dados e receba uma proposta de migracao para VOIP em ate 4 horas.</p>
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -39,6 +40,10 @@ const Backup = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Backup Corporativo e Disaster Recovery"
description="Backup automatizado em nuvem e local. Disaster Recovery, recuperacao de dados. Protecao contra ransomware."
/>
{/* Hero */}
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -49,6 +50,10 @@ const CloudComputing = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Cloud Computing e Virtualizacao"
description="Solucoes em Cloud Computing: AWS, Azure, Google Cloud. Migracao, gestao e otimizacao de infraestrutura em nuvem."
/>
{/* Hero */}
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -33,6 +34,10 @@ const Consultoria = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Consultoria em TI"
description="Consultoria estrategica em tecnologia: planejamento, gestao de projetos, outsourcing de TI. Diagnostico gratuito."
/>
{/* Hero */}
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -38,6 +39,10 @@ const Noc = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="NOC - Network Operations Center"
description="Monitoramento 24/7 de redes e servidores. NOC proprio com equipe especializada. Alertas em tempo real."
/>
{/* Hero */}
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -46,6 +47,10 @@ const Pabx = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="PABX IP e VoIP Empresarial"
description="Solucoes de telefonia IP e VoIP: PABX virtual, central telefonica, integracao com CRM. Reducao de custos em ate 60%."
/>
{/* Hero */}
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -49,6 +50,10 @@ const Redes = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Redes e Infraestrutura de TI"
description="Projetos de rede cabeada e wireless, switches, roteadores Cisco. Infraestrutura completa de TI para empresas."
/>
{/* Hero */}
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -49,6 +50,10 @@ const Seguranca = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Seguranca da Informacao"
description="Protecao completa para sua empresa: firewall, antivirus, backup, LGPD. Seguranca de rede e dados com suporte 24/7."
/>
{/* Hero */}
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -38,6 +39,10 @@ const Servidores = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Servidores e Data Center"
description="Venda e manutencao de servidores Dell, HP, IBM. Virtualizacao VMware, Hyper-V. Data Center em Guarulhos."
/>
{/* Hero */}
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -46,6 +47,10 @@ const Suporte = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Suporte Tecnico 24/7"
description="Suporte tecnico empresarial 24 horas. Help desk, atendimento remoto e presencial em Guarulhos e regiao."
/>
{/* Hero */}
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -82,6 +83,10 @@ const TiGerenciada = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="TI Gerenciada para Empresas"
description="TI Gerenciada completa para empresas em Guarulhos e Sao Paulo. Suporte 24/7, monitoramento em tempo real e equipe especializada."
/>
{/* Hero */}
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
<div className="absolute top-0 right-0 w-[800px] h-[800px] bg-[#cbf400]/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/3" />
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -77,6 +78,10 @@ const Cisco = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Suporte Redes Cisco"
description="Configuracao e suporte Cisco: switches, roteadores, firewalls ASA, Wireless. CCNA e CCNP certificados."
/>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }} />
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -77,6 +78,10 @@ const Linux = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Suporte Linux para Empresas"
description="Suporte Linux empresarial: Ubuntu, CentOS, Red Hat, Debian. Servidores, seguranca, monitoramento. Especialistas certificados."
/>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }} />
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -77,6 +78,10 @@ const PfSense = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Suporte pfSense e OPNsense"
description="Configuracao e suporte pfSense/OPNsense: firewall, VPN, load balancing, HA, multi-WAN. Especialistas certificados."
/>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }} />
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -77,6 +78,10 @@ const Proxmox = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Suporte Proxmox VE"
description="Implementacao e suporte Proxmox VE: virtualizacao, containers LXC, clusters HA, Ceph storage, backups."
/>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }} />
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">
+5
View File
@@ -1,3 +1,4 @@
import SEO from '../../components/SEO';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@@ -77,6 +78,10 @@ const Sharepoint = () => {
return (
<div className="bg-[#0e0e0e] min-h-screen">
<SEO
title="Suporte Microsoft SharePoint"
description="Implementacao e suporte SharePoint Online e on-premises: intranet, workflows, gestao documental. Microsoft 365."
/>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }} />
<section ref={heroRef} className="pt-32 pb-20 relative overflow-hidden">

Some files were not shown because too many files have changed in this diff Show More