Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad285fd53e | |||
| c41625e542 | |||
| 3f33154e16 | |||
| 9947565694 | |||
| f54126b9d8 | |||
| a4002cb9c0 | |||
| 17c278c624 | |||
| d420d33415 | |||
| 47b753b5f1 | |||
| ef968f10ae | |||
| f2e2400637 | |||
| 27b4c9e705 | |||
| e3f35d808d | |||
| fdfb4606f0 | |||
| e5c18bb6fc | |||
| 6dfdf01830 | |||
| 96563b30e9 | |||
| 80dbb986f4 | |||
| 6db7b5200b | |||
| 52cda4304b | |||
| a85a6c9b3f | |||
| 2900b9afd9 | |||
| 433773c4b8 | |||
| 6140142bab | |||
| e8f15d63ed | |||
| 24786e3512 | |||
| 15ce64e786 | |||
| 913e717a21 | |||
| aa275ef538 | |||
| 19f2848fec |
@@ -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*
|
||||
@@ -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 }}"
|
||||
-46
@@ -1,46 +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
|
||||
|
||||
# ZIPs internos (nunca commite!)
|
||||
*.zip
|
||||
avanzato-*.zip
|
||||
@@ -1,232 +0,0 @@
|
||||
# Como Usar a Configuracao Central - Avanzato
|
||||
|
||||
## O que mudou?
|
||||
|
||||
Agora **tudo** (logo, redes sociais, contatos, enderecos, menu, etc.) esta em um unico arquivo:
|
||||
|
||||
```
|
||||
src/config/site.ts
|
||||
```
|
||||
|
||||
**Nao precisa mais editar Navigation.tsx, Footer.tsx ou qualquer componente!**
|
||||
|
||||
---
|
||||
|
||||
## EDITAR APENAS ESTE ARQUIVO
|
||||
|
||||
### src/config/site.ts
|
||||
|
||||
### 1. Trocar o Logo
|
||||
|
||||
**Opcao A - SVG inline:**
|
||||
```typescript
|
||||
export const LOGO = {
|
||||
svgInline: `<svg width="40" height="40">...COLE O SVG AQUI...</svg>`,
|
||||
imageUrl: '', // Deixe vazio
|
||||
text: 'avanzato',
|
||||
textHighlight: '.',
|
||||
};
|
||||
```
|
||||
|
||||
**Opcao B - Imagem (PNG/SVG):**
|
||||
```typescript
|
||||
export const LOGO = {
|
||||
svgInline: '', // Deixe vazio
|
||||
imageUrl: '/assets/logo.png', // Ou .svg
|
||||
text: '', // Deixe vazio se nao quiser texto
|
||||
textHighlight: '',
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Trocar Redes Sociais
|
||||
|
||||
```typescript
|
||||
export const SOCIAL_LINKS = {
|
||||
facebook: {
|
||||
url: 'https://www.facebook.com/avanzatotec',
|
||||
label: 'Facebook',
|
||||
},
|
||||
youtube: {
|
||||
url: '', // Deixe vazio para OCULTAR
|
||||
label: 'YouTube',
|
||||
},
|
||||
linkedin: {
|
||||
url: 'https://www.linkedin.com/...',
|
||||
label: 'LinkedIn',
|
||||
},
|
||||
instagram: {
|
||||
url: 'https://www.instagram.com/...',
|
||||
label: 'Instagram',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Para **adicionar uma rede nova**, adicione uma linha:
|
||||
```typescript
|
||||
tiktok: {
|
||||
url: 'https://tiktok.com/...',
|
||||
label: 'TikTok',
|
||||
iconSvg: '', // Opcional: SVG customizado
|
||||
},
|
||||
```
|
||||
|
||||
### 3. Trocar Contatos
|
||||
|
||||
```typescript
|
||||
export const CONTACT = {
|
||||
phone: '(11) 4810-1704',
|
||||
phoneRaw: '551148101704',
|
||||
email: 'leads@avanzato.com.br',
|
||||
supportUrl: 'https://suporte.avanzato.com.br/',
|
||||
};
|
||||
```
|
||||
|
||||
### 4. Trocar Enderecos
|
||||
|
||||
```typescript
|
||||
export const ADDRESSES = [
|
||||
{
|
||||
label: 'Escritorio Principal',
|
||||
street: 'Rua Juiz de Fora, 76',
|
||||
city: 'Guarulhos',
|
||||
state: 'SP',
|
||||
cep: '',
|
||||
},
|
||||
// Adicione mais ou remova
|
||||
];
|
||||
```
|
||||
|
||||
### 5. Trocar Menu de Navegacao
|
||||
|
||||
```typescript
|
||||
export const NAV_LINKS = [
|
||||
{ name: 'Home', href: '/' },
|
||||
{ name: 'Sobre', href: '/sobre' },
|
||||
{ name: 'Servicos', href: '#', submenu: [...] },
|
||||
];
|
||||
```
|
||||
|
||||
### 6. Desabilitar WhatsApp Widget
|
||||
|
||||
```typescript
|
||||
export const WHATSAPP_CONFIG = {
|
||||
enabled: false, // Mude para false para ocultar
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## APOS EDITAR
|
||||
|
||||
Sempre que editar `site.ts`, faca:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
O build vai usar os novos valores automaticamente.
|
||||
|
||||
---
|
||||
|
||||
## SISTEMA DE PRESERVACAO DE ASSETS
|
||||
|
||||
Para imagens que NAO devem ser sobrescritas no deploy:
|
||||
|
||||
### 1. Crie a pasta de assets customizados
|
||||
|
||||
```bash
|
||||
mkdir -p custom-assets/logo
|
||||
mkdir -p custom-assets/images
|
||||
```
|
||||
|
||||
### 2. Coloque seus arquivos
|
||||
|
||||
```
|
||||
custom-assets/
|
||||
logo/
|
||||
logo.png <- Seu logo (substitui o do build)
|
||||
favicon.ico <- Favicon customizado
|
||||
images/
|
||||
hero-bg.jpg <- Imagem do hero
|
||||
about.jpg <- Imagem da secao sobre
|
||||
```
|
||||
|
||||
### 3. Execute o script apos o deploy
|
||||
|
||||
```bash
|
||||
bash scripts/preserve-assets.sh /caminho/do/site/no/servidor
|
||||
```
|
||||
|
||||
Ou se o site esta em `/var/www/avanzato`:
|
||||
|
||||
```bash
|
||||
bash scripts/preserve-assets.sh /var/www/avanzato/dist
|
||||
```
|
||||
|
||||
### O que o script faz?
|
||||
- Copia `logo/logo.*` para a pasta do site
|
||||
- Copia `images/*` para a pasta do site
|
||||
- Copia `fonts/*` se houver
|
||||
- **Nao altera** nenhum outro arquivo do build
|
||||
|
||||
---
|
||||
|
||||
## FLUXO DE TRABALHO RECOMENDADO
|
||||
|
||||
### Quando editar config (textos, links, etc.):
|
||||
1. Edite `src/config/site.ts`
|
||||
2. Rode `npm run build`
|
||||
3. Deploy
|
||||
|
||||
### Quando trocar apenas imagens/logo:
|
||||
1. Coloque a nova imagem em `custom-assets/`
|
||||
2. Rode `bash scripts/preserve-assets.sh /caminho/do/site`
|
||||
3. **Nao precisa rebuild!**
|
||||
|
||||
### Quando eu (dev) atualizar o site:
|
||||
1. Eu faco as mudancas no codigo
|
||||
2. Build + deploy
|
||||
3. **Voce roda:** `bash scripts/preserve-assets.sh /caminho/do/site`
|
||||
4. Seu logo e imagens sao restaurados automaticamente!
|
||||
|
||||
---
|
||||
|
||||
## EXEMPLO COMPLETO
|
||||
|
||||
### Cenario: Trocar apenas o logo
|
||||
|
||||
**Passo 1:** Coloque o novo logo em `custom-assets/logo/logo.png`
|
||||
|
||||
**Passo 2:** Execute:
|
||||
```bash
|
||||
bash scripts/preserve-assets.sh /var/www/avanzato/dist
|
||||
```
|
||||
|
||||
**Resultado:**
|
||||
```
|
||||
[LOGO] Copiando logo.png...
|
||||
Concluido! 1 arquivo(s) preservado(s).
|
||||
```
|
||||
|
||||
Pronto! O logo foi trocado sem precisar rebuild.
|
||||
|
||||
---
|
||||
|
||||
## PERGUNTAS FREQUENTES
|
||||
|
||||
**Q: Preciso rebuild quando troco o logo via custom-assets?**
|
||||
R: Nao! O script copia diretamente.
|
||||
|
||||
**Q: E se eu quiser trocar o texto do logo?**
|
||||
R: Ai sim, edite `site.ts` e faca rebuild.
|
||||
|
||||
**Q: O script funciona em qualquer servidor?**
|
||||
R: Sim! Basta passar o caminho correto como argumento.
|
||||
|
||||
**Q: Posso adicionar novas redes sociais?**
|
||||
R: Sim! Basta adicionar em `SOCIAL_LINKS` no `site.ts`.
|
||||
|
||||
---
|
||||
|
||||
*Documento criado em: Maio 2026*
|
||||
@@ -1,96 +0,0 @@
|
||||
# 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.
|
||||
-250
@@ -1,250 +0,0 @@
|
||||
# Fluxo de Desenvolvimento - Avanzato
|
||||
|
||||
## Visao Geral
|
||||
|
||||
```
|
||||
+---------------------------------------------------------------+
|
||||
| SEU MAC (Desenvolvimento) |
|
||||
| |
|
||||
| +-------------------+ bash scripts/dev.sh start |
|
||||
| | Codigo Fonte | --> http://localhost:5173 |
|
||||
| | app/src/ | |
|
||||
| +-------------------+ |
|
||||
| | |
|
||||
| | bash scripts/dev.sh build-hom |
|
||||
| | bash scripts/dev.sh deploy-hom |
|
||||
| v |
|
||||
+---------------------------------------------------------------+
|
||||
|
|
||||
v
|
||||
+---------------------------------------------------------------+
|
||||
| SERVIDOR - HOMOLOGACAO |
|
||||
| |
|
||||
| Gitea (git push) --> Hook --> Build --> Nginx |
|
||||
| |
|
||||
| https://homologacao.avanzato.com.br |
|
||||
| - Mautic: desativado |
|
||||
| - Google Ads: desativado |
|
||||
| - Facebook Pixel: desativado |
|
||||
| - noindex/nofollow (nao aparece no Google) |
|
||||
| |
|
||||
+---------------------------------------------------------------+
|
||||
|
|
||||
| Aprovado? bash scripts/dev.sh deploy-prod
|
||||
v
|
||||
+---------------------------------------------------------------+
|
||||
| SERVIDOR - PRODUCAO |
|
||||
| |
|
||||
| https://avanzato.com.br |
|
||||
| - Mautic: ativo (forms, tracking) |
|
||||
| - Google Ads: ativo |
|
||||
| - Facebook Pixel: ativo |
|
||||
| - Indexado no Google |
|
||||
| |
|
||||
+---------------------------------------------------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Primeira Configuracao (faca uma vez)
|
||||
|
||||
### 1.1 Instalar Node.js 20+
|
||||
|
||||
```bash
|
||||
# Instalar via Homebrew
|
||||
brew install node@20
|
||||
|
||||
# Verificar
|
||||
node -v # v20.x.x
|
||||
npm -v # 10.x.x
|
||||
```
|
||||
|
||||
### 1.2 Instalar dependencias
|
||||
|
||||
```bash
|
||||
cd /Users/avanzato/Projetos/Avanzato/avanzato-site/app
|
||||
npm install
|
||||
```
|
||||
|
||||
### 1.3 Configurar acesso aos servidores
|
||||
|
||||
```bash
|
||||
# Copiar template
|
||||
cp scripts/.env.local.example scripts/.env.local
|
||||
|
||||
# Editar com seus dados
|
||||
nano scripts/.env.local
|
||||
```
|
||||
|
||||
Conteudo do `.env.local`:
|
||||
|
||||
```bash
|
||||
# Homologacao
|
||||
SSH_HOM_HOST=192.168.1.100 # ou IP publico
|
||||
SSH_HOM_USER=deploy
|
||||
SSH_HOM_PATH=/var/www/avanzato-hom
|
||||
|
||||
# Producao
|
||||
SSH_PROD_HOST=201.45.120.30 # ou IP publico
|
||||
SSH_PROD_USER=deploy
|
||||
SSH_PROD_PATH=/var/www/avanzato
|
||||
```
|
||||
|
||||
### 1.4 Configurar chave SSH (se ainda nao tiver)
|
||||
|
||||
```bash
|
||||
# Gerar chave
|
||||
ssh-keygen -t ed25519 -C "dev@avanzato" -f ~/.ssh/id_ed25519
|
||||
|
||||
# Copiar para o servidor de homologacao
|
||||
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@SEU_SERVIDOR_HOM
|
||||
|
||||
# Copiar para o servidor de producao
|
||||
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@SEU_SERVIDOR_PROD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Desenvolvimento Diario
|
||||
|
||||
### 2.1 Iniciar servidor local
|
||||
|
||||
```bash
|
||||
bash scripts/dev.sh start
|
||||
# ou
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Acesse: **http://localhost:5173**
|
||||
|
||||
- Hot reload funciona automaticamente
|
||||
- Mautic/Google Ads/Facebook Pixel estao em modo simulacao
|
||||
- Edite qualquer arquivo `.tsx` e veja a mudanca ao vivo
|
||||
|
||||
### 2.2 Comandos uteis durante desenvolvimento
|
||||
|
||||
```bash
|
||||
# Ver ajuda completa
|
||||
bash scripts/dev.sh help
|
||||
|
||||
# Build para homologacao
|
||||
bash scripts/dev.sh build-hom
|
||||
|
||||
# Preview do build (para testar antes de deploy)
|
||||
bash scripts/dev.sh preview
|
||||
|
||||
# Limpar caches quando algo estranho acontecer
|
||||
bash scripts/dev.sh clean
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Deploy para Homologacao
|
||||
|
||||
### Metodo 1: Script local (rapido)
|
||||
|
||||
```bash
|
||||
# 1. Build
|
||||
bash scripts/dev.sh build-hom
|
||||
|
||||
# 2. Deploy
|
||||
bash scripts/dev.sh deploy-hom
|
||||
```
|
||||
|
||||
### Metodo 2: Git push (CI/CD)
|
||||
|
||||
```bash
|
||||
# Adicionar remote do Gitea (faca uma vez)
|
||||
git remote add homolog ssh://git@SEU_SERVIDOR_HOM:22/gitea/avanzato-site.git
|
||||
|
||||
# Sempre que quiser deployar
|
||||
git add .
|
||||
git commit -m "descricao da mudanca"
|
||||
git push homolog main
|
||||
```
|
||||
|
||||
O Gitea vai executar o pipeline automaticamente e deployar.
|
||||
|
||||
---
|
||||
|
||||
## 4. Deploy para Producao
|
||||
|
||||
**ATENCAO: Sempre teste em homologacao primeiro!**
|
||||
|
||||
```bash
|
||||
# 1. Build de producao
|
||||
bash scripts/dev.sh build
|
||||
|
||||
# 2. Deploy (pede confirmacao)
|
||||
bash scripts/dev.sh deploy-prod
|
||||
# Digite 'sim' para confirmar
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Estrutura de Comandos
|
||||
|
||||
| Comando | O que faz | URL |
|
||||
|---------|-----------|-----|
|
||||
| `bash scripts/dev.sh start` | Servidor dev local | http://localhost:5173 |
|
||||
| `bash scripts/dev.sh build` | Build producao | - |
|
||||
| `bash scripts/dev.sh build-hom` | Build homologacao | - |
|
||||
| `bash scripts/dev.sh preview` | Preview do build | http://localhost:4173 |
|
||||
| `bash scripts/dev.sh deploy-hom` | Deploy homologacao | https://homologacao.avanzato.com.br |
|
||||
| `bash scripts/dev.sh deploy-prod` | Deploy producao | https://avanzato.com.br |
|
||||
| `bash scripts/dev.sh clean` | Limpa caches | - |
|
||||
| `bash scripts/dev.sh help` | Ajuda | - |
|
||||
|
||||
---
|
||||
|
||||
## 6. Checklist antes de cada deploy
|
||||
|
||||
### Homologacao
|
||||
- [ ] Testei localmente (`npm run dev`)
|
||||
- [ ] Build funciona (`bash scripts/dev.sh build-hom`)
|
||||
- [ ] Nao ha erros no console do navegador
|
||||
|
||||
### Producao
|
||||
- [ ] Testei em homologacao
|
||||
- [ ] Aprovado pelo time
|
||||
- [ ] Build de producao (`bash scripts/dev.sh build`)
|
||||
- [ ] Backup esta funcionando no servidor
|
||||
|
||||
---
|
||||
|
||||
## 7. Solucao de Problemas
|
||||
|
||||
### Erro: "Cannot find module"
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Erro: "dist/ nao encontrado"
|
||||
```bash
|
||||
bash scripts/dev.sh build
|
||||
```
|
||||
|
||||
### Erro: "Permission denied" no deploy
|
||||
```bash
|
||||
# Verificar SSH
|
||||
ssh deploy@SEU_SERVIDOR "echo OK"
|
||||
|
||||
# Se nao funcionar, recopiar chave
|
||||
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@SEU_SERVIDOR
|
||||
```
|
||||
|
||||
### Site nao carrega depois do deploy
|
||||
```bash
|
||||
# Verificar no servidor
|
||||
ssh deploy@SEU_SERVIDOR "ls -la /var/www/avanzato"
|
||||
ssh deploy@SEU_SERVIDOR "sudo systemctl status nginx"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Dicas
|
||||
|
||||
1. **Nunca edite diretamente no servidor** - Sempre edite local e deploy
|
||||
2. **Sempre teste em homolog primeiro** - Antes de ir pra prod
|
||||
3. **Commits frequentes** - Facilita rollback se algo der errado
|
||||
4. **Mantenha o .env.local seguro** - Nunca commite este arquivo
|
||||
-116
@@ -1,116 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,244 +0,0 @@
|
||||
# 📁 Guia do .htaccess - Avanzato Tecnologia
|
||||
|
||||
## O que é o .htaccess?
|
||||
|
||||
O `.htaccess` é um arquivo de configuração do Apache (servidor web) que controla:
|
||||
- ✅ Redirecionamentos
|
||||
- ✅ Compressão de arquivos
|
||||
- ✅ Cache do navegador
|
||||
- ✅ Segurança
|
||||
- ✅ Performance
|
||||
|
||||
---
|
||||
|
||||
## 📂 Onde colocar o arquivo?
|
||||
|
||||
```
|
||||
/public_html/ ← Raiz do seu site
|
||||
├── .htaccess ← COLOQUE AQUI
|
||||
├── index.html
|
||||
├── assets/
|
||||
└── ...
|
||||
```
|
||||
|
||||
**IMPORTANTE:** O arquivo deve estar na **mesma pasta** do `index.html`
|
||||
|
||||
---
|
||||
|
||||
## 📋 Arquivos disponíveis
|
||||
|
||||
| Arquivo | Quando usar |
|
||||
|---------|-------------|
|
||||
| `.htaccess` | Versão **completa** (recomendada) |
|
||||
| `.htaccess-simples` | Se o servidor der erro 500 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Como instalar
|
||||
|
||||
### Método 1: FTP/cPanel
|
||||
1. Acesse seu servidor via FTP ou cPanel
|
||||
2. Vá para a pasta `public_html/` (ou `www/`)
|
||||
3. Faça upload do arquivo `.htaccess`
|
||||
4. Pronto!
|
||||
|
||||
### Método 2: SSH
|
||||
```bash
|
||||
# Conecte ao servidor
|
||||
ssh usuario@seudominio.com.br
|
||||
|
||||
# Vá para a pasta do site
|
||||
cd /var/www/html
|
||||
# ou
|
||||
cd /home/usuario/public_html
|
||||
|
||||
# Crie o arquivo
|
||||
nano .htaccess
|
||||
|
||||
# Cole o conteúdo do .htaccess
|
||||
# Salve: Ctrl+O, Enter, Ctrl+X
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configurações incluídas
|
||||
|
||||
### 1. **SPA (Single Page Application)**
|
||||
```apache
|
||||
RewriteRule ^ index.html [L]
|
||||
```
|
||||
→ Todas as URLs vão para `index.html` (necessário para React)
|
||||
|
||||
### 2. **Compressão GZIP**
|
||||
```apache
|
||||
AddOutputFilterByType DEFLATE text/css application/javascript
|
||||
```
|
||||
→ Reduz tamanho dos arquivos em ~70%
|
||||
|
||||
### 3. **Cache do Navegador**
|
||||
```apache
|
||||
ExpiresByType application/javascript "access plus 1 year"
|
||||
```
|
||||
→ Arquivos JS/CSS ficam no cache por 1 ano
|
||||
|
||||
### 4. **Segurança**
|
||||
- Proteção contra clickjacking
|
||||
- Proteção contra MIME sniffing
|
||||
- Bloqueio de arquivos ocultos (.git, .env)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Configurações opcionais
|
||||
|
||||
### Ativar HTTPS (quando tiver SSL)
|
||||
|
||||
Descomente estas linhas no `.htaccess`:
|
||||
|
||||
```apache
|
||||
# Forçar HTTPS
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteCond %{HTTPS} off
|
||||
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
||||
</IfModule>
|
||||
|
||||
# HSTS (segurança extra)
|
||||
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||
```
|
||||
|
||||
### Forçar SEM www
|
||||
|
||||
```apache
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
|
||||
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
|
||||
</IfModule>
|
||||
```
|
||||
|
||||
### Forçar COM www
|
||||
|
||||
```apache
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteCond %{HTTP_HOST} !^www\. [NC]
|
||||
RewriteRule ^ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
||||
</IfModule>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ❌ Problemas comuns
|
||||
|
||||
### Erro 500 (Internal Server Error)
|
||||
|
||||
**Causa:** Seu servidor não tem todos os módulos habilitados
|
||||
|
||||
**Solução:** Use o `.htaccess-simples`
|
||||
|
||||
```bash
|
||||
# Renomeie o arquivo
|
||||
mv .htaccess .htaccess-backup
|
||||
mv .htaccess-simples .htaccess
|
||||
```
|
||||
|
||||
### Página em branco
|
||||
|
||||
**Causa:** O mod_rewrite pode estar desativado
|
||||
|
||||
**Solução:** Contate seu provedor de hospedagem para ativar:
|
||||
- `mod_rewrite`
|
||||
- `mod_deflate`
|
||||
- `mod_expires`
|
||||
- `mod_headers`
|
||||
|
||||
### Rotas não funcionam (404 em /sobre, /contato)
|
||||
|
||||
**Causa:** O redirecionamento para index.html não está funcionando
|
||||
|
||||
**Solução:** Verifique se o arquivo .htaccess está na pasta correta
|
||||
|
||||
---
|
||||
|
||||
## 📊 Testar se está funcionando
|
||||
|
||||
### 1. Testar compressão GZIP
|
||||
```bash
|
||||
curl -H "Accept-Encoding: gzip" -I https://seudominio.com.br
|
||||
```
|
||||
|
||||
Deve mostrar: `Content-Encoding: gzip`
|
||||
|
||||
### 2. Testar headers de segurança
|
||||
```bash
|
||||
curl -I https://seudominio.com.br
|
||||
```
|
||||
|
||||
Deve mostrar:
|
||||
- `X-Frame-Options: SAMEORIGIN`
|
||||
- `X-Content-Type-Options: nosniff`
|
||||
|
||||
### 3. Testar cache
|
||||
```bash
|
||||
curl -I https://seudominio.com.br/assets/index-xxx.js
|
||||
```
|
||||
|
||||
Deve mostrar: `Cache-Control: max-age=31536000`
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Servidores específicos
|
||||
|
||||
### cPanel (Hostgator, Locaweb, etc)
|
||||
1. Acesse: **Arquivos** → **Gerenciador de Arquivos**
|
||||
2. Vá para `public_html/`
|
||||
3. Clique em **Upload** e envie o `.htaccess`
|
||||
|
||||
### AWS S3 + CloudFront
|
||||
O S3 não usa .htaccess. Configure:
|
||||
- **Error Document:** `index.html`
|
||||
- CloudFront: **Custom Error Response** 404 → 200 para `index.html`
|
||||
|
||||
### Vercel/Netlify
|
||||
Não precisa de .htaccess. Use `vercel.json` ou `_redirects`:
|
||||
```
|
||||
/* /index.html 200
|
||||
```
|
||||
|
||||
### Nginx (servidor diferente)
|
||||
Se usar Nginx, crie um arquivo de configuração:
|
||||
```nginx
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
gzip on;
|
||||
gzip_types text/css application/javascript;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 Suporte
|
||||
|
||||
Se tiver problemas, verifique:
|
||||
1. O arquivo está na pasta correta?
|
||||
2. O nome do arquivo é exatamente `.htaccess` (com ponto)?
|
||||
3. As permissões do arquivo são 644?
|
||||
|
||||
```bash
|
||||
# Corrigir permissões
|
||||
chmod 644 .htaccess
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist de instalação
|
||||
|
||||
- [ ] Arquivo `.htaccess` na pasta `public_html/`
|
||||
- [ ] Site carrega normalmente (sem erro 500)
|
||||
- [ ] Navegação entre páginas funciona
|
||||
- [ ] Atualizar página (F5) não dá erro 404
|
||||
- [ ] GZIP está ativo (teste com GTmetrix)
|
||||
- [ ] Cache está funcionando
|
||||
|
||||
---
|
||||
|
||||
**Última atualização:** 2024
|
||||
**Versão:** 1.0
|
||||
@@ -1,73 +0,0 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## React Compiler
|
||||
|
||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
|
||||
```js
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
|
||||
// Remove tseslint.configs.recommended and replace with this
|
||||
tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
tseslint.configs.stylisticTypeChecked,
|
||||
|
||||
// Other configs...
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
// Enable lint rules for React
|
||||
reactX.configs['recommended-typescript'],
|
||||
// Enable lint rules for React DOM
|
||||
reactDom.configs.recommended,
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
@@ -0,0 +1,22 @@
|
||||
import * as cookie from "cookie";
|
||||
import { Session } from "@contracts/constants";
|
||||
import { getSessionCookieOptions } from "./lib/cookies";
|
||||
import { createRouter, authedQuery } from "./middleware";
|
||||
|
||||
export const authRouter = createRouter({
|
||||
me: authedQuery.query((opts) => opts.ctx.user),
|
||||
logout: authedQuery.mutation(async ({ ctx }) => {
|
||||
const opts = getSessionCookieOptions(ctx.req.headers);
|
||||
ctx.resHeaders.append(
|
||||
"set-cookie",
|
||||
cookie.serialize(Session.cookieName, "", {
|
||||
httpOnly: opts.httpOnly,
|
||||
path: opts.path,
|
||||
sameSite: opts.sameSite?.toLowerCase() as "lax" | "none",
|
||||
secure: opts.secure,
|
||||
maxAge: 0,
|
||||
}),
|
||||
);
|
||||
return { success: true };
|
||||
}),
|
||||
});
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { Hono } from "hono";
|
||||
import { bodyLimit } from "hono/body-limit";
|
||||
import type { HttpBindings } from "@hono/node-server";
|
||||
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
||||
import { appRouter } from "./router";
|
||||
import { createContext } from "./context";
|
||||
import { env } from "./lib/env";
|
||||
import { createOAuthCallbackHandler } from "./kimi/auth";
|
||||
import { Paths } from "@contracts/constants";
|
||||
|
||||
const app = new Hono<{ Bindings: HttpBindings }>();
|
||||
|
||||
app.use(bodyLimit({ maxSize: 50 * 1024 * 1024 }));
|
||||
app.get(Paths.oauthCallback, createOAuthCallbackHandler());
|
||||
app.use("/api/trpc/*", async (c) => {
|
||||
return fetchRequestHandler({
|
||||
endpoint: "/api/trpc",
|
||||
req: c.req.raw,
|
||||
router: appRouter,
|
||||
createContext,
|
||||
});
|
||||
});
|
||||
app.all("/api/*", (c) => c.json({ error: "Not Found" }, 404));
|
||||
|
||||
export default app;
|
||||
|
||||
if (env.isProduction) {
|
||||
const { serve } = await import("@hono/node-server");
|
||||
const { serveStaticFiles } = await import("./lib/vite");
|
||||
serveStaticFiles(app);
|
||||
|
||||
const port = parseInt(process.env.PORT || "3000");
|
||||
serve({ fetch: app.fetch, port }, () => {
|
||||
console.log(`Server running on http://localhost:${port}/`);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch";
|
||||
import type { User } from "@db/schema";
|
||||
import { authenticateRequest } from "./kimi/auth";
|
||||
|
||||
export type TrpcContext = {
|
||||
req: Request;
|
||||
resHeaders: Headers;
|
||||
user?: User;
|
||||
};
|
||||
|
||||
export async function createContext(
|
||||
opts: FetchCreateContextFnOptions,
|
||||
): Promise<TrpcContext> {
|
||||
const ctx: TrpcContext = { req: opts.req, resHeaders: opts.resHeaders };
|
||||
try {
|
||||
ctx.user = await authenticateRequest(opts.req.headers);
|
||||
} catch {
|
||||
// Authentication is optional here
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
// ============================================================================
|
||||
// FORM ROUTER - Area do Cliente Avanzato
|
||||
// ============================================================================
|
||||
// Routers para gestao de formularios e respostas
|
||||
// ============================================================================
|
||||
|
||||
import { z } from "zod";
|
||||
import { createRouter, publicQuery, authedQuery } from "./middleware";
|
||||
import { getDb } from "./queries/connection";
|
||||
import { clientForms, formSections, formQuestions, formAnswers, formSubmissions } from "../db/schema";
|
||||
import { eq, desc, and } from "drizzle-orm";
|
||||
|
||||
// Gerar codigo aleatorio de 8 caracteres
|
||||
function generateCode(): string {
|
||||
return Math.random().toString(36).substring(2, 10).toUpperCase();
|
||||
}
|
||||
|
||||
export const formRouter = createRouter({
|
||||
// ==========================================================================
|
||||
// ADMIN - Gerenciamento de Formularios
|
||||
// ==========================================================================
|
||||
|
||||
// Listar todos os formularios
|
||||
list: publicQuery.query(async () => {
|
||||
const db = getDb();
|
||||
const forms = await db.select().from(clientForms).orderBy(desc(clientForms.createdAt));
|
||||
return forms;
|
||||
}),
|
||||
|
||||
// Criar novo formulario
|
||||
create: publicQuery
|
||||
.input(
|
||||
z.object({
|
||||
title: z.string().min(1),
|
||||
description: z.string().optional(),
|
||||
clientName: z.string().min(1),
|
||||
clientEmail: z.string().email().optional(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const db = getDb();
|
||||
const code = generateCode();
|
||||
|
||||
await db.insert(clientForms).values({
|
||||
title: input.title,
|
||||
description: input.description || "",
|
||||
clientName: input.clientName,
|
||||
clientEmail: input.clientEmail || "",
|
||||
status: "active",
|
||||
accessCode: code,
|
||||
createdBy: "Avanzato",
|
||||
});
|
||||
|
||||
return { code, message: "Formulario criado com sucesso" };
|
||||
}),
|
||||
|
||||
// Atualizar status (active/inactive/archived)
|
||||
updateStatus: publicQuery
|
||||
.input(
|
||||
z.object({
|
||||
accessCode: z.string(),
|
||||
status: z.enum(["active", "inactive", "archived"]),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const db = getDb();
|
||||
await db
|
||||
.update(clientForms)
|
||||
.set({ status: input.status })
|
||||
.where(eq(clientForms.accessCode, input.accessCode));
|
||||
return { message: "Status atualizado" };
|
||||
}),
|
||||
|
||||
// Excluir formulario
|
||||
delete: publicQuery
|
||||
.input(z.object({ accessCode: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
const db = getDb();
|
||||
// Primeiro excluir respostas e perguntas
|
||||
await db.delete(formAnswers).where(eq(formAnswers.formId, input.accessCode));
|
||||
await db.delete(formQuestions).where(eq(formQuestions.formId, input.accessCode));
|
||||
await db.delete(formSections).where(eq(formSections.formId, input.accessCode));
|
||||
await db.delete(formSubmissions).where(eq(formSubmissions.formId, input.accessCode));
|
||||
await db.delete(clientForms).where(eq(clientForms.accessCode, input.accessCode));
|
||||
return { message: "Formulario excluido" };
|
||||
}),
|
||||
|
||||
// Renovar codigo de acesso
|
||||
regenerateCode: publicQuery
|
||||
.input(z.object({ accessCode: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
const db = getDb();
|
||||
const newCode = generateCode();
|
||||
await db
|
||||
.update(clientForms)
|
||||
.set({ accessCode: newCode })
|
||||
.where(eq(clientForms.accessCode, input.accessCode));
|
||||
return { newCode };
|
||||
}),
|
||||
|
||||
// ==========================================================================
|
||||
// PUBLICO - Acesso ao Formulario via Codigo
|
||||
// ==========================================================================
|
||||
|
||||
// Buscar formulario por codigo (publico)
|
||||
getByCode: publicQuery
|
||||
.input(z.object({ code: z.string() }))
|
||||
.query(async ({ input }) => {
|
||||
const db = getDb();
|
||||
const [form] = await db
|
||||
.select()
|
||||
.from(clientForms)
|
||||
.where(eq(clientForms.accessCode, input.code));
|
||||
|
||||
if (!form) return null;
|
||||
if (form.status !== "active") return { ...form, _blocked: true };
|
||||
|
||||
// Buscar perguntas agrupadas por secao
|
||||
const questions = await db
|
||||
.select()
|
||||
.from(formQuestions)
|
||||
.where(eq(formQuestions.formId, input.code))
|
||||
.orderBy(formQuestions.id);
|
||||
|
||||
const sections = await db
|
||||
.select()
|
||||
.from(formSections)
|
||||
.where(eq(formSections.formId, input.code))
|
||||
.orderBy(formSections.id);
|
||||
|
||||
return { ...form, questions, sections };
|
||||
}),
|
||||
|
||||
// Submeter respostas do formulario
|
||||
submit: publicQuery
|
||||
.input(
|
||||
z.object({
|
||||
formId: z.string(),
|
||||
code: z.string(),
|
||||
answers: z.array(
|
||||
z.object({
|
||||
questionId: z.string(),
|
||||
answer: z.string(),
|
||||
})
|
||||
),
|
||||
submittedBy: z.string().optional(),
|
||||
submittedByEmail: z.string().optional(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const db = getDb();
|
||||
|
||||
// Verificar se formulario esta ativo
|
||||
const [form] = await db
|
||||
.select()
|
||||
.from(clientForms)
|
||||
.where(eq(clientForms.accessCode, input.code));
|
||||
|
||||
if (!form || form.status !== "active") {
|
||||
throw new Error("Formulario nao encontrado ou inativo");
|
||||
}
|
||||
|
||||
// Inserir submissao
|
||||
await db.insert(formSubmissions).values({
|
||||
formId: input.formId,
|
||||
submittedBy: input.submittedBy || "Anonimo",
|
||||
submittedByEmail: input.submittedByEmail || "",
|
||||
});
|
||||
|
||||
// Inserir respostas
|
||||
for (const a of input.answers) {
|
||||
await db.insert(formAnswers).values({
|
||||
formId: input.formId,
|
||||
questionId: a.questionId,
|
||||
answer: a.answer,
|
||||
});
|
||||
}
|
||||
|
||||
return { message: "Respostas submetidas com sucesso" };
|
||||
}),
|
||||
|
||||
// ==========================================================================
|
||||
// ADMIN - Visualizar Respostas
|
||||
// ==========================================================================
|
||||
|
||||
// Listar submissoes de um formulario
|
||||
getSubmissions: publicQuery
|
||||
.input(z.object({ accessCode: z.string() }))
|
||||
.query(async ({ input }) => {
|
||||
const db = getDb();
|
||||
const submissions = await db
|
||||
.select()
|
||||
.from(formSubmissions)
|
||||
.where(eq(formSubmissions.formId, input.accessCode))
|
||||
.orderBy(desc(formSubmissions.createdAt));
|
||||
return submissions;
|
||||
}),
|
||||
|
||||
// Obter todas as respostas de uma submissao
|
||||
getAnswers: publicQuery
|
||||
.input(z.object({ formId: z.string() }))
|
||||
.query(async ({ input }) => {
|
||||
const db = getDb();
|
||||
const answers = await db
|
||||
.select()
|
||||
.from(formAnswers)
|
||||
.where(eq(formAnswers.formId, input.formId))
|
||||
.orderBy(formAnswers.id);
|
||||
return answers;
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
import type { Context } from "hono";
|
||||
import { setCookie } from "hono/cookie";
|
||||
import * as jose from "jose";
|
||||
import * as cookie from "cookie";
|
||||
import { env } from "../lib/env";
|
||||
import { getSessionCookieOptions } from "../lib/cookies";
|
||||
import { Session } from "@contracts/constants";
|
||||
import { Errors } from "@contracts/errors";
|
||||
import { signSessionToken, verifySessionToken } from "./session";
|
||||
import { users as kimiUsers } from "./platform";
|
||||
import { findUserByUnionId, upsertUser } from "../queries/users";
|
||||
import type { TokenResponse } from "./types";
|
||||
|
||||
async function exchangeAuthCode(
|
||||
code: string,
|
||||
redirectUri: string,
|
||||
): Promise<TokenResponse> {
|
||||
const body = new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
code,
|
||||
client_id: env.appId,
|
||||
redirect_uri: redirectUri,
|
||||
client_secret: env.appSecret,
|
||||
});
|
||||
|
||||
const resp = await fetch(`${env.kimiAuthUrl}/api/oauth/token`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: body.toString(),
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
const text = await resp.text();
|
||||
throw new Error(`Token exchange failed (${resp.status}): ${text}`);
|
||||
}
|
||||
|
||||
return resp.json() as Promise<TokenResponse>;
|
||||
}
|
||||
|
||||
const jwks = jose.createRemoteJWKSet(
|
||||
new URL(`${env.kimiAuthUrl}/api/.well-known/jwks.json`),
|
||||
);
|
||||
|
||||
async function verifyAccessToken(
|
||||
accessToken: string,
|
||||
): Promise<{ userId: string; clientId: string }> {
|
||||
const { payload } = await jose.jwtVerify(accessToken, jwks);
|
||||
const userId = payload.user_id as string;
|
||||
const clientId = payload.client_id as string;
|
||||
if (!userId) {
|
||||
throw new Error("user_id missing from access token");
|
||||
}
|
||||
return { userId, clientId };
|
||||
}
|
||||
|
||||
export async function authenticateRequest(headers: Headers) {
|
||||
const cookies = cookie.parse(headers.get("cookie") || "");
|
||||
const token = cookies[Session.cookieName];
|
||||
if (!token) {
|
||||
console.warn("[auth] No session cookie found in request.");
|
||||
throw Errors.forbidden("Invalid authentication token.");
|
||||
}
|
||||
const claim = await verifySessionToken(token);
|
||||
if (!claim) {
|
||||
throw Errors.forbidden("Invalid authentication token.");
|
||||
}
|
||||
const user = await findUserByUnionId(claim.unionId);
|
||||
if (!user) {
|
||||
throw Errors.forbidden("User not found. Please re-login.");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
export function createOAuthCallbackHandler() {
|
||||
return async (c: Context) => {
|
||||
const code = c.req.query("code");
|
||||
const state = c.req.query("state");
|
||||
const error = c.req.query("error");
|
||||
const errorDescription = c.req.query("error_description");
|
||||
|
||||
if (error) {
|
||||
if (error === "access_denied") {
|
||||
return c.redirect("/", 302);
|
||||
}
|
||||
return c.json(
|
||||
{ error, error_description: errorDescription },
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
if (!code || !state) {
|
||||
return c.json({ error: "code and state are required" }, 400);
|
||||
}
|
||||
|
||||
try {
|
||||
const redirectUri = atob(state);
|
||||
const tokenResp = await exchangeAuthCode(code, redirectUri);
|
||||
const { userId } = await verifyAccessToken(tokenResp.access_token);
|
||||
const userProfile = await kimiUsers.getProfile(tokenResp.access_token);
|
||||
if (!userProfile) {
|
||||
throw new Error("Failed to fetch user profile from Kimi Open");
|
||||
}
|
||||
|
||||
await upsertUser({
|
||||
unionId: userId,
|
||||
name: userProfile.name,
|
||||
avatar: userProfile.avatar_url,
|
||||
lastSignInAt: new Date(),
|
||||
});
|
||||
|
||||
const token = await signSessionToken({
|
||||
unionId: userId,
|
||||
clientId: env.appId,
|
||||
});
|
||||
|
||||
const cookieOpts = getSessionCookieOptions(c.req.raw.headers);
|
||||
setCookie(c, Session.cookieName, token, {
|
||||
...cookieOpts,
|
||||
maxAge: Session.maxAgeMs / 1000,
|
||||
});
|
||||
|
||||
return c.redirect("/", 302);
|
||||
} catch (error) {
|
||||
console.error("[OAuth] Callback failed", error);
|
||||
return c.json({ error: "OAuth callback failed" }, 500);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export { exchangeAuthCode, verifyAccessToken };
|
||||
@@ -0,0 +1,30 @@
|
||||
import { env } from "../lib/env";
|
||||
import type { UserProfile } from "./types";
|
||||
|
||||
async function kimiRequest<T>(
|
||||
path: string,
|
||||
token: string,
|
||||
init?: RequestInit,
|
||||
): Promise<T | null> {
|
||||
const resp = await fetch(`${env.kimiOpenUrl}${path}`, {
|
||||
...init,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
...init?.headers,
|
||||
},
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const text = await resp.text();
|
||||
console.warn(
|
||||
`[kimi] Request to ${path} failed (${resp.status}): ${text}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return resp.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export const users = {
|
||||
getProfile: (token: string) =>
|
||||
kimiRequest<UserProfile>("/v1/users/me/profile", token),
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import * as jose from "jose";
|
||||
import { env } from "../lib/env";
|
||||
import type { SessionPayload } from "./types";
|
||||
|
||||
const JWT_ALG = "HS256";
|
||||
|
||||
export async function signSessionToken(
|
||||
payload: SessionPayload,
|
||||
): Promise<string> {
|
||||
const secret = new TextEncoder().encode(env.appSecret);
|
||||
return new jose.SignJWT(payload)
|
||||
.setProtectedHeader({ alg: JWT_ALG })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime("1 year")
|
||||
.sign(secret);
|
||||
}
|
||||
|
||||
export async function verifySessionToken(
|
||||
token: string,
|
||||
): Promise<SessionPayload | null> {
|
||||
if (!token) {
|
||||
console.warn("[session] No token provided for verification.");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const secret = new TextEncoder().encode(env.appSecret);
|
||||
const { payload } = await jose.jwtVerify(token, secret, {
|
||||
algorithms: [JWT_ALG],
|
||||
});
|
||||
const { unionId, clientId } = payload;
|
||||
if (!unionId || !clientId) {
|
||||
console.warn("[session] JWT payload missing required fields.");
|
||||
return null;
|
||||
}
|
||||
return { unionId, clientId } as SessionPayload;
|
||||
} catch (error) {
|
||||
console.warn("[session] JWT verification failed:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export type TokenResponse = {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
expires_in: number;
|
||||
refresh_token?: string;
|
||||
scope: string;
|
||||
};
|
||||
|
||||
export type SessionPayload = {
|
||||
unionId: string;
|
||||
clientId: string;
|
||||
};
|
||||
|
||||
export type UserProfile = {
|
||||
user_id: string;
|
||||
name: string;
|
||||
avatar_url: string;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { CookieOptions } from "hono/utils/cookie";
|
||||
|
||||
function isLocalhost(headers: Headers): boolean {
|
||||
const host = headers.get("host") || "";
|
||||
return host.startsWith("localhost:") || host.startsWith("127.0.0.1:");
|
||||
}
|
||||
|
||||
export function getSessionCookieOptions(headers: Headers): CookieOptions {
|
||||
const localhost = isLocalhost(headers);
|
||||
|
||||
return {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: localhost ? "Lax" : "None",
|
||||
secure: !localhost,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import "dotenv/config";
|
||||
|
||||
function required(name: string): string {
|
||||
const value = process.env[name];
|
||||
if (!value && process.env.NODE_ENV === "production") {
|
||||
throw new Error(`Missing required environment variable: ${name}`);
|
||||
}
|
||||
return value ?? "";
|
||||
}
|
||||
|
||||
export const env = {
|
||||
appId: required("APP_ID"),
|
||||
appSecret: required("APP_SECRET"),
|
||||
isProduction: process.env.NODE_ENV === "production",
|
||||
databaseUrl: required("DATABASE_URL"),
|
||||
kimiAuthUrl: required("KIMI_AUTH_URL"),
|
||||
kimiOpenUrl: required("KIMI_OPEN_URL"),
|
||||
ownerUnionId: process.env.OWNER_UNION_ID ?? "",
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
interface RequestConfig extends RequestInit {
|
||||
baseUrl?: string;
|
||||
params?: Record<string, string | number>;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export class HttpClient {
|
||||
private baseUrl: string;
|
||||
private defaultHeaders: Record<string, string>;
|
||||
|
||||
constructor(baseURL: string, opts?: { headers?: Record<string, string> }) {
|
||||
this.baseUrl = baseURL;
|
||||
this.defaultHeaders = {
|
||||
"Content-Type": "application/json",
|
||||
...opts?.headers,
|
||||
};
|
||||
}
|
||||
|
||||
async request<T>(endpoint: string, config: RequestConfig = {}): Promise<T> {
|
||||
const {
|
||||
method = "GET",
|
||||
params,
|
||||
body,
|
||||
headers,
|
||||
timeout = 30000,
|
||||
...rest
|
||||
} = config;
|
||||
|
||||
const url = new URL(`${this.baseUrl}${endpoint}`);
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([key, value]) =>
|
||||
url.searchParams.append(key, value.toString()),
|
||||
);
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
|
||||
try {
|
||||
const response = await fetch(url.toString(), {
|
||||
...rest,
|
||||
method,
|
||||
headers: { ...this.defaultHeaders, ...headers },
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = (await response
|
||||
.json()
|
||||
.catch(() => ({}))) as Record<string, string>;
|
||||
throw new Error(errorData.message || `HTTP Error: ${response.status}`);
|
||||
}
|
||||
|
||||
return (await response.json()) as T;
|
||||
} catch (error: any) {
|
||||
if (error.name === "AbortError") {
|
||||
throw new Error("Request timeout");
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
get<T>(
|
||||
url: string,
|
||||
params?: RequestConfig["params"],
|
||||
config?: RequestConfig,
|
||||
) {
|
||||
return this.request<T>(url, { ...config, method: "GET", params });
|
||||
}
|
||||
|
||||
post<T>(url: string, body?: any, config?: RequestConfig) {
|
||||
return this.request<T>(url, { ...config, method: "POST", body });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { Hono } from "hono";
|
||||
import type { HttpBindings } from "@hono/node-server";
|
||||
import { serveStatic } from "@hono/node-server/serve-static";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
type App = Hono<{ Bindings: HttpBindings }>;
|
||||
|
||||
export function serveStaticFiles(app: App) {
|
||||
const distPath = path.resolve(import.meta.dirname, "../dist/public");
|
||||
|
||||
app.use("*", serveStatic({ root: "./dist/public" }));
|
||||
|
||||
app.notFound((c) => {
|
||||
const accept = c.req.header("accept") ?? "";
|
||||
if (!accept.includes("text/html")) {
|
||||
return c.json({ error: "Not Found" }, 404);
|
||||
}
|
||||
const indexPath = path.resolve(distPath, "index.html");
|
||||
const content = fs.readFileSync(indexPath, "utf-8");
|
||||
return c.html(content);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { ErrorMessages } from "@contracts/constants";
|
||||
import { initTRPC, TRPCError } from "@trpc/server";
|
||||
import superjson from "superjson";
|
||||
import type { TrpcContext } from "./context";
|
||||
|
||||
const t = initTRPC.context<TrpcContext>().create({
|
||||
transformer: superjson,
|
||||
});
|
||||
|
||||
export const createRouter = t.router;
|
||||
export const publicQuery = t.procedure;
|
||||
|
||||
const requireAuth = t.middleware(async (opts) => {
|
||||
const { ctx, next } = opts;
|
||||
|
||||
if (!ctx.user) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: ErrorMessages.unauthenticated,
|
||||
});
|
||||
}
|
||||
|
||||
return next({ ctx: { ...ctx, user: ctx.user } });
|
||||
});
|
||||
|
||||
function requireRole(role: string) {
|
||||
return t.middleware(async (opts) => {
|
||||
const { ctx, next } = opts;
|
||||
|
||||
if (!ctx.user || ctx.user.role !== role) {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: ErrorMessages.insufficientRole,
|
||||
});
|
||||
}
|
||||
|
||||
return next({ ctx: { ...ctx, user: ctx.user } });
|
||||
});
|
||||
}
|
||||
|
||||
export const authedQuery = t.procedure.use(requireAuth);
|
||||
export const adminQuery = authedQuery.use(requireRole("admin"));
|
||||
@@ -0,0 +1,18 @@
|
||||
import { drizzle } from "drizzle-orm/mysql2";
|
||||
import { env } from "../lib/env";
|
||||
import * as schema from "@db/schema";
|
||||
import * as relations from "@db/relations";
|
||||
|
||||
const fullSchema = { ...schema, ...relations };
|
||||
|
||||
let instance: ReturnType<typeof drizzle<typeof fullSchema>>;
|
||||
|
||||
export function getDb() {
|
||||
if (!instance) {
|
||||
instance = drizzle(env.databaseUrl, {
|
||||
mode: "planetscale",
|
||||
schema: fullSchema,
|
||||
});
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import * as schema from "@db/schema";
|
||||
import type { InsertUser } from "@db/schema";
|
||||
import { getDb } from "./connection";
|
||||
import { env } from "../lib/env";
|
||||
|
||||
export async function findUserByUnionId(unionId: string) {
|
||||
const rows = await getDb()
|
||||
.select()
|
||||
.from(schema.users)
|
||||
.where(eq(schema.users.unionId, unionId))
|
||||
.limit(1);
|
||||
return rows.at(0);
|
||||
}
|
||||
|
||||
export async function upsertUser(data: InsertUser) {
|
||||
const values = { ...data };
|
||||
const updateSet: Partial<InsertUser> = {
|
||||
lastSignInAt: new Date(),
|
||||
...data,
|
||||
};
|
||||
|
||||
if (
|
||||
values.role === undefined &&
|
||||
values.unionId &&
|
||||
values.unionId === env.ownerUnionId
|
||||
) {
|
||||
values.role = "admin";
|
||||
updateSet.role = "admin";
|
||||
}
|
||||
|
||||
await getDb()
|
||||
.insert(schema.users)
|
||||
.values(values)
|
||||
.onDuplicateKeyUpdate({ set: updateSet });
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { authRouter } from "./auth-router";
|
||||
import { formRouter } from "./form-router";
|
||||
import { createRouter, publicQuery } from "./middleware";
|
||||
|
||||
export const appRouter = createRouter({
|
||||
ping: publicQuery.query(() => ({ ok: true, ts: Date.now() })),
|
||||
auth: authRouter,
|
||||
form: formRouter,
|
||||
});
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
@@ -1,185 +0,0 @@
|
||||
# Setup do Ambiente CI/CD - Avanzato
|
||||
|
||||
## Arquitetura
|
||||
|
||||
```
|
||||
+------------------------------------------------------------------+
|
||||
| SEU SERVIDOR |
|
||||
| |
|
||||
| +-------------------+ +-------------------+ +-----------+ |
|
||||
| | Git Bare Repo | | Pipeline | | Site | |
|
||||
| | /var/git/site |--->| /var/ci/ |--->| Nginx | |
|
||||
| | | | | | | |
|
||||
| | - hooks/post-receive | - build.sh | | /var/www | |
|
||||
| | - hooks/pre-receive | - deploy.sh | | | |
|
||||
| +-------------------+ +-------------------+ +-----------+ |
|
||||
| ^ | |
|
||||
| | v |
|
||||
| +-------------------+ +-------------------+ |
|
||||
| | Seu Computador | | Mautic | |
|
||||
| | | | mkt.avanzato | |
|
||||
| | git push | | | |
|
||||
| +-------------------+ +-------------------+ |
|
||||
+------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
## 1. Instalar dependencias no servidor
|
||||
|
||||
```bash
|
||||
# Atualizar sistema
|
||||
apt update && apt upgrade -y
|
||||
|
||||
# Instalar Git
|
||||
apt install -y git curl
|
||||
|
||||
# Instalar Node.js 20
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
||||
apt install -y nodejs
|
||||
|
||||
# Verificar
|
||||
node -v # v20.x.x
|
||||
npm -v # 10.x.x
|
||||
|
||||
# Instalar Nginx
|
||||
apt install -y nginx
|
||||
|
||||
# Instalar PM2 (opcional, para gerenciar processos)
|
||||
npm install -g pm2
|
||||
```
|
||||
|
||||
## 2. Criar usuario de deploy
|
||||
|
||||
```bash
|
||||
# Criar usuario dedicado
|
||||
useradd -m -s /bin/bash deploy
|
||||
|
||||
# Adicionar ao grupo www-data
|
||||
usermod -aG www-data deploy
|
||||
|
||||
# Criar chave SSH (como usuario deploy)
|
||||
su - deploy
|
||||
ssh-keygen -t ed25519 -C "deploy" -f ~/.ssh/id_ed25519 -N ""
|
||||
|
||||
# Ver chave publica (para autorizar pushes)
|
||||
cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
|
||||
```
|
||||
|
||||
## 3. Criar repositorio Git bare
|
||||
|
||||
```bash
|
||||
# Criar pasta
|
||||
mkdir -p /var/git/site.git
|
||||
cd /var/git/site.git
|
||||
git init --bare
|
||||
|
||||
# Permissoes
|
||||
chown -R deploy:deploy /var/git/site.git
|
||||
chmod -R 755 /var/git/site.git
|
||||
```
|
||||
|
||||
## 4. Configurar hooks Git
|
||||
|
||||
```bash
|
||||
# Copiar hooks
|
||||
sudo su - deploy
|
||||
cp /var/ci/hooks/post-receive /var/git/site.git/hooks/post-receive
|
||||
chmod +x /var/git/site.git/hooks/post-receive
|
||||
```
|
||||
|
||||
## 5. Configurar Nginx
|
||||
|
||||
```bash
|
||||
# Criar config
|
||||
nano /etc/nginx/sites-available/avanzato
|
||||
|
||||
# Ver link: CI/CD > nginx-config.conf
|
||||
|
||||
# Ativar
|
||||
ln -s /etc/nginx/sites-available/avanzato /etc/nginx/sites-enabled/
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
|
||||
# SSL com Let's Encrypt
|
||||
certbot --nginx -d avanzato.com.br -d www.avanzato.com.br
|
||||
```
|
||||
|
||||
## 6. Criar estrutura de pastas
|
||||
|
||||
```bash
|
||||
mkdir -p /var/www/avanzato # Site em producao
|
||||
mkdir -p /var/www/avanzato-staging # Site em staging (opcional)
|
||||
mkdir -p /var/ci # Pipeline scripts
|
||||
mkdir -p /var/ci/logs # Logs de build/deploy
|
||||
mkdir -p /var/ci/backups # Backups automaticos
|
||||
mkdir -p /var/ci/assets # Assets preservados
|
||||
chown -R deploy:www-data /var/www
|
||||
chmod -R 755 /var/www
|
||||
```
|
||||
|
||||
## 7. Configurar pipeline
|
||||
|
||||
```bash
|
||||
# Copiar scripts do pipeline
|
||||
cp /var/ci/scripts/* /var/ci/
|
||||
chmod +x /var/ci/*.sh
|
||||
|
||||
# Configurar variaveis
|
||||
nano /var/ci/config.env
|
||||
|
||||
# Conteudo:
|
||||
DEPLOY_PATH=/var/www/avanzato
|
||||
STAGING_PATH=/var/www/avanzato-staging
|
||||
BACKUP_PATH=/var/ci/backups
|
||||
LOG_PATH=/var/ci/logs
|
||||
MAUTIC_URL=https://mkt.avanzato.com.br
|
||||
```
|
||||
|
||||
## 8. Configurar seu computador local
|
||||
|
||||
```bash
|
||||
# Adicionar remote no seu projeto local
|
||||
cd /caminho/do/projeto/avanzato-site
|
||||
git remote add servidor ssh://deploy@SEU_SERVIDOR/var/git/site.git
|
||||
|
||||
# Testar conexao
|
||||
ssh deploy@SEU_SERVIDOR "echo 'Conectado!'"
|
||||
|
||||
# Push inicial
|
||||
git push servidor main
|
||||
```
|
||||
|
||||
## Comandos de uso diario
|
||||
|
||||
| Comando | Resultado |
|
||||
|---------|-----------|
|
||||
| `git push servidor main` | Deploy automatico para producao |
|
||||
| `git push servidor staging` | Deploy para staging |
|
||||
| `ssh deploy@SERVIDOR "cat /var/ci/logs/last-deploy.log"` | Ver ultimo log |
|
||||
| `ssh deploy@SERVIDOR "ls /var/ci/backups"` | Listar backups |
|
||||
|
||||
## Estrutura final no servidor
|
||||
|
||||
```
|
||||
/var/
|
||||
git/
|
||||
site.git/ # Repo bare (recebe pushes)
|
||||
hooks/
|
||||
post-receive # Hook de deploy automatico
|
||||
ci/
|
||||
scripts/
|
||||
build.sh # Compila o site
|
||||
deploy.sh # Envia para producao
|
||||
preserve-assets.sh # Preserva logo/imagens
|
||||
backup.sh # Cria backup
|
||||
hooks/
|
||||
post-receive # Template do hook
|
||||
logs/ # Logs de execucao
|
||||
backups/ # Backups automaticos
|
||||
assets/ # Assets preservados
|
||||
config.env # Variaveis de ambiente
|
||||
www/
|
||||
avanzato/ # Site em producao
|
||||
index.html
|
||||
assets/
|
||||
backups/ # Backups antigos
|
||||
```
|
||||
@@ -1,57 +0,0 @@
|
||||
# ============================================================================
|
||||
# Configuracao do Pipeline CI/CD - Avanzato
|
||||
# ============================================================================
|
||||
# Edite estas variaveis conforme seu ambiente.
|
||||
# ============================================================================
|
||||
|
||||
# --- CAMINHOS ---
|
||||
|
||||
# Caminho onde o site fica em producao
|
||||
DEPLOY_PATH=/var/www/avanzato
|
||||
|
||||
# Caminho do site em staging (opcional)
|
||||
STAGING_PATH=/var/www/avanzato-staging
|
||||
|
||||
# Caminho para backups
|
||||
BACKUP_PATH=/var/ci/backups
|
||||
|
||||
# Caminho para logs
|
||||
LOG_PATH=/var/ci/logs
|
||||
|
||||
# Caminho para assets preservados (logo, imagens)
|
||||
ASSETS_PATH=/var/ci/assets
|
||||
|
||||
# --- SERVIDOR ---
|
||||
|
||||
# URL do site
|
||||
SITE_URL=https://avanzato.com.br
|
||||
|
||||
# URL do site em staging
|
||||
STAGING_URL=https://staging.avanzato.com.br
|
||||
|
||||
# --- INTEGRACOES ---
|
||||
|
||||
# URL do Mautic
|
||||
MAUTIC_URL=https://mkt.avanzato.com.br
|
||||
|
||||
# URL do EspoCRM
|
||||
ESPOCRM_URL=https://crm.avanzato.com.br
|
||||
|
||||
# Chave API do Brevo (opcional)
|
||||
BREVO_API_KEY=
|
||||
|
||||
# Google Ads ID
|
||||
GOOGLE_ADS_ID=AW-16714813566
|
||||
|
||||
# --- E-MAIL ---
|
||||
|
||||
# E-mail para notificacoes
|
||||
NOTIFY_EMAIL=leads@avanzato.com.br
|
||||
|
||||
# --- CONFIGURACOES ---
|
||||
|
||||
# Numero de backups a manter
|
||||
MAX_BACKUPS=10
|
||||
|
||||
# Timeout do build (segundos)
|
||||
BUILD_TIMEOUT=300
|
||||
@@ -1,240 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# Git Hook: post-receive
|
||||
# ============================================================================
|
||||
# Este hook executa automaticamente apos cada git push.
|
||||
# Ele faz build e deploy do site sem intervencao manual.
|
||||
#
|
||||
# Instalacao:
|
||||
# cp ci/hooks/post-receive /var/git/site.git/hooks/
|
||||
# chmod +x /var/git/site.git/hooks/post-receive
|
||||
# ============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# Cores
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Configuracoes
|
||||
REPO_DIR="$(pwd)"
|
||||
DEPLOY_DIR="/var/tmp/site-deploy-$(date +%s)"
|
||||
CI_DIR="/var/ci"
|
||||
LOG_FILE="/var/ci/logs/deploy-$(date +%Y%m%d-%H%M%S).log"
|
||||
LAST_LOG="/var/ci/logs/last-deploy.log"
|
||||
CONFIG_FILE="/var/ci/config.env"
|
||||
|
||||
# Criar pasta de logs
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
# Funcao de log
|
||||
log() {
|
||||
echo -e "$1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Redirect output to log
|
||||
exec > >(tee -a "$LOG_FILE")
|
||||
exec 2>&1
|
||||
|
||||
log "${BLUE}============================================================${NC}"
|
||||
log "${BLUE} Avanzato CI/CD - Deploy Automatico${NC}"
|
||||
log "${BLUE}============================================================${NC}"
|
||||
log ""
|
||||
log "Data: $(date)"
|
||||
log "Branch recebida: $1"
|
||||
log "Commit: $2"
|
||||
log ""
|
||||
|
||||
# Carregar configuracoes
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
source "$CONFIG_FILE"
|
||||
log "${GREEN}[OK]${NC} Configuracoes carregadas."
|
||||
else
|
||||
log "${YELLOW}[AVISO]${NC} Arquivo config.env nao encontrado. Usando defaults."
|
||||
DEPLOY_PATH="/var/www/avanzato"
|
||||
STAGING_PATH="/var/www/avanzato-staging"
|
||||
BACKUP_PATH="/var/ci/backups"
|
||||
fi
|
||||
|
||||
# Detectar branch
|
||||
while read oldrev newrev refname; do
|
||||
BRANCH=$(git rev-parse --symbolic --abbrev-ref "$refname")
|
||||
log "Processando branch: ${YELLOW}${BRANCH}${NC}"
|
||||
|
||||
case "$BRANCH" in
|
||||
main|master)
|
||||
TARGET="production"
|
||||
DEPLOY_TARGET="$DEPLOY_PATH"
|
||||
;;
|
||||
staging)
|
||||
TARGET="staging"
|
||||
DEPLOY_TARGET="$STAGING_PATH"
|
||||
;;
|
||||
*)
|
||||
log "${YELLOW}[IGNORADO]${NC} Branch '${BRANCH}' nao configurada para deploy."
|
||||
log "Branches validas: main, master, staging"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
log ""
|
||||
log "${BLUE}------------------------------------------------------------${NC}"
|
||||
log "Ambiente: ${YELLOW}${TARGET}${NC}"
|
||||
log "Destino: ${DEPLOY_TARGET}"
|
||||
log "${BLUE}------------------------------------------------------------${NC}"
|
||||
log ""
|
||||
|
||||
# ==========================================================================
|
||||
# PASSO 1: Checkout do codigo
|
||||
# ==========================================================================
|
||||
log "${BLUE}[1/6]${NC} Checkout do codigo..."
|
||||
mkdir -p "$DEPLOY_DIR"
|
||||
git --git-dir="$REPO_DIR" --work-tree="$DEPLOY_DIR" checkout -f "$BRANCH" 2>&1
|
||||
log "${GREEN}[OK]${NC} Codigo extraido para ${DEPLOY_DIR}"
|
||||
log ""
|
||||
|
||||
# ==========================================================================
|
||||
# PASSO 2: Instalar dependencias
|
||||
# ==========================================================================
|
||||
log "${BLUE}[2/6]${NC} Instalando dependencias..."
|
||||
cd "$DEPLOY_DIR"
|
||||
if [ -f "package.json" ]; then
|
||||
npm ci --silent 2>&1 || {
|
||||
log "${RED}[ERRO]${NC} Falha ao instalar dependencias."
|
||||
exit 1
|
||||
}
|
||||
log "${GREEN}[OK]${NC} Dependencias instaladas."
|
||||
else
|
||||
log "${YELLOW}[AVISO]${NC} package.json nao encontrado. Pulando."
|
||||
fi
|
||||
log ""
|
||||
|
||||
# ==========================================================================
|
||||
# PASSO 3: Build
|
||||
# ==========================================================================
|
||||
log "${BLUE}[3/6]${NC} Executando build..."
|
||||
cd "$DEPLOY_DIR"
|
||||
if [ -f "package.json" ]; then
|
||||
npm run build 2>&1 || {
|
||||
log "${RED}[ERRO]${NC} Falha no build."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Verificar se a pasta dist foi criada
|
||||
if [ ! -d "dist" ]; then
|
||||
log "${RED}[ERRO]${NC} Pasta dist/ nao encontrada apos build."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "${GREEN}[OK]${NC} Build concluido com sucesso."
|
||||
log "Tamanho: $(du -sh dist/ | cut -f1)"
|
||||
else
|
||||
# Se nao tem package.json, usar o proprio diretorio
|
||||
log "${YELLOW}[AVISO]${NC} Projeto estatico detectado. Usando arquivos diretamente."
|
||||
fi
|
||||
log ""
|
||||
|
||||
# ==========================================================================
|
||||
# PASSO 4: Preservar assets
|
||||
# ==========================================================================
|
||||
log "${BLUE}[4/6]${NC} Preservando assets customizados..."
|
||||
cd "$DEPLOY_DIR"
|
||||
|
||||
# Se existir pasta custom-assets, preservar
|
||||
if [ -d "custom-assets" ]; then
|
||||
log "Assets customizados encontrados. Preservando..."
|
||||
bash "${DEPLOY_DIR}/scripts/preserve-assets.sh" "${DEPLOY_DIR}/dist" 2>&1 || {
|
||||
log "${YELLOW}[AVISO]${NC} Erro ao preservar assets (nao critico)."
|
||||
}
|
||||
else
|
||||
log "${YELLOW}[AVISO]${NC} Nenhum asset customizado encontrado."
|
||||
fi
|
||||
|
||||
# Se existem assets preservados no /var/ci/assets, copiar
|
||||
if [ -d "/var/ci/assets" ] && [ "$(ls -A /var/ci/assets)" ]; then
|
||||
log "Copiando assets preservados..."
|
||||
cp -r /var/ci/assets/* "${DEPLOY_DIR}/dist/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log "${GREEN}[OK]${NC} Assets processados."
|
||||
log ""
|
||||
|
||||
# ==========================================================================
|
||||
# PASSO 5: Backup do site atual
|
||||
# ==========================================================================
|
||||
log "${BLUE}[5/6]${NC} Criando backup..."
|
||||
if [ -d "$DEPLOY_TARGET" ] && [ "$(ls -A "$DEPLOY_TARGET" 2>/dev/null)" ]; then
|
||||
BACKUP_NAME="backup-$(date +%Y%m%d-%H%M%S)"
|
||||
mkdir -p "$BACKUP_PATH"
|
||||
|
||||
# Criar backup (excluindo backups antigos para nao duplicar)
|
||||
tar -czf "${BACKUP_PATH}/${BACKUP_NAME}.tar.gz" \
|
||||
-C "$(dirname "$DEPLOY_TARGET")" \
|
||||
--exclude="backups" \
|
||||
"$(basename "$DEPLOY_TARGET")" 2>/dev/null || true
|
||||
|
||||
# Limpar backups antigos (manter 10)
|
||||
ls -t "${BACKUP_PATH}"/*.tar.gz 2>/dev/null | tail -n +11 | xargs -r rm -f
|
||||
|
||||
log "${GREEN}[OK]${NC} Backup criado: ${BACKUP_NAME}.tar.gz"
|
||||
else
|
||||
log "${YELLOW}[AVISO]${NC} Nada para fazer backup (primeiro deploy)."
|
||||
fi
|
||||
log ""
|
||||
|
||||
# ==========================================================================
|
||||
# PASSO 6: Deploy
|
||||
# ==========================================================================
|
||||
log "${BLUE}[6/6]${NC} Fazendo deploy..."
|
||||
|
||||
# Criar diretorio se nao existir
|
||||
mkdir -p "$DEPLOY_TARGET"
|
||||
|
||||
# Limpar conteudo antigo (exceto backups)
|
||||
find "$DEPLOY_TARGET" -mindepth 1 -not -path "${DEPLOY_TARGET}/backups/*" -not -name "backups" -delete 2>/dev/null || rm -rf "${DEPLOY_TARGET:?}"/*
|
||||
|
||||
# Copiar novos arquivos
|
||||
if [ -d "${DEPLOY_DIR}/dist" ]; then
|
||||
cp -r "${DEPLOY_DIR}/dist/"* "$DEPLOY_TARGET/"
|
||||
else
|
||||
# Se nao tem dist, copiar tudo
|
||||
cp -r "${DEPLOY_DIR}/"* "$DEPLOY_TARGET/"
|
||||
fi
|
||||
|
||||
# Permissoes
|
||||
chown -R www-data:www-data "$DEPLOY_TARGET"
|
||||
chmod -R 755 "$DEPLOY_TARGET"
|
||||
|
||||
# Limpar pasta temporaria
|
||||
rm -rf "$DEPLOY_DIR"
|
||||
|
||||
log "${GREEN}[OK]${NC} Deploy concluido!"
|
||||
log ""
|
||||
|
||||
# ==========================================================================
|
||||
# FINAL
|
||||
# ==========================================================================
|
||||
log "${BLUE}============================================================${NC}"
|
||||
log "${GREEN} Deploy concluido com sucesso!${NC}"
|
||||
log "${BLUE}============================================================${NC}"
|
||||
log ""
|
||||
log "Ambiente: ${TARGET}"
|
||||
log "Caminho: ${DEPLOY_TARGET}"
|
||||
log "Backup: ${BACKUP_PATH}"
|
||||
log "Log: ${LOG_FILE}"
|
||||
log "Data: $(date)"
|
||||
log ""
|
||||
|
||||
# Atualizar link do ultimo log
|
||||
ln -sf "$LOG_FILE" "$LAST_LOG"
|
||||
|
||||
# Notificar via webhook (opcional)
|
||||
# curl -X POST "https://hooks.slack.com/services/..." \
|
||||
# -H 'Content-Type: application/json' \
|
||||
# -d "{\"text\":\"Deploy ${TARGET} concluido!\"}" 2>/dev/null || true
|
||||
|
||||
exit 0
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# Git Hook: pre-receive
|
||||
# ============================================================================
|
||||
# Executado ANTES do push ser aceito.
|
||||
# Pode bloquear pushes que nao atendem criterios.
|
||||
# ============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# Rejeitar pushes diretos na main (forcar via PR)
|
||||
# Descomente se quiser forcar code review:
|
||||
# while read oldrev newrev refname; do
|
||||
# BRANCH=$(git rev-parse --symbolic --abbrev-ref "$refname")
|
||||
# if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
||||
# echo "Push direto na main bloqueado. Use pull request."
|
||||
# exit 1
|
||||
# fi
|
||||
# done
|
||||
|
||||
# Aceitar todos os pushes por padrao
|
||||
exit 0
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# Script de Backup - Avanzato
|
||||
# ============================================================================
|
||||
# Uso: bash ci/scripts/backup.sh [ambiente]
|
||||
#
|
||||
# Faz backup manual do site.
|
||||
# ============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Carregar config
|
||||
CONFIG_FILE="/var/ci/config.env"
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
source "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
DEPLOY_PATH="${DEPLOY_PATH:-/var/www/avanzato}"
|
||||
BACKUP_PATH="${BACKUP_PATH:-/var/ci/backups}"
|
||||
AMBIENTE="${1:-production}"
|
||||
|
||||
# Determinar diretorio
|
||||
if [ "$AMBIENTE" = "staging" ]; then
|
||||
DEPLOY_PATH="${STAGING_PATH:-$DEPLOY_PATH-staging}"
|
||||
fi
|
||||
|
||||
BACKUP_NAME="backup-$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
mkdir -p "$BACKUP_PATH"
|
||||
|
||||
if [ ! -d "$DEPLOY_PATH" ] || [ ! "$(ls -A "$DEPLOY_PATH" 2>/dev/null)" ]; then
|
||||
echo -e "${RED}Nada para fazer backup.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Criando backup...${NC}"
|
||||
echo "Origem: $DEPLOY_PATH"
|
||||
echo "Destino: $BACKUP_PATH/${BACKUP_NAME}.tar.gz"
|
||||
|
||||
tar -czf "${BACKUP_PATH}/${BACKUP_NAME}.tar.gz" \
|
||||
-C "$(dirname "$DEPLOY_PATH")" \
|
||||
--exclude="backups" \
|
||||
"$(basename "$DEPLOY_PATH")"
|
||||
|
||||
echo -e "${GREEN}Backup criado: ${BACKUP_NAME}.tar.gz${NC}"
|
||||
|
||||
# Limpar backups antigos
|
||||
MAX_BACKUPS="${MAX_BACKUPS:-10}"
|
||||
echo "Limpando backups antigos (manter $MAX_BACKUPS)..."
|
||||
ls -t "${BACKUP_PATH}"/*.tar.gz 2>/dev/null | tail -n +$((MAX_BACKUPS + 1)) | xargs -r rm -f
|
||||
echo -e "${GREEN}Concluido!${NC}"
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# Script de Rollback - Avanzato
|
||||
# ============================================================================
|
||||
# Uso: bash ci/scripts/rollback.sh [numero]
|
||||
#
|
||||
# Restaura um backup anterior.
|
||||
# ============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
CONFIG_FILE="/var/ci/config.env"
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
source "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
DEPLOY_PATH="${DEPLOY_PATH:-/var/www/avanzato}"
|
||||
BACKUP_PATH="${BACKUP_PATH:-/var/ci/backups}"
|
||||
AMBIENTE="${1:-production}"
|
||||
|
||||
if [ "$AMBIENTE" = "staging" ]; then
|
||||
DEPLOY_PATH="${STAGING_PATH:-$DEPLOY_PATH-staging}"
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}============================================================${NC}"
|
||||
echo -e "${BLUE} Avanzato - Rollback${NC}"
|
||||
echo -e "${BLUE}============================================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Listar backups disponiveis
|
||||
echo "Backups disponiveis:"
|
||||
echo ""
|
||||
|
||||
BACKUPS=()
|
||||
i=1
|
||||
for backup in $(ls -t "${BACKUP_PATH}"/*.tar.gz 2>/dev/null); do
|
||||
nome=$(basename "$backup")
|
||||
tamanho=$(du -h "$backup" | cut -f1)
|
||||
data=$(stat -c %y "$backup" | cut -d'.' -f1)
|
||||
printf " ${GREEN}%2d.${NC} %s (${tamanho}) %s\n" "$i" "$nome" "$data"
|
||||
BACKUPS+=("$backup")
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
if [ ${#BACKUPS[@]} -eq 0 ]; then
|
||||
echo -e "${RED}Nenhum backup encontrado.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
read -p "Digite o numero do backup para restaurar: " NUM
|
||||
|
||||
if ! [[ "$NUM" =~ ^[0-9]+$ ]] || [ "$NUM" -lt 1 ] || [ "$NUM" -gt ${#BACKUPS[@]} ]; then
|
||||
echo -e "${RED}Numero invalido.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BACKUP="${BACKUPS[$((NUM - 1))]}"
|
||||
echo ""
|
||||
echo -e "Selecionado: ${YELLOW}$(basename "$BACKUP")${NC}"
|
||||
echo "Destino: $DEPLOY_PATH"
|
||||
read -p "Confirma? (s/N): " CONFIRM
|
||||
|
||||
if [ "$CONFIRM" != "s" ] && [ "$CONFIRM" != "S" ]; then
|
||||
echo "Cancelado."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${YELLOW}Fazendo backup do estado atual antes do rollback...${NC}"
|
||||
bash "$(dirname "$0")/backup.sh" "$AMBIENTE"
|
||||
|
||||
echo ""
|
||||
echo -e "${YELLOW}Restaurando backup...${NC}"
|
||||
rm -rf "${DEPLOY_PATH:?}"/*
|
||||
tar -xzf "$BACKUP" -C "$(dirname "$DEPLOY_PATH")" --strip-components=1
|
||||
|
||||
echo -e "${GREEN}Rollback concluido!${NC}"
|
||||
echo ""
|
||||
echo "Verifique: ${DEPLOY_PATH}"
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# Script de Status - Avanzato
|
||||
# ============================================================================
|
||||
# Uso: bash ci/scripts/status.sh
|
||||
#
|
||||
# Mostra status do site, ultimos deploys e backups.
|
||||
# ============================================================================
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
CONFIG_FILE="/var/ci/config.env"
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
source "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
DEPLOY_PATH="${DEPLOY_PATH:-/var/www/avanzato}"
|
||||
BACKUP_PATH="${BACKUP_PATH:-/var/ci/backups}"
|
||||
LOG_PATH="${LOG_PATH:-/var/ci/logs}"
|
||||
|
||||
echo -e "${BLUE}============================================================${NC}"
|
||||
echo -e "${BLUE} Avanzato - Status do Ambiente${NC}"
|
||||
echo -e "${BLUE}============================================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Status do site
|
||||
echo -e "${GREEN}SITUACAO DO SITE${NC}"
|
||||
echo " URL: ${SITE_URL:-https://avanzato.com.br}"
|
||||
echo " Caminho: $DEPLOY_PATH"
|
||||
echo " Tamanho: $(du -sh "$DEPLOY_PATH" 2>/dev/null | cut -f1 || echo 'N/A')"
|
||||
echo ""
|
||||
|
||||
# Status do Nginx
|
||||
echo -e "${GREEN}NGINX${NC}"
|
||||
systemctl is-active nginx 2>/dev/null && \
|
||||
echo -e " Status: ${GREEN}Ativo${NC}" || \
|
||||
echo -e " Status: ${RED}Inativo${NC}"
|
||||
echo ""
|
||||
|
||||
# Ultimo deploy
|
||||
echo -e "${GREEN}ULTIMOS DEPLOYS${NC}"
|
||||
if [ -f "${LOG_PATH}/last-deploy.log" ]; then
|
||||
echo " Log: ${LOG_PATH}/last-deploy.log"
|
||||
echo " Data: $(stat -c %y "${LOG_PATH}/last-deploy.log" | cut -d'.' -f1)"
|
||||
else
|
||||
echo -e " ${YELLOW}Nenhum deploy registrado.${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Backups
|
||||
echo -e "${GREEN}BACKUPS${NC}"
|
||||
echo " Caminho: $BACKUP_PATH"
|
||||
echo " Quantidade: $(ls "${BACKUP_PATH}"/*.tar.gz 2>/dev/null | wc -l)"
|
||||
echo " Tamanho total: $(du -sh "$BACKUP_PATH" 2>/dev/null | cut -f1 || echo '0')"
|
||||
echo ""
|
||||
|
||||
# Ultimos logs
|
||||
echo -e "${GREEN}ULTIMOS LOGS${NC}"
|
||||
ls -t "${LOG_PATH}"/*.log 2>/dev/null | head -5 | while read log; do
|
||||
echo " $(basename "$log") - $(stat -c %y "$log" | cut -d'.' -f1)"
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Assets preservados
|
||||
echo -e "${GREEN}ASSETS PRESERVADOS${NC}"
|
||||
echo " Caminho: ${ASSETS_PATH:-/var/ci/assets}"
|
||||
echo " Conteudo:"
|
||||
ls -la "${ASSETS_PATH:-/var/ci/assets}" 2>/dev/null || echo " (vazio)"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}============================================================${NC}"
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "postcss.config.js",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"registries": {}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export const Session = {
|
||||
cookieName: "kimi_sid",
|
||||
maxAgeMs: 365 * 24 * 60 * 60 * 1000,
|
||||
} as const;
|
||||
|
||||
export const ErrorMessages = {
|
||||
unauthenticated: "Authentication required",
|
||||
insufficientRole: "Insufficient permissions",
|
||||
} as const;
|
||||
|
||||
export const Paths = {
|
||||
login: "/login",
|
||||
oauthCallback: "/api/oauth/callback",
|
||||
} as const;
|
||||
@@ -0,0 +1,15 @@
|
||||
type AppError = { tag: "app_error"; status: number; message: string };
|
||||
|
||||
function appError(status: number, message: string): AppError {
|
||||
return { tag: "app_error", status, message };
|
||||
}
|
||||
|
||||
export const Errors = {
|
||||
badRequest: (msg: string) => appError(400, msg),
|
||||
unauthorized: (msg: string) => appError(401, msg),
|
||||
forbidden: (msg: string) => appError(403, msg),
|
||||
notFound: (msg: string) => appError(404, msg),
|
||||
internal: (msg: string) => appError(500, msg),
|
||||
} as const;
|
||||
|
||||
export type { AppError };
|
||||
@@ -0,0 +1,2 @@
|
||||
export type * from "../db/schema";
|
||||
export * from "./errors";
|
||||
@@ -0,0 +1 @@
|
||||
import {} from "./schema";
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
mysqlTable,
|
||||
mysqlEnum,
|
||||
serial,
|
||||
varchar,
|
||||
text,
|
||||
timestamp,
|
||||
// bigint,
|
||||
} from "drizzle-orm/mysql-core";
|
||||
|
||||
export const users = mysqlTable("users", {
|
||||
id: serial("id").primaryKey(),
|
||||
unionId: varchar("unionId", { length: 255 }).notNull().unique(),
|
||||
name: varchar("name", { length: 255 }),
|
||||
email: varchar("email", { length: 320 }),
|
||||
avatar: text("avatar"),
|
||||
role: mysqlEnum("role", ["user", "admin"]).default("user").notNull(),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt")
|
||||
.defaultNow()
|
||||
.notNull()
|
||||
.$onUpdate(() => new Date()),
|
||||
lastSignInAt: timestamp("lastSignInAt").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type User = typeof users.$inferSelect;
|
||||
export type InsertUser = typeof users.$inferInsert;
|
||||
|
||||
// ============================================================================
|
||||
// SISTEMA DE FORMULARIOS - Area do Cliente Avanzato
|
||||
// ============================================================================
|
||||
|
||||
// Formularios criados pela Avanzato para clientes
|
||||
export const clientForms = mysqlTable("client_forms", {
|
||||
id: serial("id").primaryKey(),
|
||||
title: varchar("title", { length: 255 }).notNull(),
|
||||
description: text("description"),
|
||||
clientName: varchar("clientName", { length: 255 }).notNull(),
|
||||
clientEmail: varchar("clientEmail", { length: 320 }),
|
||||
status: mysqlEnum("status", ["active", "inactive", "archived"]).default("inactive").notNull(),
|
||||
accessCode: varchar("access_code", { length: 32 }).notNull().unique(),
|
||||
createdBy: varchar("created_by", { length: 255 }),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at").defaultNow().notNull().$onUpdate(() => new Date()),
|
||||
});
|
||||
|
||||
export type ClientForm = typeof clientForms.$inferSelect;
|
||||
export type InsertClientForm = typeof clientForms.$inferInsert;
|
||||
|
||||
// Secoes de cada formulario
|
||||
export const formSections = mysqlTable("form_sections", {
|
||||
id: serial("id").primaryKey(),
|
||||
formId: varchar("form_id", { length: 32 }).notNull(),
|
||||
title: varchar("title", { length: 255 }).notNull(),
|
||||
description: text("description"),
|
||||
orderNum: serial("order_num"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type FormSection = typeof formSections.$inferSelect;
|
||||
|
||||
// Perguntas/Campos de cada secao
|
||||
export const formQuestions = mysqlTable("form_questions", {
|
||||
id: serial("id").primaryKey(),
|
||||
formId: varchar("form_id", { length: 32 }).notNull(),
|
||||
sectionId: varchar("section_id", { length: 32 }),
|
||||
question: text("question").notNull(),
|
||||
fieldType: mysqlEnum("field_type", ["text", "textarea", "number", "select", "multiselect", "date", "file"]).default("text").notNull(),
|
||||
options: text("options"), // JSON string para select/multiselect
|
||||
required: mysqlEnum("required", ["yes", "no"]).default("no").notNull(),
|
||||
orderNum: serial("order_num"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type FormQuestion = typeof formQuestions.$inferSelect;
|
||||
|
||||
// Respostas submetidas pelos clientes
|
||||
export const formAnswers = mysqlTable("form_answers", {
|
||||
id: serial("id").primaryKey(),
|
||||
formId: varchar("form_id", { length: 32 }).notNull(),
|
||||
questionId: varchar("question_id", { length: 32 }).notNull(),
|
||||
answer: text("answer"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type FormAnswer = typeof formAnswers.$inferSelect;
|
||||
|
||||
// Submissoes completas (uma por formulario preenchido)
|
||||
export const formSubmissions = mysqlTable("form_submissions", {
|
||||
id: serial("id").primaryKey(),
|
||||
formId: varchar("form_id", { length: 32 }).notNull(),
|
||||
submittedBy: varchar("submitted_by", { length: 255 }),
|
||||
submittedByEmail: varchar("submitted_by_email", { length: 320 }),
|
||||
notes: text("notes"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export type FormSubmission = typeof formSubmissions.$inferSelect;
|
||||
//
|
||||
// Example:
|
||||
// export const posts = mysqlTable("posts", {
|
||||
// id: serial("id").primaryKey(),
|
||||
// title: varchar("title", { length: 255 }).notNull(),
|
||||
// content: text("content"),
|
||||
// createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
// });
|
||||
//
|
||||
// Note: FK columns referencing a serial() PK must use:
|
||||
// bigint("columnName", { mode: "number", unsigned: true }).notNull()
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
// ============================================================================
|
||||
// SEED - Formulario UEA-PRODEM-ANGOLA
|
||||
// ============================================================================
|
||||
// Executar: npx tsx db/seed.ts
|
||||
// ============================================================================
|
||||
|
||||
import { getDb } from "../api/queries/connection";
|
||||
import { clientForms, formSections, formQuestions } from "./schema";
|
||||
|
||||
const FORM_ID = "uea-prodem-angola";
|
||||
|
||||
const sections = [
|
||||
{ id: "s1", title: "1. DADOS INSTITUCIONAIS", description: "Informacoes sobre a estrutura atual da UEA", order: 1 },
|
||||
{ id: "s2", title: "2. PROJECAO DE CRESCIMENTO", description: "Estimativas de crescimento para os proximos 36 meses", order: 2 },
|
||||
{ id: "s3", title: "3. REGISTO E VALIDACAO", description: "Processo de aprovacao e validacao de membros", order: 3 },
|
||||
{ id: "s4", title: "4. ESTRUTURA DE MEMBROS", description: "Categorias, niveis e beneficios", order: 4 },
|
||||
{ id: "s5", title: "5. PAGAMENTOS", description: "Plataformas e regras de faturacao", order: 5 },
|
||||
{ id: "s6", title: "6. INCUBADORA EMPRESARIAL", description: "Funil de evolucao e acompanhamento", order: 6 },
|
||||
{ id: "s7", title: "7. FORMACAO E CERTIFICACAO", description: "Cursos, conteudos e certificados", order: 7 },
|
||||
{ id: "s8", title: "8. MARKETPLACE B2B", description: "Produtos, servicos e intermedicao", order: 8 },
|
||||
{ id: "s9", title: "9. EVENTOS", description: "Gestao e transmissao de eventos", order: 9 },
|
||||
{ id: "s10", title: "10. BANCO CENTRAL DE DADOS (BCD)", description: "Objetivos, dados e dashboards", order: 10 },
|
||||
{ id: "s11", title: "11. DOCUMENTACAO LEGAL", description: "Documentos necessarios para o projeto", order: 11 },
|
||||
{ id: "s12", title: "12. INFRAESTRUTURA E HOSPEDAGEM", description: "Ambiente, cloud e suporte tecnico", order: 12 },
|
||||
{ id: "s13", title: "13. RELATORIOS EXECUTIVOS", description: "Relatorios para a Direcao", order: 13 },
|
||||
];
|
||||
|
||||
const questions = [
|
||||
// SECAO 1 - DADOS INSTITUCIONAIS
|
||||
{ sectionId: "s1", q: "1.1 Quantidade atual de membros individuais ativos", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s1", q: "1.2 Quantidade atual de empresas associadas", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s1", q: "1.3 Quantidade total de cadastros existentes", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s1", q: "1.4 Quantidade de membros pagantes", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s1", q: "1.5 Quantidade de membros inadimplentes", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s1", q: "1.6 Quantidade de provincias atualmente representadas", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s1", q: "1.7 Quantidade de colaboradores administrativos da UEA", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s1", q: "1.8 Quantidade de utilizadores internos que terao acesso administrativo ao sistema", type: "number" as const, required: "yes" as const },
|
||||
|
||||
// SECAO 2 - PROJECAO DE CRESCIMENTO
|
||||
{ sectionId: "s2", q: "2.1 Membros previstos para 12 meses", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s2", q: "2.1 Membros previstos para 24 meses", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s2", q: "2.1 Membros previstos para 36 meses", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s2", q: "2.2 Empresas previstas para 12 meses", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s2", q: "2.2 Empresas previstas para 24 meses", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s2", q: "2.2 Empresas previstas para 36 meses", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s2", q: "2.3 Numero medio de novos cadastros esperado por mes", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s2", q: "2.4 Numero medio de acessos simultaneos esperado durante eventos ou campanhas nacionais", type: "number" as const, required: "yes" as const },
|
||||
|
||||
// SECAO 3 - REGISTO E VALIDACAO
|
||||
{ sectionId: "s3", q: "3.1 Quem aprovara os novos membros?", type: "textarea" as const, required: "yes" as const },
|
||||
{ sectionId: "s3", q: "3.2 Existirao administradores provinciais?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s3", q: "3.3 Existirao administradores municipais?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s3", q: "3.4 O processo de validacao sera centralizado ou descentralizado?", type: "select" as const, options: JSON.stringify(["Centralizado", "Descentralizado", "Misto", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s3", q: "3.5 Quais documentos serao obrigatorios para cada categoria de membro?", type: "textarea" as const, required: "yes" as const },
|
||||
{ sectionId: "s3", q: "3.6 Sera necessaria assinatura digital dos documentos?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s3", q: "3.7 O certificado digital do membro devera possuir QR Code de validacao publica?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
|
||||
// SECAO 4 - ESTRUTURA DE MEMBROS
|
||||
{ sectionId: "s4", q: "4.1 Confirmar todas as categorias de membros", type: "textarea" as const, required: "yes" as const },
|
||||
{ sectionId: "s4", q: "4.2 Confirmar niveis de adesao: Bronze, Prata, Ouro, Platina - havera outros?", type: "textarea" as const, required: "yes" as const },
|
||||
{ sectionId: "s4", q: "4.3 Quais beneficios especificos existirao em cada categoria?", type: "textarea" as const, required: "yes" as const },
|
||||
{ sectionId: "s4", q: "4.4 Cada categoria tera valor de quota diferente?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s4", q: "4.5 O associado podera alterar sua categoria diretamente pela plataforma?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
|
||||
// SECAO 5 - PAGAMENTOS
|
||||
{ sectionId: "s5", q: "5.1 Quais plataformas de pagamento serao utilizadas? (Multicaixa Express, Referencia Bancaria, Transferencia, Cartao, Outras)", type: "multiselect" as const, options: JSON.stringify(["Multicaixa Express", "Referencia Bancaria", "Transferencia Bancaria", "Cartao", "Outras"]), required: "yes" as const },
|
||||
{ sectionId: "s5", q: "5.2 Existe fornecedor ja homologado para integracao de pagamentos?", type: "textarea" as const, required: "yes" as const },
|
||||
{ sectionId: "s5", q: "5.3 O sistema de faturacao ja foi definido?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "Em desenvolvimento"]), required: "yes" as const },
|
||||
{ sectionId: "s5", q: "5.4 Sera necessaria emissao automatica de: Faturas, Recibos, Declaracoes de pagamento?", type: "multiselect" as const, options: JSON.stringify(["Faturas", "Recibos", "Declaracoes de pagamento"]), required: "yes" as const },
|
||||
{ sectionId: "s5", q: "5.5 Qual sera a regra de bloqueio por inadimplencia?", type: "textarea" as const, required: "yes" as const },
|
||||
|
||||
// SECAO 6 - INCUBADORA EMPRESARIAL
|
||||
{ sectionId: "s6", q: "6.1 Quais etapas existirao no funil de evolucao das empresas? (Ideia, Planeamento, MVP, Operacao, Crescimento, Escala)", type: "multiselect" as const, options: JSON.stringify(["Ideia", "Planeamento", "MVP", "Operacao", "Crescimento", "Escala"]), required: "yes" as const },
|
||||
{ sectionId: "s6", q: "6.2 Quem acompanhara os incubados?", type: "textarea" as const, required: "yes" as const },
|
||||
{ sectionId: "s6", q: "6.3 Havera metas obrigatorias?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s6", q: "6.4 Havera indicadores de desempenho (KPIs)?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s6", q: "6.5 Quais relatorios deverao ser produzidos?", type: "textarea" as const, required: "yes" as const },
|
||||
|
||||
// SECAO 7 - FORMACAO E CERTIFICACAO
|
||||
{ sectionId: "s7", q: "7.1 Quantidade estimada de cursos", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s7", q: "7.2 Os cursos serao hospedados na propria plataforma ou em ambiente externo?", type: "select" as const, options: JSON.stringify(["Plataforma propria", "Ambiente externo", "Misto", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s7", q: "7.3 Quem produzira os conteudos?", type: "textarea" as const, required: "yes" as const },
|
||||
{ sectionId: "s7", q: "7.4 Os certificados possuirao validacao online?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s7", q: "7.5 Sera necessario historico academico dos membros?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
|
||||
// SECAO 8 - MARKETPLACE B2B
|
||||
{ sectionId: "s8", q: "8.1 Quais tipos de produtos e servicos poderao ser anunciados?", type: "textarea" as const, required: "yes" as const },
|
||||
{ sectionId: "s8", q: "8.2 Existira intermediacao financeira pela UEA?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s8", q: "8.3 Existirao avaliacoes dos fornecedores?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s8", q: "8.4 Existira sistema de mensagens entre membros?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
|
||||
// SECAO 9 - EVENTOS
|
||||
{ sectionId: "s9", q: "9.1 Quantidade media de eventos por ano", type: "number" as const, required: "yes" as const },
|
||||
{ sectionId: "s9", q: "9.2 Existira venda de ingressos?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s9", q: "9.3 Existira transmissao online?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s9", q: "9.4 Existira emissao de certificados por participacao?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
|
||||
// SECAO 10 - BANCO CENTRAL DE DADOS
|
||||
{ sectionId: "s10", q: "10.1 Qual o objetivo operacional do BCD?", type: "textarea" as const, required: "yes" as const },
|
||||
{ sectionId: "s10", q: "10.2 Quais dados deverao ser consolidados?", type: "textarea" as const, required: "yes" as const },
|
||||
{ sectionId: "s10", q: "10.3 Quem tera acesso aos dados?", type: "textarea" as const, required: "yes" as const },
|
||||
{ sectionId: "s10", q: "10.4 Existirao dashboards provinciais?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s10", q: "10.5 Existirao dashboards nacionais?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s10", q: "10.6 Quais indicadores deverao ser acompanhados em tempo real?", type: "textarea" as const, required: "yes" as const },
|
||||
|
||||
// SECAO 11 - DOCUMENTACAO LEGAL
|
||||
{ sectionId: "s11", q: "11.1 Estatuto completo da UEA - sera enviado?", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
|
||||
{ sectionId: "s11", q: "11.2 Certidao de Registo", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
|
||||
{ sectionId: "s11", q: "11.3 Regulamentos internos", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
|
||||
{ sectionId: "s11", q: "11.4 Organograma oficial", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
|
||||
{ sectionId: "s11", q: "11.5 Politica de privacidade", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
|
||||
{ sectionId: "s11", q: "11.6 Politica de protecao de dados", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
|
||||
{ sectionId: "s11", q: "11.7 Manual de identidade visual", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
|
||||
{ sectionId: "s11", q: "11.8 Logotipos oficiais em formato vetorial", type: "select" as const, options: JSON.stringify(["Sim, ja disponivel", "Sera enviado", "Nao disponivel"]), required: "no" as const },
|
||||
|
||||
// SECAO 12 - INFRAESTRUTURA
|
||||
{ sectionId: "s12", q: "12.1 O ambiente sera hospedado em Angola ou internacionalmente?", type: "select" as const, options: JSON.stringify(["Angola", "Internacional", "Hibrido", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s12", q: "12.2 Existe preferencia por provedor cloud? (AWS, Azure, Google Cloud, Digital Ocean, Infraestrutura propria)", type: "multiselect" as const, options: JSON.stringify(["AWS", "Azure", "Google Cloud", "Digital Ocean", "Infraestrutura propria", "A definir"]), required: "yes" as const },
|
||||
{ sectionId: "s12", q: "12.3 Qual o orcamento previsto para hospedagem mensal?", type: "text" as const, required: "no" as const },
|
||||
{ sectionId: "s12", q: "12.4 Existe equipa tecnica interna para suporte?", type: "select" as const, options: JSON.stringify(["Sim", "Nao", "Parcial"]), required: "yes" as const },
|
||||
|
||||
// SECAO 13 - RELATORIOS EXECUTIVOS
|
||||
{ sectionId: "s13", q: "13.1 Relatorios desejados: Novos associados por periodo", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
|
||||
{ sectionId: "s13", q: "13.2 Relatorios desejados: Associados por provincia", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
|
||||
{ sectionId: "s13", q: "13.3 Relatorios desejados: Empresas incubadas", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
|
||||
{ sectionId: "s13", q: "13.4 Relatorios desejados: Quotas recebidas", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
|
||||
{ sectionId: "s13", q: "13.5 Relatorios desejados: Quotas em atraso", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
|
||||
{ sectionId: "s13", q: "13.6 Relatorios desejados: Participacao em cursos", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
|
||||
{ sectionId: "s13", q: "13.7 Relatorios desejados: Participacao em eventos", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
|
||||
{ sectionId: "s13", q: "13.8 Relatorios desejados: Crescimento por sector economico", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
|
||||
{ sectionId: "s13", q: "13.9 Relatorios desejados: Crescimento por provincia", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
|
||||
{ sectionId: "s13", q: "13.10 Relatorios desejados: Indicadores de empregabilidade", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
|
||||
{ sectionId: "s13", q: "13.11 Relatorios desejados: Indicadores de impacto economico", type: "select" as const, options: JSON.stringify(["Sim", "Nao"]), required: "yes" as const },
|
||||
];
|
||||
|
||||
async function seed() {
|
||||
const db = getDb();
|
||||
console.log("[Seed] Inserindo formulario UEA-PRODEM-ANGOLA...");
|
||||
|
||||
// Criar formulario
|
||||
const code = Math.random().toString(36).substring(2, 10).toUpperCase();
|
||||
await db.insert(clientForms).values({
|
||||
id: 1,
|
||||
title: "Levantamento de Requisitos - Portal e Plataforma Digital UEA-PRODEM-ANGOLA",
|
||||
description: "Formulario completo para levantamento de requisitos do ecossistema digital composto por Portal Institucional, Gestao de Associados, BCD, Area Restrita, Sistema de Pagamento, Biblioteca Digital, Plataforma de Formacao, Sistema de Certificacao, Incubadora Empresarial, Gestao de Mentorias, Marketplace B2B, Dashboard Executivo, Gestao de Eventos e Relatorios Gerenciais.",
|
||||
clientName: "UEA-PRODEM-ANGOLA",
|
||||
clientEmail: "",
|
||||
status: "active",
|
||||
accessCode: code,
|
||||
createdBy: "Avanzato",
|
||||
});
|
||||
console.log(`[Seed] Formulario criado com codigo: ${code}`);
|
||||
|
||||
// Inserir secoes
|
||||
for (const s of sections) {
|
||||
await db.insert(formSections).values({
|
||||
formId: FORM_ID,
|
||||
title: s.title,
|
||||
description: s.description,
|
||||
});
|
||||
}
|
||||
console.log(`[Seed] ${sections.length} secoes inseridas`);
|
||||
|
||||
// Inserir perguntas
|
||||
let count = 0;
|
||||
for (const q of questions) {
|
||||
await db.insert(formQuestions).values({
|
||||
formId: FORM_ID,
|
||||
sectionId: q.sectionId,
|
||||
question: q.q,
|
||||
fieldType: q.type,
|
||||
options: q.options || null,
|
||||
required: q.required,
|
||||
});
|
||||
count++;
|
||||
}
|
||||
console.log(`[Seed] ${count} perguntas inseridas`);
|
||||
console.log("[Seed] Concluido! Acesse o formulario em: /formulario/" + code);
|
||||
}
|
||||
|
||||
seed().catch(console.error);
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+34
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+5
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{y as n,K as i,G as s,J as e,h as d,a as l,s as m,S as x}from"./index-DLMsdH5y.js";import{S as h}from"./SEO-BQEmBC0N.js";import{F as g}from"./file-text-etdcuywr.js";const u=[["path",{d:"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5",key:"1gvzjb"}],["path",{d:"M9 18h6",key:"x1upvd"}],["path",{d:"M10 22h4",key:"ceow96"}]],p=n("lightbulb",u);s.registerPlugin(x);const b=[{titulo:"Diagnóstico",descricao:"Análise completa da sua TI atual.",icon:m},{titulo:"Planejamento",descricao:"Roadmap estratégico tecnológico.",icon:p},{titulo:"Auditoria",descricao:"Avaliação de processos e infraestrutura.",icon:g}],v=()=>{const c=i.useRef(null),r=i.useRef(null);return i.useEffect(()=>{const t=s.context(()=>{const a=c.current?.querySelectorAll(".animate-item");a?.length&&s.fromTo(a,{y:50,opacity:0},{y:0,opacity:1,duration:.8,stagger:.1,ease:"expo.out"});const o=r.current?.querySelectorAll(".servico-card");o?.length&&s.fromTo(o,{y:50,opacity:0},{y:0,opacity:1,duration:.6,stagger:.1,ease:"back.out(1.7)",scrollTrigger:{trigger:r.current,start:"top 80%"}})});return()=>t.revert()},[]),e.jsxs("div",{className:"bg-[#0e0e0e] min-h-screen",children:[e.jsx(h,{title:"Consultoria em TI",description:"Consultoria estrategica em tecnologia: planejamento, gestao de projetos, outsourcing de TI. Diagnostico gratuito."}),e.jsxs("section",{ref:c,className:"pt-32 pb-20 relative overflow-hidden",children:[e.jsx("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"}),e.jsx("div",{className:"container-custom relative z-10",children:e.jsxs("div",{className:"grid lg:grid-cols-2 gap-12 items-center",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"animate-item flex items-center gap-2 mb-6",children:[e.jsx(d,{className:"w-5 h-5 text-[#cbf400]"}),e.jsx("span",{className:"text-[#cbf400] font-semibold",children:"Serviços"}),e.jsx("span",{className:"text-gray-500",children:"/"}),e.jsx("span",{className:"text-white",children:"Consultoria"})]}),e.jsxs("h1",{className:"animate-item text-4xl md:text-5xl lg:text-6xl font-black text-white mb-6",children:["Consultoria em ",e.jsx("span",{className:"text-gradient",children:"TI"})]}),e.jsx("p",{className:"animate-item text-xl text-gray-400 mb-8 leading-relaxed",children:"Planejamento estratégico tecnológico."}),e.jsx("div",{className:"animate-item flex flex-wrap gap-4",children:e.jsxs("a",{href:"https://api.whatsapp.com/send?phone=551148101704&text=Olá!%20Gostaria%20de%20consultoria",target:"_blank",rel:"noopener noreferrer",className:"btn-primary inline-flex items-center gap-2",children:["Agendar reunião",e.jsx(l,{className:"w-5 h-5"})]})})]}),e.jsxs("div",{className:"animate-item relative",children:[e.jsx("div",{className:"absolute -inset-4 bg-gradient-to-r from-[#cbf400]/20 to-transparent rounded-2xl blur-2xl"}),e.jsx("img",{src:"/about-image.jpg",alt:"Consultoria",className:"relative w-full h-auto animate-float"})]})]})})]}),e.jsx("section",{ref:r,className:"py-20 bg-[#1a1a1a]",children:e.jsxs("div",{className:"container-custom",children:[e.jsx("div",{className:"text-center mb-16",children:e.jsx("h2",{className:"text-3xl md:text-4xl font-black text-white mb-4",children:"Nossos Serviços"})}),e.jsx("div",{className:"grid md:grid-cols-3 gap-6",children:b.map((t,a)=>e.jsxs("div",{className:"servico-card bg-[#0e0e0e] rounded-2xl p-8 border border-white/5 hover:border-[#cbf400]/30 transition-colors text-center",children:[e.jsx("div",{className:"w-16 h-16 rounded-xl bg-[#cbf400]/10 flex items-center justify-center mx-auto mb-6 text-[#cbf400]",children:e.jsx(t.icon,{className:"w-8 h-8"})}),e.jsx("h4",{className:"text-xl font-bold text-white mb-3",children:t.titulo}),e.jsx("p",{className:"text-gray-400",children:t.descricao})]},a))})]})}),e.jsx("section",{className:"py-20 bg-[#0e0e0e]",children:e.jsx("div",{className:"container-custom",children:e.jsxs("div",{className:"bg-gradient-to-r from-[#cbf400]/10 to-transparent rounded-2xl p-12 text-center border border-[#cbf400]/20",children:[e.jsx("h2",{className:"text-3xl md:text-4xl font-black text-white mb-4",children:"Transforme sua TI"}),e.jsx("p",{className:"text-gray-400 mb-8",children:"Agende uma reunião estratégica."}),e.jsxs("a",{href:"https://api.whatsapp.com/send?phone=551148101704&text=Olá!%20Gostaria%20de%20reunião%20consultoria",target:"_blank",rel:"noopener noreferrer",className:"btn-primary inline-flex items-center gap-2",children:["Agendar reunião",e.jsx(l,{className:"w-5 h-5"})]})]})})})]})};export{v as default};
|
||||
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{J as a,M as o}from"./index-DLMsdH5y.js";import{F as i,c as r}from"./dnsApi-B1ISl2yG.js";import{S as s}from"./SEO-BQEmBC0N.js";import{a as t,c}from"./schema-B9cmWQ6T.js";import"./circle-check-big-BcmvPvF4.js";const e=[{q:"O que e DKIM?",a:"DKIM e uma assinatura digital criptografica que garante que um e-mail nao foi alterado durante o transporte e realmente veio do servidor autorizado."},{q:"Como ativar DKIM no Microsoft 365?",a:"No centro de administracao do Microsoft 365, va em Politicas de e-mail > DKIM. Selecione seu dominio e ative as assinaturas. Os selectors serao selector1 e selector2."},{q:"DKIM e obrigatorio?",a:"Nao e obrigatorio, mas altamente recomendado. Gmail, Microsoft e outros provedores priorizam e-mails com DKIM autenticado."},{q:"Quais selectors a ferramenta verifica?",a:"Verificamos os selectors mais comuns: default, google, selector1, selector2, mail e dkim."}],m=t(e),n=c([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"DKIM Checker",url:"https://avanzato.com.br/ferramentas/dkim-checker"}]),d={"@context":"https://schema.org","@type":"SoftwareApplication",name:"DKIM Checker",description:"Ferramenta gratuita para verificar configuracao DKIM de dominios.",url:"https://avanzato.com.br/ferramentas/dkim-checker",applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}},u=[m,n,d],g=()=>a.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[a.jsx(s,{title:"DKIM Checker Gratuito | Avanzato Tools",description:"Valide a assinatura digital DKIM do seu dominio. Garanta que seus e-mails sejam autenticos. Ferramenta gratuita da Avanzato.",keywords:"dkim checker, verificar dkim, domainkeys, assinatura digital email, autenticacao email",schema:u}),a.jsx(i,{nome:"DKIM Checker Gratuito",descricao:"Valide a assinatura digital DKIM do seu dominio para garantir a autenticidade dos e-mails enviados.",icone:a.jsx(o,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"DKIM (DomainKeys Identified Mail) e uma assinatura digital que seu servidor de e-mail adiciona a cada mensagem enviada. O destinatario pode verificar essa assinatura consultando uma chave publica no DNS do seu dominio. Sem DKIM, provedores de e-mail como Gmail e Outlook tem dificuldade para confirmar que a mensagem realmente veio do seu servidor, o que aumenta a chance de seus e-mails irem para a pasta de spam.",comoFunciona:["Digite o dominio que deseja verificar no campo acima.","Nossa ferramenta testa os selectors DKIM mais comuns.","Consultamos os registros TXT nos servidores DNS publicos.","Analisamos a chave publica e configuracao do DKIM.","Voce recebe um score e recomendacoes personalizadas."],perguntasUteis:e.slice(0,4),onConsultar:r})]});export{g as default};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{J as a}from"./index-DLMsdH5y.js";import{F as o,a as r}from"./dnsApi-B1ISl2yG.js";import{S as t}from"./SEO-BQEmBC0N.js";import{a as i,c as n}from"./schema-B9cmWQ6T.js";import{L as s}from"./lock-i-9JWRKi.js";import"./circle-check-big-BcmvPvF4.js";const e=[{q:"O que e DMARC?",a:"DMARC e um protocolo que instrui provedores de e-mail a rejeitar ou colocar em quarentena mensagens que nao passam na autenticacao SPF ou DKIM."},{q:"Qual politica DMARC devo usar?",a:"Comece com p=none para monitorar, depois mude para p=quarantine e finalmente p=reject para protecao maxima."},{q:"O que sao relatorios DMARC?",a:"Sao e-mails automaticos enviados por provedores (Google, Microsoft) informando quem esta tentando usar seu dominio para enviar e-mails."},{q:"DMARC sozinho protege contra phishing?",a:"Nao. DMARC funciona junto com SPF e DKIM. Os tres juntos formam a triade de seguranca de e-mail."}],c=i(e),m=n([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"DMARC Checker",url:"https://avanzato.com.br/ferramentas/dmarc-checker"}]),p={"@context":"https://schema.org","@type":"SoftwareApplication",name:"DMARC Checker",description:"Ferramenta gratuita para analisar politica DMARC de dominios.",url:"https://avanzato.com.br/ferramentas/dmarc-checker",applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}},u=[c,m,p],A=()=>a.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[a.jsx(t,{title:"DMARC Checker Gratuito | Avanzato Tools",description:"Analise a politica DMARC do seu dominio. Proteja contra phishing e spoofing. Ferramenta gratuita da Avanzato.",keywords:"dmarc checker, verificar dmarc, politica dmarc, protecao email, anti phishing",schema:u}),a.jsx(o,{nome:"DMARC Checker Gratuito",descricao:"Analise a politica DMARC do seu dominio e descubra se voce esta protegido contra phishing e spoofing.",icone:a.jsx(s,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"DMARC (Domain-based Message Authentication, Reporting, and Conformance) e um protocolo que instrui provedores de e-mail sobre como tratar mensagens que falham na autenticacao SPF ou DKIM. Com DMARC, voce pode instruir provedores a rejeitar e-mails falsos e ainda recebe relatorios sobre tentativas de uso nao autorizado do seu dominio.",comoFunciona:["Digite o dominio que deseja analisar no campo acima.","Consultamos o registro DMARC nos servidores DNS.","Analisamos a politica configurada (none, quarantine, reject).","Verificamos se voce esta recebendo relatorios (rua).","Receba recomendacoes para fortalecer sua protecao."],perguntasUteis:e.slice(0,4),onConsultar:r})]});export{A as default};
|
||||
+1
@@ -0,0 +1 @@
|
||||
import{J as a}from"./index-DLMsdH5y.js";import{F as o,b as r}from"./dnsApi-B1ISl2yG.js";import{S as i}from"./SEO-BQEmBC0N.js";import{a as c,c as m}from"./schema-B9cmWQ6T.js";import{C as t}from"./chart-column-D1WP4JxN.js";import"./circle-check-big-BcmvPvF4.js";const e=[{q:"O que o Email Security Score avalia?",a:"Avaliamos 4 componentes: SPF (autorizacao de servidores), DKIM (assinatura digital), DMARC (politica de conformidade) e MX (infraestrutura de e-mail)."},{q:"Qual score minimo recomendado?",a:"Recomendamos score minimo de 80. Abaixo de 70, sua empresa esta vulneravel a spoofing e phishing."},{q:"Como melhorar meu score?",a:"Comece corrigindo os componentes com nota mais baixa. Configurar DMARC com p=reject e o impacto mais significativo."},{q:"O score e atualizado em tempo real?",a:"Sim. Cada consulta verifica o estado atual dos seus registros DNS publicos."}],s=c(e),n=m([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"Email Security Score",url:"https://avanzato.com.br/ferramentas/email-security-score"}]),l={"@context":"https://schema.org","@type":"SoftwareApplication",name:"Email Security Score",description:"Ferramenta gratuita para avaliar seguranca completa de e-mail de dominios.",url:"https://avanzato.com.br/ferramentas/email-security-score",applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}},u=[s,n,l],v=()=>a.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[a.jsx(i,{title:"Email Security Score | Avanzato Tools",description:"Avalie a seguranca de e-mail do seu dominio. Score de 0 a 100 com recomendacoes. SPF, DKIM, DMARC e MX. Gratuito.",keywords:"email security score, seguranca email, score email, avaliar email, protecao email",schema:u}),a.jsx(o,{nome:"Email Security Score",descricao:"Avalie a seguranca completa do e-mail do seu dominio com um score de 0 a 100 e recomendacoes detalhadas.",icone:a.jsx(t,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"Nossa ferramenta Email Security Score analisa todos os aspectos da seguranca de e-mail do seu dominio em uma unica consulta: SPF, DKIM, DMARC e registros MX. Cada componente e avaliado individualmente e voce recebe um score global de 0 a 100, com recomendacoes praticas e priorizadas para melhorar a seguranca.",comoFunciona:["Digite o dominio que deseja avaliar no campo acima.","Analisamos SPF, DKIM, DMARC e MX simultaneamente.","Cada componente recebe uma nota de 0 a 100.","Calculamos o score global de seguranca de e-mail.","Voce recebe um plano de acao priorizado para melhorias."],perguntasUteis:e.slice(0,4),onConsultar:r})]});export{v as default};
|
||||
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{J as a,B as e}from"./index-DLMsdH5y.js";import{F as r,e as i}from"./dnsApi-B1ISl2yG.js";import{S as s}from"./SEO-BQEmBC0N.js";import{a as c,c as t}from"./schema-B9cmWQ6T.js";import"./circle-check-big-BcmvPvF4.js";const o=[{q:"A ferramenta funciona so para Microsoft 365?",a:"Sim, esta ferramenta e especifica para Microsoft 365. Para analise generica de e-mail, use nosso Email Security Score."},{q:"O que verifica no Microsoft 365?",a:"Verificamos: registros MX apontando para Microsoft, SPF com include:_spf.microsoft.com, DKIM ativado e DMARC configurado."},{q:"Como ativar DKIM no Microsoft 365?",a:"No Centro de Administracao > Seguranca > DKIM. Selecione o dominio e ative. Os selectors sao selector1 e selector2."},{q:"A Avanzato oferece suporte Microsoft 365?",a:"Sim. Somos especialistas em Microsoft 365 e oferecemos migracao, configuracao, suporte e seguranca."}],n=c(o),m=t([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"Microsoft 365 Analyzer",url:"https://avanzato.com.br/ferramentas/microsoft-365-analyzer"}]),f={"@context":"https://schema.org","@type":"SoftwareApplication",name:"Microsoft 365 Analyzer",description:"Ferramenta gratuita para analisar configuracao Microsoft 365 de dominios.",url:"https://avanzato.com.br/ferramentas/microsoft-365-analyzer",applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}},d=[n,m,f],h=()=>a.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[a.jsx(s,{title:"Microsoft 365 Analyzer | Avanzato Tools",description:"Analise a configuracao do Microsoft 365 do seu dominio. Verifique seguranca e otimizacao. Ferramenta gratuita da Avanzato.",keywords:"microsoft 365 analyzer, verificar microsoft 365, configuracao m365, seguranca microsoft 365",schema:d}),a.jsx(r,{nome:"Microsoft 365 Analyzer",descricao:"Analise a configuracao do Microsoft 365 do seu dominio e descubra oportunidades de melhoria e seguranca.",icone:a.jsx(e,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"O Microsoft 365 e a suite de produtividade mais usada por empresas no mundo, mas muitas configuracoes de seguranca ficam desativadas por padrao. Nossa ferramenta analisa se seu dominio esta corretamente configurado com o Microsoft 365, verifica SPF, DKIM e DMARC especificos para o ambiente Microsoft, e identifica oportunidades de melhoria.",comoFunciona:["Digite o dominio configurado no Microsoft 365.","Verificamos se os registros MX apontam para a Microsoft.","Analisamos SPF, DKIM e DMARC especificos para Microsoft 365.","Identificamos configuracoes de seguranca recomendadas.","Receba um relatorio com acoes priorizadas."],perguntasUteis:o.slice(0,4),onConsultar:i})]});export{h as default};
|
||||
+1
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{J as e,u as o}from"./index-DLMsdH5y.js";import{F as r,d as s}from"./dnsApi-B1ISl2yG.js";import{S as i}from"./SEO-BQEmBC0N.js";import{a as t,c as n}from"./schema-B9cmWQ6T.js";import"./circle-check-big-BcmvPvF4.js";const a=[{q:"O que sao registros MX?",a:"Registros MX sao entradas DNS que indicam quais servidores de e-mail recebem mensagens enviadas ao seu dominio."},{q:"Quantos servidores MX devo ter?",a:"Recomendamos pelo menos 2 servidores MX com prioridades diferentes para redundancia. Se o primeiro falhar, o segundo assume."},{q:"Como migrar e-mail para Microsoft 365?",a:"Voce precisa alterar os registros MX para apontar para os servidores Microsoft. A Avanzato pode fazer essa migracao sem perda de e-mails."},{q:"MX afeta envio de e-mails?",a:"Nao. Registros MX controlam apenas o recebimento. Para envio, use SPF, DKIM e DMARC."}],m=t(a),c=n([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"MX Lookup",url:"https://avanzato.com.br/ferramentas/mx-lookup"}]),d={"@context":"https://schema.org","@type":"SoftwareApplication",name:"MX Lookup",description:"Ferramenta gratuita para consultar registros MX de dominios.",url:"https://avanzato.com.br/ferramentas/mx-lookup",applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}},p=[m,c,d],M=()=>e.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[e.jsx(i,{title:"MX Lookup Gratuito | Avanzato Tools",description:"Consulte os registros MX do seu dominio. Verifique a infraestrutura de e-mail. Ferramenta gratuita da Avanzato.",keywords:"mx lookup, consultar mx, registros mx, servidor email, infraestrutura email",schema:p}),e.jsx(r,{nome:"MX Lookup Gratuito",descricao:"Consulte os registros MX do seu dominio e verifique a infraestrutura de recebimento de e-mails.",icone:e.jsx(o,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"Registros MX (Mail Exchange) definem quais servidores sao responsaveis por receber e-mails enviados ao seu dominio. Sem registros MX configurados corretamente, seu dominio nao pode receber e-mails. Empresas que migram para Microsoft 365 ou Google Workspace precisam atualizar seus registros MX para apontar para os servidores corretos.",comoFunciona:["Digite o dominio que deseja consultar no campo acima.","Nossa ferramenta consulta os registros MX nos servidores DNS.","Identificamos o provedor de e-mail (Microsoft 365, Google, etc).","Mostramos a prioridade de cada servidor MX.","Verificamos redundancia e configuracao."],perguntasUteis:a.slice(0,4),onConsultar:s})]});export{M as default};
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{J as e,L as s,p as r,a as o,P as t}from"./index-DLMsdH5y.js";import{S as i}from"./SEO-BQEmBC0N.js";import{o as n,c}from"./schema-B9cmWQ6T.js";const d=c([{name:"Home",url:"https://avanzato.com.br/"},{name:"Onde Atuamos",url:"https://avanzato.com.br/onde-atuamos"}]),l=[n,d],m=[{cidade:"Guarulhos",slug:"guarulhos",regiao:"Grande SP",desc:"Matriz da Avanzato. Atendimento completo com equipe local."},{cidade:"Mogi das Cruzes",slug:"mogi-das-cruzes",regiao:"Alto Tiete",desc:"Suporte tecnico para industrias e comercios da regiao."},{cidade:"Ipiranga",slug:"ipiranga",regiao:"Zona Sul SP",desc:"Atendimento a clinicas, escolas e comercios do bairro."},{cidade:"Vila Formosa",slug:"vila-formosa",regiao:"Zona Leste SP",desc:"Suporte TI para industrias e logistica da regiao leste."},{cidade:"Cerqueira Cesar",slug:"cerqueira-cesar",regiao:"Zona Oeste SP",desc:"Atendimento a escritorios e empresas de servicos."},{cidade:"Santana",slug:"santana",regiao:"Zona Norte SP",desc:"Suporte para comercios e prestadores de servicos do norte."},{cidade:"Bras",slug:"bras",regiao:"Centro SP",desc:"Atendimento a lojas e empresas do polo comercial do Bras."},{cidade:"Ribeirao Preto",slug:"ribeirao-preto",regiao:"Interior SP",desc:"Suporte remoto e visitas periodicas para empresas do interior."}],g=()=>e.jsxs("div",{className:"min-h-screen bg-[#0e0e0e]",children:[e.jsx(i,{title:"Onde Atuamos - Suporte de TI em Sao Paulo",description:"Avanzato Tecnologia atende em Guarulhos, Mogi das Cruzes, Ipiranga, Zona Leste, Zona Norte, Centro e Ribeirao Preto. Suporte presencial e remoto.",keywords:"suporte ti sao paulo, ti guarulhos, ti mogi das cruzes, terceirizacao ti sp",schema:l}),e.jsxs("div",{className:"max-w-7xl mx-auto px-6 py-16",children:[e.jsxs("nav",{className:"text-sm text-gray-400 mb-6",children:[e.jsx(s,{to:"/",className:"hover:text-[#cbf400]",children:"Home"}),e.jsx("span",{className:"mx-2",children:"/"}),e.jsx("span",{className:"text-white",children:"Onde Atuamos"})]}),e.jsxs("h1",{className:"text-4xl md:text-5xl font-bold text-white mb-4",children:["Onde a ",e.jsx("span",{className:"text-[#cbf400]",children:"Avanzato"})," Atua"]}),e.jsx("p",{className:"text-xl text-gray-300 mb-12 max-w-3xl",children:"Presente em toda a regiao metropolitana de Sao Paulo e interior. Atendimento presencial e remoto para empresas de todos os portes."}),e.jsx("div",{className:"grid md:grid-cols-2 lg:grid-cols-4 gap-6 mb-16",children:m.map(a=>e.jsxs(s,{to:`/suporte-ti-${a.slug}`,className:"bg-[#1a1a1a] rounded-2xl p-6 border border-[#2a2a2a] hover:border-[#cbf400]/50 transition-all group",children:[e.jsx(r,{className:"w-8 h-8 text-[#cbf400] mb-4"}),e.jsx("h3",{className:"text-xl font-semibold text-white mb-1 group-hover:text-[#cbf400] transition-colors",children:a.cidade}),e.jsx("p",{className:"text-sm text-gray-500 mb-2",children:a.regiao}),e.jsx("p",{className:"text-gray-400 text-sm",children:a.desc}),e.jsxs("div",{className:"mt-4 flex items-center gap-2 text-[#cbf400] text-sm font-medium",children:["Ver servicos ",e.jsx(o,{className:"w-4 h-4"})]})]},a.slug))}),e.jsxs("div",{className:"bg-gradient-to-r from-[#cbf400]/10 to-transparent rounded-2xl p-8 border border-[#cbf400]/30 text-center",children:[e.jsx("h2",{className:"text-3xl font-bold text-white mb-4",children:"Sua cidade nao esta na lista?"}),e.jsx("p",{className:"text-gray-300 mb-6 max-w-2xl mx-auto",children:"Atendemos toda a Grande Sao Paulo e interior com suporte remoto e visitas periodicas. Entre em contato para verificar disponibilidade na sua regiao."}),e.jsxs("div",{className:"flex flex-col sm:flex-row gap-4 justify-center",children:[e.jsxs("a",{href:"https://wa.me/551148101704",target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-2 bg-[#cbf400] text-[#0e0e0e] px-8 py-4 rounded-xl font-semibold hover:bg-[#b8e000] transition-colors",children:[e.jsx(t,{className:"w-5 h-5"}),"Falar no WhatsApp"]}),e.jsx(s,{to:"/contato",className:"inline-flex items-center gap-2 border border-[#cbf400] text-[#cbf400] px-8 py-4 rounded-xl font-semibold hover:bg-[#cbf400]/10 transition-colors",children:"Entrar em Contato"})]})]})]})]});export{g as default};
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
import{K as a,G as r,J as e,v as t,M as d}from"./index-DLMsdH5y.js";import{S as c}from"./SEO-BQEmBC0N.js";import{F as n}from"./file-text-etdcuywr.js";import{U as l}from"./user-D1fYDUoc.js";import{E as m}from"./eye-CsU633qe.js";import{L as u}from"./lock-i-9JWRKi.js";import{S as p}from"./share-2-B1apKnnQ.js";const x=[{icon:e.jsx(n,{className:"w-6 h-6"}),title:"1. Introdução",content:`
|
||||
A Avanzato Tecnologia valoriza a privacidade de seus usuários e está comprometida em proteger
|
||||
as informações pessoais coletadas. Esta Política de Privacidade descreve como coletamos,
|
||||
usamos, armazenamos e protegemos seus dados.
|
||||
|
||||
Ao utilizar nossos serviços, você concorda com os termos desta política. Recomendamos a
|
||||
leitura atenta deste documento.
|
||||
`},{icon:e.jsx(l,{className:"w-6 h-6"}),title:"2. Dados Coletados",content:`
|
||||
Podemos coletar os seguintes tipos de informações:
|
||||
|
||||
• Dados de identificação: nome, email, telefone, CNPJ
|
||||
• Dados de navegação: endereço IP, cookies, páginas visitadas
|
||||
• Dados de uso: informações sobre como você utiliza nossos serviços
|
||||
• Dados técnicos: informações sobre seus dispositivos e sistemas
|
||||
|
||||
A coleta ocorre quando você:
|
||||
- Preenche formulários em nosso site
|
||||
- Entra em contato conosco
|
||||
- Contrata nossos serviços
|
||||
- Navega em nosso site
|
||||
`},{icon:e.jsx(m,{className:"w-6 h-6"}),title:"3. Uso das Informações",content:`
|
||||
Utilizamos seus dados para:
|
||||
|
||||
• Fornecer e melhorar nossos serviços
|
||||
• Responder solicitações e dúvidas
|
||||
• Enviar comunicações sobre produtos e serviços
|
||||
• Cumprir obrigações legais e regulatórias
|
||||
• Prevenir fraudes e garantir segurança
|
||||
• Realizar análises e pesquisas de mercado
|
||||
|
||||
Não utilizamos seus dados para fins não relacionados aos nossos serviços
|
||||
sem seu consentimento explícito.
|
||||
`},{icon:e.jsx(u,{className:"w-6 h-6"}),title:"4. Proteção de Dados",content:`
|
||||
Implementamos medidas de segurança técnicas e organizacionais para proteger
|
||||
suas informações:
|
||||
|
||||
• Criptografia de dados em trânsito e em repouso
|
||||
• Firewalls e sistemas de detecção de intrusão
|
||||
• Controles de acesso rigorosos
|
||||
• Treinamento de colaboradores em segurança
|
||||
• Auditorias regulares de segurança
|
||||
• Backups automatizados e seguros
|
||||
|
||||
Apesar de todos os esforços, nenhum sistema é 100% seguro. Em caso de
|
||||
incidente de segurança, notificaremos você conforme exigido por lei.
|
||||
`},{icon:e.jsx(p,{className:"w-6 h-6"}),title:"5. Compartilhamento de Dados",content:`
|
||||
Podemos compartilhar seus dados com:
|
||||
|
||||
• Prestadores de serviços: empresas que nos auxiliam na operação
|
||||
• Autoridades legais: quando exigido por lei ou ordem judicial
|
||||
• Parceiros comerciais: com seu consentimento explícito
|
||||
|
||||
Não vendemos seus dados pessoais a terceiros. Qualquer compartilhamento
|
||||
é feito com garantias de proteção e conformidade com a LGPD.
|
||||
`},{icon:e.jsx(t,{className:"w-6 h-6"}),title:"6. Seus Direitos (LGPD)",content:`
|
||||
De acordo com a Lei Geral de Proteção de Dados (LGPD), você tem direito a:
|
||||
|
||||
• Acessar seus dados pessoais
|
||||
• Corrigir dados incompletos ou desatualizados
|
||||
• Solicitar a exclusão de seus dados
|
||||
• Revogar seu consentimento
|
||||
• Solicitar a portabilidade dos dados
|
||||
• Opor-se ao processamento de dados
|
||||
• Solicitar informações sobre o compartilhamento
|
||||
|
||||
Para exercer seus direitos, entre em contato pelo email:
|
||||
privacidade@avanzato.com.br
|
||||
`},{icon:e.jsx(d,{className:"w-6 h-6"}),title:"7. Contato",content:`
|
||||
Para questões relacionadas à privacidade e proteção de dados:
|
||||
|
||||
Email: privacidade@avanzato.com.br
|
||||
Telefone: (11) 4810-1704
|
||||
Endereço: Av. Dr. Carlos de Campos, 43 - Guarulhos/SP
|
||||
|
||||
Nosso Encarregado de Proteção de Dados (DPO) está disponível para
|
||||
atender suas solicitações.
|
||||
`}],j=()=>{const s=a.useRef(null);return a.useEffect(()=>{s.current&&r.fromTo(s.current.querySelectorAll(".section-item"),{y:30,opacity:0},{y:0,opacity:1,duration:.5,stagger:.1,ease:"expo.out"})},[]),e.jsxs("div",{className:"bg-[#0e0e0e] min-h-screen",children:[e.jsx(c,{title:"Politica de Privacidade",description:"Politica de Privacidade da Avanzato Tecnologia. Conformidade com a LGPD. Protecao de dados pessoais.",noindex:!0}),e.jsxs("section",{className:"pt-32 pb-20 relative overflow-hidden",children:[e.jsx("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"}),e.jsx("div",{className:"container-custom relative z-10",children:e.jsxs("div",{className:"text-center max-w-3xl mx-auto",children:[e.jsx("span",{className:"inline-block px-4 py-2 rounded-full bg-[#cbf400]/10 text-[#cbf400] text-sm font-semibold mb-6 border border-[#cbf400]/20",children:"Legal"}),e.jsxs("h1",{className:"text-4xl md:text-5xl lg:text-6xl font-black text-white mb-6",children:["Política de ",e.jsx("span",{className:"text-gradient",children:"Privacidade"})]}),e.jsx("p",{className:"text-xl text-gray-400",children:"Última atualização: 15 de Janeiro de 2024"})]})})]}),e.jsx("section",{className:"py-20",children:e.jsx("div",{className:"container-custom",children:e.jsxs("div",{className:"max-w-4xl mx-auto",children:[e.jsx("div",{ref:s,className:"space-y-8",children:x.map((o,i)=>e.jsxs("div",{className:"section-item bg-[#1a1a1a] rounded-2xl p-8 border border-white/5",children:[e.jsxs("div",{className:"flex items-center gap-4 mb-6",children:[e.jsx("div",{className:"w-12 h-12 rounded-xl bg-[#cbf400]/10 flex items-center justify-center text-[#cbf400]",children:o.icon}),e.jsx("h2",{className:"text-xl font-bold text-white",children:o.title})]}),e.jsx("div",{className:"text-gray-400 whitespace-pre-line leading-relaxed",children:o.content})]},i))}),e.jsx("div",{className:"mt-12 p-6 bg-[#cbf400]/5 rounded-xl border border-[#cbf400]/20 text-center",children:e.jsx("p",{className:"text-gray-400",children:"Ao utilizar nossos serviços, você concorda com esta Política de Privacidade. Para mais informações, entre em contato conosco."})})]})})})]})};export{j as default};
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{V as j,J as t,m as g,E as u}from"./index-DLMsdH5y.js";const d="https://avanzato.com.br",y="Avanzato Tecnologia | TI Gerenciada, Cloud e Seguranca",a="Solucoes em TI Gerenciada, Cloud Computing e Seguranca da Informacao. +23 anos de experiencia, +500 clientes atendidos. Suporte 24/7 em Guarulhos e regiao.",m="https://avanzato.com.br/og-image.jpg";function _({title:r,description:e,keywords:s,ogImage:c,ogType:p="website",noindex:l=!1,schema:i}){const x=j().pathname,n=`${d}${x}`,o=r?`${r} | Avanzato`:y;return t.jsxs(g,{children:[t.jsx("title",{children:o}),t.jsx("meta",{name:"title",content:o}),e&&t.jsx("meta",{name:"description",content:e}),!e&&t.jsx("meta",{name:"description",content:a}),s&&t.jsx("meta",{name:"keywords",content:s}),t.jsx("link",{rel:"canonical",href:n}),l||!u.allowIndexing?t.jsx("meta",{name:"robots",content:"noindex, nofollow"}):t.jsx("meta",{name:"robots",content:"index, follow"}),t.jsx("meta",{property:"og:type",content:p}),t.jsx("meta",{property:"og:url",content:n}),t.jsx("meta",{property:"og:title",content:o}),t.jsx("meta",{property:"og:description",content:e||a}),t.jsx("meta",{property:"og:image",content:c||m}),t.jsx("meta",{property:"og:locale",content:"pt_BR"}),t.jsx("meta",{property:"og:site_name",content:"Avanzato Tecnologia"}),t.jsx("meta",{property:"twitter:card",content:"summary_large_image"}),t.jsx("meta",{property:"twitter:url",content:n}),t.jsx("meta",{property:"twitter:title",content:o}),t.jsx("meta",{property:"twitter:description",content:e||a}),t.jsx("meta",{property:"twitter:image",content:c||m}),i&&t.jsx("script",{type:"application/ld+json",children:JSON.stringify(i)})]})}export{_ as S};
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{J as e,v as s}from"./index-DLMsdH5y.js";import{F as t,f as n}from"./dnsApi-B1ISl2yG.js";import{S as c}from"./SEO-BQEmBC0N.js";import{a as m,c as u}from"./schema-B9cmWQ6T.js";import"./circle-check-big-BcmvPvF4.js";const a=[{q:"O que e SPF?",a:"SPF (Sender Policy Framework) e um registro DNS que define quais servidores de e-mail estao autorizados a enviar mensagens em nome do seu dominio. Ele ajuda a prevenir spoofing e phishing."},{q:"Por que o SPF e importante para empresas?",a:"Sem SPF, qualquer pessoa pode enviar e-mails falsos usando o nome do seu dominio. Isso facilita ataques de phishing que usam sua marca para enganar clientes e funcionarios."},{q:"Como configurar SPF no Microsoft 365?",a:"Adicione um registro TXT no DNS com: v=spf1 include:_spf.microsoft.com ~all. Depois de testar, mude ~all para -all para protecao total."},{q:"Qual a diferenca entre ~all e -all?",a:"~all (softfail) marca e-mails nao autorizados como suspeitos. -all (hardfail) instrui provedores a rejeitar e-mails de servidores nao listados."},{q:"Quantos includes posso ter no SPF?",a:"O SPF permite no maximo 10 lookups DNS. Mais que isso gera erro e o SPF nao funciona. Use mecanismos ip4/ip6 quando possivel para reduzir lookups."},{q:"O SPF sozinho e suficiente?",a:"Nao. SPF deve ser usado junto com DKIM e DMARC para protecao completa contra spoofing e phishing. Recomendamos usar nossa ferramenta Email Security Score para verificar tudo."}],p=m(a),l=u([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"SPF Checker",url:"https://avanzato.com.br/ferramentas/spf-checker"}]),d=S("SPF Checker","Ferramenta gratuita para verificar configuracao SPF de dominios.","https://avanzato.com.br/ferramentas/spf-checker"),f=[p,l,d],P=()=>e.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[e.jsx(c,{title:"SPF Checker Gratuito | Avanzato Tools",description:"Verifique se o SPF do seu dominio esta configurado corretamente. Proteja contra spoofing e phishing. Ferramenta gratuita da Avanzato.",keywords:"spf checker, verificar spf, configurar spf, sender policy framework, seguranca email",schema:f}),e.jsx(t,{nome:"SPF Checker Gratuito",descricao:"Verifique se o dominio da sua empresa esta protegido contra falsificacao de e-mails.",icone:e.jsx(s,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"O SPF (Sender Policy Framework) e um registro DNS que lista os servidores autorizados a enviar e-mails em nome do seu dominio. Quando um e-mail chega ao destinatario, o servidor dele consulta o SPF do seu dominio para verificar se o remetente e legitimo. Sem SPF, qualquer servidor pode enviar e-mails como se fosse voce, facilitando phishing e spoofing. Empresas que usam Microsoft 365, Google Workspace ou qualquer servico de e-mail devem ter SPF configurado corretamente.",comoFunciona:["Digite o dominio que deseja verificar no campo acima.","Nossa ferramenta consulta os servidores DNS publicos em tempo real.","Analisamos o registro SPF, mecanismos e politica de falha.","Voce recebe um score de 0 a 100 com recomendacoes praticas.","Cadastre-se gratuitamente para receber um relatorio completo por e-mail."],perguntasUteis:a.slice(0,4),onConsultar:n})]});function S(o,r,i){return{"@context":"https://schema.org","@type":"SoftwareApplication",name:o,description:r,url:i,applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}}}export{P as default};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{J as e}from"./index-DLMsdH5y.js";import{F as o,g as r}from"./dnsApi-B1ISl2yG.js";import{S as s}from"./SEO-BQEmBC0N.js";import{a as t,c as i}from"./schema-B9cmWQ6T.js";import{L as c}from"./lock-i-9JWRKi.js";import"./circle-check-big-BcmvPvF4.js";const a=[{q:"O que e SSL/TLS?",a:"SSL e um protocolo de criptografia que protege a comunicacao entre navegador e servidor. TLS e a versao mais moderna e segura."},{q:"Meu site precisa de SSL?",a:"Sim. Todo site precisa de SSL. O Google penaliza sites sem HTTPS e navegadores exibem aviso de Nao Seguro."},{q:"SSL e gratuito?",a:"Sim. Let's Encrypt oferece certificados SSL gratuitos com renovacao automatica."},{q:"O que acontece se o SSL expirar?",a:"Seu site ficara inacessivel ou mostrara aviso de seguranca. E crucial renovar antes da expiracao."}],n=t(a),m=i([{name:"Home",url:"https://avanzato.com.br/"},{name:"Ferramentas",url:"https://avanzato.com.br/ferramentas"},{name:"SSL Checker",url:"https://avanzato.com.br/ferramentas/ssl-checker"}]),d={"@context":"https://schema.org","@type":"SoftwareApplication",name:"SSL Checker",description:"Ferramenta gratuita para verificar certificados SSL de sites.",url:"https://avanzato.com.br/ferramentas/ssl-checker",applicationCategory:"SecurityApplication",operatingSystem:"Web",offers:{"@type":"Offer",price:"0",priceCurrency:"BRL"},author:{"@type":"Organization",name:"Avanzato Tecnologia",url:"https://avanzato.com.br"}},u=[n,m,d],h=()=>e.jsxs("div",{className:"min-h-screen bg-[#0e0e0e] pt-16 pb-8",children:[e.jsx(s,{title:"SSL Checker Gratuito | Avanzato Tools",description:"Verifique a validade do certificado SSL do seu site. Garanta seguranca e confianca. Ferramenta gratuita da Avanzato.",keywords:"ssl checker, verificar ssl, certificado ssl, seguranca site, https",schema:u}),e.jsx(o,{nome:"SSL Checker Gratuito",descricao:"Verifique a validade e configuracao do certificado SSL do seu site. Garanta seguranca para seus visitantes.",icone:e.jsx(c,{}),placeholder:"Digite seu dominio (ex: avanzato.com.br)",explicacao:"Um certificado SSL criptografa a comunicacao entre o navegador do visitante e seu servidor web. Sem SSL, dados sensiveis como senhas, cartoes de credito e informacoes pessoais transitam em texto puro pela internet. Alem disso, o Google penaliza sites sem SSL nos resultados de busca e navegadores exibem aviso de Nao Seguro.",comoFunciona:["Digite o dominio do site que deseja verificar.","Nossa ferramenta consulta o banco de dados de certificados publicos.","Verificamos o emissor, validade e configuracao do certificado.","Calculamos quantos dias faltam para a expiracao.","Receba alertas e recomendacoes de seguranca."],perguntasUteis:a.slice(0,4),onConsultar:r})]});export{h as default};
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+68
@@ -0,0 +1,68 @@
|
||||
import{y as i,K as a,G as t,J as e}from"./index-DLMsdH5y.js";import{S as c}from"./SEO-BQEmBC0N.js";import{F as d}from"./file-text-etdcuywr.js";import{C as n}from"./circle-check-big-BcmvPvF4.js";import{C as l}from"./circle-alert-CGKZzOIC.js";import{S as m}from"./scale-DoSnSg-M.js";import{R as p}from"./refresh-cw-Cbsf85Yz.js";const u=[["path",{d:"m14 13-8.381 8.38a1 1 0 0 1-3.001-3l8.384-8.381",key:"pgg06f"}],["path",{d:"m16 16 6-6",key:"vzrcl6"}],["path",{d:"m21.5 10.5-8-8",key:"a17d9x"}],["path",{d:"m8 8 6-6",key:"18bi4p"}],["path",{d:"m8.5 7.5 8 8",key:"1oyaui"}]],x=i("gavel",u),v=[{icon:e.jsx(d,{className:"w-6 h-6"}),title:"1. Aceitação dos Termos",content:`
|
||||
Ao acessar e utilizar o site e os serviços da Avanzato Tecnologia, você concorda
|
||||
em cumprir e estar vinculado a estes Termos de Uso. Se você não concordar com
|
||||
qualquer parte destes termos, não deverá utilizar nossos serviços.
|
||||
|
||||
Reservamo-nos o direito de modificar estes termos a qualquer momento. As alterações
|
||||
entrarão em vigor imediatamente após a publicação no site. O uso continuado dos
|
||||
serviços após as modificações constitui aceitação dos novos termos.
|
||||
`},{icon:e.jsx(n,{className:"w-6 h-6"}),title:"2. Descrição dos Serviços",content:`
|
||||
A Avanzato Tecnologia oferece serviços de:
|
||||
|
||||
• TI Gerenciada (Managed IT Services)
|
||||
• Cloud Computing e Migração para Nuvem
|
||||
• Segurança da Informação
|
||||
• Suporte Técnico
|
||||
• Consultoria em TI
|
||||
|
||||
Os serviços são prestados conforme contrato específico firmado com cada cliente,
|
||||
incluindo escopo, SLAs (Service Level Agreements) e condições comerciais.
|
||||
`},{icon:e.jsx(l,{className:"w-6 h-6"}),title:"3. Obrigações do Cliente",content:`
|
||||
O cliente se compromete a:
|
||||
|
||||
• Fornecer informações verdadeiras e atualizadas
|
||||
• Manter a confidencialidade de credenciais de acesso
|
||||
• Utilizar os serviços de forma ética e legal
|
||||
• Notificar prontamente sobre qualquer problema ou incidente
|
||||
• Efetuar pagamentos conforme acordo comercial
|
||||
• Cooperar com a equipe técnica durante implementações e suporte
|
||||
|
||||
O cliente é responsável por manter backups de dados críticos, exceto quando
|
||||
o serviço de backup estiver explicitamente contratado.
|
||||
`},{icon:e.jsx(m,{className:"w-6 h-6"}),title:"4. Limitação de Responsabilidade",content:`
|
||||
A Avanzato Tecnologia se esforça para garantir a qualidade e disponibilidade
|
||||
dos serviços, porém:
|
||||
|
||||
• Não nos responsabilizamos por perdas de dados não cobertas por contrato de backup
|
||||
• Não garantimos que o site estará livre de erros ou interrupções
|
||||
• Não somos responsáveis por danos indiretos ou consequenciais
|
||||
• Nossa responsabilidade está limitada ao valor pago pelos serviços no período
|
||||
|
||||
O cliente reconhece que a segurança da informação é responsabilidade compartilhada
|
||||
e que deve adotar boas práticas de segurança.
|
||||
`},{icon:e.jsx(x,{className:"w-6 h-6"}),title:"5. Propriedade Intelectual",content:`
|
||||
Todo o conteúdo do site, incluindo textos, imagens, logotipos, software e
|
||||
materiais, é propriedade da Avanzato Tecnologia ou de seus licenciadores e
|
||||
está protegido por leis de direitos autorais.
|
||||
|
||||
É proibido:
|
||||
• Copiar, reproduzir ou distribuir o conteúdo sem autorização
|
||||
• Modificar ou criar trabalhos derivados
|
||||
• Utilizar nossos logotipos sem permissão expressa
|
||||
• Realizar engenharia reversa de nossos sistemas
|
||||
|
||||
O cliente mantém a propriedade de seus dados e informações.
|
||||
`},{icon:e.jsx(p,{className:"w-6 h-6"}),title:"6. Rescisão",content:`
|
||||
O contrato de prestação de serviços pode ser rescindido:
|
||||
|
||||
• Pelo cliente: mediante aviso prévio conforme cláusula contratual
|
||||
• Pela Avanzato: em caso de violação dos termos ou inadimplência
|
||||
• Por comum acordo: a qualquer momento
|
||||
|
||||
Em caso de rescisão:
|
||||
• Os dados do cliente serão devolvidos ou excluídos conforme solicitação
|
||||
• O cliente deve quitar valores pendentes
|
||||
• Cláusulas de confidencialidade permanecem em vigor
|
||||
|
||||
O prazo para devolução de dados é de até 30 dias após a rescisão.
|
||||
`}],z=()=>{const o=a.useRef(null);return a.useEffect(()=>{o.current&&t.fromTo(o.current.querySelectorAll(".section-item"),{y:30,opacity:0},{y:0,opacity:1,duration:.5,stagger:.1,ease:"expo.out"})},[]),e.jsxs("div",{className:"bg-[#0e0e0e] min-h-screen",children:[e.jsx(c,{title:"Termos de Uso",description:"Termos de Uso do site da Avanzato Tecnologia. Condicoes de acesso e utilizacao dos servicos.",noindex:!0}),e.jsxs("section",{className:"pt-32 pb-20 relative overflow-hidden",children:[e.jsx("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"}),e.jsx("div",{className:"container-custom relative z-10",children:e.jsxs("div",{className:"text-center max-w-3xl mx-auto",children:[e.jsx("span",{className:"inline-block px-4 py-2 rounded-full bg-[#cbf400]/10 text-[#cbf400] text-sm font-semibold mb-6 border border-[#cbf400]/20",children:"Legal"}),e.jsxs("h1",{className:"text-4xl md:text-5xl lg:text-6xl font-black text-white mb-6",children:["Termos de ",e.jsx("span",{className:"text-gradient",children:"Uso"})]}),e.jsx("p",{className:"text-xl text-gray-400",children:"Última atualização: 15 de Janeiro de 2024"})]})})]}),e.jsx("section",{className:"py-20",children:e.jsx("div",{className:"container-custom",children:e.jsxs("div",{className:"max-w-4xl mx-auto",children:[e.jsx("div",{ref:o,className:"space-y-8",children:v.map((s,r)=>e.jsxs("div",{className:"section-item bg-[#1a1a1a] rounded-2xl p-8 border border-white/5",children:[e.jsxs("div",{className:"flex items-center gap-4 mb-6",children:[e.jsx("div",{className:"w-12 h-12 rounded-xl bg-[#cbf400]/10 flex items-center justify-center text-[#cbf400]",children:s.icon}),e.jsx("h2",{className:"text-xl font-bold text-white",children:s.title})]}),e.jsx("div",{className:"text-gray-400 whitespace-pre-line leading-relaxed",children:s.content})]},r))}),e.jsxs("div",{className:"mt-12 space-y-6",children:[e.jsxs("div",{className:"p-6 bg-[#1a1a1a] rounded-xl border border-white/5",children:[e.jsx("h3",{className:"text-lg font-bold text-white mb-3",children:"Lei Aplicável"}),e.jsx("p",{className:"text-gray-400",children:"Estes termos são regidos pelas leis da República Federativa do Brasil. Qualquer disputa será resolvida nos tribunais de Guarulhos/SP."})]}),e.jsxs("div",{className:"p-6 bg-[#1a1a1a] rounded-xl border border-white/5",children:[e.jsx("h3",{className:"text-lg font-bold text-white mb-3",children:"Contato"}),e.jsxs("p",{className:"text-gray-400",children:["Para questões sobre estes termos, entre em contato:",e.jsx("br",{}),"Email: juridico@avanzato.com.br",e.jsx("br",{}),"Telefone: (11) 4810-1704"]})]})]}),e.jsx("div",{className:"mt-12 p-6 bg-[#cbf400]/5 rounded-xl border border-[#cbf400]/20 text-center",children:e.jsx("p",{className:"text-gray-400",children:"Ao utilizar nossos serviços, você confirma que leu, entendeu e concorda com estes Termos de Uso."})})]})})})]})};export{z as default};
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{y as e}from"./index-DLMsdH5y.js";const t=[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"18",height:"18",x:"3",y:"4",rx:"2",key:"1hopcy"}],["path",{d:"M3 10h18",key:"8toen8"}]],c=e("calendar",t);export{c as C};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{y as a}from"./index-DLMsdH5y.js";const c=[["path",{d:"M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z",key:"18u6gg"}],["circle",{cx:"12",cy:"13",r:"3",key:"1vg3eu"}]],o=a("camera",c);export{o as C};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{y as t}from"./index-DLMsdH5y.js";const a=[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16",key:"c24i48"}],["path",{d:"M18 17V9",key:"2bz60n"}],["path",{d:"M13 17V5",key:"1frdt8"}],["path",{d:"M8 17v-3",key:"17ska0"}]],c=t("chart-column",a);export{c as C};
|
||||
+1
@@ -0,0 +1 @@
|
||||
import{y as o}from"./index-DLMsdH5y.js";const t=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],c=o("chevron-right",t);export{c as C};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{y as e}from"./index-DLMsdH5y.js";const c=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]],y=e("circle-alert",c);export{y as C};
|
||||
+1
@@ -0,0 +1 @@
|
||||
import{y as c}from"./index-DLMsdH5y.js";const e=[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]],t=c("circle-check-big",e);export{t as C};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{y as e}from"./index-DLMsdH5y.js";const a=[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3",key:"msslwz"}],["path",{d:"M3 5V19A9 3 0 0 0 21 19V5",key:"1wlel7"}],["path",{d:"M3 12A9 3 0 0 0 21 12",key:"mv7ke4"}]],t=e("database",a);export{t as D};
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{y as c}from"./index-DLMsdH5y.js";const e=[["path",{d:"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",key:"1nclc0"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]],r=c("eye",e);export{r as E};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{y as e}from"./index-DLMsdH5y.js";const a=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"m9 15 2 2 4-4",key:"1grp1n"}]],o=e("file-check",a);export{o as F};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{y as e}from"./index-DLMsdH5y.js";const a=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M10 9H8",key:"b1mrlr"}],["path",{d:"M16 13H8",key:"t4e002"}],["path",{d:"M16 17H8",key:"z1uh3a"}]],o=e("file-text",a);export{o as F};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
import{y as e}from"./index-DLMsdH5y.js";const o=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]],a=e("globe",o);export{a as G};
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
-246
File diff suppressed because one or more lines are too long
Vendored
+41
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user