Compare commits

..

No commits in common. "main" and "Release/ajustes-headers" have entirely different histories.

75 changed files with 2075 additions and 6256 deletions

View File

@ -1,48 +0,0 @@
# Napkin Runbook
## Curation Rules
- Re-prioritize on every read.
- Keep recurring, high-value notes only.
- Max 10 items per category.
- Each item includes date + "Do instead".
## Execution & Validation (Highest Priority)
1. **[2026-03-20] Use clean-build.sh after VS 2022 updates**
Do instead: run `./clean-build.sh` before debugging OAuth or NuGet issues.
2. **[2026-03-20] Google OAuth fails in Edge browser**
Do instead: test OAuth in Vivaldi or Chrome; clear browser data for localhost:49178.
## Razor View Editing
1. **[2026-03-20] replace_all corrupts @inject namespace if brand name appears in it**
Do instead: run replace_all BEFORE adding @inject, or use namespace-specific strings to avoid replacing C# namespace occurrences.
## Shell & Command Reliability
1. **[2026-03-20] Windows environment — use Unix shell syntax**
Do instead: use forward slashes and bash syntax (not PowerShell) in all shell commands.
## Domain Behavior Guardrails
1. **[2026-03-20] PageStatus enum has explicit numeric values**
Do instead: always reference by name (e.g., `PageStatus.Creating`), never by magic number. Values: Creating=6, PendingModeration=4, Rejected=5, Active=0, Inactive=3, Expired=1, PendingPayment=2.
2. **[2026-03-20] Preview tokens expire in 4 hours**
Do instead: generate fresh tokens via `POST /Admin/GeneratePreviewToken/{id}` before accessing non-Active pages.
3. **[2026-03-20] Non-Active pages require preview token for access**
Do instead: always append `?preview={token}` when testing Creating/PendingModeration/Rejected pages.
4. **[2026-03-20] Plans source of truth is appsettings.json ["Plans"] section**
Do instead: read plan limits/features from config, not README (README has outdated values). Plans: Trial(free,1p,3l), Basic(R$12.90,3p,8l), Professional(R$25.90,5p,20l,DECOY), Premium(R$29.90,15p,∞l,PDF), PremiumAffiliate(R$34.90,15p,∞l,links produto). Annual variants save 2 months.
5. **[2026-03-20] MaxLinks=-1 means unlimited (Premium and PremiumAffiliate)**
Do instead: check `if (maxLinks == -1) return true;` before comparing counts.
6. **[2026-03-20] Subscription model stores plan limits at time of purchase**
Do instead: read limits from Subscription entity (MaxLinks, AllowCustomThemes etc.), not from current plan config — they may diverge after plan changes.
## User Directives
1. **[2026-03-20] Project targets Brazilian/Spanish markets**
Do instead: use pt-BR or es as default locale in UI text; keep pricing in BRL (R$).
2. **[2026-03-20] appsettings.json contains live credentials committed to git**
Do instead: be aware that MongoDB, OAuth, SendGrid secrets are in the repo. Never log or expose them further. Do not add more secrets to appsettings.json.

View File

@ -34,28 +34,7 @@
"Bash(netstat:*)", "Bash(netstat:*)",
"Bash(ss:*)", "Bash(ss:*)",
"Bash(lsof:*)", "Bash(lsof:*)",
"Bash(dotnet run:*)", "Bash(dotnet run:*)"
"Bash(dotnet user-secrets:*)",
"Bash(xargs grep:*)",
"mcp__stripe__list_products",
"mcp__stripe__list_prices",
"mcp__stripe__get_stripe_account_info",
"mcp__stripe__search_stripe_resources",
"Bash(docker exec:*)",
"mcp__stripe__list_subscriptions",
"mcp__stripe__create_product",
"mcp__stripe__create_price",
"Bash(git push:*)",
"mcp__stripe__stripe_api_execute",
"mcp__stripe__stripe_api_search",
"Bash(ctx csharp:*)",
"Bash(ctx auto:*)",
"Bash(git log:*)",
"Bash(git mv:*)",
"Bash(python3 -c \"import sys,json; [print\\(json.loads\\(l\\).get\\('MessageTemplate',''\\) + ' ' + str\\(json.loads\\(l\\).get\\('Level',''\\)\\)\\) for l in sys.stdin if 'categ' in l.lower\\(\\) or 'Error' in l or 'error' in l.lower\\(\\)]\")",
"Bash(python3 -c \" import sys, json for line in sys.stdin: line = line.strip\\(\\).rstrip\\(','\\) try: obj = json.loads\\(line\\) lvl = obj.get\\('Level',''\\) msg = obj.get\\('MessageTemplate',''\\) or obj.get\\('message',''\\) if lvl in \\('Error','Warning','Fatal'\\) or 'categ' in msg.lower\\(\\) or 'initial' in msg.lower\\(\\): print\\(f'[{lvl}] {msg}'\\) except: pass \")",
"Bash(grep -E \"\\\\.\\(cs|csproj\\)$\")",
"Bash(git -C /c/vscode/bcards log --oneline --follow src/BCards.Web/Views/Admin/CreatePage.cshtml)"
] ]
}, },
"enableAllProjectMcpServers": false "enableAllProjectMcpServers": false

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18 # Visual Studio Version 17
VisualStudioVersion = 18.2.11415.280 d18.0 VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BCards.Web", "src\BCards.Web\BCards.Web.csproj", "{2E8F4B5C-9B3A-4F8E-8C7D-1A2B3C4D5E6F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BCards.Web", "src\BCards.Web\BCards.Web.csproj", "{2E8F4B5C-9B3A-4F8E-8C7D-1A2B3C4D5E6F}"
EndProject EndProject
@ -21,7 +21,6 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
Conexoes.txt = Conexoes.txt Conexoes.txt = Conexoes.txt
deploy\docker-stack.release.yml = deploy\docker-stack.release.yml
README.md = README.md README.md = README.md
EndProjectSection EndProjectSection
EndProject EndProject

View File

@ -243,46 +243,4 @@ if (page.Status == PageStatus.Creating || page.Status == PageStatus.Rejected)
} }
``` ```
This architecture supports a production-ready SaaS application with complex business rules, payment integration, and content moderation workflows. This architecture supports a production-ready SaaS application with complex business rules, payment integration, and content moderation workflows.
# Project instructions
## ctx — use before reading files
This project has `ctx` available in PATH. Use it to understand the codebase **before** reading files directly. It produces compact markdown summaries that cost far fewer tokens than raw file content.
### When to use
**Always use `ctx` first** when you need to:
- Understand project structure → `ctx csharp project` or `ctx react project`
- Understand a file's structure → `ctx csharp outline <file>` or `ctx react outline <file>`
- Check build errors → `ctx csharp errors` or `ctx react errors`
- Understand git state → `ctx git`
- Detect what stack this project uses → `ctx auto detect`
### Workflow
1. **Start of session:** run `ctx auto detect` to see what's here, then `ctx <stack> project` for an overview.
2. **Before reading a file:** run `ctx <stack> outline <file>` first. Only read the full file if the outline isn't enough (e.g., you need to see method body logic).
3. **After making changes:** run `ctx <stack> errors` instead of `dotnet build` or `tsc` — the output is pre-filtered to only relevant diagnostics.
4. **Before committing:** run `ctx git` for a compact diff summary.
### Available commands
```
ctx auto detect # detect stack(s) in current directory
ctx auto project # run project summary for all detected stacks
ctx csharp project # .NET solution overview (projects, refs, packages)
ctx csharp outline <file.cs> # file structure without method bodies
ctx csharp errors # filtered dotnet build output (errors + top warnings)
ctx git # branch, status, recent commits, diff summary
```
### Important
- `ctx` output is a **summary**, not the full picture. If you need implementation details (method bodies, exact logic, specific lines), read the file directly.
- Do not run `ctx` commands that don't match the project stack. Use `ctx auto detect` if unsure.
- `ctx csharp errors` assumes `dotnet restore` was already run. If you get restore errors, run `dotnet restore` first, then `ctx csharp errors`.

View File

@ -1,59 +0,0 @@
# Plano de Novos Artigos
Data de criação: 2026-04-30
## LuzLinks — 4 artigos
- [x] **Como criar sua bio de fé do zero**
`Content/Tenants/luzlinks/Artigos/como-criar-sua-bio-de-fe-do-zero.pt-BR.md`
- [x] **Como divulgar cultos e eventos pelo WhatsApp com um único link**
`Content/Tenants/luzlinks/Artigos/como-divulgar-cultos-e-eventos-pelo-whatsapp.pt-BR.md`
- [x] **Por que pastores precisam de presença digital**
`Content/Tenants/luzlinks/Artigos/por-que-pastores-precisam-de-presenca-digital.pt-BR.md`
- [x] **Como receber dízimos online no seu ministério**
`Content/Tenants/luzlinks/Artigos/como-receber-dizimos-online-no-seu-ministerio.pt-BR.md`
## SpicyLinks — 4 artigos
- [x] **Como configurar sua bio para ganhar mais seguidores**
`Content/Tenants/spicylinks/Artigos/como-configurar-sua-bio-para-ganhar-mais-seguidores.pt-BR.md`
- [x] **Os melhores links para colocar na sua bio do Instagram**
`Content/Tenants/spicylinks/Artigos/os-melhores-links-para-sua-bio-do-instagram.pt-BR.md`
- [x] **Como criadores de conteúdo ganham dinheiro com links de afiliados**
`Content/Tenants/spicylinks/Artigos/como-criadores-de-conteudo-ganham-dinheiro-com-afiliados.pt-BR.md`
- [x] **SpicyLinks vs Linktree: qual é melhor para criadores?**
`Content/Tenants/spicylinks/Artigos/spicylinks-vs-linktree-qual-e-melhor-para-criadores.pt-BR.md`
---
## Melhorias de qualidade (SEO / estrutura)
### LuzLinks — aplicar em todos
- [ ] bio de fé do zero — "Para quem é" + prós/contras + Leia também
- [ ] WhatsApp + cultos — "Para quem é" + prós/contras + Leia também
- [ ] Pastores e digital — "Para quem é" + prós/contras + Leia também
- [ ] Dízimos online — "Para quem é" + prós/contras + Leia também
### SpicyLinks — aplicar em todos
- [ ] Bio e seguidores — "Para quem é" + prós/contras + Leia também
- [ ] Melhores links bio — "Para quem é" + prós/contras + Leia também
- [ ] Afiliados criadores — "Para quem é" + prós/contras + Leia também
- [ ] SpicyLinks vs Linktree — "Para quem é" + prós/contras + Leia também
### BCards — analisar e ajustar artigos existentes
- [ ] bcards-para-corretores-de-imoveis
- [ ] bcards-vs-linktree
- [ ] transformacao-digital-pequenos-negocios
- [ ] Tutoriais/advocacia/bcards-para-escritorios-de-advocacia
- [ ] Tutoriais/advocacia/como-advogados-podem-usar-bcards
- [ ] Tutoriais/tecnologia/bcards-para-freelancers-de-ti
- [ ] Tutoriais/tecnologia/como-criar-um-bcard

View File

@ -1,57 +0,0 @@
version: '3.8'
configs:
bcards-appsettings:
external: true
services:
bcards-app:
image: registry.redecarneir.us/bcards:latest
networks:
- bcards-net
deploy:
replicas: 4
placement:
max_replicas_per_node: 2
update_config:
parallelism: 1
order: stop-first
delay: 10s
monitor: 60s
failure_action: rollback
rollback_config:
parallelism: 0
delay: 5s
configs:
- source: bcards-appsettings
target: /app/appsettings.Production.json
mode: 0444
volumes:
- type: bind
source: /opt/bcards-content/bcards
target: /app/Content/Tenants/bcards
read_only: true
environment:
ASPNETCORE_ENVIRONMENT: Production
ASPNETCORE_URLS: http://+:8080
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
MongoDb__ConnectionString: mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/BCardsDB?replicaSet=rs0&authSource=admin
MongoDb__DatabaseName: BCardsDB
Serilog__OpenSearchUrl: http://141.148.162.114:19201
Serilog__OpenSearchFallback: http://129.146.116.218:19202
Logging__LogLevel__Default: Information
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
ports:
- published: 8080
target: 8080
protocol: tcp
mode: ingress
networks:
bcards-net:
external: true

View File

@ -1,57 +0,0 @@
version: '3.8'
configs:
luzlinks-appsettings:
external: true
services:
app:
image: registry.redecarneir.us/bcards:latest
networks:
- bcards-net
deploy:
replicas: 2
placement:
max_replicas_per_node: 1
update_config:
parallelism: 1
order: stop-first
delay: 10s
monitor: 60s
failure_action: rollback
rollback_config:
parallelism: 0
delay: 5s
configs:
- source: luzlinks-appsettings
target: /app/appsettings.Production.json
mode: 0444
volumes:
- type: bind
source: /opt/bcards-content/luzlinks
target: /app/Content/Tenants/luzlinks
read_only: true
environment:
ASPNETCORE_ENVIRONMENT: Production
ASPNETCORE_URLS: http://+:8080
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
MongoDb__ConnectionString: mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/LuzLinksDB?replicaSet=rs0&authSource=admin
MongoDb__DatabaseName: LuzLinksDB
Serilog__OpenSearchUrl: http://141.148.162.114:19201
Serilog__OpenSearchFallback: http://129.146.116.218:19202
Logging__LogLevel__Default: Information
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
ports:
- published: 8083
target: 8080
protocol: tcp
mode: ingress
networks:
bcards-net:
external: true

View File

@ -1,57 +0,0 @@
version: '3.8'
configs:
spicylinks-appsettings:
external: true
services:
app:
image: registry.redecarneir.us/bcards:latest
networks:
- bcards-net
deploy:
replicas: 2
placement:
max_replicas_per_node: 1
update_config:
parallelism: 1
order: stop-first
delay: 10s
monitor: 60s
failure_action: rollback
rollback_config:
parallelism: 0
delay: 5s
configs:
- source: spicylinks-appsettings
target: /app/appsettings.Production.json
mode: 0444
volumes:
- type: bind
source: /opt/bcards-content/spicylinks
target: /app/Content/Tenants/spicylinks
read_only: true
environment:
ASPNETCORE_ENVIRONMENT: Production
ASPNETCORE_URLS: http://+:8080
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
MongoDb__ConnectionString: mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/SpicyLinksDB?replicaSet=rs0&authSource=admin
MongoDb__DatabaseName: SpicyLinksDB
Serilog__OpenSearchUrl: http://141.148.162.114:19201
Serilog__OpenSearchFallback: http://129.146.116.218:19202
Logging__LogLevel__Default: Information
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
ports:
- published: 8082
target: 8080
protocol: tcp
mode: ingress
networks:
bcards-net:
external: true

View File

@ -1,116 +0,0 @@
# Multi-tenant Nginx config
# Deploy to: /etc/nginx/sites-available/tenants.conf
# Symlink: ln -s /etc/nginx/sites-available/tenants.conf /etc/nginx/sites-enabled/tenants.conf
#
# Requires: certbot certificates for each domain
# certbot --nginx -d bcards.site -d www.bcards.site
# certbot --nginx -d spicylinks.site -d www.spicylinks.site
# certbot --nginx -d luzlinks.site -d www.luzlinks.site
# ─── bcards.site → :8080 ───────────────────────────────────────────────────
upstream bcards {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name bcards.site www.bcards.site;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name bcards.site www.bcards.site;
ssl_certificate /etc/letsencrypt/live/bcards.site/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bcards.site/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
client_max_body_size 10M;
location / {
proxy_pass http://bcards;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_read_timeout 120s;
}
}
# ─── spicylinks.site → :8082 ───────────────────────────────────────────────
upstream spicylinks {
server 127.0.0.1:8082;
keepalive 32;
}
server {
listen 80;
server_name spicylinks.site www.spicylinks.site;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name spicylinks.site www.spicylinks.site;
ssl_certificate /etc/letsencrypt/live/spicylinks.site/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/spicylinks.site/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
client_max_body_size 10M;
location / {
proxy_pass http://spicylinks;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_read_timeout 120s;
}
}
# ─── luzlinks.site → :8083 ─────────────────────────────────────────────────
upstream luzlinks {
server 127.0.0.1:8083;
keepalive 32;
}
server {
listen 80;
server_name luzlinks.site www.luzlinks.site;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name luzlinks.site www.luzlinks.site;
ssl_certificate /etc/letsencrypt/live/luzlinks.site/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/luzlinks.site/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
client_max_body_size 10M;
location / {
proxy_pass http://luzlinks;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_read_timeout 120s;
}
}

BIN
saida.md

Binary file not shown.

View File

@ -1,14 +1,12 @@
@model List<BCards.Web.Areas.Tutoriais.Models.ArticleMetadata> @model List<BCards.Web.Areas.Tutoriais.Models.ArticleMetadata>
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value; ViewData["Title"] = "Artigos BCards - Inspiração e Conhecimento";
ViewData["Title"] = $"Artigos {tenant.SiteName} - Inspiração e Conhecimento";
} }
<div class="container py-5"> <div class="container py-5">
<div class="row mb-5"> <div class="row mb-5">
<div class="col-lg-8 mx-auto text-center"> <div class="col-lg-8 mx-auto text-center">
<h1 class="display-4 mb-3">✨ Artigos @tenant.SiteName</h1> <h1 class="display-4 mb-3">✨ Artigos BCards</h1>
<p class="lead text-muted">Insights, tendências e inspiração para transformar sua presença digital</p> <p class="lead text-muted">Insights, tendências e inspiração para transformar sua presença digital</p>
</div> </div>
</div> </div>
@ -22,7 +20,7 @@
<div class="card h-100 border-0 shadow-sm hover-shadow"> <div class="card h-100 border-0 shadow-sm hover-shadow">
@if (!string.IsNullOrEmpty(artigo.Image)) @if (!string.IsNullOrEmpty(artigo.Image))
{ {
<img src="@artigo.Image" class="card-img-top" alt="@artigo.Title" style="height: 200px; object-fit: cover;" onerror="this.remove()"> <img src="@artigo.Image" class="card-img-top" alt="@artigo.Title" style="height: 200px; object-fit: cover;">
} }
<div class="card-body"> <div class="card-body">
<h5 class="card-title">@artigo.Title</h5> <h5 class="card-title">@artigo.Title</h5>
@ -57,7 +55,7 @@
<div class="card bg-primary text-white border-0 shadow"> <div class="card bg-primary text-white border-0 shadow">
<div class="card-body text-center p-5"> <div class="card-body text-center p-5">
<h3 class="mb-3">Quer ver tutoriais práticos?</h3> <h3 class="mb-3">Quer ver tutoriais práticos?</h3>
<p class="mb-4">Acesse nossa seção de tutoriais e aprenda passo a passo como usar o @tenant.SiteName</p> <p class="mb-4">Acesse nossa seção de tutoriais e aprenda passo a passo como usar o BCards</p>
<a href="@Url.Action("Index", "Tutoriais", new { area = "Tutoriais" })" class="btn btn-light btn-lg"> <a href="@Url.Action("Index", "Tutoriais", new { area = "Tutoriais" })" class="btn btn-light btn-lg">
<i class="fas fa-book-open me-2"></i> Ver Tutoriais <i class="fas fa-book-open me-2"></i> Ver Tutoriais
</a> </a>

View File

@ -20,18 +20,10 @@ public class SupportFabViewComponent : ViewComponent
try try
{ {
var userId = UserClaimsPrincipal?.FindFirst(ClaimTypes.NameIdentifier)?.Value; var userId = UserClaimsPrincipal?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
// Não mostrar botão de ajuda para usuários não autenticados
if (string.IsNullOrEmpty(userId))
{
_logger.LogDebug("SupportFab não exibido - usuário não autenticado");
return Content(string.Empty);
}
var options = await _supportService.GetAvailableOptionsAsync(userId); var options = await _supportService.GetAvailableOptionsAsync(userId);
_logger.LogDebug("SupportFab invocado para usuário {UserId} - Opções: Rating={CanRate}, Form={CanUseContactForm}, Telegram={CanAccessTelegram}", _logger.LogDebug("SupportFab invocado para usuário {UserId} - Opções: Rating={CanRate}, Form={CanUseContactForm}, Telegram={CanAccessTelegram}",
userId, options.CanRate, options.CanUseContactForm, options.CanAccessTelegram); userId ?? "anônimo", options.CanRate, options.CanUseContactForm, options.CanAccessTelegram);
return View(options); return View(options);
} }

View File

@ -1,9 +1,7 @@
using BCards.Web.Areas.Tutoriais.Models; using BCards.Web.Areas.Tutoriais.Models;
using BCards.Web.Areas.Tutoriais.Models.ViewModels; using BCards.Web.Areas.Tutoriais.Models.ViewModels;
using BCards.Web.Configuration;
using Markdig; using Markdig;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NamingConventions;
@ -20,13 +18,11 @@ public class MarkdownService : IMarkdownService
public MarkdownService( public MarkdownService(
IMemoryCache cache, IMemoryCache cache,
ILogger<MarkdownService> logger, ILogger<MarkdownService> logger,
IWebHostEnvironment environment, IWebHostEnvironment environment)
IOptions<TenantSettings> tenantSettings)
{ {
_cache = cache; _cache = cache;
_logger = logger; _logger = logger;
_contentBasePath = Path.Combine( _contentBasePath = Path.Combine(environment.ContentRootPath, "Content");
environment.ContentRootPath, "Content", "Tenants", tenantSettings.Value.ContentFolder);
// Pipeline Markdig com extensões avançadas // Pipeline Markdig com extensões avançadas
_markdownPipeline = new MarkdownPipelineBuilder() _markdownPipeline = new MarkdownPipelineBuilder()

View File

@ -1,12 +0,0 @@
namespace BCards.Web.Configuration;
public class LinkTypeConfig
{
public string Icon { get; set; } = "";
public string Label { get; set; } = "";
public string Prefix { get; set; } = "https://";
public string? VisualPrefix { get; set; }
public string Placeholder { get; set; } = "";
public string Instructions { get; set; } = "";
public string Color { get; set; } = "bg-primary";
}

View File

@ -1,8 +0,0 @@
namespace BCards.Web.Configuration;
public class TenantFeature
{
public string Icon { get; set; } = "";
public string Title { get; set; } = "";
public string Description { get; set; } = "";
}

View File

@ -1,49 +0,0 @@
namespace BCards.Web.Configuration;
public class TenantSettings
{
public string SiteName { get; set; } = "BCards";
public string SiteDescription { get; set; } = "Crie sua página profissional com links organizados. A melhor alternativa para ter sua bio / links. Criada para profissionais e empresas no Brasil.";
public string Tagline { get; set; } = "Crie sua bios / links Profissional";
public string SupportEmail { get; set; } = "suporte@bcards.site";
public string ContentFolder { get; set; } = "bcards";
public bool AgeGated { get; set; } = false;
public string UrlExample { get; set; } = "bcards.site/corretor/seu-nome";
public string DpoEmail { get; set; } = "dpo@bcards.site";
public List<LinkTypeConfig> AllowedLinkTypes { get; set; } = new();
// Hero section
public string HeroHeadline { get; set; } = "Sua presença digital profissional em um só lugar";
public string HeroDescription { get; set; } = "Organize todos os seus links, portfólio, contatos e redes sociais em uma página única e elegante.";
public string HeroCtaText { get; set; } = "Criar Minha Página Grátis";
// Features section
public string FeaturesHeadline { get; set; } = "Por que escolher o {SiteName}?";
public List<TenantFeature> Features { get; set; } = new();
// Bottom CTA section
public string CtaHeadline { get; set; } = "Pronto para começar?";
public string CtaDescription { get; set; } = "Crie sua página agora mesmo e comece a organizar seus links.";
public string CtaButtonText { get; set; } = "Começar Grátis";
// SEO / Layout
public string MetaKeywords { get; set; } = "cartão digital, página de links, bio links, linktree brasil, página profissional";
public string FooterTagline { get; set; } = "Sua presença online, simplificada.";
// Branding / Colors
public string HeroGradient { get; set; } = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)";
public string PrimaryColor { get; set; } = "#0d6efd";
public string PrimaryColorDark { get; set; } = "#0a58ca";
// Category seeding (se vazio, usa os padrões do BCards)
public List<CategorySeedItem> DefaultCategories { get; set; } = new();
}
public class CategorySeedItem
{
public string Name { get; set; } = "";
public string Slug { get; set; } = "";
public string Icon { get; set; } = "";
public string Description { get; set; } = "";
public List<string> SeoKeywords { get; set; } = new();
}

View File

@ -1,255 +0,0 @@
---
title: "Como Corretores de Imóveis Usam o BCards para Fechar Mais Negócios"
description: "Guia prático para corretores de imóveis organizarem sua presença digital com uma bio link profissional — do CRECI ao WhatsApp, portfólio de imóveis e redes sociais."
keywords: "corretor de imóveis digital, bio link corretor, CRECI online, captação de clientes imóveis, presença digital imobiliária"
author: "Equipe BCards"
date: 2026-03-29
lastMod: 2026-03-29
image: "/images/artigos/corretor-imoveis.jpg"
culture: "pt-BR"
category: "negocios"
---
# Como Corretores de Imóveis Usam o BCards para Fechar Mais Negócios
Imagine um potencial comprador que acabou de ver um imóvel seu no Instagram. Ele quer saber mais: quem é você, que outros imóveis tem, como entrar em contato agora mesmo. Ele clica na sua bio — e encontra apenas um link para o site da imobiliária onde você trabalha, que não tem seu nome em lugar nenhum.
Oportunidade perdida.
A captação de clientes imobiliários mudou. Hoje, o primeiro contato acontece online — no Instagram, no YouTube, no Google. O corretor que converte mais é o que reduz o atrito entre "interesse" e "contato".
---
## A Realidade do Corretor de Imóveis no Brasil
O mercado imobiliário brasileiro tem mais de 400 mil corretores registrados no COFECI. A maioria concorre nas mesmas plataformas — portais de anúncios, redes sociais, grupos de WhatsApp — com os mesmos imóveis e as mesmas fotos.
O que diferencia não é o imóvel. É a confiança que o corretor consegue construir antes do primeiro contato.
Uma bio link profissional é uma das formas mais simples de estabelecer essa confiança.
---
## O que um BCards de Corretor Deve Ter
### Informações que Geram Confiança
**1. Seu nome completo e CRECI**
O CRECI visível na bio link é sinal imediato de profissionalismo. Para compradores e vendedores que recebem abordagens de toda sorte de pessoas, ver o número do CRECI ali, no início, elimina objeções antes que elas existam.
```
João Silva | Corretor de Imóveis
CRECI-SP 123456
```
**2. Especialização**
Corretores que se posicionam em um nicho específico atraem clientes mais qualificados e cobram mais bem. Seja direto:
```
Especialista em imóveis residenciais em Campinas e região
```
ou
```
Imóveis de alto padrão — Alphaville e Tamboré
```
ou
```
Imóveis comerciais e salas no centro de São Paulo
```
**3. Regiões de atuação**
Compradores pesquisam por localização. Deixe claro onde você atua para não gerar contatos fora do seu mercado.
---
## Os Links Essenciais para Corretores
### 1. WhatsApp Comercial (Primeiro Link)
O WhatsApp é onde a negociação começa. Coloque como primeiro link com mensagem pré-preenchida:
```
Título: 💬 Falar com João Corretor
URL: https://wa.me/5511999999999?text=Olá João, vi seu perfil e gostaria de saber mais sobre os imóveis disponíveis
```
A mensagem pré-preenchida filtra curiosos e já contextualiza o contato. Quem clica e envia está genuinamente interessado.
### 2. Portfólio de Imóveis
Sua página com imóveis disponíveis — seja no portal da imobiliária, no seu site próprio, ou num perfil específico no Viva Real / ZAP:
```
Título: 🏠 Ver Imóveis Disponíveis
URL: https://zapimoveis.com.br/corretor/joao-silva
```
### 3. Instagram
Corretores que postam conteúdo de valor no Instagram (tours virtuais, dicas de compra, bastidores do mercado) constroem audiência qualificada. Inclua o link:
```
Título: 📸 Ver Imóveis no Instagram
URL: https://instagram.com/joaosilvacorretor
```
### 4. LinkedIn
Para corretores focados em imóveis comerciais ou alto padrão, o LinkedIn tem peso diferente. Clientes corporativos pesquisam por aí:
```
Título: 💼 Perfil Profissional no LinkedIn
URL: https://linkedin.com/in/joaosilva-corretor
```
### 5. YouTube ou Canal de Vídeos
Tours virtuais e vídeos de bairro têm alta conversão. Se você produz esse conteúdo, inclua:
```
Título: 🎥 Tours Virtuais e Dicas de Imóveis
URL: https://youtube.com/@joaosilvacorretor
```
### 6. Verificação do CRECI
O Conselho Regional de Corretores de Imóveis disponibiliza consulta pública online. Incluir o link direto para consulta do seu CRECI é um diferencial pouco usado, mas extremamente eficaz para credibilidade:
```
Título: ✅ Verificar meu CRECI
URL: https://creci-sp.org.br/consulta-corretor/...
```
Poucos fazem isso. Quem faz se destaca imediatamente.
### 7. Email Profissional
Para comunicação formal e documentação:
```
Título: ✉️ Enviar Email
URL: mailto:joao@jsilva-imoveis.com.br
```
---
## Como Usar o BCards Para Diferentes Tipos de Negociação
### Captação de Proprietários
Quando você aborda um proprietário que quer vender, precisa demonstrar profissionalismo em segundos. Compartilhe seu BCards logo no início da conversa:
*"Meu nome é João Silva, sou corretor com CRECI-SP 123456. Aqui está minha página com meus contatos e os últimos imóveis que comercializei."*
A página bem feita faz seu trabalho por você enquanto o cliente consulta com calma.
### Atendimento a Compradores
Compradores pesquisam online antes de entrar em contato. Quando alguém chega até você por indicação ou pelo Instagram, ter um BCards organizado confirma a decisão deles:
- Veem que você tem CRECI
- Veem os imóveis disponíveis
- Veem reviews ou histórico
- Escolhem como entrar em contato
### Parcerias com Outros Corretores
Corretores frequentemente fecham negócios em parceria. Uma página profissional facilita apresentações a colegas de outras imobiliárias:
```
bcards.site/negocios/joao-silva-corretor
```
---
## Erros Comuns de Corretores na Presença Digital
### ❌ Usar apenas o perfil da imobiliária
Quando você sai da imobiliária, perde toda a presença digital construída. Construa sua marca pessoal, paralela à da empresa onde trabalha.
### ❌ Link na bio que leva para todos os imóveis do portal
O cliente clicou para te conhecer, não para navegar em 15 mil anúncios. Direcione para seus imóveis específicos ou para seu perfil no portal.
### ❌ WhatsApp sem mensagem pré-preenchida
"Olá" como primeira mensagem exige que você pergunte o contexto. Uma mensagem pré-preenchida já chega com contexto — e aumenta a taxa de resposta.
### ❌ Não atualizar os links quando os imóveis são vendidos
Nada pior para a credibilidade do que imóveis "disponíveis" que já foram vendidos. Mantenha os links atualizados — é a mesma lógica de manter anúncios honestos.
### ❌ Bio sem CRECI
Sem CRECI visível, você parece igual a qualquer pessoa que vende imóvel sem habilitação. É a credencial mais importante que você tem — use-a.
---
## Slug e URL: Como Aparecer na Busca
Escolha um slug que combine seu nome com a especialização:
```
joao-silva-corretor → bcards.site/negocios/joao-silva-corretor
marina-costa-alphaville → bcards.site/negocios/marina-costa-alphaville
pedro-alves-comercial-sp → bcards.site/negocios/pedro-alves-comercial-sp
```
Evite slugs genéricos como `corretor123` — você vai precisar ditar esse link para clientes.
---
## Integrando o BCards ao Seu Processo Comercial
**No cartão de visita físico:**
Coloque o QR code ou a URL do seu BCards. Substituir o site da imobiliária pelo seu link pessoal transforma cada cartão em um funil direto para você.
**Na assinatura de email:**
```
João Silva | Corretor de Imóveis CRECI-SP 123456
📱 (11) 99999-9999
🔗 bcards.site/negocios/joao-silva-corretor
```
**No rodapé dos materiais de apresentação de imóveis:**
Toda proposta, laudo ou apresentação que você envia por email deve ter seu BCards no rodapé.
**Nos stories do Instagram:**
Use o sticker de link para direcionar para seu BCards em vez do perfil da imobiliária.
---
## Construindo Credibilidade com Avaliações
O mercado imobiliário é movido por indicação. Uma forma de digitalizar isso é incluir na sua bio link:
- Link para suas avaliações no Google Meu Negócio
- Link para recomendações no LinkedIn
- Depoimento em vídeo de cliente satisfeito no YouTube
Compradores e vendedores pesquisam por corretores no Google antes de ligar. Uma avaliação de 4.8 com 50 reviews aparece nos resultados — e faz toda a diferença.
---
## Conclusão
O mercado imobiliário é competitivo, mas poucos corretores investem em presença digital própria. A maioria depende dos portais e da imobiliária onde trabalha.
Um BCards profissional com CRECI visível, WhatsApp otimizado e portfólio atualizado diferencia você antes mesmo do primeiro contato — e mantém sua presença digital intacta independentemente de onde você trabalha.
**Próximos passos:**
1. Crie sua conta no BCards
2. Defina seu slug com nome e especialização
3. Adicione CRECI, WhatsApp e portfólio de imóveis
4. Atualize a bio do Instagram com o novo link
5. Inclua o link no cartão de visita e na assinatura de email
[Criar meu BCards de corretor →](https://bcards.site/)
---
**Última atualização:** Março 2026

View File

@ -1,269 +0,0 @@
---
title: "BCards para Escritórios de Advocacia: Gerenciando Múltiplos Sócios e Áreas"
description: "Como escritórios de advocacia com múltiplos sócios podem usar o BCards para organizar a presença digital de cada advogado e das diferentes áreas de atuação."
keywords: "escritório advocacia, sócios advogados, marketing jurídico, gestão digital escritório, OAB digital"
author: "Equipe BCards"
date: 2026-01-20
lastMod: 2026-01-20
image: "/images/tutoriais/escritorio-advocacia.jpg"
culture: "pt-BR"
category: "advocacia"
---
# BCards para Escritórios de Advocacia: Gerenciando Múltiplos Sócios e Áreas
Escritórios com mais de um advogado enfrentam um desafio específico: como centralizar a presença digital sem perder a identidade individual de cada sócio? O BCards resolve isso de forma elegante com o suporte a múltiplas páginas por conta.
## O Problema dos Escritórios Tradicionais
Cenário comum:
- O sócio João tem o LinkedIn desatualizado
- A sócia Maria usa o Instagram pessoal como contato profissional
- O escritório tem um site, mas ninguém lembra a URL para falar rápido
- Cada advogado associado indica um WhatsApp diferente
- Novos clientes ficam confusos sobre quem contatar
**Resultado:** clientes em potencial desistem antes de falar com qualquer sócio.
## A Estratégia com BCards para Escritórios
### Estrutura Recomendada
Um escritório bem organizado no BCards pode ter:
```
bcards.site/advocacia/escritorio-santos-silva ← Página geral do escritório
bcards.site/advocacia/dr-jose-santos-empresarial ← Sócio 1 (Direito Empresarial)
bcards.site/advocacia/dra-ana-silva-trabalhista ← Sócia 2 (Direito Trabalhista)
bcards.site/advocacia/dr-pedro-costa-familia ← Associado (Família e Sucessões)
```
Cada página tem propósito diferente:
| Página | Público | Links Principais |
|---|---|---|
| Escritório geral | Clientes sem área definida | Site, WhatsApp geral, email geral, localização |
| Sócio empresarial | Empresas e contratos | LinkedIn, email direto, agenda online |
| Sócia trabalhista | Empregados e empregadores | WhatsApp, email, artigos publicados |
| Associado família | Pessoas físicas | WhatsApp, email, localização |
---
## Configurando a Página Principal do Escritório
### Informações Básicas
**Nome da Página:**
```
Santos & Silva Advogados
```
**Slug sugerido:**
```
santos-silva-advogados
```
**Descrição (respeitando a OAB):**
```
Escritório de advocacia especializado em Direito Empresarial,
Trabalhista e de Família. Atendimento presencial e online.
São Paulo/SP | OAB/SP - Registro Ativo
```
### Links Essenciais da Página do Escritório
1. **📍 Localização** — Google Maps do escritório
2. **📞 WhatsApp de Triagem** — Para primeiro contato e direcionamento
3. **✉️ Email de Contato** — contato@santossilva.adv.br
4. **🌐 Site do Escritório** — www.santossilva.adv.br
5. **💼 Área Empresarial** — Link para a página do Dr. Santos
6. **👷 Área Trabalhista** — Link para a página da Dra. Silva
7. **👨‍👩‍👧 Área de Família** — Link para a página do Dr. Costa
8. **📅 Agendar Consulta** — Link do sistema de agenda
> **Dica OAB:** Usar links para páginas de sócios específicos é uma forma elegante de direcionar o cliente para a área correta sem fazer promessas ou comparações.
---
## Configurando a Página Individual de Cada Sócio
Cada sócio deve ter sua própria página com foco na especialização.
### Exemplo: Sócio de Direito Empresarial
**Slug:** `dr-jose-santos-empresarial`
**Descrição:**
```
Advogado | Direito Empresarial e Contratos
OAB/SP 123.456
Especialista em M&A, Contratos Comerciais e Recuperação Judicial
Mestre em Direito Empresarial — FGV Direito SP
```
**Links recomendados:**
1. 💼 LinkedIn (perfil profissional atualizado)
2. 📧 Email direto do sócio
3. 📅 Calendly para agendamento
4. 📄 Artigos publicados (JusBrasil, Migalhas)
5. 🌐 Página do Escritório (link de volta)
6. 📍 Localização do escritório
---
### Exemplo: Sócia de Direito Trabalhista
**Slug:** `dra-ana-silva-trabalhista`
**Descrição:**
```
Advogada Trabalhista | OAB/SP 234.567
Representação de Empregados e Empregadores
Pós-graduada em Direito do Trabalho — PUC-SP
Atendimento presencial e online em todo o Brasil
```
**Links recomendados:**
1. 📱 WhatsApp Business (trabalhista)
2. 📧 Email direto
3. 📸 Instagram jurídico educativo
4. 📄 Canal YouTube (dicas trabalhistas)
5. 🌐 Página do Escritório
6. 📅 Agendar Consulta Inicial
---
## Como Coordenar a Comunicação Visual
Mesmo com páginas individuais, é possível manter identidade visual consistente:
### Temas Coordenados (Plano Premium)
- **Escritório geral:** Tema Profissional (azul marinho)
- **Cada sócio:** Mesmo tema, cores ligeiramente personalizadas
- **Associados:** Tema Clássico (mais neutro)
### Fotos de Perfil Padronizadas
Invista em um ensaio fotográfico único para todos os advogados:
- Mesmo estilo de foto (fundo neutro, vestimenta formal)
- Mesmo enquadramento
- Mesma paleta de cores no fundo
Resultado: aspecto de equipe coesa em todas as páginas.
---
## Integrando o BCards com a Estratégia Digital do Escritório
### 1. Cartão de Visita Digital
No verso do cartão físico impresso, inclua o QR Code do BCards:
```
QR Code → bcards.site/advocacia/dr-jose-santos-empresarial
```
Quando o cliente escanear, tem todos os contatos na palma da mão.
### 2. Assinatura de Email
```
Dr. José Santos | Santos & Silva Advogados
OAB/SP 123.456 | Direito Empresarial
📱 (11) 99999-9999
🔗 bcards.site/advocacia/dr-jose-santos-empresarial
```
### 3. Bio em Redes Sociais (LinkedIn)
```
Advogado Empresarial | OAB/SP 123.456
Santos & Silva Advogados
M&A | Contratos | Recuperação Judicial
🔗 Todos os contatos e publicações:
bcards.site/advocacia/dr-jose-santos-empresarial
```
### 4. Eventos e Congressos
Ao participar de eventos jurídicos, compartilhe seu BCards:
- Inclua o link no material de apresentação
- Coloque no crachá (QR Code impresso)
- Compartilhe no grupo do evento
---
## Gerenciando o Plano Premium para Escritórios
Com o **Plano Premium** (R$ 29,90/mês), cada advogado pode ter **até 15 páginas**.
**Estratégia para escritórios em crescimento:**
Um único sócio com conta Premium pode criar:
- 1 página geral do escritório
- 1 página por especialização
- 1 página para cada filial ou cidade de atuação
- 1 página para eventos ou projetos especiais
**Exemplo de uso completo:**
```
santos-silva-advogados ← Escritório SP
santos-silva-advogados-rj ← Escritório RJ (se houver)
dr-jose-santos-empresarial ← Sócio principal
dr-jose-santos-ingles ← Página em inglês para clientes internacionais
santos-silva-contratos ← Especialização em contratos
santos-silva-ma ← Especialização em M&A
```
---
## Checklist para Escritórios
**Configuração inicial:**
- [ ] Criar conta com email profissional do escritório
- [ ] Definir slug padrão para o escritório
- [ ] Criar página geral do escritório
- [ ] Criar página individual para cada sócio
- [ ] Alinhar temas visuais entre as páginas
- [ ] Padronizar fotos de perfil
- [ ] Testar todos os links antes de publicar
- [ ] Submeter todas as páginas para moderação
**Comunicação:**
- [ ] Atualizar bio do LinkedIn de cada sócio
- [ ] Atualizar assinatura de email
- [ ] Imprimir QR Code para cartões de visita
- [ ] Atualizar bio do Instagram do escritório (se houver)
**Manutenção mensal:**
- [ ] Verificar se todos os links continuam funcionando
- [ ] Atualizar publicações e artigos recentes
- [ ] Revisar dados de contato (mudança de celular, email)
- [ ] Analisar métricas: quais links mais clicados
---
## Conclusão
O BCards permite que escritórios de advocacia profissionalizem sua presença digital com uma estrutura clara: uma página geral como porta de entrada e páginas individuais para cada sócio, organizadas por especialização.
**Benefícios para o escritório:**
- ✅ Clientes chegam ao advogado certo mais rapidamente
- ✅ Identidade visual consistente em toda equipe
- ✅ Facilita o trabalho em rede (networking entre áreas)
- ✅ Atualização simples sem precisar de desenvolvedor
- ✅ Analytics por sócio para medir performance individual
**Lembre-se:** Todas as páginas devem respeitar o Provimento OAB n° 205/2021 sobre publicidade na advocacia.
[Criar sua página agora →](https://bcards.site/)
---
**Referências:**
- Provimento OAB n° 205/2021
- Código de Ética e Disciplina da OAB
**Última atualização:** Janeiro 2026

View File

@ -1,364 +0,0 @@
---
title: "BCards para Freelancers de TI: Como Fechar Mais Projetos com uma Bio Profissional"
description: "Guia prático para desenvolvedores, designers e profissionais de TI que trabalham como freelancers usarem o BCards para atrair clientes e fechar mais projetos."
keywords: "freelancer TI, desenvolvedor freelance, bio links dev, portfolio desenvolvedor, freelancer tecnologia brasil"
author: "Equipe BCards"
date: 2026-01-22
lastMod: 2026-01-22
image: "/images/tutoriais/freelancer-ti.jpg"
culture: "pt-BR"
category: "tecnologia"
---
# BCards para Freelancers de TI: Como Fechar Mais Projetos com uma Bio Profissional
Você passa horas desenvolvendo projetos incríveis, mas quando um cliente em potencial pede "onde posso ver seu trabalho?", você envia um link do GitHub que a maioria não consegue interpretar, ou pior — uma pasta no Google Drive mal organizada.
O BCards resolve esse problema de forma elegante.
## Por Que Freelancers de TI Precisam de uma Bio Link
### O Momento Decisivo
Imagine o cenário: você está em um evento de networking, uma startup interessante pergunta sobre seu trabalho. Você tem 30 segundos. O que você envia?
**Sem BCards:**
```
github.com/seuusuario (só devs entendem)
behance.net/seuusuario (só designers)
linkedin.com/in/seuusuario (corporativo demais)
seusite.com (se existir, se estiver atualizado)
```
**Com BCards:**
```
bcards.site/tecnologia/joao-dev-fullstack
• Portfolio visual
• GitHub
• LinkedIn
• WhatsApp para proposta
• Calendly para call
• Email profissional
```
Tudo em uma URL que você memoriza e dita no telefone.
---
## O Que Colocar na Sua Página de Freelancer TI
### Informações Principais
**Nome e especialização:**
```
João Silva | Dev Full Stack React + Node.js
```
ou
```
Marina Costa — UX/UI Designer • Figma • Framer
```
ou
```
Pedro Alves • DevOps & Cloud AWS/GCP
```
**Slug recomendado:**
```
joao-silva-fullstack
marina-costa-ux
pedro-alves-devops
```
**Descrição (seja específico — clientes não contratam generalistas):**
✅ Bom exemplo:
```
Desenvolvedor Full Stack com 6 anos de experiência.
Especialidade: aplicações React + Node.js para SaaS e e-commerce.
Atendimento remoto para startups e empresas brasileiras.
Disponível para novos projetos em abril/2026.
```
❌ Evitar:
```
Desenvolvedor apaixonado por tecnologia e inovação
que busca novos desafios e oportunidades de crescimento.
```
---
## Os 8 Links Essenciais para Freelancers de TI
### 1. WhatsApp para Propostas
```
Título: 💬 Solicitar Orçamento
URL: https://wa.me/5511999999999?text=Olá João, vi seu perfil no BCards e gostaria de conversar sobre um projeto
```
> Pré-preencha a mensagem. Facilita o primeiro contato e já filtra leads sérios.
### 2. Portfólio Visual
```
Título: 🎨 Ver Portfólio
URL: https://seuportfolio.com
```
> Se não tiver site próprio, use: Behance, Read.cv, Notion público ou até um Figma compartilhado.
### 3. GitHub
```
Título: 💻 Repositórios GitHub
URL: https://github.com/seuusuario
```
> Só inclua se o perfil estiver apresentável: README atualizado, projetos fixados relevantes.
### 4. LinkedIn
```
Título: 💼 LinkedIn Profissional
URL: https://linkedin.com/in/seu-perfil
```
### 5. Agendamento de Call
```
Título: 📅 Agendar uma Call
URL: https://calendly.com/seuusuario/30min
```
> Use Calendly gratuito. Isso elimina a ladainha de "que hora você pode?".
### 6. Email Profissional
```
Título: ✉️ Enviar Email
URL: mailto:joao@seudominio.dev
```
> Invista em um domínio próprio. joao@gmail.com vs joao@jsilva.dev — a diferença de percepção é enorme.
### 7. Currículo ou Proposta Padrão (PDF)
```
Título: 📄 Baixar Currículo/Portfólio
URL: https://drive.google.com/...
```
> Tenha um PDF bem feito, de no máximo 2 páginas, sempre atualizado.
### 8. Depoimentos ou Case de Sucesso
```
Título: ⭐ Depoimentos de Clientes
URL: https://linkedin.com/in/seu-perfil#recommendations
```
> Recomendações do LinkedIn são a prova social mais confiável para B2B.
---
## Estratégias Avançadas para Fechar Mais Projetos
### 1. Mencione sua Disponibilidade
Clientes odeiam entrar em contato e descobrir que você está ocupado por 6 meses. Seja direto na descrição:
```
✅ Disponível para projetos a partir de maio/2026
🕐 Capacidade atual: 20h/semana
```
Atualize isso mensalmente. É um diferencial enorme.
### 2. Especialização > Generalismo
Um freelancer de "Python, Java, PHP, React, Angular, Vue, mobile, backend, frontend, dados e cloud" não passa confiança.
Escolha 1-2 tecnologias onde você é realmente forte e faça sua página comunicar isso:
**Para dev:**
```
Especialista em React + TypeScript para aplicações B2B
```
**Para designer:**
```
UX Design para produtos digitais B2C — do discovery ao handoff
```
**Para DevOps:**
```
Infraestrutura AWS para startups em fase de escala
```
### 3. Mostre Resultados, Não Tarefas
Na descrição e no portfólio, fale sobre impacto:
❌ "Desenvolvi uma aplicação de e-commerce"
✅ "Desenvolvi uma aplicação de e-commerce que aumentou a conversão do cliente em 32%"
❌ "Redesenhei o app de delivery"
✅ "Redesenhei o app de delivery — avaliação na App Store subiu de 3.1 para 4.6"
### 4. Link de "Como Trabalho Comigo"
Crie uma página Notion pública (gratuita) explicando seu processo de trabalho:
- Como você cobra
- Formas de pagamento aceitas
- Prazo médio de projetos
- Ferramentas que usa para comunicação
- O que você precisa do cliente para começar
Adicione como link no BCards:
```
Título: 📋 Como trabalho comigo
URL: https://notion.so/seu-processo
```
Isso filtra clientes problemáticos antes de qualquer call.
---
## Usando o Analytics do BCards Para Otimizar
Após sua página estar ativa, monitore no Dashboard:
### Métricas que Importam
**Taxa de clique por link:**
- Se o "Solicitar Orçamento" tem poucos cliques → sua descrição não está convencendo
- Se o "Portfolio" tem muitos cliques mas "WhatsApp" tem poucos → seu portfolio não está convertendo
**Volume de visitantes:**
- Picos após posts no LinkedIn = LinkedIn está trazendo resultado
- Picos após eventos = networking presencial funciona para você
### Ciclo de Otimização
```
1. Atualize a descrição (mais específica)
2. Aguarde 2 semanas
3. Compare métricas
4. Teste outra variação
5. Repita
```
---
## Configuração por Perfil de Freelancer
### Desenvolvedor Backend
```
Links prioritários:
1. GitHub (projetos relevantes pinados)
2. WhatsApp para proposta
3. LinkedIn
4. Calendly para call técnica
5. Artigos no Dev.to ou Medium
```
### Desenvolvedor Frontend / Full Stack
```
Links prioritários:
1. Portfolio visual (site próprio ou Behance)
2. WhatsApp para proposta
3. GitHub
4. LinkedIn
5. Calendly
```
### UX/UI Designer
```
Links prioritários:
1. Behance ou Dribbble (trabalhos visuais)
2. Figma Community (se tiver projetos compartilhados)
3. WhatsApp para proposta
4. LinkedIn
5. Calendly
```
### DevOps / Cloud
```
Links prioritários:
1. LinkedIn (certificações visíveis)
2. GitHub (scripts e automações)
3. Blog técnico (Medium, Hashnode, Dev.to)
4. Email profissional
5. Calendly para call técnica
```
### QA / Tester
```
Links prioritários:
1. LinkedIn
2. Portfolio de relatórios de bugs (Notion público)
3. GitHub (scripts de automação de testes)
4. WhatsApp ou Email
5. Calendly
```
---
## Erros Comuns de Freelancers de TI
### ❌ Slug genérico
`bcards.site/tecnologia/dev` — sem personalidade, difícil de lembrar
✅ Use seu nome + especialização: `joao-silva-fullstack`
### ❌ GitHub com projetos de faculdade
Projetos de "calculadora", "lista de tarefas" ou fork sem contribuições passam imagem de iniciante.
✅ Fixe seus 6 melhores projetos. Arquive o resto.
### ❌ Sem foto de perfil profissional
Freelancers sem foto parecem bot ou perfil falso.
✅ Qualquer foto com boa iluminação e fundo limpo serve. Não precisa ser ensaio fotográfico.
### ❌ Bio genérica com buzzwords
"Profissional apaixonado por inovação e tecnologia que busca desafios"
✅ Seja específico: linguagens, tipos de projeto, nível de experiência, disponibilidade.
### ❌ Não atualizar a disponibilidade
Nada pior que entrar em contato e descobrir que a pessoa está ocupada por 3 meses.
✅ Atualize todo mês: "Disponível a partir de [mês]"
---
## Checklist Final
**Antes de publicar:**
- [ ] Slug tem seu nome + especialização?
- [ ] Descrição menciona tecnologias específicas?
- [ ] Descrição menciona disponibilidade atual?
- [ ] Todos os links testados e funcionando?
- [ ] GitHub está apresentável (se incluído)?
- [ ] Foto de perfil nítida e profissional?
- [ ] WhatsApp ou email para contato incluído?
- [ ] Calendly ou outra forma de agendamento incluído?
**Após aprovação:**
- [ ] Bio do LinkedIn atualizada com link BCards?
- [ ] Bio do GitHub atualizada (`README.md` de perfil)?
- [ ] Instagram bio atualizada (se usar para trabalho)?
- [ ] Assinatura de email atualizada?
---
## Conclusão
Freelancers de TI que centralizam sua presença em um BCards profissional fecham projetos mais rápido porque eliminam a fricção do primeiro contato.
Em vez de mandar links espalhados, você manda uma URL só — limpa, profissional, memorável.
**Próximos passos:**
1. Crie sua conta no BCards
2. Configure sua página em 10 minutos
3. Atualize todas as suas bios com o novo link
4. Monitore os cliques e ajuste a copy
[Criar meu BCards de freelancer →](https://bcards.site/)
---
**Tempo de leitura:** 10 minutos
**Dificuldade:** Iniciante a Intermediário
**Última atualização:** Janeiro 2026

View File

@ -1,160 +0,0 @@
---
title: "Como a Tecnologia Está Transformando o Ministério Moderno"
description: "Reflexão sobre como pastores, padres e líderes religiosos podem usar ferramentas digitais para alcançar mais pessoas com a mensagem de fé, sem perder a essência do ministério."
keywords: "ministério digital, igreja online, pastor digital, tecnologia e fé, evangelismo digital"
author: "Equipe LuzLinks"
date: 2026-01-28
lastMod: 2026-01-28
image: "/images/artigos/ministerio-digital.jpg"
culture: "pt-BR"
category: "ministerio"
---
# Como a Tecnologia Está Transformando o Ministério Moderno
Há dez anos, a maioria das igrejas considerava ter um site "suficiente" para a presença digital. Hoje, a pergunta não é mais *se* um ministério precisa estar online, mas *como* estar de forma consistente, autêntica e eficaz.
A pandemia acelerou uma transformação que já estava em curso. Congregações que resistiam ao digital foram obrigadas a se adaptar — e muitas descobriram que o alcance das live transmissões superava o número de cadeiras da sede física.
---
## O Que Mudou: Da Presença Física à Presença Contínua
A Igreja sempre soube alcançar pessoas além dos muros do templo: missionários, rádio evangélico, televisão. A diferença agora é a **bidirecionalidade** e o **custo praticamente zero** de produção e distribuição.
Um pastor em uma cidade do interior do Piauí pode, hoje:
- Transmitir um culto para membros no exterior
- Publicar um estudo bíblico que é compartilhado por completos desconhecidos
- Responder perguntas de pessoas que nunca pisaram em sua igreja
- Construir uma comunidade de fé com pessoas que jamais se encontrarão presencialmente
Isso não substitui a comunhão presencial — que permanece insubstituível. Mas expande o alcance do ministério de formas inimagináveis há uma geração.
---
## Ferramentas Digitais e suas Vocações no Ministério
### YouTube: O Púlpito que Nunca Fecha
O YouTube se tornou o maior arquivo de pregações da história da humanidade. Uma mensagem publicada hoje pode ser assistida daqui a dez anos por alguém que ainda não chegou à fé.
**Usos estratégicos:**
- Pregações completas dos cultos
- Séries de estudos bíblicos organizados em playlists
- Devocional diário (formato curto, 5-10 minutos)
- Transmissão ao vivo de cultos especiais
> Uma série bem-estruturada no YouTube funciona como um "cartão de visita" permanente do ministério. Um novo convertido pode acompanhar meses de ensino antes de aparecer pela primeira vez presencialmente.
### Instagram: O Espaço da Comunidade Cotidiana
O Instagram não é para pregações longas. É para presença diária, humanização do líder e construção de comunidade.
**O que funciona no Instagram para ministérios:**
- Versículos do dia com design cuidado
- Bastidores da preparação do culto
- Histórias de transformação (com autorização)
- Reels curtos com reflexões de 60 segundos
- Stories de interação com a congregação
O Instagram permite que membros que não puderam comparecer ao culto se sintam parte da comunidade mesmo à distância.
### WhatsApp e Telegram: A Pastoral Digital
Esses aplicativos estão se tornando, para muitos pastores, a principal ferramenta de acompanhamento pastoral. Um grupo bem gerenciado pode:
- Enviar a agenda semanal automaticamente
- Compartilhar o link da live antes do culto
- Receber pedidos de oração em tempo real
- Coordenar equipes de ministério
- Manter membros em células conectados entre os encontros
**Atenção:** Grupos grandes demais perdem a qualidade de interação. A estratégia de múltiplos grupos menores (por célula, por faixa etária) é mais eficaz pastoralmente.
### Podcast: Ministério para o Trânsito e a Academia
O brasileiro passa em média 1h30 no trânsito por dia. O podcast transforma esse tempo em tempo de edificação.
Muitas pessoas que nunca ouviriam uma pregação completa no YouTube consomem regularmente episódios de podcast no caminho para o trabalho.
---
## Os Desafios do Ministério Digital
### A Armadilha da Performance
A lógica das redes sociais recompensa engajamento — curtidas, comentários, compartilhamentos. Há um risco real de que líderes comecem a moldar sua mensagem para maximizar métricas, não para edificar a congregação.
A mensagem do evangelho não é otimizável para o algoritmo. E não deveria ser.
**A pergunta certa não é:** "Como esse conteúdo vai performar?"
**A pergunta certa é:** "Esse conteúdo serve à minha congregação e ao chamado do ministério?"
### A Dispersão de Atenção
Estar em todas as plataformas ao mesmo tempo, com qualidade, é impossível para a maioria dos ministérios. A tentação de estar no YouTube, Instagram, TikTok, Twitter, Telegram e Podcast simultaneamente leva à superficialidade em todas.
**Estratégia recomendada:** Domine um canal antes de expandir. Para a maioria dos ministérios, YouTube + Instagram + WhatsApp é suficiente e sustentável.
### A Ilusão da Comunidade Online
Seguidores não são membros. Assistentes de live não são congregação. A profundidade de formação e de compromisso que acontece na comunidade presencial não pode ser replicada digitalmente.
A presença digital deve ser entendida como **portal de entrada**, não como substituto da vida comunitária presencial.
---
## O Papel da Bio Link no Ministério Digital
Um problema prático que surge com a multiplicação de plataformas: como uma pessoa que descobre seu ministério no Instagram encontra seu canal no YouTube? Como alguém que assistiu uma pregação no YouTube entra em contato?
Sem uma centralização clara, o esforço de presença digital se fragmenta.
É por isso que a bio link — uma página única com todos os contatos e plataformas do ministério — se tornou uma necessidade básica para líderes que levam a sério a presença digital.
Com uma boa página no LuzLinks, qualquer pessoa que encontra o ministério em qualquer plataforma chega ao mesmo lugar: uma porta de entrada organizada, com links para as lives, o grupo da comunidade, a agenda, e como contribuir com o ministério.
---
## Princípios para um Ministério Digital Saudável
### 1. Autenticidade Acima de Produção
Uma câmera de celular com iluminação natural e mensagem genuína supera uma produção cara e vazia. As pessoas percebem autenticidade.
### 2. Consistência é mais valiosa que perfeição
Publicar toda semana com qualidade razoável é mais eficaz do que publicar raramente com qualidade impecável. O algoritmo e a audiência valorizam consistência.
### 3. O digital serve o presencial
A presença digital deve convidar pessoas para a comunidade real, não substituí-la. Toda estratégia digital deve ter, em algum ponto, um caminho de volta para o encontro presencial.
### 4. Proteja a privacidade da congregação
Histórias de transformação são poderosas — mas exigem autorização explícita de quem as vive. Rosto de crianças, situações de crise, momentos íntimos de culto: antes de publicar, pergunte-se se a pessoa envolvida autorizaria.
### 5. Descanse do digital
O líder que nunca se desconecta esgota sua capacidade de ouvir a voz de Deus e de sua congregação. Dias de silêncio, de oração sem câmera, de comunhão sem story — são tão importantes quanto a presença online.
---
## Conclusão
A tecnologia não mudou a mensagem do evangelho. Mas mudou profundamente o alcance e os meios pelos quais essa mensagem pode ser levada.
Líderes que abraçam o digital com discernimento — entendendo seus limites, evitando suas armadilhas, e usando-o como ferramenta a serviço do Reino — têm hoje uma capacidade missionária sem precedentes na história da Igreja.
O desafio não é técnico. É espiritual: usar bem o que temos disponível, sem nos perdermos no processo.
---
**Quer centralizar a presença digital do seu ministério?**
Crie sua página no LuzLinks e ofereça à sua congregação uma porta de entrada organizada para tudo que você produz.
[Criar minha página no LuzLinks →](https://luzlinks.site/)
---
**Última atualização:** Janeiro 2026

View File

@ -1,190 +0,0 @@
---
title: "Como Criar Sua Bio de Fé do Zero"
description: "Um guia prático para pastores, líderes e ministérios que querem criar uma página de links organizada, bonita e que realmente represente o trabalho do ministério online."
keywords: "bio link pastor, como criar pagina ministerio, presenca digital igreja, links para pastores, ministerio digital"
author: "Equipe LuzLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/bio-de-fe.jpg"
culture: "pt-BR"
category: "ministerio"
---
# Como Criar Sua Bio de Fé do Zero
Você já precisou enviar vários links diferentes para alguém que queria saber mais sobre o seu ministério? Link do YouTube, link do grupo do WhatsApp, link da agenda, Pix para dízimos… cada coisa num lugar diferente.
Existe uma forma muito mais simples de resolver isso: uma bio de links. Uma página única que reúne tudo que seu ministério oferece online, pronta para ser compartilhada com um só endereço.
Este guia mostra como criar a sua do zero, mesmo que você não tenha experiência com tecnologia.
---
## Este artigo é para você se...
- Você é pastor, padre, líder ou membro ativo de um ministério
- Você manda links diferentes para a congregação e quer centralizar tudo num único endereço
- Você ainda não tem nenhuma bio de links — ou tem uma desatualizada
- Você não precisa de experiência técnica: tudo aqui é feito pelo celular ou computador, sem programação
**O que você encontra aqui:** como escolher e organizar os links do ministério, o que escrever na descrição, erros comuns para evitar e o passo a passo completo para publicar.
---
## O que é uma bio de links?
Uma bio de links é uma página simples na internet com o seu nome, uma foto, uma descrição breve e uma lista de links para tudo que você quer que as pessoas encontrem.
Funciona assim: você cria uma página no LuzLinks, configura seus links, e recebe um endereço único — como `luzlinks.site/pastor/seu-nome`. Esse endereço vai na bio do Instagram, no perfil do WhatsApp, no final de cada vídeo do YouTube, em qualquer lugar que você queira.
Quem clicar chega numa página organizada com acesso a tudo.
---
## Antes de criar: o que você precisa ter em mãos
Antes de começar, separe estas informações:
**Foto de perfil**
Use uma foto de rosto clara, com boa iluminação. Não precisa ser profissional, mas precisa ser nítida. Uma foto em frente a uma janela iluminada já resolve bem.
**Nome do ministério ou do pastor**
Decida se vai usar seu nome pessoal, o nome da igreja ou o nome do ministério. Se você tem ambos, pode colocar "Pastor João Silva — Igreja Esperança".
**Descrição breve (até 2 linhas)**
O que você faz? Para quem? Onde?
Exemplo: *"Pastor evangélico em São Paulo. Pregações, estudos bíblicos e agenda de cultos."*
**Lista de links que você quer incluir**
Anote todos os links que você usa. Depois você vai organizar por prioridade.
---
## Quais links colocar (e em que ordem)
A ordem importa. As pessoas clicam mais nos primeiros links, então coloque o mais importante no topo.
### Sugestão de ordem para ministérios:
**1. Canal do YouTube**
Se você posta pregações ou estudos bíblicos, este é o link mais valioso. Uma pessoa que acabou de descobrir seu ministério vai querer assistir antes de qualquer outra coisa.
**2. Grupo do WhatsApp ou Telegram**
A comunidade da sua congregação fica aqui. Facilita muito para novos interessados entrarem em contato.
**3. Agenda de cultos ou eventos**
Um link direto para onde você divulga os próximos eventos — pode ser uma página do Instagram, um Google Agenda público ou um link simples.
**4. Dízimos e doações**
Se o seu ministério recebe dízimos ou ofertas pelo Pix, inclua um link aqui. Pode ser uma página de doação, um link do Vakinha, ou um link que abre o Pix no celular.
**5. Instagram**
Para quem quer acompanhar o dia a dia do ministério.
**6. Site da igreja (se tiver)**
Informações mais completas, histórico, endereço físico, horários fixos.
**7. Material para download (se tiver)**
Apostilas, hinários, materiais de célula — tudo que você distribui pode ter um link direto.
---
## Como escrever a descrição da sua página
Muitos líderes escrevem uma descrição vaga como "Ministério da Graça". Isso diz muito pouco para quem ainda não te conhece.
Uma boa descrição responde três perguntas rápidas:
- Quem é você?
- O que você oferece?
- Onde/como a pessoa pode participar?
**Exemplos práticos:**
*Versão genérica (evitar):*
> "Ministério da Graça — Servindo ao Senhor"
*Versão mais eficaz:*
> "Pastor João Silva — Pregações toda semana no YouTube. Cultos às quartas e domingos em São Paulo. Comunidade aberta no WhatsApp."
A segunda versão faz a pessoa entender em segundos se quer continuar explorando ou não.
---
## Dicas para a foto e aparência da página
**Foto de perfil:** Olhos nos olhos com a câmera, expressão acolhedora, fundo neutro ou de preferência relacionado ao ministério (púlpito, biblia, bandeira da igreja).
**Tema visual:** Escolha um tema que combine com a identidade do ministério. Tons de azul e branco transmitem paz e serenidade. Tons de dourado e vinho remetem a tradição e reverência. Evite cores muito agitadas — simplicidade passa mais confiança.
**Nome na página:** Use o nome pelo qual você é conhecido, não necessariamente o nome legal. "Pastor João" pode ser mais reconhecível do que "João Ferreira da Silva".
---
## Erros comuns ao criar uma bio de fé
**Colocar links quebrados**
Antes de publicar, clique em cada link para confirmar que está funcionando. Links quebrados afastam as pessoas.
**Usar descrição muito longa**
A bio não é um sermão. Duas ou três linhas são o suficiente. Quem quiser saber mais vai clicar nos links.
**Não atualizar quando algo muda**
Se você cria um novo grupo do WhatsApp, muda o canal do YouTube ou abre um novo site, lembre de atualizar a bio também.
**Ignorar a foto de perfil**
Uma página sem foto parece abandonada. Mesmo que seja uma imagem simples, coloque uma foto.
---
## Passo a passo para criar no LuzLinks
1. Acesse [luzlinks.site](https://luzlinks.site/) e crie sua conta com email ou Google
2. Clique em "Criar nova página"
3. Escolha a categoria que melhor representa seu ministério (pastor, padre, líder, etc.)
4. Defina sua URL personalizada — ex: `luzlinks.site/pastor/joao-silva`
5. Adicione foto de perfil e descrição
6. Adicione seus links na ordem de prioridade
7. Escolha um tema visual
8. Envie para moderação e aguarde a aprovação
Depois de aprovada, compartilhe o link em todas as suas redes e coloque na bio do Instagram e no status do WhatsApp.
---
## Por onde começar hoje?
Se você está começando do zero, não espere ter tudo pronto. Comece com o básico:
- Foto de perfil
- Descrição de duas linhas
- Três ou quatro links principais
Depois você vai ajustando conforme usa. Uma página simples e funcionando é muito melhor do que uma página perfeita que nunca sai do papel.
---
## O que pode não funcionar
- **Sem foto de perfil:** páginas sem foto têm taxa de saída muito mais alta — as pessoas não confiam num perfil anônimo
- **Links quebrados:** um link que não abre faz a pessoa desistir e não voltar; cheque tudo antes de publicar
- **Descrição genérica:** "Ministério da Graça" sem contexto não converte — seja específico sobre o que você oferece
- **Página desatualizada:** um horário de culto errado ou um grupo lotado (sem acesso) frustra quem quer participar
---
## Leia também
- [Como Divulgar Cultos e Eventos pelo WhatsApp com um Único Link](/artigos/como-divulgar-cultos-e-eventos-pelo-whatsapp)
- [Por Que Pastores Precisam de Presença Digital](/artigos/por-que-pastores-precisam-de-presenca-digital)
- [Como Receber Dízimos Online no Seu Ministério](/artigos/como-receber-dizimos-online-no-seu-ministerio)
---
**Pronto para centralizar a presença digital do seu ministério?**
Crie sua página no LuzLinks e ofereça à sua congregação um único link para tudo.
[Criar minha bio de fé →](https://luzlinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,189 +0,0 @@
---
title: "Como Divulgar Cultos e Eventos pelo WhatsApp com um Único Link"
description: "Chega de mandar vários links diferentes no grupo da igreja. Veja como centralizar a agenda, o link da live e as informações do culto em uma única página que você compartilha com facilidade."
keywords: "divulgar culto whatsapp, agenda igreja online, link ministerio whatsapp, como divulgar eventos religiosos, bio link pastor whatsapp"
author: "Equipe LuzLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/cultos-whatsapp.jpg"
culture: "pt-BR"
category: "ministerio"
---
# Como Divulgar Cultos e Eventos pelo WhatsApp com um Único Link
Todo líder religioso que usa o WhatsApp para se comunicar com a congregação conhece bem essa situação: é véspera de culto, você precisa mandar o lembrete para o grupo, mas junto precisa enviar o link da live, o endereço no Google Maps, o link para o pedido de oração e talvez o Pix para a oferta especial.
Resultado: uma mensagem enorme, cheia de links, que a maioria das pessoas simplesmente para de ler no meio.
Existe uma solução bem mais simples para isso.
---
## Este artigo é para você se...
- Você já usa grupos de WhatsApp para se comunicar com a congregação
- Você manda vários links separados nos grupos e sente que as pessoas se perdem
- Você quer que um novo contato encontre tudo em um só lugar, sem precisar perguntar
- Você quer economizar tempo na comunicação semanal do ministério
**O que você encontra aqui:** como estruturar a bio de links para eventos, quando atualizar o link da live, modelos de mensagem prontos para usar e como aplicar o mesmo link em outros canais.
---
## O problema com múltiplos links no WhatsApp
Quando você manda vários links numa mensagem, acontece algo previsível: as pessoas abrem um ou dois, ignoram o resto, e ficam sem as informações que precisariam ter visto.
Isso não é falta de atenção da congregação. É assim que as pessoas funcionam com mensagens longas — especialmente em grupos com muitas notificações.
Além disso, toda vez que algo muda (nova live, novo link do Zoom, endereço diferente), você precisa mandar tudo de novo. O grupo começa a ficar poluído com mensagens repetidas.
---
## A solução: um único link que tem tudo
Com uma página no LuzLinks, você cria um endereço único — como `luzlinks.site/pastor/seu-nome` — que concentra tudo que a congregação precisa saber.
A mensagem do grupo vira algo assim:
> "Culto de quarta à noite às 19h. Acesse aqui todos os links: luzlinks.site/pastor/joao-silva"
Uma linha. Um link. Tudo que a pessoa precisa está lá quando ela clicar.
---
## Como estruturar a página para divulgação de eventos
O segredo está em como você organiza os links. Para uma congregação que acompanha eventos regularmente, esta ordem funciona bem:
**1. Link da live (transmissão ao vivo)**
Esse é o mais urgente e deve ficar no topo. Quando tem culto ao vivo, a pessoa que chega tarde ou não pôde ir precisa encontrar esse link rápido.
Dica: se o link da live muda toda semana (como acontece com algumas plataformas), use um link que aponte para a playlist do canal — assim você não precisa atualizar a bio a cada culto.
**2. Agenda de cultos**
Um link para onde está a programação completa. Pode ser uma nota no Instagram, um post fixado, ou uma página do Google Sites com os horários.
**3. Endereço da igreja (Google Maps)**
Para quem vai presencialmente. O link do Maps abre direto no aplicativo de navegação do celular.
**4. Grupo do WhatsApp principal**
Para novos membros que chegaram pelo link e ainda não estão no grupo.
**5. Pedidos de oração**
Um formulário simples (Google Forms funciona muito bem) ou um link direto para mensagem no WhatsApp.
**6. Dízimos e ofertas**
Pix, link de doação ou a plataforma que seu ministério usa.
**7. Canal do YouTube**
Para quem quer ver pregações anteriores.
---
## Quando atualizar o link da live
A maior dúvida de quem usa essa estratégia é: "E quando o link da live muda?"
**Se você transmite pelo YouTube:** O link do canal não muda nunca. Ao invés de linkar uma live específica, link o canal. Quem acessa vai ver a live no topo automaticamente quando ela estiver acontecendo.
**Se você transmite pelo Zoom:** O ID da reunião costuma ser sempre o mesmo quando você agenda recorrente. Verifique nas configurações do Zoom se pode usar "ID de reunião pessoal" — assim o link nunca muda.
**Se você transmite pelo Instagram Live:** Infelizmente não existe link direto para lives ao vivo no Instagram. Nesse caso, link seu perfil do Instagram e oriente a congregação a abrir o app e ir direto ao seu perfil.
---
## Modelo de mensagem para o grupo
Com a bio montada, você pode usar sempre a mesma estrutura de mensagem:
---
*Modelo para culto semanal:*
> 📅 Culto de quarta — hoje às 19h
>
> ▶️ Live: [link direto caso seja específico]
> 📍 Presencial: [endereço]
>
> Tudo em um só lugar: luzlinks.site/pastor/seu-nome
---
*Modelo para evento especial:*
> 🙏 Retiro de jovens — sábado, 10 de maio
>
> Informações completas, como chegar e inscrições:
> luzlinks.site/pastor/seu-nome
---
Perceba que a mensagem é curta. O link faz o trabalho pesado.
---
## Coloque o link no status do WhatsApp
Além do grupo, coloque o link da sua bio no status do WhatsApp. O status fica visível por 24 horas para todos os seus contatos, não apenas para quem está no grupo da igreja.
Isso alcança pessoas que você conhece pessoalmente mas que ainda não fazem parte da congregação — um contato de trabalho, um familiar, um vizinho que você encontra raramente.
Uma mensagem simples no status:
> "Culto hoje às 19h — link de tudo aqui 👆 luzlinks.site/pastor/seu-nome"
---
## Usando o link em outros lugares
O mesmo link que você usa no WhatsApp funciona em qualquer lugar:
**Bio do Instagram:** cole na bio e sempre que fizer stories peça para a pessoa "clicar no link da bio"
**Cartões de visita:** se você distribui cartões físicos, o link da bio pode ser o único endereço que você precisa colocar
**Assinatura de email:** coloque no final dos seus emails como "Acesse minha agenda: luzlinks.site/pastor/seu-nome"
**Final de pregações gravadas:** ao editar os vídeos, inclua o link nas telas finais e na descrição do YouTube
---
## Resumo prático
A bio de links não resolve apenas o problema do WhatsApp. Ela resolve o problema de ter presença digital fragmentada — cada plataforma com uma informação diferente, a congregação sem saber onde olhar.
Com um único link atualizado, você:
- Manda mensagens mais curtas e eficazes
- Facilita para pessoas novas encontrarem tudo de uma vez
- Reduz as mensagens repetidas nos grupos
- Tem um único lugar para atualizar quando algo muda
---
## O que pode não funcionar
- **Link da live que muda toda semana:** se você precisa atualizar o link da bio a cada culto, considere linkar o canal do YouTube ao invés da live específica — o canal sempre mostra a transmissão ativa no topo
- **Grupos lotados no WhatsApp:** se seu grupo atingiu o limite de participantes, o link de convite para e funcionar; mantenha a bio atualizada com o link do grupo atual
- **Bio usada apenas no grupo principal:** o maior potencial do link único é alcançar pessoas *fora* do grupo já existente — divulgue também no status do WhatsApp, no Instagram e em outras redes
- **Página sem agenda atualizada:** uma agenda desatualizada (com cultos que já passaram) confunde quem está tentando participar pela primeira vez
---
## Leia também
- [Como Criar Sua Bio de Fé do Zero](/artigos/como-criar-sua-bio-de-fe-do-zero)
- [Como Receber Dízimos Online no Seu Ministério](/artigos/como-receber-dizimos-online-no-seu-ministerio)
- [Por Que Pastores Precisam de Presença Digital](/artigos/por-que-pastores-precisam-de-presenca-digital)
---
**Quer configurar sua página e simplificar a comunicação com a congregação?**
Crie sua bio de fé no LuzLinks e comece a usar um link para tudo.
[Criar minha página →](https://luzlinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,177 +0,0 @@
---
title: "Como Receber Dízimos Online no Seu Ministério"
description: "Um guia prático sobre as opções disponíveis para receber dízimos, ofertas e doações online — com foco em segurança, transparência e facilidade para a congregação."
keywords: "dizimo online, receber oferta online, pix igreja, doacao ministerio, contribuicao igreja digital"
author: "Equipe LuzLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/dizimos-online.jpg"
culture: "pt-BR"
category: "ministerio"
---
# Como Receber Dízimos Online no Seu Ministério
A oferta no culto presencial tem um significado especial — é um gesto de fé, um ato de adoração feito em comunidade. Isso não muda com o digital.
Mas a realidade é que cada vez menos pessoas carregam dinheiro em espécie. E membros que assistem remotamente, que estão viajando, ou que simplesmente esqueceram o envelope em casa, podem querer contribuir de outras formas.
Disponibilizar opções de contribuição online não é substituir a oferta presencial — é incluir mais pessoas na prática.
---
## Este artigo é para você se...
- Você lidera um ministério e quer facilitar a contribuição de membros que assistem remotamente
- Você percebe que parte da congregação não contribui simplesmente porque não carrega dinheiro em espécie
- Você quer saber quais ferramentas existem no Brasil para receber contribuições online, com custo baixo ou zero
- Você quer fazer isso com transparência e sem complicar a gestão financeira do ministério
**O que você encontra aqui:** as principais opções de recebimento digital disponíveis no Brasil (Pix, plataformas de doação, internacionais), como comunicar isso à congregação e boas práticas de transparência financeira.
---
## Por que oferecer opções digitais de dízimo
**Membros remotos também querem contribuir**
Quem acompanha o ministério pelo YouTube ou vive em outra cidade frequentemente não tem como participar da oferta presencial. Uma forma digital de contribuir mantém esse vínculo.
**A geração mais jovem não usa dinheiro em espécie**
Pessoas entre 20 e 35 anos raramente têm notas no bolso. Se a única forma de contribuir é com dinheiro físico, essa parte da congregação simplesmente não contribui — não por falta de vontade, mas por falta de meio.
**Maior regularidade nas contribuições**
Uma pessoa que configura um Pix recorrente ou uma assinatura de contribuição tende a ser mais consistente do que uma que depende de ter dinheiro em mãos todo domingo.
**Facilidade de prestação de contas**
Contribuições digitais geram registro automático, o que facilita a gestão financeira e a transparência com a congregação.
---
## As principais opções disponíveis no Brasil
### Pix
O Pix é a opção mais simples e com menos burocracia. Qualquer banco ou fintech oferece gratuitamente, e a transferência é imediata.
**Como disponibilizar:**
- Crie uma chave Pix com um email ou número dedicado ao ministério (diferente do pessoal)
- Gere um QR Code estático de cobrança — sem valor fixo, a pessoa decide o valor
- Coloque o QR Code nos cultos, nos slides de apresentação, e o link na sua bio do LuzLinks
**Vantagem:** sem taxa, imediato, qualquer pessoa com app de banco consegue usar
**Desvantagem:** não tem gestão automática de recorrência — é uma transferência avulsa cada vez
---
### Conta bancária para crédito (TED/DOC)
Para dízimos maiores ou pessoas mais acostumadas com transferência bancária tradicional, ter os dados bancários disponíveis é importante.
Mantenha esses dados em lugar acessível — na bio do LuzLinks, no site da igreja, nos grupos do WhatsApp.
---
### Plataformas de doação recorrente
Para ministérios que querem gestão mais profissional de contribuições:
**Vakinha**
Focado em campanhas pontuais (reforma da sede, compra de equipamento, projeto especial). Ótimo para arrecadações com objetivo definido.
**PagSeguro / Mercado Pago**
Permitem criar links de doação com ou sem valor fixo, e configurar doações recorrentes. Cobram uma taxa por transação (em torno de 3-5%).
**Gerencianet / Efí**
Plataformas financeiras brasileiras que oferecem cobranças recorrentes via Pix ou boleto. Mais adequadas para ministérios que querem estrutura financeira mais completa.
**Assinaturas pelo WhatsApp/Telegram**
Algumas igrejas criam grupos "de membros contribuintes" onde a contribuição é o critério de participação — combinando comunicação especial com a prática do dízimo.
---
### Para contribuições internacionais
Se você tem membros ou doadores no exterior, o PayPal e o Wise são opções viáveis. Ambos permitem receber transferências de outros países com taxas razoáveis.
---
## Como comunicar as opções de forma eficaz
Disponibilizar as formas de contribuição não é suficiente — você precisa comunicar com clareza e naturalidade.
**No culto presencial:**
Reserve um momento breve para mostrar o QR Code do Pix no telão. Uma frase simples funciona bem: *"Para quem prefere contribuir pelo celular, o Pix está disponível aqui."*
**Nas transmissões ao vivo:**
Mencione as formas de contribuição no início e no final da transmissão. Não precisa ser uma campanha — uma referência breve e direta é suficiente.
**Na sua bio do LuzLinks:**
Inclua um link de dízimos na sua página. Algo como "Dízimos e Ofertas" com um link para o Pix ou para a página de doação. Quando alguém descobre seu ministério e quer contribuir, esse link é o caminho mais direto.
**No grupo do WhatsApp:**
Fixe uma mensagem com todas as formas de contribuição para que novos membros consigam encontrar facilmente.
---
## Transparência: a base da confiança financeira
Independente do método usado, a transparência é o que constrói e mantém a confiança da congregação nas finanças do ministério.
**Boas práticas:**
- Apresente um relatório financeiro periódico para a congregação (mensalmente ou trimestralmente)
- Explique para onde vai o dinheiro das ofertas e dízimos
- Tenha mais de uma pessoa responsável pela gestão financeira — nunca uma pessoa só
- Separe contas: conta pessoal do pastor e conta do ministério devem ser sempre diferentes
Ministérios que tratam as finanças com transparência raramente enfrentam crises de confiança. E transparência é mais fácil quando as contribuições são digitais, porque o registro é automático e consultável.
---
## Um sistema simples que funciona
Para a maioria dos ministérios, este conjunto básico já atende bem:
1. **Pix com QR Code** para contribuições espontâneas no culto e online
2. **Link do Pix ou página de doação** na bio do LuzLinks
3. **Menção breve nos cultos** sobre as formas disponíveis
4. **Relatório financeiro mensal** compartilhado com a liderança e, em versão resumida, com a congregação
Você pode começar com isso hoje, sem custo e sem complicação.
---
## Sobre a parte espiritual
Este artigo focou nos aspectos práticos — ferramentas, plataformas, comunicação. Mas é importante lembrar que o dízimo e a oferta têm um significado espiritual que vai além da logística.
A facilidade de contribuição digital deve ser uma ferramenta a serviço da prática de fé, não uma substituição do seu significado. O que você ensina sobre mordomia e contribuição é tão importante quanto as ferramentas que você disponibiliza.
Use o digital para remover obstáculos práticos — não para transformar a oferta em algo mecânico.
---
## O que pode não funcionar
- **Usar apenas o Pix pessoal do pastor:** misturar finanças pessoais e do ministério é a raiz de muitos problemas de confiança — crie um email e chave Pix separados para o ministério
- **Não comunicar as formas disponíveis:** disponibilizar sem avisar é como ter uma porta aberta num quarto escuro; mencione regularmente nos cultos e nas transmissões
- **Depender de uma única plataforma:** plataformas mudam políticas e taxas; ter Pix como opção principal e uma segunda alternativa garante continuidade
- **Não prestar contas:** contribuições digitais sem prestação de contas periódica geram desconfiança; um relatório simples, mesmo que informal, faz diferença
---
## Leia também
- [Como Criar Sua Bio de Fé do Zero](/artigos/como-criar-sua-bio-de-fe-do-zero)
- [Como Divulgar Cultos e Eventos pelo WhatsApp com um Único Link](/artigos/como-divulgar-cultos-e-eventos-pelo-whatsapp)
- [Por Que Pastores Precisam de Presença Digital](/artigos/por-que-pastores-precisam-de-presenca-digital)
---
**Coloque o link dos dízimos na sua bio de fé.**
Com uma página no LuzLinks, sua congregação encontra todas as formas de contribuição em um só lugar.
[Criar minha página no LuzLinks →](https://luzlinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,168 +0,0 @@
---
title: "Por Que Pastores Precisam de Presença Digital"
description: "Uma análise honesta de por que a presença digital deixou de ser opcional para líderes religiosos — e como começar sem abrir mão da autenticidade e dos valores do ministério."
keywords: "pastor presenca digital, igreja digital, lider religioso internet, ministerio online, como comecar presenca digital pastor"
author: "Equipe LuzLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/presenca-digital-pastor.jpg"
culture: "pt-BR"
category: "ministerio"
---
# Por Que Pastores Precisam de Presença Digital
Há um argumento que muitos líderes religiosos usam para resistir ao mundo digital: "Meu trabalho é com pessoas, não com telas."
É um argumento honesto. E parcialmente correto — o coração do ministério é mesmo o encontro real, a comunhão presencial, o olho no olho.
Mas esse argumento ignora uma realidade importante: as pessoas que você quer alcançar estão online. E se você não está lá, alguém com uma mensagem completamente diferente está.
---
## Este artigo é para você se...
- Você ainda não tem presença digital — ou tem algo desatualizado que não representa bem o seu ministério
- Você já ouviu falar em "estar online" mas não está convencido de que faz diferença para um ministério
- Você quer entender os riscos reais de não estar no digital, com exemplos práticos
- Você quer começar de forma simples, sem transformar a vida em produção de conteúdo
**O que você encontra aqui:** por que a presença digital mudou de opcional para necessária, os custos reais de não estar online, e o mínimo que já faz diferença — com passos concretos para começar.
---
## Como as pessoas encontram novas igrejas hoje
Vinte anos atrás, uma família que se mudava para uma cidade nova perguntava para vizinhos ou parentes onde havia uma boa igreja. Hoje, ela abre o Google e digita "igreja evangélica perto de mim" ou "pastor presbitariano em [cidade]".
Se a sua congregação não aparece nessa busca, simplesmente não existe para essa família.
Pesquisas no Brasil mostram que mais de 70% das pessoas que começaram a frequentar uma nova comunidade religiosa nos últimos anos a descobriram primeiro online — seja pelo YouTube, pelo Instagram ou por uma pesquisa no Google.
A janela de entrada da sua congregação não é mais apenas a porta física da sede. É também o que aparece quando alguém pesquisa sobre seu ministério na internet.
---
## A presença digital não substitui o presencial
Antes de continuar, é importante deixar isso claro: presença digital não é concorrente do ministério presencial. É complemento.
Um culto ao vivo transmitido pelo YouTube não substitui a comunhão de estar presente no templo. Uma pregação no Instagram não tem a profundidade de um retiro espiritual.
Mas a live pode alcançar um membro que está doente e não pode sair. A pregação no Instagram pode ser o primeiro contato de alguém que nunca pisou numa igreja mas está buscando algo diferente.
O ministério digital abre portas. O ministério presencial é o que acontece quando as pessoas entram por essas portas.
---
## O custo de não estar online
Quando um ministério não tem presença digital, o custo não é apenas invisibilidade. São oportunidades concretas que se perdem:
**Pessoas que estão buscando mas não encontram**
Alguém com dúvidas espirituais pesquisa no YouTube por respostas. Encontra vários canais de líderes religiosos. O seu não está lá.
**Membros que se afastam por falta de conexão**
Um membro que se muda para outra cidade ou que passa por um período de dificuldade para comparecer presencialmente perde o vínculo com a comunidade se não há uma forma de continuar conectado.
**Jovens que não se identificam com o que veem**
Para a geração que cresceu com smartphone, uma comunidade que não tem presença digital parece desatualizada — mesmo que o conteúdo da mensagem seja profundo e relevante.
**Crises sem canais de comunicação**
Em situações de urgência — cancelamento de evento, mudança de horário, situação de emergência — um ministério sem canais digitais depende de ligações individuais ou grupos de WhatsApp desorganizados.
---
## O mínimo que faz diferença
Você não precisa ter uma equipe de produção de conteúdo ou postar todos os dias. O mínimo que já muda a situação de um ministério:
### 1. Um ponto de referência digital
Uma página simples com: nome do ministério, endereço, horários de culto, contato e links para redes sociais. Isso já garante que alguém que pesquisa seu nome encontre algo.
O LuzLinks foi criado exatamente para isso — criar esse ponto de referência de forma simples, sem precisar montar um site completo.
### 2. Presença consistente em uma plataforma
Escolha uma plataforma e mantenha-a atualizada regularmente. YouTube funciona bem para ministérios com pregações. Instagram funciona bem para comunidade e rotina. WhatsApp funciona bem para comunicação interna.
Você não precisa estar em todos os lugares. Mas precisa estar em algum lugar de forma consistente.
### 3. Comunicação clara sobre eventos
As pessoas precisam saber quando e onde os cultos acontecem. Isso parece óbvio, mas é surpreendente quantos ministérios não têm essa informação em lugar nenhum online.
---
## Objeções comuns — e respostas honestas
**"Não tenho tempo para ficar postando."**
Presença digital não precisa ser conteúdo novo todos os dias. Uma pregação postada por mês no YouTube, um post de agenda no Instagram por semana e um grupo de WhatsApp organizado já são suficientes para começar.
**"Tenho medo de expor minha vida pessoal."**
Você define o que compartilha. Muitos líderes mantêm uma presença digital completamente focada no ministério — pregações, estudos, agenda — sem expor nada da vida privada. Isso é possível e saudável.
**"Minha congregação é mais velha e não usa redes sociais."**
Parte da sua congregação atual pode não usar. Mas as pessoas que você ainda não alcançou — os filhos desses membros, a família de quem já frequenta — usam. E quando esses membros mais velhos indicam sua igreja para alguém, a primeira coisa que essa pessoa vai fazer é pesquisar online.
**"Não sei nada de tecnologia."**
Esse argumento ficou mais fraco com o tempo. As ferramentas de hoje são projetadas para serem usadas por qualquer pessoa. Criar uma página no LuzLinks, por exemplo, leva menos de 30 minutos e não exige nenhum conhecimento técnico.
---
## Por onde começar
Se você ainda não tem nenhuma presença digital, comece pelos fundamentos:
**Passo 1:** Crie uma conta no LuzLinks e monte uma página simples com os links do seu ministério. Isso leva menos de meia hora.
**Passo 2:** Coloque esse link na bio do Instagram e no status do WhatsApp.
**Passo 3:** Se você já grava os cultos em vídeo, comece a postar no YouTube. Mesmo sem edição — uma gravação direta da câmera do celular já serve para começar.
**Passo 4:** Mantenha as informações atualizadas. Horários, endereço, links — nada de informação desatualizada.
Depois que esses fundamentos estiverem funcionando, você avança com mais tranquilidade para outras plataformas, se quiser.
---
## A pergunta certa
A pergunta não é "por que devo estar online?" — porque as razões são claras.
A pergunta é: "Como posso estar online de uma forma que seja autêntica ao meu ministério e sustentável para a minha rotina?"
E essa é uma pergunta que você pode responder com calma, no seu ritmo, começando pelo mais simples.
---
## O que pode não funcionar
- **Tentar estar em todas as plataformas ao mesmo tempo:** leva à superficialidade em todas. Comece com uma e domine antes de expandir
- **Postar esporadicamente:** o algoritmo e a audiência valorizam consistência. Uma postagem por semana, sempre, supera dez postagens num mês e sumindo depois
- **Separar completamente digital do presencial:** a presença digital deve convidar para o encontro real, não substituí-lo. Ministérios que tratam os dois como mundos separados perdem o sentido de comunidade
- **Não monitorar o que está funcionando:** criar conteúdo sem nunca verificar se as pessoas estão chegando é trabalho sem aprendizado
---
## Leia também
- [Como Criar Sua Bio de Fé do Zero](/artigos/como-criar-sua-bio-de-fe-do-zero)
- [Como Divulgar Cultos e Eventos pelo WhatsApp com um Único Link](/artigos/como-divulgar-cultos-e-eventos-pelo-whatsapp)
- [Como Receber Dízimos Online no Seu Ministério](/artigos/como-receber-dizimos-online-no-seu-ministerio)
---
**Pronto para dar o primeiro passo?**
Crie a página do seu ministério no LuzLinks e garanta que as pessoas que estão buscando uma comunidade de fé possam te encontrar.
[Criar minha página no LuzLinks →](https://luzlinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,287 +0,0 @@
---
title: "Como Criar sua Página no LuzLinks: Guia Completo para Líderes Religiosos"
description: "Passo a passo para pastores, padres, líderes e ministérios criarem sua página profissional no LuzLinks e alcançarem mais pessoas com sua mensagem de fé."
keywords: "luzlinks tutorial, página pastor, bio links ministerio, presença digital iglesia, como criar página pastor"
author: "Equipe LuzLinks"
date: 2026-01-25
lastMod: 2026-01-25
image: "/images/tutoriais/pastor-luzlinks.jpg"
culture: "pt-BR"
category: "ministerio"
---
# Como Criar sua Página no LuzLinks: Guia Completo para Líderes Religiosos
Sua congregação está em vários lugares ao mesmo tempo: no Instagram, no YouTube, no WhatsApp, no Telegram, na agenda de eventos — e você fica repetindo os mesmos links toda semana. O LuzLinks resolve isso com uma página única que reúne tudo.
Este guia mostra o passo a passo completo para líderes religiosos criarem sua bio de fé.
---
## Por Que Líderes Religiosos Precisam de uma Página de Links
### A Realidade da Igreja Digital
Depois da pandemia, toda congregação se tornou também digital. Sua comunidade espera encontrar:
- O link da live do culto
- A série de estudos bíblicos no YouTube
- O grupo do WhatsApp dos membros
- A agenda de eventos do mês
- Como fazer dízimos e ofertas online
- O endereço da sede para quem ainda não foi
Quando alguém perguntar "onde acompanho o ministério?", você precisa ter uma resposta simples. Uma URL só.
---
## Passo 1: Criar sua Conta no LuzLinks
### 1.1. Acesse o Site
Vá para [luzlinks.site](https://luzlinks.site) e clique em **"Entrar"** no menu.
### 1.2. Login Social (Recomendado)
Escolha login com **Google** ou **Microsoft**. É mais rápido e seguro — sem necessidade de criar nova senha.
> **Dica:** Use o email principal do ministério para criar a conta. Assim, qualquer pessoa autorizada da equipe pode acessar.
---
## Passo 2: Criar Sua Primeira Página
Após o login, clique em **"Criar Minha Página"**.
### Escolhendo o Nome da Página
O nome deve ser claro e reconhecível pela sua congregação:
| Tipo de Líder | Exemplo de Nome |
|---|---|
| Pastor titular | Pr. João Silva — Igreja Graça Viva |
| Padre | Pe. Carlos Mendes — Paróquia São José |
| Missionário | Missionário Paulo — Missão África |
| Ministério de louvor | Ministério Ágape |
| Igreja geral | Igreja Batista Central — Belo Horizonte |
| Líder de jovens | Pr. André — Juventude Transformada |
### Escolhendo o Slug (URL)
Sua URL no LuzLinks seguirá o padrão:
```
luzlinks.site/ministerio/seu-slug
```
Boas opções:
```
pr-joao-silva → luzlinks.site/ministerio/pr-joao-silva
igreja-graca-viva → luzlinks.site/ministerio/igreja-graca-viva
ministerio-agape → luzlinks.site/ministerio/ministerio-agape
pe-carlos-mendes → luzlinks.site/ministerio/pe-carlos-mendes
```
> **Dica:** Escolha algo simples que qualquer membro consiga digitar de memória.
### Escrevendo a Descrição
Seja claro sobre quem você é e o que sua comunidade encontra aqui:
**Exemplo — Pastor:**
```
Pr. João Silva | Igreja Graça Viva — São Paulo/SP
Pregação, estudos bíblicos e agenda de cultos.
Domingo às 9h e 19h | Quarta às 20h (online e presencial)
```
**Exemplo — Ministério de Louvor:**
```
Ministério Ágape | Louvor e adoração
Novas músicas toda semana no YouTube
Shows, CDs e partituras disponíveis abaixo
```
**Exemplo — Padre:**
```
Pe. Carlos Mendes | Paróquia São José — Rio de Janeiro
Missas, catequese e eventos paroquiais
Confissões: terça e quinta, 16h às 18h
```
---
## Passo 3: Montando Seus Links
### Os Links Mais Importantes para Ministérios
**1. 📺 Canal do YouTube (Lives e Pregações)**
```
Título: Assistir Pregações e Lives
URL: https://youtube.com/@seuministerio
```
> Coloque no topo. É o que mais pessoas buscam.
**2. 📱 Grupo do WhatsApp**
```
Título: Entrar no Grupo da Igreja
URL: https://chat.whatsapp.com/seu-link-de-convite
```
> Limite de 1024 membros por grupo. Se sua congregação for maior, use o Telegram.
**3. 📅 Agenda de Cultos e Eventos**
```
Título: Ver Próximos Eventos
URL: https://linkdoseusite.com/agenda
```
> Pode ser uma página do Google Sites, Notion, ou o site da igreja.
**4. 🙏 Dízimos e Ofertas Online**
```
Título: Contribuir com o Ministério
URL: https://pix.bcb.gov.br/qr/seu-qr-code
```
> Ou: link para a página de doação do site da igreja, PagSeguro, Stripe etc.
**5. 📖 Série Atual de Estudos**
```
Título: Série: [Nome da Série] — Episódio Atual
URL: Link direto para o episódio mais recente
```
> Atualize semanalmente. Mostra que a página está viva.
**6. 📸 Instagram da Igreja**
```
Título: Seguir no Instagram
URL: https://instagram.com/seuministerio
```
**7. 📍 Endereço da Sede**
```
Título: Como Chegar — Sede São Paulo
URL: https://maps.google.com/?q=Rua+da+Igreja+123+São+Paulo
```
**8. ✉️ Email de Contato**
```
Título: Falar com Secretaria
URL: mailto:secretaria@suaigreja.com.br
```
---
## Passo 4: Escolhendo o Tema Visual
O LuzLinks oferece temas adequados para conteúdo espiritual. Recomendamos:
- **Clássico ou Minimalista** para igrejas tradicionais (católicas, luteranas, presbiterianas)
- **Gradiente suave ou Azul** para igrejas evangélicas pentecostais
- **Dourado ou Roxo** para ministérios de louvor e adoração
- **Verde ou Natural** para missões e trabalho social
> O tema pode ser alterado a qualquer momento sem precisar refazer a página.
---
## Passo 5: Submeter para Aprovação
Após configurar tudo, clique em **"Submeter para Moderação"**. Nossa equipe revisa em até 48 horas.
### O Que a Equipe Verifica
- Links funcionando e direcionando para conteúdo apropriado
- Informações claras e sem conteúdo enganoso
- Conteúdo compatível com a proposta da plataforma
---
## Passo 6: Divulgando Sua Página na Congregação
Com a página aprovada, divulgue por todos os canais:
### No Culto Presencial
Projete no telão durante os avisos:
```
"Encontre todos os nossos links e contatos em:
luzlinks.site/ministerio/sua-church"
```
### No Instagram
Atualize a bio:
```
Igreja Graça Viva | São Paulo
Cultos: Dom 9h e 19h | Qua 20h
🔗 Todos os links: luzlinks.site/ministerio/igreja-graca-viva
```
### No WhatsApp dos Membros
Envie uma mensagem no grupo:
```
Irmãos, agora temos uma página única com todos os nossos links:
cultos, agenda, YouTube, dízimos e mais.
👇 Salve e compartilhe:
luzlinks.site/ministerio/igreja-graca-viva
```
### No YouTube
Fixe o link como comentário pinado nos vídeos e inclua na descrição:
```
Todos os nossos contatos e próximos eventos:
luzlinks.site/ministerio/sua-church
```
---
## Manutenção da Página
Uma página atualizada é mais eficaz do que uma página perfeita e desatualizada.
**Atualizações semanais recomendadas:**
- Atualizar o link da série atual de estudos
- Adicionar links de eventos do mês
- Verificar se o grupo de WhatsApp não está cheio (link de convite expirado)
**Atualizações mensais:**
- Revisar se todos os links ainda funcionam
- Checar métricas no Dashboard: quais links têm mais cliques?
- Atualizar a descrição se houver mudança de horários de culto
---
## Dúvidas Frequentes
**Posso ter uma página para a igreja e outra para mim como pastor?**
Sim! Com o plano Premium você pode ter múltiplas páginas. Muitos líderes têm uma página pessoal e outra institucional da igreja.
**O link do grupo de WhatsApp expira. O que fazer?**
Sempre que renovar o link de convite, acesse o Dashboard e atualize o link na sua página. Leva menos de 1 minuto.
**Posso colocar link de canal privado (assinatura)?**
Sim, desde que o conteúdo seja compatível com a plataforma. O LuzLinks é focado em conteúdo espiritual e educativo.
**Posso ter a página em português e espanhol?**
Para congregações bilíngues, recomendamos duas páginas: uma em pt-BR e outra em es. O plano Premium permite isso.
---
## Conclusão
Criar sua página no LuzLinks é o primeiro passo para organizar a presença digital do seu ministério. Em vez de repetir links toda semana, você compartilha uma URL simples que sua congregação encontra tudo.
**Recapitulando:**
1. ✅ Criar conta com email do ministério
2. ✅ Definir nome e URL da página
3. ✅ Adicionar links essenciais (YouTube, WhatsApp, agenda, dízimos)
4. ✅ Escolher tema adequado ao ministério
5. ✅ Submeter para moderação
6. ✅ Divulgar para a congregação
[Criar minha página de fé →](https://luzlinks.site/)
---
**Última atualização:** Janeiro 2026

View File

@ -1,197 +0,0 @@
---
title: "Como Configurar Sua Bio para Ganhar Mais Seguidores"
description: "Sua bio é a porta de entrada do seu perfil. Veja como configurar uma página de links que converte visitantes em seguidores — e seguidores em assinantes."
keywords: "bio link seguidores, como ganhar seguidores instagram, bio link criadora, aumentar seguidores bio, configurar bio profissional"
author: "Equipe SpicyLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/bio-seguidores.jpg"
culture: "pt-BR"
category: "criadores"
---
# Como Configurar Sua Bio para Ganhar Mais Seguidores
A bio do Instagram tem um limite de 150 caracteres e permite um único link. Com esse espaço pequeno, você precisa convencer uma pessoa que acabou de ver seu conteúdo pela primeira vez a clicar, explorar e — idealmente — te seguir ou assinar.
A maioria das criadoras desperdiça essa oportunidade. Este artigo mostra como não desperdiçar.
---
## Este artigo é para você se...
- Você já cria conteúdo no Instagram mas sente que poderia estar convertendo mais visitantes em seguidores
- Você não sabe exatamente o que colocar na bio ou na página de links para chamar atenção
- Você quer uma estratégia clara — não dicas genéricas, mas um passo a passo que dá para aplicar hoje
- Você está crescendo e quer que a estrutura da sua bio acompanhe esse crescimento
**O que você encontra aqui:** como a jornada do visitante funciona, o que uma boa foto e bio fazem, como organizar os links por prioridade, e como usar métricas para melhorar ao longo do tempo.
---
## A jornada do visitante
Antes de falar sobre configuração, entenda o caminho que uma pessoa percorre antes de clicar em "seguir":
1. **Descobre você** — pelo feed, por um Reels, por um compartilhamento, por indicação
2. **Visita seu perfil** — analisa a foto, lê a bio, olha os posts recentes
3. **Clica no link da bio** — se a bio despertou interesse suficiente
4. **Explora a página de links** — vê o que você oferece além do que já viu
5. **Toma uma decisão** — seguir, assinar, ou sair
Cada etapa é uma chance de perder ou converter o visitante. Sua bio e sua página de links juntas determinam o que acontece nas etapas 2 a 4.
---
## A foto de perfil: primeiro impacto visual
Antes de ler uma palavra da sua bio, a pessoa vê sua foto. Ela precisa passar algumas mensagens em frações de segundo:
**Identificação clara:** É fácil reconhecer quem é você? O rosto deve estar visível e nítido, mesmo na versão pequena (a miniatura do Instagram é pequena).
**Coerência com o conteúdo:** Se você produz conteúdo com uma estética específica, a foto de perfil deve refletir isso. Quem clica no seu perfil depois de ver um Reels quer reconhecer a mesma pessoa.
**Qualidade:** Não precisa ser fotógrafo profissional, mas precisa ter boa iluminação. Uma janela com luz natural já faz grande diferença.
**Dica prática:** Evite fotos de grupo, fotos muito escuras, selfies com câmera frontal em ambientes com pouca luz, ou fotos com filtro tão forte que desfiguram o rosto.
---
## Os 150 caracteres da bio: cada palavra conta
A bio do Instagram tem espaço para aproximadamente 150 caracteres — umas 2 ou 3 linhas curtas. Esse é o seu espaço para responder a pergunta que o visitante está fazendo mentalmente: *"O que vou encontrar aqui?"*
**O que funciona na bio:**
**Uma descrição direta do conteúdo**
> "Conteúdo exclusivo toda semana 🔥 | Fitness e estilo de vida | Link abaixo 👇"
**Uma proposta de valor com emoji**
> "Sua dose diária de entretenimento ✨ | Novidades toda semana | Acesse meu link 👇"
**Uma chamada para ação com curiosidade**
> "Tem conteúdo exclusivo que não posto aqui 👀 | Veja no meu link ↓"
**O que evitar:**
❌ Bio vazia ou só com o nome
❌ Lista de redes sociais que já estão visíveis no perfil
❌ Frases muito genéricas que poderiam ser de qualquer pessoa
❌ Texto longo demais que a pessoa não lê
---
## A página de links: onde a conversão acontece
Quando alguém clica no link da sua bio, chega na sua página do SpicyLinks. É aqui que você tem mais espaço para convencer.
**Capriche no nome e na descrição da página**
O nome é o que aparece em destaque. Use como você quer ser reconhecida — seu nome artístico, apelido, ou o nome que você usa nas redes.
A descrição da página é mais longa que a bio do Instagram. Use para dar contexto sobre quem você é e o que oferece. Seja específica:
*Evite:* "Criadora de conteúdo"
*Prefira:* "Criadora de conteúdo de lifestyle e fitness. Conteúdo exclusivo toda semana, dicas de treino e meus looks favoritos."
**A ordem dos links define o que converte**
As primeiras posições recebem a maioria dos cliques. Coloque no topo o que você mais quer que as pessoas acessem:
```
1. Conteúdo exclusivo / plataforma principal
2. Instagram (para quem ainda não te segue lá)
3. Twitter/X (se você usa ativamente)
4. TikTok (se você usa)
5. Lista de desejos / outras plataformas
```
Se o objetivo é ganhar mais seguidores nas redes gratuitas, coloque os links delas antes das plataformas pagas. Se o objetivo é converter em assinantes, coloque a plataforma paga primeiro.
**O título de cada link importa**
O título do link é o que a pessoa lê antes de clicar. "Instagram" como título é genérico. Mas "Me segue lá também 📸" ou "Meus stories do dia a dia" já cria uma razão para clicar.
Teste títulos diferentes e veja o que gera mais cliques nas suas métricas.
---
## Consistência visual entre perfil e página de links
Um detalhe que faz diferença: a aparência da sua página de links deve combinar com a estética do seu perfil.
Se você tem um feed com paleta de cores clara e minimalista, escolha um tema do SpicyLinks que reflita isso. Se seu perfil é vibrante e colorido, vá em direção a algo mais ousado.
Quando a pessoa passa do Instagram para a sua página de links e vê uma identidade visual coerente, a sensação é de profissionalismo. Quando parece uma coisa diferente, causa estranheza — e estranheza não converte.
---
## Usando o SpicyLinks para crescer no Instagram especificamente
Se crescer no Instagram é o objetivo prioritário, configure sua página assim:
**Posição 1:** Um link para o seu Instagram com título que incentive o follow
**Posição 2:** Seu conteúdo principal (plataforma exclusiva, YouTube, etc.)
**Posição 3+:** Outras redes e links relevantes
**Na bio do Instagram**, teste este tipo de texto:
> "Sempre tem coisa nova aqui 📲 | Conteúdo exclusivo no link 👇"
Ao invés de só falar em "conteúdo exclusivo pago", mencione o que é gratuito também. A pessoa que ainda não está pronta para pagar pode te seguir agora e assinar mais tarde.
---
## Análise: o que está funcionando?
Configure sua página e depois use as métricas disponíveis para entender o comportamento das pessoas:
- Qual link recebe mais cliques?
- Quantas pessoas visitam a página mas não clicam em nada?
- De onde as pessoas chegam na sua página (Instagram, Twitter, etc.)?
Se muita gente visita mas poucos clicam, o problema pode estar nos títulos dos links ou na descrição da página.
Se muita gente clica no Instagram mas poucos clicam no conteúdo exclusivo, talvez valha a pena testar uma ordem diferente dos links ou um título mais atrativo para a plataforma principal.
---
## Checklist para uma bio que converte
Antes de publicar, confira:
- [ ] Foto de perfil nítida e com rosto visível
- [ ] Bio do Instagram clara, com no máximo 3 linhas
- [ ] Link da bio atualizado (apontando para o SpicyLinks)
- [ ] Página do SpicyLinks com nome e descrição preenchidos
- [ ] Links organizados por prioridade
- [ ] Títulos dos links descritivos (não só o nome da plataforma)
- [ ] Tema visual coerente com a estética do perfil
- [ ] Todos os links testados (clicando para confirmar que funcionam)
---
## O que pode não funcionar
- **Foto de perfil genérica ou de baixa qualidade:** é a primeira coisa que as pessoas veem — uma foto ruim faz a pessoa sair antes de ler a bio
- **Bio do Instagram e página de links com visual completamente diferente:** a falta de coerência visual causa estranheza e diminui a confiança
- **Atualizar a bio raramente:** o que convertia bem 6 meses atrás pode não converter mais — revise os títulos dos links e a descrição a cada 2 ou 3 meses
- **Muitos links sem hierarquia:** 10 links todos com a mesma importância visual fazem a pessoa não saber o que clicar; prioridade clara é fundamental
---
## Leia também
- [Os Melhores Links para Colocar na Sua Bio do Instagram](/artigos/os-melhores-links-para-sua-bio-do-instagram)
- [Como Criadores de Conteúdo Ganham Dinheiro com Links de Afiliados](/artigos/como-criadores-de-conteudo-ganham-dinheiro-com-afiliados)
- [SpicyLinks vs Linktree: Qual é Melhor para Criadores?](/artigos/spicylinks-vs-linktree-qual-e-melhor-para-criadores)
---
**Sua bio pronta para converter visitantes em seguidores?**
Configure sua página no SpicyLinks e transforme cada visita em uma oportunidade.
[Criar meu SpicyLinks →](https://spicylinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,204 +0,0 @@
---
title: "Como Criadores de Conteúdo Ganham Dinheiro com Links de Afiliados"
description: "Marketing de afiliados é uma das formas mais acessíveis de renda extra para criadores. Veja como funciona, como começar e como usar sua bio de links para maximizar as conversões."
keywords: "links afiliados criadora, marketing afiliados instagram, ganhar dinheiro afiliados, renda extra criadora conteudo, afiliados bio link"
author: "Equipe SpicyLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/afiliados-criadores.jpg"
culture: "pt-BR"
category: "criadores"
---
# Como Criadores de Conteúdo Ganham Dinheiro com Links de Afiliados
Você recomenda roupas, produtos de beleza, acessórios, equipamentos de fotografia — coisas que realmente usa e aprecia. Seus seguidores confiam no que você indica. Mas quando alguém vai comprar, você não vê nada dessa venda.
O marketing de afiliados muda isso: você ganha uma comissão cada vez que alguém compra um produto pela sua indicação. E a parte boa é que não precisa estocar nada, não precisa vender diretamente, e o produto nem precisa ser seu.
Este artigo explica como funciona e como você pode começar.
---
## Este artigo é para você se...
- Você recomenda produtos para suas seguidoras mas ainda não monetiza essas indicações
- Você quer uma fonte de renda que funcione mesmo quando não está postando
- Você quer entender o básico de afiliados sem enrolação — como funciona, por onde começa e quanto dá para ganhar de forma realista
- Você quer saber como organizar os links de afiliado na sua bio de forma profissional
**O que você encontra aqui:** como funciona o marketing de afiliados, os principais programas disponíveis no Brasil, como divulgar sem parecer forçada e como organizar tudo na sua bio do SpicyLinks.
---
## O que é marketing de afiliados
Marketing de afiliados é um modelo onde uma empresa te paga uma comissão pela venda que você gerou. Você recebe um link único (seu link de afiliado), compartilha com sua audiência, e quando alguém compra através desse link, você recebe uma porcentagem do valor.
**Exemplo prático:**
Você usa um conjunto de maquiagem que adora. A marca tem um programa de afiliados e te oferece 15% de comissão. Você posta sobre o produto com seu link de afiliado. Uma seguidora compra — digamos R$ 200. Você recebe R$ 30 dessa venda, sem ter feito mais nada além de compartilhar.
Multiplique isso por vários produtos, várias seguidoras e um período de semanas ou meses, e fica claro por que afiliados podem ser uma fonte relevante de renda.
---
## Por que afiliados funcionam bem para criadores de conteúdo
**Sua audiência já confia em você**
A principal vantagem de um criador de conteúdo no marketing de afiliados é a relação de confiança já construída. Uma recomendação sua tem muito mais peso do que um anúncio tradicional. Suas seguidoras te conhecem, te acompanham, e tendem a confiar no que você indica.
**Não exige capital inicial**
Diferente de vender produtos próprios, afiliados não exigem nenhum investimento. Você não compra estoque, não precisa de depósito, não tem risco financeiro.
**Funciona enquanto você dorme**
Um link de afiliado colocado na sua bio continua gerando comissões 24 horas por dia, mesmo quando você não está postando. Um post antigo com link de afiliado pode gerar comissão meses depois que foi publicado.
**Complementa outras formas de receita**
Afiliados não concorrem com suas assinaturas ou com outras fontes de renda — eles somam.
---
## Os principais programas de afiliados no Brasil
### Amazon Associates (Parceiros Amazon)
O maior programa de afiliados do mundo. Qualquer produto vendido na Amazon pode ser divulgado com comissão — de 2% a 10% dependendo da categoria.
**Ideal para:** maquiagem, acessórios, eletrônicos, roupas, decoração, livros, qualquer coisa que você compra e usa.
Como entrar: [associados.amazon.com.br](https://associados.amazon.com.br) — cadastro gratuito, aprovação em poucos dias.
### Hotmart, Eduzz e Monetizze
Plataformas brasileiras de produtos digitais (cursos, ebooks, programas). Comissões costumam ser mais altas — 30% a 60% do valor do produto.
**Ideal para:** indicar cursos relacionados ao seu nicho (fotografia, edição de vídeo, fitness, etc.)
### Shein, Renner, Dafiti (programas próprios)
Lojas de moda que têm programas de afiliados diretos. Comissões menores (3-8%), mas produtos com alta taxa de compra entre seguidoras de moda e lifestyle.
### Programa de Afiliados do SpicyLinks
O Plano Premium+Afiliados do SpicyLinks permite incluir links de afiliado com tracking diretamente na sua página, de forma organizada e com métricas de cliques para cada link.
---
## O que você precisa para começar
**1. Escolha um ou dois programas**
Não tente entrar em todos de uma vez. Comece com o que faz mais sentido para o seu nicho e o que você já compra naturalmente.
**2. Crie sua conta nos programas escolhidos**
A maioria exige apenas email, CPF e dados bancários para pagamento. Aprovação costuma ser rápida para quem já tem alguma audiência.
**3. Gere seus links de afiliado**
Cada produto que você quiser divulgar tem um link único com seu código. Quando alguém compra por esse link, a venda é atribuída a você.
**4. Compartilhe de forma autêntica**
Divulgue produtos que você realmente usa ou que fazem sentido para o seu público. Seguidoras percebem quando a indicação é genuína — e quando não é.
---
## Como organizar os links de afiliado na sua bio
Uma das melhores formas de aproveitar links de afiliado é mantê-los sempre visíveis na sua página do SpicyLinks.
**Estrutura sugerida:**
```
🛒 Favoritos que uso (lista de afiliados)
→ Cuidados com a pele que eu amo
→ Meus itens de roupa favoritos do mês
→ Equipamentos que uso para produzir
```
Você pode criar uma seção específica de "produtos que recomendo" na sua página, com links de afiliado para cada categoria.
**Vantagem do SpicyLinks:** Com o plano Premium+Afiliados, você pode incluir links de produto com tracking, vendo exatamente quantas pessoas clicam em cada recomendação.
---
## Como divulgar sem parecer forçada
Este é o ponto que diferencia criadoras que conseguem renda com afiliados das que irritam a audiência tentando vender o tempo todo.
**Regras práticas:**
**Só indique o que você genuinamente usa ou aprovaria usar**
Se você nunca testou o produto, não é indicação — é anúncio. Sua audiência sente a diferença, e um produto ruim indicado por você vai custar credibilidade.
**Seja transparente sobre a parceria**
Quando você usa um link de afiliado, é boa prática mencionar isso. Algo simples como "link de afiliado — não custa nada extra pra você, mas me ajuda" é suficiente. Além de ser honesta, essa transparência costuma aumentar — não diminuir — a conversão.
**Contextualize a indicação**
"Esse protetor solar é do meu link de afiliados" converte menos do que "Uso esse protetor todo dia e nunca descamou no meu rosto — link pra comprar abaixo". O contexto cria o desejo.
**Não exagere na frequência**
Se todo post é uma indicação de produto, sua audiência passa a ignorar. Reserve as indicações de afiliados para momentos em que surgem naturalmente — quando você realmente está usando o produto ou quando alguém pergunta.
---
## Quanto dá para ganhar?
A resposta honesta é: depende muito da audiência e de como você usa.
Uma criadora com 10 mil seguidores engajados pode ganhar mais com afiliados do que outra com 100 mil seguidores que não engaja.
Para ter uma referência:
- Um link de afiliado para produto de R$ 150 com 5% de comissão = R$ 7,50 por venda
- Se 20 seguidoras compram num mês = R$ 150
- Com 5 produtos diferentes gerando 20 vendas cada = R$ 750 por mês em comissões
Isso é adicional à renda de assinaturas e publipost, sem trabalho extra além de compartilhar links que você já usaria para indicar os produtos mesmo sem ser paga.
Com o tempo e com o crescimento da audiência, esses números escalam.
---
## Erros para evitar
**Indicar produtos de má qualidade por comissão mais alta**
A comissão não compensa a perda de credibilidade com a audiência.
**Usar links de afiliado em stories sem salvar nos destaques**
Stories somem em 24 horas. Mantenha os links de afiliado sempre disponíveis na sua bio do SpicyLinks.
**Não acompanhar o que está convertendo**
Use as métricas disponíveis nos programas de afiliados e na sua página do SpicyLinks para saber quais produtos geram mais cliques e vendas.
**Esquecer de atualizar quando um produto muda ou sai de linha**
Links quebrados geram frustração e custam credibilidade.
---
## O que pode não funcionar
- **Indicar produtos sem ter testado:** uma indicação de produto ruim custa muito mais do que a comissão ganha — a credibilidade com a audiência é seu ativo mais valioso
- **Não deixar os links de afiliado sempre visíveis:** guardar para posts esporádicos reduz muito a conversão; links fixos na bio trabalham 24h por dia
- **Esperar resultados rápidos:** afiliados são renda que cresce com o tempo e com o crescimento da audiência — nos primeiros meses os valores são pequenos, mas escalam
- **Não declarar que é link de afiliado:** além de ser a prática honesta, a transparência aumenta a confiança e frequentemente melhora a conversão
---
## Leia também
- [Os Melhores Links para Colocar na Sua Bio do Instagram](/artigos/os-melhores-links-para-sua-bio-do-instagram)
- [Como Configurar Sua Bio para Ganhar Mais Seguidores](/artigos/como-configurar-sua-bio-para-ganhar-mais-seguidores)
- [SpicyLinks vs Linktree: Qual é Melhor para Criadores?](/artigos/spicylinks-vs-linktree-qual-e-melhor-para-criadores)
---
**Pronta para adicionar afiliados à sua estratégia de renda?**
Com o plano Premium+Afiliados do SpicyLinks, você organiza e acompanha todos os seus links de produto em um só lugar.
[Criar meu SpicyLinks →](https://spicylinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,217 +0,0 @@
---
title: "Como Monetizar seu Conteúdo com uma Bio Link Profissional"
description: "Como criadoras de conteúdo adulto podem usar uma bio link bem estruturada para aumentar conversões, diversificar fontes de receita e construir uma marca pessoal sólida."
keywords: "monetizar conteudo criadora, bio link conversao, aumentar assinantes, diversificar renda criadora, marca pessoal criadora"
author: "Equipe SpicyLinks"
date: 2026-01-28
lastMod: 2026-01-28
image: "/images/artigos/monetizacao-criadora.jpg"
culture: "pt-BR"
category: "criadores"
---
# Como Monetizar seu Conteúdo com uma Bio Link Profissional
A maioria das criadoras de conteúdo perde entre 30% e 50% das conversões possíveis por um motivo simples: a jornada entre "interesse" e "assinatura" tem fricção demais.
Alguém descobre seu perfil no Instagram, quer saber mais, clica na bio — e encontra um link confuso, desatualizado ou que leva para apenas uma plataforma. Ela vai embora antes de virar assinante.
Uma bio link profissional elimina essa fricção. Este artigo mostra como usar isso para monetizar melhor o que você já produz.
---
## O Problema com "Link na Bio" Sem Estratégia
"Link na bio" virou uma instrução automática que a maioria das criadoras usa sem pensar. Mas o que está nesse link faz toda a diferença.
**Cenário comum:**
- Bio diz "link na bio"
- Link vai direto para uma única plataforma de assinatura
- Visitante que não quer assinar imediatamente não encontra alternativa
- Saída sem conversão
**O que uma bio link estratégica faz:**
- Oferece múltiplos pontos de conversão (assinatura, lista de desejos, redes sociais)
- Captura pessoas em diferentes estágios de interesse
- Permite que a visitante escolha como se relacionar com você antes de decidir assinar
Uma pessoa que ainda não está pronta para assinar pode:
- Te seguir no Instagram
- Adicionar itens da sua lista de desejos
- Começar a te acompanhar no Twitter/X
- Voltar e assinar depois
---
## As Fontes de Receita que uma Bio Link Deve Cobrir
Uma boa bio link não é apenas sobre assinatura. É sobre diversificação de receita.
### 1. Assinatura em Plataforma Exclusiva
A principal fonte de receita recorrente. É o link que deve aparecer primeiro e com título mais atrativo.
**Exemplos de títulos que convertem mais:**
❌ "OnlyFans"
✅ "Conteúdo Exclusivo que Não Posto em Lugar Nenhum 🔥"
❌ "Minha Plataforma"
✅ "Assinar — Novo Conteúdo Toda Semana ❤️"
A diferença é que o segundo título comunica **valor** antes de comunicar a ação.
### 2. Lista de Desejos
Fonte de receita passiva que muitas criadoras subestimam. Uma lista de desejos bem mantida (Amazon ou similar) gera presentes de fãs de forma contínua.
**Para maximizar:**
- Atualize quinzenalmente
- Varie itens de diferentes faixas de preço (de R$ 30 a R$ 300+)
- Quando receber presente, agradeça publicamente (stories, tweet) — incentiva outros fãs
### 3. Segunda Plataforma de Assinatura
Criadoras com audiência consolidada frequentemente usam duas plataformas com propostas diferentes:
- Plataforma A: conteúdo mais acessível (preço menor, público maior)
- Plataforma B: conteúdo premium exclusivo (preço maior, público menor)
Isso permite capturar clientes de diferentes poder aquisitivo e criar um funil de upgrade.
### 4. Conteúdo Avulso / Pay-Per-View
Algumas plataformas permitem vender conteúdo específico sem assinatura. Se você usa esse modelo, inclua um link direto.
### 5. Parcerias e Publipost
Um email profissional bem posicionado na bio link — especificando que é para parcerias — pode gerar propostas de marcas relevantes para o seu nicho.
---
## A Psicologia da Ordem dos Links
A posição dos links na sua página importa mais do que parece.
### O Princípio da Atenção Decrescente
Visitantes leem de cima para baixo e perdem atenção progressivamente. As primeiras 3 posições recebem a maior parte dos cliques.
**Estrutura recomendada:**
```
Posição 1: Assinatura principal (sua principal receita)
Posição 2: Segunda plataforma ou lista de desejos
Posição 3: Conteúdo free mais popular (Instagram ou Twitter/X)
Posição 4: Segundo perfil social
Posição 5+: Outros links relevantes
```
### O Papel dos Links Gratuitos
Links gratuitos (Instagram, Twitter/X) na sua bio link parecem contraditórios — você está mandando pessoas para onde não ganha dinheiro.
Mas eles cumprem um papel importante: **construção de confiança**.
Uma visitante que ainda não conhece seu trabalho pode preferir primeiro te seguir no Instagram antes de pagar. Se o Instagram convence, ela volta e assina. Se não estiver na bio link, ela não te segue e você a perde.
---
## Sua Bio Link como Cartão de Visita Profissional
### Para Parceiros e Marcas
Quando uma marca te encontra e quer fazer publipost, ela vai avaliar sua profissionalidade. Uma página organizada no SpicyLinks — com bio clara, links funcionando, aparência coesa — passa uma impressão completamente diferente de um perfil bagunçado.
Isso afeta diretamente o valor que você consegue cobrar por parcerias.
### Para Plataformas de Afiliados
O **Plano Premium+Afiliados** do SpicyLinks permite incluir links de produto com tracking. Se você recomenda produtos (roupas, acessórios, equipamentos para produção de conteúdo), pode monetizar essas recomendações com links de afiliados organizados na mesma página.
---
## Usando Métricas para Otimizar sua Receita
O Dashboard do SpicyLinks mostra exatamente quantas pessoas clicam em cada link. Use isso ativamente.
### Diagnósticos comuns
**Assinatura recebe poucos cliques, mas há muitas visitas à página:**
- O título do link não está comunicando valor
- A descrição da página não está aquecendo a audiência antes dos links
- Tente mudar o título e o copy da descrição
**Lista de desejos tem muitos cliques mas poucos presentes:**
- Itens podem estar fora do alcance do seu público
- Adicione itens de menor valor (R$ 20-50)
**Instagram recebe mais cliques do que assinatura:**
- Pode ser positivo (construção de audiência) ou negativo (sua audiência prefere te seguir gratuitamente)
- Considere criar mais urgência no título do link de assinatura
### Ciclo de otimização
```
Mês 1: Configure os links básicos
Mês 2: Analise o que está convertendo
Mês 3: Mude os títulos dos links mais fracos
Mês 4: Compare os resultados
Mês 5: Mantenha o que funciona, teste variações
```
---
## Construindo uma Marca Pessoal de Longo Prazo
Criadoras que pensam em carreira de longo prazo não dependem apenas de assinaturas mensais. Elas constroem uma marca pessoal que tem valor independente de qualquer plataforma.
Uma plataforma pode mudar suas políticas, banir contas ou fechar. Sua marca pessoal, construída ao longo de anos, vai com você para qualquer plataforma.
### O que constrói marca pessoal
- **Consistência visual:** mesmas cores, estilo fotográfico e identidade em todas as plataformas
- **Voz única:** um jeito de se comunicar que é reconhecidamente seu
- **Presença além do conteúdo pago:** conteúdo gratuito de qualidade que faz as pessoas te recomendar
- **Relacionamento genuíno com a audiência:** responder comentários, interagir nos stories, ouvir o que as pessoas querem ver
Uma bio link profissional é o hub de toda essa presença. É onde tudo converge.
---
## Privacidade e Segurança
Ao construir sua presença no SpicyLinks, proteja-se:
**Nunca inclua na bio link:**
- Seu endereço ou cidade específica
- Número de documento pessoal
- Informações bancárias diretamente
**Inclua apenas:**
- Plataformas profissionais já públicas
- Email de trabalho (diferente do pessoal)
- WhatsApp comercial (número diferente do pessoal, se optar por incluir)
A separação entre vida pessoal e profissional é uma das decisões mais importantes para criadoras com carreira de longo prazo.
---
## Conclusão
Uma bio link profissional não é um detalhe estético — é infraestrutura de negócio. Criadoras que tratam a presença digital com seriedade, organizam seus links estrategicamente e monitoram o que funciona convertem mais visitantes em receita com o mesmo esforço de criação de conteúdo.
**Próximos passos:**
1. Configure sua página no SpicyLinks
2. Organize seus links por prioridade de receita
3. Atualize todas as suas bios com o novo link
4. Analise as métricas depois de 30 dias
5. Otimize o que não está convertendo
O conteúdo que você cria merece uma vitrine profissional.
[Criar meu SpicyLinks →](https://spicylinks.site/)
---
**Última atualização:** Janeiro 2026

View File

@ -1,212 +0,0 @@
---
title: "Os Melhores Links para Colocar na Sua Bio do Instagram"
description: "Quais links realmente valem a pena ter na sua bio? Uma análise prática sobre o que coloca, o que evita e como organizar para converter mais — seja em seguidores, assinantes ou vendas."
keywords: "links bio instagram, o que colocar na bio, melhores links bio criadora, bio link instagram criador, links que convertem instagram"
author: "Equipe SpicyLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/links-bio-instagram.jpg"
culture: "pt-BR"
category: "criadores"
---
# Os Melhores Links para Colocar na Sua Bio do Instagram
O Instagram permite um único link na bio. Então a maioria das criadoras coloca uma página com vários links — e essa decisão, quando bem feita, multiplica as possibilidades de conversão.
Mas quais links colocar? Em que ordem? O que realmente faz diferença?
Este artigo responde essas perguntas com base no que funciona na prática.
---
## Este artigo é para você se...
- Você tem uma página de links mas não sabe se está aproveitando bem o espaço
- Você adiciona links sem critério e quer entender qual ordem gera mais resultados
- Você quer saber o que realmente converte — e o que está desperdiçando o espaço dos primeiros cliques
- Você acabou de criar sua conta e quer montar a bio já com uma estrutura inteligente
**O que você encontra aqui:** a regra dos três primeiros links, quais links valem a pena incluir por objetivo, o que evitar, modelos prontos para diferentes estratégias e como manter a página atualizada.
---
## A regra dos primeiros três links
Pesquisas de comportamento em páginas de links mostram um padrão consistente: os três primeiros links recebem entre 60% e 80% de todos os cliques. O quarto em diante recebe cada vez menos atenção.
Isso significa que a decisão mais importante na sua bio de links não é quantos links ter, mas *quais links ficam nas três primeiras posições*.
Defina seu objetivo antes de ordenar:
- Se o objetivo é assinantes: plataforma exclusiva no topo
- Se o objetivo é crescer no Instagram: Instagram no topo (para quem chega por outra rede)
- Se o objetivo é vendas: loja ou lista de desejos no topo
---
## Os links que mais valem a pena
### 1. Sua plataforma de conteúdo exclusivo
Se você tem uma plataforma de assinatura, este deve ser o primeiro link. É a sua principal fonte de receita e merece a posição de maior visibilidade.
**Como nomear:** Não coloque só o nome da plataforma. Crie um título que comunique valor:
✅ "Conteúdo exclusivo que não posto em lugar nenhum 🔥"
✅ "Assinar — novidade toda semana ❤️"
✅ "Acesso VIP ao meu conteúdo especial"
❌ "OnlyFans"
❌ "Minha plataforma"
❌ "Link aqui"
### 2. Instagram
Parece estranho colocar o link do Instagram na bio do próprio Instagram, mas faz sentido em dois casos:
**Caso 1:** Você compartilha sua bio em outros lugares (WhatsApp, Twitter/X, TikTok). Quem chega por essas plataformas pode não te seguir no Instagram ainda.
**Caso 2:** Você tem um perfil secundário ou um perfil profissional separado que quer divulgar.
Se você só tem um Instagram e compartilha a bio principalmente por lá, esse link pode ir para posições mais baixas ou nem aparecer.
### 3. Twitter/X ou TikTok
Se você tem presença ativa nessas plataformas e quer crescer lá também, inclua. Escolha uma ou duas — não tente colocar todas as redes.
**Dica:** Coloque a plataforma onde seu conteúdo mais converte ou onde você tem mais potencial de crescimento.
### 4. Lista de desejos
Uma lista de desejos (Amazon Wishlist ou similar) é receita passiva. Fãs que querem presentear encontram aqui o que você quer. Quanto mais visível, mais presente funciona.
**Como nomear:**
✅ "Minha lista de desejos 🎁"
✅ "Me presentear? Tem aqui 🥰"
### 5. WhatsApp ou Telegram VIP
Se você tem um grupo privado de fãs mais próximos, coloque o link de acesso. Funciona bem como um nível intermediário — entre seguir gratuitamente e assinar o conteúdo exclusivo.
### 6. YouTube
Se você posta conteúdo gratuito no YouTube (vlogs, tutoriais, bastidores), inclua o canal. Conteúdo gratuito de qualidade constrói confiança e eventualmente converte em assinante.
### 7. Email para parcerias
Se você quer fechar contratos com marcas, um email profissional dedicado a parcerias é fundamental. Coloque com um título claro:
✅ "Parcerias e publipost → email aqui"
✅ "Contato comercial"
---
## Links que você provavelmente não precisa incluir
**Linktree ou outras ferramentas de bio link além do SpicyLinks**
Não faz sentido ter um link para outra página de links dentro da sua página de links. Centralize tudo em um lugar só.
**Facebook**
A menos que você tenha uma página ativa e relevante no Facebook, não precisa incluir. A plataforma tem relevância muito menor para o público jovem que consome conteúdo de criadores.
**Snapchat**
Plataforma com uso muito específico. Inclua apenas se for uma parte real da sua estratégia.
**Link para post específico**
Links para posts individuais ficam desatualizados rápido. Prefira linkar para o seu perfil ou canal — assim o visitante sempre vê o conteúdo mais recente.
---
## A questão da quantidade: menos é mais?
Não necessariamente. A pergunta certa não é "quantos links", mas "cada link aqui tem um propósito claro?"
Uma página com 4 links bem escolhidos converte melhor do que uma com 12 links desorganizados onde a pessoa não sabe o que clicar.
**Regra prática:** Se você hesita se um link deve estar na página, provavelmente não precisa estar.
---
## Organização por objetivo: modelos prontos
**Para quem quer maximizar assinaturas:**
```
1. Conteúdo exclusivo (plataforma principal)
2. Segundo perfil (plataforma alternativa)
3. Lista de desejos
4. Instagram
5. Twitter/X
6. Email para parcerias
```
**Para quem quer crescer em seguidores:**
```
1. Instagram
2. TikTok
3. Twitter/X
4. Conteúdo exclusivo
5. Lista de desejos
6. Email para parcerias
```
**Para quem quer equilibrar crescimento e receita:**
```
1. Conteúdo exclusivo
2. Instagram
3. TikTok ou YouTube
4. Lista de desejos
5. Email para parcerias
```
---
## Mantendo a página atualizada
Sua página de links precisa de manutenção periódica. A cada 2-4 semanas, verifique:
- Todos os links ainda funcionam?
- Os títulos continuam relevantes?
- Surgiu alguma nova plataforma ou oferta que merece aparecer?
- Algum link está desatualizado e deveria ser removido?
Uma página com links quebrados ou informações antigas passa uma impressão de descuido — o contrário do que você quer para a sua imagem profissional.
---
## Testando o que funciona
Se você usa o SpicyLinks, as métricas mostram quantas pessoas clicam em cada link. Use esses dados para tomar decisões:
**Se um link tem muitas visitas mas poucos cliques:** O título pode não estar sendo atraente o suficiente. Teste uma versão diferente por 2 semanas e compare.
**Se os links de baixo nunca são clicados:** Eles estão ocupando espaço de links que poderiam converter mais. Considere remover ou reorganizar.
**Se você recebe muitas visitas mas conversão baixa:** O problema pode ser na descrição da página ou na coerência entre o que as pessoas esperam e o que encontram.
---
## O que pode não funcionar
- **Título genérico nos links:** "Instagram" ou "OnlyFans" não convence ninguém — o título precisa comunicar o que a pessoa vai encontrar ao clicar
- **Links que apontam para conteúdo específico desatualizado:** um post antigo ou uma live encerrada gera frustração; prefira links para perfis ou canais completos
- **Não testar variações:** a ordem e o título dos links que você configurou hoje não são definitivos — teste, analise e ajuste
- **Ignorar as métricas:** se um link nunca recebe cliques, ele está ocupando o espaço de algo que poderia converter
---
## Leia também
- [Como Configurar Sua Bio para Ganhar Mais Seguidores](/artigos/como-configurar-sua-bio-para-ganhar-mais-seguidores)
- [SpicyLinks vs Linktree: Qual é Melhor para Criadores?](/artigos/spicylinks-vs-linktree-qual-e-melhor-para-criadores)
- [Como Criadores de Conteúdo Ganham Dinheiro com Links de Afiliados](/artigos/como-criadores-de-conteudo-ganham-dinheiro-com-afiliados)
---
**Configure sua bio com os links certos e veja a diferença.**
Crie ou atualize sua página no SpicyLinks com uma estratégia clara.
[Criar meu SpicyLinks →](https://spicylinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,210 +0,0 @@
---
title: "SpicyLinks vs Linktree: Qual é Melhor para Criadores?"
description: "Uma comparação direta entre SpicyLinks e Linktree para criadores de conteúdo brasileiros — preço, recursos, suporte e o que cada plataforma realmente entrega."
keywords: "spicylinks vs linktree, linktree alternativa brasil, melhor bio link criadora, linktree concorrente, bio link para criadores"
author: "Equipe SpicyLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/spicylinks-vs-linktree.jpg"
culture: "pt-BR"
category: "criadores"
---
# SpicyLinks vs Linktree: Qual é Melhor para Criadores?
O Linktree é a plataforma de bio link mais conhecida do mundo. Então por que existem alternativas — e por que algumas criadoras preferem usar o SpicyLinks?
Este artigo faz uma comparação direta, sem rodeios, para ajudar você a decidir o que faz mais sentido para a sua situação.
---
## Este artigo é para você se...
- Você está avaliando qual plataforma de bio link usar e não sabe a diferença entre as opções
- Você já usa o Linktree mas está questionando se vale a pena continuar — especialmente pelo custo em dólar
- Você quer uma comparação direta, sem papo de vendedor, sobre o que cada plataforma entrega
- Você quer saber se migrar faz sentido e se o processo é complicado
**O que você encontra aqui:** pontos fortes e fracos de cada plataforma, comparação de preços real (dólar vs real), diferenças nos recursos principais e quando cada uma faz sentido.
---
## O Linktree: o que é e para quem serve bem
O Linktree foi lançado em 2016 e hoje tem mais de 40 milhões de usuários. É a escolha padrão para quem quer algo rápido, reconhecível e sem muita configuração.
**Pontos fortes do Linktree:**
- Reconhecimento de marca — todo mundo sabe o que é
- Versão gratuita disponível
- Interface simples, fácil de aprender
- Integrações com várias plataformas
- Disponível em muitos idiomas
**Limitações do Linktree:**
- Preços em dólar (caro para o bolso brasileiro)
- Suporte em inglês
- Não tem moderação de conteúdo — qualquer conteúdo pode ser hospedado
- Termos de serviço genéricos, não adaptados ao criador brasileiro
- Analytics básico nos planos gratuito e mais barato
- URL padrão é `linktr.ee/seu-nome` — não tem URL personalizada com contexto
---
## O SpicyLinks: para quem foi feito
O SpicyLinks foi criado especificamente para criadores de conteúdo brasileiros que precisam de uma plataforma que entenda a realidade local — em termos de preços, moeda, suporte e tipos de conteúdo.
**Diferenciais principais:**
- Preços em reais, sem variação cambial
- URL com contexto: `spicylinks.site/categoria/seu-nome`
- Suporte em português
- Plano com links de afiliados nativos
- Analytics de cliques por link
- Moderação que protege a plataforma e os criadores
- Temas visuais adaptados para o nicho de criadores
---
## Comparação direta
### Preço
| Plano | Linktree | SpicyLinks |
|-------|----------|-----------|
| Gratuito/Trial | Sim (limitado) | 7 dias grátis |
| Plano básico | US$ 5/mês (~R$ 28) | R$ 12,90/mês |
| Plano intermediário | US$ 9/mês (~R$ 51) | R$ 25,90/mês |
| Plano completo | US$ 24/mês (~R$ 136) | R$ 29,90/mês |
| Com afiliados | Não incluso | R$ 34,90/mês |
*Cotação do dólar varia. Preços Linktree podem ter se alterado desde esta publicação.*
**Vantagem:** SpicyLinks em todos os planos — com preço significativamente menor em reais e sem risco de variação cambial.
---
### URL personalizada
**Linktree:** Sua URL fica `linktr.ee/seu-nome`. Não tem contexto — qualquer pessoa pode ter essa URL, de qualquer nicho.
**SpicyLinks:** Sua URL inclui sua categoria: `spicylinks.site/modelos/seu-nome` ou `spicylinks.site/influencers/seu-nome`. Isso cria contexto e credibilidade — a URL já comunica quem você é antes de a pessoa clicar.
**Vantagem:** SpicyLinks para quem quer uma presença mais profissional e contextualizada.
---
### Links de afiliados
**Linktree:** Você pode colocar links de afiliados manualmente como qualquer outro link, mas não tem funcionalidade específica para isso — sem tracking por link dentro da plataforma.
**SpicyLinks:** O Plano Premium+Afiliados inclui links de produto com tracking nativo. Você vê exatamente quantas pessoas clicam em cada link de afiliado, o que permite otimizar o que está sendo divulgado.
**Vantagem:** SpicyLinks para quem trabalha ativamente com marketing de afiliados.
---
### Analytics
**Linktree gratuito:** Apenas total de views e cliques.
**Linktree Pro:** Análise por link, dados demográficos.
**SpicyLinks:** Analytics de cliques por link disponível nos planos pagos. Dados de onde as pessoas chegam à sua página.
**Vantagem:** Similar nos planos pagos de ambas.
---
### Temas visuais
**Linktree:** Dezenas de temas, mas muitos são genéricos e o resultado final costuma parecer igual ao de milhares de outras pessoas.
**SpicyLinks:** Temas desenvolvidos com a estética do criador de conteúdo em mente. Nos planos superiores, temas premium exclusivos. Menos temas no total, mas mais adequados ao nicho.
**Vantagem:** Depende da preferência — Linktree tem mais quantidade, SpicyLinks tem mais adequação ao nicho.
---
### Suporte
**Linktree:** Suporte em inglês, por email, com tempo de resposta que pode demorar dias. Para usuários fora do plano Pro, suporte bastante limitado.
**SpicyLinks:** Suporte em português. Planos Premium têm suporte prioritário e acesso via Telegram.
**Vantagem:** SpicyLinks para quem precisa de suporte em português.
---
### Confiabilidade e moderação
**Linktree:** Plataforma estável e com alta disponibilidade. Porém, por ter conteúdo de qualquer tipo, há riscos de mudanças na política de uso que afetem criadores de conteúdo adulto.
**SpicyLinks:** Moderação de conteúdo garante que a plataforma se mantém organizada. Criadores aprovados na moderação têm mais estabilidade.
**Vantagem:** SpicyLinks para criadores que querem uma plataforma dedicada ao seu tipo de conteúdo.
---
## Quando o Linktree pode ser a escolha certa
Seria desonesto dizer que o Linktree não tem vantagens em nenhum cenário. Faz sentido usar o Linktree se:
- Você já tem conta antiga e não quer migrar o histórico
- Você produz conteúdo de vários nichos completamente diferentes e precisa de muitos temas
- Você tem audiência internacional e quer a plataforma mais reconhecida globalmente
- Você quer começar gratuitamente sem compromisso de nenhum período
Para todos esses casos, o Linktree resolve. Mas para a maioria das criadoras brasileiras, o custo maior (em dólar) e o suporte apenas em inglês são desvantagens reais.
---
## Quando o SpicyLinks é a escolha certa
O SpicyLinks faz mais sentido se:
- Você é criadora de conteúdo e quer uma plataforma feita para o seu nicho
- Você prefere pagar em reais sem variação cambial
- Você trabalha com links de afiliados e quer acompanhar o desempenho de cada um
- Você valoriza suporte em português
- Você quer uma URL com contexto profissional
- Você está começando e quer um plano básico acessível
---
## Migrar do Linktree é difícil?
Não. O processo leva menos de 30 minutos:
1. Crie sua conta no SpicyLinks
2. Adicione os mesmos links que você tem no Linktree
3. Configure o tema visual
4. Envie para moderação e aguarde aprovação
5. Quando aprovada, atualize o link da bio do Instagram e de qualquer outro lugar onde você divulga
Seus seguidores não percebem a mudança — só encontram uma página de links mais organizada.
---
## Conclusão
Para criadoras de conteúdo brasileiras que querem uma plataforma feita para o seu nicho, com preço em reais e suporte em português, o SpicyLinks entrega mais valor pelo dinheiro do que o Linktree.
O Linktree é a escolha padrão por reconhecimento de marca. O SpicyLinks é a escolha inteligente para quem pesquisa antes de decidir.
---
## Leia também
- [Como Configurar Sua Bio para Ganhar Mais Seguidores](/artigos/como-configurar-sua-bio-para-ganhar-mais-seguidores)
- [Os Melhores Links para Colocar na Sua Bio do Instagram](/artigos/os-melhores-links-para-sua-bio-do-instagram)
- [Como Criadores de Conteúdo Ganham Dinheiro com Links de Afiliados](/artigos/como-criadores-de-conteudo-ganham-dinheiro-com-afiliados)
---
**Teste você mesma e compare.**
Crie sua página no SpicyLinks — são 7 dias grátis para avaliar sem compromisso.
[Criar meu SpicyLinks →](https://spicylinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,334 +0,0 @@
---
title: "Como Configurar seu SpicyLinks: Guia Completo para Criadoras de Conteúdo"
description: "Passo a passo para criadoras de conteúdo adulto criarem uma bio profissional no SpicyLinks, centralizando suas plataformas e aumentando conversões."
keywords: "spicylinks tutorial, bio links criadora, como criar bio conteudo adulto, configurar página criadora"
author: "Equipe SpicyLinks"
date: 2026-01-25
lastMod: 2026-01-25
image: "/images/tutoriais/criadora-spicylinks.jpg"
culture: "pt-BR"
category: "criadores"
---
# Como Configurar seu SpicyLinks: Guia Completo para Criadoras de Conteúdo
Você está no Instagram com 50 mil seguidores, tem conta no Twitter/X, lista de desejos na Amazon e assinatura em plataforma exclusiva — mas sua bio diz apenas "link na bio" e leva para um único lugar. Você está deixando dinheiro na mesa.
Este guia mostra como configurar seu SpicyLinks do zero para centralizar tudo e converter mais visitantes em assinantes e clientes.
---
## Por Que uma Bio Profissional Faz Diferença
### O Funil de Conversão da Criadora
Toda visitante que chega no seu perfil passa por um funil:
```
Perfil Instagram/Twitter
Bio (1 clique)
Página SpicyLinks
Decisão: qual plataforma acessar?
```
Se sua bio link for desorganizada, lenta ou confusa, a visitante desiste antes de chegar no passo final. Cada ponto de atrito custou uma assinante.
### O que uma boa bio link resolve
- ✅ Centraliza todas as suas plataformas em um lugar
- ✅ Você muda um link e atualiza em todos os canais de uma vez
- ✅ Analytics de quais plataformas convertem mais
- ✅ Aparência profissional que gera mais confiança
- ✅ Verificação de idade automática — segurança jurídica
---
## Passo 1: Criar Sua Conta no SpicyLinks
### 1.1. Acesse o Site
Vá para [spicylinks.site](https://spicylinks.site) e clique em **"Entrar"**.
A plataforma possui verificação de idade automática antes de qualquer acesso — isso protege você juridicamente.
### 1.2. Criando sua Conta
Use login com **Google** ou **Microsoft** para facilidade. Recomendamos usar um email dedicado ao trabalho de criação de conteúdo, separado do email pessoal.
---
## Passo 2: Configurando sua Página
### Escolhendo o Nome da Página
Use o nome que sua audiência já conhece — o mesmo username que você usa no Instagram ou Twitter/X:
```
✅ Luna Rodrigues
✅ Ana_V Oficial
✅ Mel Santos — Criadora
```
Evite nomes genéricos que não te identificam.
### Escolhendo o Slug (URL)
Sua URL será:
```
spicylinks.site/modelo/seu-slug
```
Opções:
```
luna-rodrigues → spicylinks.site/modelo/luna-rodrigues
anav-oficial → spicylinks.site/modelo/anav-oficial
mel-santos-cr → spicylinks.site/modelo/mel-santos-cr
```
> **Dica:** Escolha algo fácil de ditar e memorizar. Você vai falar esse link em vídeos e stories.
### Escrevendo a Descrição
Seja direta, confiante e deixe claro o que sua audiência encontra aqui:
**Exemplo eficaz:**
```
Luna Rodrigues 🔥
Conteúdo exclusivo, bastidores e lista de desejos.
Links de todas as minhas plataformas abaixo 👇
Assinantes especiais: acesso prioritário ao conteúdo inédito.
```
**Outro exemplo:**
```
Criadora de conteúdo desde 2021 ✨
Fotos, vídeos e muito mais nas plataformas abaixo.
Lista de desejos atualizada semanalmente 🎁
```
---
## Passo 3: Montando Seus Links
A ordem dos links importa. Coloque primeiro o que mais converte.
### Estrutura Recomendada de Links
**1. ❤️ Assinatura Principal**
```
Título: Assinar Conteúdo Exclusivo ❤️
URL: https://suaplataforma.com/seuusuario
```
> Este deve ser o primeiro link. É sua principal fonte de receita.
**2. ❤️ Segunda Plataforma de Assinatura (se tiver)**
```
Título: Conteúdo Exclusivo — Plataforma 2
URL: https://outraplataforma.com/seuusuario
```
> Algumas criadoras usam plataformas diferentes para faixas de preço distintas.
**3. 🛒 Lista de Desejos**
```
Título: Minha Lista de Desejos 🎁
URL: https://amazon.com.br/hz/wishlist/ls/seu-id
```
> Atualize regularmente. Listas desatualizadas perdem conversão.
**4. 📸 Instagram Principal**
```
Título: Me Seguir no Instagram 📸
URL: https://instagram.com/seuusuario
```
**5. 🐦 Twitter/X**
```
Título: Twitter/X 🔥
URL: https://x.com/seuusuario
```
**6. 🎵 TikTok (se usar)**
```
Título: TikTok 🎵
URL: https://tiktok.com/@seuusuario
```
**7. 📞 WhatsApp (Opcional — para comunicação direta)**
```
Título: Falar Diretamente 💬
URL: https://wa.me/5511999999999
```
> Use com critério. Muitas criadoras preferem não disponibilizar WhatsApp público.
**8. ✉️ Email para Parcerias**
```
Título: Parcerias e Contato Profissional ✉️
URL: mailto:contato@seudominio.com
```
> Separe o contato de fãs do contato profissional.
---
## Passo 4: Escolhendo o Tema Visual
Seu tema deve ser extensão da sua identidade visual. Pense nas cores que você já usa:
- **Rosa/Magenta** → energia, sensualidade, feminilidade
- **Roxo/Dark** → mistério, premium, exclusividade
- **Vermelho/Preto** → ousadia, confiança
- **Dourado/Preto** → luxo, sofisticação
- **Rosa claro/Branco** → elegância, suavidade
O tema pode ser trocado a qualquer momento. Experimente alguns antes de decidir.
---
## Passo 5: Moderação e Aprovação
Após configurar, clique em **"Submeter para Moderação"**. Nossa equipe revisa:
- Verificação de identidade (para conteúdo adulto)
- Confirmação de que os links levam a plataformas legais
- Verificação de que o conteúdo respeita as diretrizes da plataforma
**Plano Premium+Afiliados** tem fila de moderação prioritária — aprovação em até 24 horas.
---
## Passo 6: Divulgando Sua Página
### Instagram e TikTok
Bio atualizada:
```
Luna Rodrigues 🔥
Criadora de conteúdo exclusivo ✨
👇 Todas as minhas plataformas
spicylinks.site/modelo/luna-rodrigues
```
### Twitter/X
Pinned tweet:
```
Links de tudo aqui 👇
spicylinks.site/modelo/luna-rodrigues
✅ Conteúdo exclusivo
✅ Lista de desejos
✅ Bastidores
```
### Stories Frequentes
Toda semana:
- "Link na bio" com Sticker de Link apontando para seu SpicyLinks
- Stories mostrando o que está disponível em cada plataforma
---
## Usando as Métricas para Crescer
O Dashboard do SpicyLinks mostra quais links estão recebendo mais cliques.
**Como interpretar:**
| Situação | O que fazer |
|---|---|
| Assinatura com poucos cliques | Mude o título do link para algo mais atrativo |
| Lista de desejos com muitos cliques | Mantenha sempre atualizada — está convertendo |
| Muitas visitas, poucos cliques | A descrição não está convencendo — reescreva |
| Pico de visitas em um dia | Algum post viralizou — descubra qual e replique |
### Teste A/B Simples
Mude o título de um link, espere 2 semanas e compare:
```
Versão A: "Assinar Conteúdo Exclusivo"
Versão B: "Conteúdo Que Não Posto em Lugar Nenhum 🔥"
```
Títulos com emoção e especificidade geralmente convertem mais.
---
## Dicas para Maximizar Conversão
### 1. Atualize Regularmente
Uma página com links atualizados performa melhor. A audiência percebe quando algo está desatualizado.
**Sugestão de rotina:**
- Toda segunda: verificar se todos os links funcionam
- Toda semana: atualizar link da série ou conteúdo em destaque
- Todo mês: revisar a descrição e o tema
### 2. Mencione seu SpicyLinks em Vídeos
Em cada vídeo, no final:
```
"Todos os meus links estão no meu SpicyLinks —
link na bio, é só clicar!"
```
### 3. Crie Links Temporários para Promoções
Se você está fazendo uma promoção de assinatura por tempo limitado, crie um link específico e destaque no topo da página durante o período.
### 4. Foto de Perfil Profissional
Use a mesma foto que você usa no Instagram principal. A consistência entre plataformas gera mais confiança.
---
## Segurança e Privacidade
O SpicyLinks possui verificação de idade automática em todas as páginas. Isso significa:
- ✅ Menores de idade são bloqueados antes de acessar seu conteúdo
- ✅ Você tem proteção jurídica adicional
- ✅ Maior segurança para sua audiência e para você
**Nunca coloque:**
- Seu endereço residencial
- Número de CPF ou documentos pessoais
- Informações que identifiquem sua localização exata
---
## Checklist Final
**Antes de submeter para moderação:**
- [ ] Nome é o mesmo que uso nas redes sociais?
- [ ] URL (slug) é simples e fácil de lembrar?
- [ ] Descrição é atrativa e deixa claro o que ofereço?
- [ ] Links de assinatura estão no topo?
- [ ] Todos os links foram testados e funcionam?
- [ ] Lista de desejos está atualizada?
- [ ] Foto de perfil é a mesma das redes principais?
- [ ] Tema visual combina com minha identidade?
**Após aprovação:**
- [ ] Bio do Instagram atualizada com o link?
- [ ] Bio do Twitter/X atualizada?
- [ ] TikTok bio atualizada (se usar)?
- [ ] Pinned tweet com o link (Twitter/X)?
- [ ] Primeiro story anunciando a nova bio link?
---
## Conclusão
Sua página no SpicyLinks é o centro da sua presença digital como criadora. Em vez de perder seguidores que não sabem como te encontrar em cada plataforma, você oferece uma experiência limpa e profissional com um link só.
Criadoras com bio link profissional convertem mais porque reduzem a fricção no caminho entre "interesse" e "assinatura".
[Criar meu SpicyLinks agora →](https://spicylinks.site/)
---
**Última atualização:** Janeiro 2026

View File

@ -482,10 +482,182 @@ public class AdminController : Controller
} }
} }
[HttpPost]
[Route("CreatePage")] [Route("CreatePage")]
public IActionResult CreatePage() public async Task<IActionResult> CreatePage(CreatePageViewModel model)
{ {
return RedirectToAction("ManagePage", new { id = "new" }); var user = await _authService.GetCurrentUserAsync(User);
if (user == null)
return RedirectToAction("Login", "Auth");
// Check if user already has a page
var existingPage = await _userPageService.GetUserPageAsync(user.Id);
if (existingPage != null)
return RedirectToAction("EditPage");
if (!ModelState.IsValid)
{
var categories = await _categoryService.GetAllCategoriesAsync();
var themes = await _themeService.GetAvailableThemesAsync();
ViewBag.Categories = categories;
ViewBag.Themes = themes;
return View(model);
}
// Generate slug if not provided
if (string.IsNullOrEmpty(model.Slug))
{
model.Slug = await _userPageService.GenerateSlugAsync(model.Category, model.DisplayName);
}
// Check if slug is available
if (!await _userPageService.ValidateSlugAsync(model.Category, model.Slug))
{
ModelState.AddModelError("Slug", "Esta URL já está em uso. Tente outra.");
var categories = await _categoryService.GetAllCategoriesAsync();
var themes = await _themeService.GetAvailableThemesAsync();
ViewBag.Categories = categories;
ViewBag.Themes = themes;
return View(model);
}
// Check if user can create the requested number of links
var activeLinksCount = model.Links?.Count ?? 0;
if (!await _userPageService.CanCreateLinksAsync(user.Id, activeLinksCount))
{
ModelState.AddModelError("", "Você excedeu o limite de links do seu plano atual.");
var categories = await _categoryService.GetAllCategoriesAsync();
var themes = await _themeService.GetAvailableThemesAsync();
ViewBag.Categories = categories;
ViewBag.Themes = themes;
return View(model);
}
// Convert ViewModel to UserPage
var userPage = new UserPage
{
UserId = user.Id,
DisplayName = model.DisplayName,
Category = model.Category,
BusinessType = model.BusinessType,
Bio = model.Bio,
Slug = model.Slug,
Theme = await _themeService.GetThemeByNameAsync(model.SelectedTheme) ?? _themeService.GetDefaultTheme(),
Links = model.Links?.Select(l => new LinkItem
{
Title = l.Title,
Url = l.Url,
Description = l.Description,
Icon = l.Icon,
IsActive = true,
Order = model.Links.IndexOf(l)
}).ToList() ?? new List<LinkItem>()
};
// Add social media links
var socialLinks = new List<LinkItem>();
if (!string.IsNullOrEmpty(model.WhatsAppNumber))
{
socialLinks.Add(new LinkItem
{
Title = "WhatsApp",
Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}",
Icon = "fab fa-whatsapp",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.FacebookUrl))
{
socialLinks.Add(new LinkItem
{
Title = "Facebook",
Url = model.FacebookUrl,
Icon = "fab fa-facebook",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.TwitterUrl))
{
socialLinks.Add(new LinkItem
{
Title = "X / Twitter",
Url = model.TwitterUrl,
Icon = "fab fa-x-twitter",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.InstagramUrl))
{
socialLinks.Add(new LinkItem
{
Title = "Instagram",
Url = model.InstagramUrl,
Icon = "fab fa-instagram",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.TiktokUrl))
{
socialLinks.Add(new LinkItem
{
Title = "TikTok",
Url = model.TiktokUrl,
Icon = "fab fa-tiktok",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.PinterestUrl))
{
socialLinks.Add(new LinkItem
{
Title = "Pinterest",
Url = model.PinterestUrl,
Icon = "fab fa-pinterest",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.DiscordUrl))
{
socialLinks.Add(new LinkItem
{
Title = "Discord",
Url = model.DiscordUrl,
Icon = "fab fa-discord",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.KawaiUrl))
{
socialLinks.Add(new LinkItem
{
Title = "Kawai",
Url = model.KawaiUrl,
Icon = "fas fa-heart",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
userPage.Links.AddRange(socialLinks);
await _userPageService.CreatePageAsync(userPage);
TempData["Success"] = "Página criada com sucesso!";
return RedirectToAction("Dashboard");
} }
[HttpGet] [HttpGet]
@ -665,16 +837,6 @@ public class AdminController : Controller
FileSize = d.FileSize, FileSize = d.FileSize,
UploadedAt = d.UploadedAt UploadedAt = d.UploadedAt
}).ToList() ?? new List<ManageDocumentViewModel>(), }).ToList() ?? new List<ManageDocumentViewModel>(),
// Social media fields — extracted from Links so the edit form pre-fills correctly
WhatsAppNumber = page.Links?.FirstOrDefault(l => l.Icon?.Contains("whatsapp") == true)?.Url
?.Replace("https://wa.me/", "").Replace("whatsapp://", "") ?? string.Empty,
FacebookUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("facebook") == true)?.Url ?? string.Empty,
InstagramUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("instagram") == true)?.Url ?? string.Empty,
TwitterUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("twitter") == true)?.Url ?? string.Empty,
TiktokUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("tiktok") == true)?.Url ?? string.Empty,
PinterestUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("pinterest") == true)?.Url ?? string.Empty,
DiscordUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("discord") == true)?.Url ?? string.Empty,
KawaiUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("kawai") == true)?.Url ?? string.Empty,
AvailableCategories = categories, AvailableCategories = categories,
AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(), AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(),
MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(), MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(),
@ -730,13 +892,10 @@ public class AdminController : Controller
if (!string.IsNullOrEmpty(model.WhatsAppNumber)) if (!string.IsNullOrEmpty(model.WhatsAppNumber))
{ {
var whatsappDigits = model.WhatsAppNumber
.Replace("https://wa.me/", "").Replace("whatsapp://", "")
.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "");
socialLinks.Add(new LinkItem socialLinks.Add(new LinkItem
{ {
Title = "WhatsApp", Title = "WhatsApp",
Url = $"https://wa.me/{whatsappDigits}", Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}",
Icon = "fab fa-whatsapp", Icon = "fab fa-whatsapp",
IsActive = true, IsActive = true,
Order = currentOrder++ Order = currentOrder++
@ -1082,13 +1241,10 @@ public class AdminController : Controller
if (!string.IsNullOrEmpty(model.WhatsAppNumber)) if (!string.IsNullOrEmpty(model.WhatsAppNumber))
{ {
var whatsappDigits = model.WhatsAppNumber
.Replace("https://wa.me/", "").Replace("whatsapp://", "")
.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "");
socialLinks.Add(new LinkItem socialLinks.Add(new LinkItem
{ {
Title = "WhatsApp", Title = "WhatsApp",
Url = $"https://wa.me/{whatsappDigits}", Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}",
Icon = "fab fa-whatsapp", Icon = "fab fa-whatsapp",
IsActive = true, IsActive = true,
Order = currentOrder++ Order = currentOrder++

View File

@ -1,50 +0,0 @@
using BCards.Web.Configuration;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace BCards.Web.Controllers;
public class AgeGateController : Controller
{
private readonly TenantSettings _tenant;
public AgeGateController(IOptions<TenantSettings> tenantSettings)
{
_tenant = tenantSettings.Value;
}
[HttpGet("/age-gate")]
public IActionResult Index(string? returnUrl)
{
if (Request.Cookies.ContainsKey("age_verified"))
return Redirect(LocalUrl(returnUrl));
ViewBag.ReturnUrl = returnUrl;
return View();
}
[HttpPost("/age-gate/confirm")]
[ValidateAntiForgeryToken]
public IActionResult Confirm(string? returnUrl)
{
Response.Cookies.Append("age_verified", "1", new CookieOptions
{
Expires = DateTimeOffset.UtcNow.AddYears(1),
HttpOnly = true,
SameSite = SameSiteMode.Lax,
Secure = true
});
return Redirect(LocalUrl(returnUrl));
}
[HttpPost("/age-gate/deny")]
[ValidateAntiForgeryToken]
public IActionResult Deny()
{
return View("Denied");
}
private string LocalUrl(string? returnUrl) =>
!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl) ? returnUrl : "/";
}

View File

@ -2,8 +2,6 @@ using Microsoft.AspNetCore.Mvc;
using System.Text; using System.Text;
using System.Xml.Linq; using System.Xml.Linq;
using BCards.Web.Services; using BCards.Web.Services;
using BCards.Web.Areas.Tutoriais.Services;
using BCards.Web.Repositories;
namespace BCards.Web.Controllers; namespace BCards.Web.Controllers;
@ -11,21 +9,15 @@ public class SitemapController : Controller
{ {
private readonly IUserPageService _userPageService; private readonly IUserPageService _userPageService;
private readonly ILivePageService _livePageService; private readonly ILivePageService _livePageService;
private readonly IMarkdownService _markdownService;
private readonly ICategoryRepository _categoryRepository;
private readonly ILogger<SitemapController> _logger; private readonly ILogger<SitemapController> _logger;
public SitemapController( public SitemapController(
IUserPageService userPageService, IUserPageService userPageService,
ILivePageService livePageService, ILivePageService livePageService,
IMarkdownService markdownService,
ICategoryRepository categoryRepository,
ILogger<SitemapController> logger) ILogger<SitemapController> logger)
{ {
_userPageService = userPageService; _userPageService = userPageService;
_livePageService = livePageService; _livePageService = livePageService;
_markdownService = markdownService;
_categoryRepository = categoryRepository;
_logger = logger; _logger = logger;
} }
@ -35,31 +27,14 @@ public class SitemapController : Controller
{ {
try try
{ {
XNamespace ns = "http://www.sitemaps.org/schemas/sitemap/0.9"; // 🔥 NOVA FUNCIONALIDADE: Usar LivePages em vez de UserPages
var livePages = await _livePageService.GetAllActiveAsync(); var livePages = await _livePageService.GetAllActiveAsync();
// Artigos // Define namespace corretamente para evitar conflitos
var artigos = await _markdownService.GetAllArticlesAsync("Artigos", "pt-BR"); XNamespace ns = "http://www.sitemaps.org/schemas/sitemap/0.9";
// Tutoriais por categoria // Construir URLs das páginas dinâmicas separadamente para evitar problemas
var categories = await _categoryRepository.GetAllActiveAsync(); var dynamicUrls = livePages.Select(page =>
var tutorialUrls = new List<XElement>();
foreach (var cat in categories)
{
var tutorials = await _markdownService.GetArticlesByCategoryAsync(cat.Slug, "pt-BR");
foreach (var t in tutorials)
{
tutorialUrls.Add(new XElement(ns + "url",
new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/tutoriais/{cat.Slug}/{t.Slug}"),
new XElement(ns + "lastmod", t.LastMod.ToString("yyyy-MM-dd")),
new XElement(ns + "changefreq", "monthly"),
new XElement(ns + "priority", "0.7")
));
}
}
var dynamicUrls = livePages.Select(page =>
new XElement(ns + "url", new XElement(ns + "url",
new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/page/{page.Category?.Replace(" ", "-")?.ToLower()}/{page.Slug}"), new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/page/{page.Category?.Replace(" ", "-")?.ToLower()}/{page.Slug}"),
new XElement(ns + "lastmod", page.LastSyncAt.ToString("yyyy-MM-dd")), new XElement(ns + "lastmod", page.LastSyncAt.ToString("yyyy-MM-dd")),
@ -67,19 +42,11 @@ public class SitemapController : Controller
new XElement(ns + "priority", "0.8") new XElement(ns + "priority", "0.8")
) )
).ToList(); ).ToList();
var artigoUrls = artigos.Select(a =>
new XElement(ns + "url",
new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/artigos/{a.Slug}"),
new XElement(ns + "lastmod", a.LastMod.ToString("yyyy-MM-dd")),
new XElement(ns + "changefreq", "monthly"),
new XElement(ns + "priority", "0.7")
)
).ToList();
var sitemap = new XDocument( var sitemap = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"), new XDeclaration("1.0", "utf-8", "yes"),
new XElement(ns + "urlset", new XElement(ns + "urlset",
// Add static pages
new XElement(ns + "url", new XElement(ns + "url",
new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/"), new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/"),
new XElement(ns + "lastmod", DateTime.UtcNow.ToString("yyyy-MM-dd")), new XElement(ns + "lastmod", DateTime.UtcNow.ToString("yyyy-MM-dd")),
@ -92,27 +59,14 @@ public class SitemapController : Controller
new XElement(ns + "changefreq", "weekly"), new XElement(ns + "changefreq", "weekly"),
new XElement(ns + "priority", "0.9") new XElement(ns + "priority", "0.9")
), ),
new XElement(ns + "url",
new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/artigos"), // Add live pages (SEO-optimized URLs only)
new XElement(ns + "lastmod", DateTime.UtcNow.ToString("yyyy-MM-dd")),
new XElement(ns + "changefreq", "weekly"),
new XElement(ns + "priority", "0.8")
),
new XElement(ns + "url",
new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/tutoriais"),
new XElement(ns + "lastmod", DateTime.UtcNow.ToString("yyyy-MM-dd")),
new XElement(ns + "changefreq", "weekly"),
new XElement(ns + "priority", "0.8")
),
artigoUrls,
tutorialUrls,
dynamicUrls dynamicUrls
) )
); );
_logger.LogInformation("Generated sitemap with {LivePages} live pages, {Artigos} artigos, {Tutoriais} tutoriais", _logger.LogInformation($"Generated sitemap with {livePages.Count} live pages");
livePages.Count, artigos.Count, tutorialUrls.Count);
return Content(sitemap.ToString(SaveOptions.DisableFormatting), "application/xml", Encoding.UTF8); return Content(sitemap.ToString(SaveOptions.DisableFormatting), "application/xml", Encoding.UTF8);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -1,58 +0,0 @@
using BCards.Web.Configuration;
using Microsoft.Extensions.Options;
namespace BCards.Web.Middleware;
public class AgeGateMiddleware
{
private readonly RequestDelegate _next;
private readonly bool _isAgeGated;
private static readonly HashSet<string> _excludedPrefixes = new(StringComparer.OrdinalIgnoreCase)
{
"/age-gate",
"/auth",
"/admin",
"/webhook",
"/health",
"/ready",
"/live",
"/lib",
"/css",
"/js",
"/images",
"/favicon"
};
public AgeGateMiddleware(RequestDelegate next, IOptions<TenantSettings> tenantSettings)
{
_next = next;
_isAgeGated = tenantSettings.Value.AgeGated;
}
public async Task InvokeAsync(HttpContext context)
{
if (_isAgeGated && !IsVerified(context) && !IsExcluded(context.Request.Path))
{
var returnUrl = Uri.EscapeDataString(context.Request.Path + context.Request.QueryString);
context.Response.Redirect($"/age-gate?returnUrl={returnUrl}");
return;
}
await _next(context);
}
private static bool IsVerified(HttpContext context) =>
context.Request.Cookies.ContainsKey("age_verified");
private static bool IsExcluded(PathString path)
{
var value = path.Value ?? "";
foreach (var prefix in _excludedPrefixes)
{
if (value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
}

View File

@ -35,16 +35,13 @@ Serilog.Debugging.SelfLog.Enable(msg =>
System.Diagnostics.Debug.WriteLine($"[SERILOG SELF] {msg}"); System.Diagnostics.Debug.WriteLine($"[SERILOG SELF] {msg}");
}); });
var tenantName = builder.Configuration["Tenant:SiteName"] ?? "BCards";
var loggerConfig = new LoggerConfiguration() var loggerConfig = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration) .ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext() .Enrich.FromLogContext()
.Enrich.WithEnvironmentName() .Enrich.WithEnvironmentName()
.Enrich.WithProcessId() .Enrich.WithProcessId()
.Enrich.WithThreadId() .Enrich.WithThreadId()
.Enrich.WithProperty("ApplicationName", tenantName) .Enrich.WithProperty("ApplicationName", builder.Configuration["ApplicationName"] ?? "BCards")
.Enrich.WithProperty("Tenant", tenantName)
.Enrich.WithProperty("Environment", builder.Environment.EnvironmentName) .Enrich.WithProperty("Environment", builder.Environment.EnvironmentName)
.Enrich.WithProperty("Hostname", hostname); .Enrich.WithProperty("Hostname", hostname);
@ -71,37 +68,40 @@ if (isDevelopment)
var openSearchUrl = builder.Configuration["Serilog:OpenSearchUrl"]; var openSearchUrl = builder.Configuration["Serilog:OpenSearchUrl"];
if (!string.IsNullOrEmpty(openSearchUrl)) if (!string.IsNullOrEmpty(openSearchUrl))
{ {
var tenantSlug = tenantName.ToLower().Replace("+", "").Replace(" ", "-"); var indexFormat = "b-cards-dev-{0:yyyy-MM}";
var indexFormat = $"{tenantSlug}-dev-{{0:yyyy-MM}}";
Console.WriteLine($"[OPENSEARCH DEV] Configurando sink → {openSearchUrl} (index: {indexFormat})");
// TODO: após confirmar que conecta, restaurar try/catch silencioso (opcional) try
loggerConfig.WriteTo.Async(a => a.OpenSearch(new OpenSearchSinkOptions(new Uri(openSearchUrl))
{ {
IndexFormat = indexFormat, // OpenSearch configurado para ser MUITO agressivo no envio
AutoRegisterTemplate = false, loggerConfig.WriteTo.Async(a => a.OpenSearch(new OpenSearchSinkOptions(new Uri(openSearchUrl))
ModifyConnectionSettings = conn => conn
.RequestTimeout(TimeSpan.FromSeconds(5))
.PingTimeout(TimeSpan.FromSeconds(3)),
MinimumLogEventLevel = LogEventLevel.Debug,
EmitEventFailure = EmitEventFailureHandling.WriteToSelfLog,
RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway,
BatchPostingLimit = 10,
Period = TimeSpan.FromSeconds(2),
TemplateCustomSettings = new Dictionary<string, string>
{ {
{"number_of_shards", "1"}, IndexFormat = indexFormat,
{"number_of_replicas", "0"} AutoRegisterTemplate = true,
} BufferBaseFilename = "./logs/opensearch-buffer", // Buffer em disco
}), ModifyConnectionSettings = conn => conn
bufferSize: 10000, .RequestTimeout(TimeSpan.FromSeconds(8))
blockWhenFull: false); .PingTimeout(TimeSpan.FromSeconds(4)),
MinimumLogEventLevel = LogEventLevel.Debug,
Console.WriteLine($"[OPENSEARCH DEV] Sink registrado. Erros de envio aparecem como [SERILOG SELF] no console."); EmitEventFailure = EmitEventFailureHandling.WriteToSelfLog,
} RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway,
else BatchPostingLimit = 10, // Lotes pequenos = envio mais frequente
{ Period = TimeSpan.FromSeconds(2), // Envia a cada 2 segundos
Console.WriteLine("[OPENSEARCH DEV] OpenSearchUrl não configurado — sem sink OpenSearch."); // Configurações para máxima persistência
BufferRetainedInvalidPayloadsLimitBytes = 100 * 1024 * 1024, // 100MB buffer
BufferLogShippingInterval = TimeSpan.FromSeconds(1), // Tenta reenviar rapidamente
TemplateCustomSettings = new Dictionary<string, string>
{
{"number_of_shards", "1"},
{"number_of_replicas", "0"}
}
}),
bufferSize: 10000, // Buffer grande na memória
blockWhenFull: false); // Nunca bloquear aplicação
}
catch (Exception)
{
// Falha silenciosa - logs continuam no console e arquivo
}
} }
} }
else else
@ -137,8 +137,7 @@ else
_ => environment _ => environment
}; };
var tenantSlugProd = tenantName.ToLower().Replace("+", "").Replace(" ", "-"); var indexFormat = $"b-cards-{envMapping}-{{0:yyyy-MM}}";
var indexFormat = $"{tenantSlugProd}-{envMapping}-{{0:yyyy-MM}}";
try try
{ {
@ -157,10 +156,9 @@ else
Period = TimeSpan.FromSeconds(5), Period = TimeSpan.FromSeconds(5),
}), bufferSize: 10000, blockWhenFull: false); }), bufferSize: 10000, blockWhenFull: false);
} }
catch (Exception ex) catch (Exception)
{ {
// OpenSearch é opcional — app continua com console/arquivo // Falha silenciosa em produção - logs continuam no console/arquivo
Console.WriteLine($"[OPENSEARCH PROD] Falha ao configurar sink → {openSearchUrl}: {ex.Message}");
} }
} }
} }
@ -264,9 +262,6 @@ else
} }
// Stripe Configuration with validation // Stripe Configuration with validation
builder.Services.Configure<TenantSettings>(
builder.Configuration.GetSection("Tenant"));
builder.Services.Configure<StripeSettings>( builder.Services.Configure<StripeSettings>(
builder.Configuration.GetSection("Stripe")); builder.Configuration.GetSection("Stripe"));
@ -314,18 +309,8 @@ authBuilder.AddCookie(options =>
options.SlidingExpiration = true; options.SlidingExpiration = true;
options.Cookie.HttpOnly = true; options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true; options.Cookie.IsEssential = true;
options.Cookie.SameSite = SameSiteMode.None; // Para Cloudflare
// SameSite: None para produção (Cloudflare), Lax para desenvolvimento (OAuth local) options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
if (builder.Environment.IsDevelopment())
{
options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // Permite HTTP em dev
}
else
{
options.Cookie.SameSite = SameSiteMode.None; // Para Cloudflare
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
}
}); });
// Always register Google and Microsoft authentication schemes // Always register Google and Microsoft authentication schemes
@ -607,9 +592,11 @@ if (!app.Environment.IsDevelopment())
{ {
context.Request.Scheme = "https"; context.Request.Scheme = "https";
// Fix para Cloudflare - remover porta 443 explícita em qualquer domínio if (context.Request.Host.Host == "bcards.site")
if (context.Request.Host.Port == 443) {
context.Request.Host = new HostString(context.Request.Host.Host); // Fix para Cloudflare - não especificar porta explícita
context.Request.Host = new HostString("bcards.site");
}
await next(); await next();
}); });
@ -625,15 +612,15 @@ app.Use(async (context, next) =>
// Content Security Policy - Protects against XSS attacks // Content Security Policy - Protects against XSS attacks
var csp = "default-src 'self'; " + var csp = "default-src 'self'; " +
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://code.jquery.com https://cdn.jsdelivr.net/npm/bootstrap@5.3.2 https://accounts.google.com https://apis.google.com https://www.clarity.ms https://static.cloudflareinsights.com; " + "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://code.jquery.com https://cdn.jsdelivr.net/npm/bootstrap@5.3.2 https://accounts.google.com https://apis.google.com; " +
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@5.3.2 https://cdnjs.cloudflare.com; " + "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@5.3.2; " +
"img-src 'self' data: https: blob:; " + "img-src 'self' data: https: blob:; " +
"font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; " + "font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net; " +
"connect-src 'self' https://accounts.google.com https://apis.google.com https://login.microsoftonline.com https://www.clarity.ms; " + "connect-src 'self' https://accounts.google.com https://apis.google.com https://login.microsoftonline.com; " +
"frame-src 'self' https://accounts.google.com https://login.microsoftonline.com; " + "frame-src 'self' https://accounts.google.com https://login.microsoftonline.com; " +
"object-src 'none'; " + "object-src 'none'; " +
"base-uri 'self'; " + "base-uri 'self'; " +
"form-action 'self' https://accounts.google.com https://login.microsoftonline.com"; "form-action 'self'";
context.Response.Headers.Append("Content-Security-Policy", csp); context.Response.Headers.Append("Content-Security-Policy", csp);
// Load balancer e debugging headers // Load balancer e debugging headers
@ -743,7 +730,6 @@ app.Use(async (context, next) =>
app.UseMiddleware<SmartCacheMiddleware>(); app.UseMiddleware<SmartCacheMiddleware>();
app.UseMiddleware<AuthCacheMiddleware>(); app.UseMiddleware<AuthCacheMiddleware>();
app.UseMiddleware<AgeGateMiddleware>();
app.UseMiddleware<PlanLimitationMiddleware>(); app.UseMiddleware<PlanLimitationMiddleware>();
app.UseMiddleware<PageStatusMiddleware>(); app.UseMiddleware<PageStatusMiddleware>();
app.UseMiddleware<ModerationAuthMiddleware>(); app.UseMiddleware<ModerationAuthMiddleware>();
@ -895,7 +881,11 @@ using (var scope = app.Services.CreateScope())
await themeService.InitializeDefaultThemesAsync(); await themeService.InitializeDefaultThemesAsync();
} }
await categoryService.InitializeDefaultCategoriesAsync(); var existingCategories = await categoryService.GetAllCategoriesAsync();
if (!existingCategories.Any())
{
await categoryService.InitializeDefaultCategoriesAsync();
}
Log.Information("Default themes and categories initialized successfully"); Log.Information("Default themes and categories initialized successfully");
} }

View File

@ -15,22 +15,6 @@
"ASPNETCORE_ENVIRONMENT": "Testing" "ASPNETCORE_ENVIRONMENT": "Testing"
}, },
"applicationUrl": "https://localhost:49178;http://localhost:49179" "applicationUrl": "https://localhost:49178;http://localhost:49179"
},
"SpicyLinks": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Spicylinks"
},
"applicationUrl": "https://localhost:49182;http://localhost:49183"
},
"LuzLinks": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Luzlinks"
},
"applicationUrl": "https://localhost:49184;http://localhost:49185"
} }
} }
} }

View File

@ -1,7 +1,5 @@
using BCards.Web.Configuration;
using BCards.Web.Models; using BCards.Web.Models;
using BCards.Web.Repositories; using BCards.Web.Repositories;
using Microsoft.Extensions.Options;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
@ -12,12 +10,10 @@ namespace BCards.Web.Services;
public class CategoryService : ICategoryService public class CategoryService : ICategoryService
{ {
private readonly ICategoryRepository _categoryRepository; private readonly ICategoryRepository _categoryRepository;
private readonly TenantSettings _tenant;
public CategoryService(ICategoryRepository categoryRepository, IOptions<TenantSettings> tenantOptions) public CategoryService(ICategoryRepository categoryRepository)
{ {
_categoryRepository = categoryRepository; _categoryRepository = categoryRepository;
_tenant = tenantOptions.Value;
} }
public async Task<List<Category>> GetAllCategoriesAsync() public async Task<List<Category>> GetAllCategoriesAsync()
@ -58,39 +54,6 @@ public class CategoryService : ICategoryService
public async Task InitializeDefaultCategoriesAsync() public async Task InitializeDefaultCategoriesAsync()
{ {
Console.WriteLine($"[CategoryService] DefaultCategories count: {_tenant.DefaultCategories.Count}, SiteName: {_tenant.SiteName}");
// Se tenant tem DefaultCategories configuradas, sempre sincroniza (tenant config é fonte da verdade)
if (_tenant.DefaultCategories.Any())
{
var existing = await _categoryRepository.GetAllActiveAsync();
var existingSlugs = existing.Select(c => c.Slug).ToHashSet();
var configSlugs = _tenant.DefaultCategories
.Select(i => string.IsNullOrWhiteSpace(i.Slug) ? GenerateSlug(i.Name) : i.Slug)
.ToHashSet();
// Remove categorias que não estão mais na config
foreach (var cat in existing.Where(c => !configSlugs.Contains(c.Slug)))
await _categoryRepository.DeleteAsync(cat.Id!);
// Adiciona categorias novas
foreach (var item in _tenant.DefaultCategories)
{
var slug = string.IsNullOrWhiteSpace(item.Slug) ? GenerateSlug(item.Name) : item.Slug;
if (!existingSlugs.Contains(slug))
{
await _categoryRepository.CreateAsync(new Category
{
Name = item.Name,
Slug = slug,
Icon = item.Icon,
Description = item.Description,
SeoKeywords = item.SeoKeywords
});
}
}
return;
}
var categories = await _categoryRepository.GetAllActiveAsync(); var categories = await _categoryRepository.GetAllActiveAsync();
if (categories.Any()) return; if (categories.Any()) return;

View File

@ -1,5 +1,3 @@
using BCards.Web.Configuration;
using Microsoft.Extensions.Options;
using SendGrid; using SendGrid;
using SendGrid.Helpers.Mail; using SendGrid.Helpers.Mail;
@ -10,18 +8,15 @@ public class EmailService : IEmailService
private readonly ISendGridClient _sendGridClient; private readonly ISendGridClient _sendGridClient;
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
private readonly ILogger<EmailService> _logger; private readonly ILogger<EmailService> _logger;
private readonly TenantSettings _tenant;
public EmailService( public EmailService(
ISendGridClient sendGridClient, ISendGridClient sendGridClient,
IConfiguration configuration, IConfiguration configuration,
ILogger<EmailService> logger, ILogger<EmailService> logger)
IOptions<TenantSettings> tenantSettings)
{ {
_sendGridClient = sendGridClient; _sendGridClient = sendGridClient;
_configuration = configuration; _configuration = configuration;
_logger = logger; _logger = logger;
_tenant = tenantSettings.Value;
} }
public async Task SendModerationStatusAsync(string userEmail, string userName, string pageTitle, string status, string? reason = null, string? previewUrl = null) public async Task SendModerationStatusAsync(string userEmail, string userName, string pageTitle, string status, string? reason = null, string? previewUrl = null)
@ -101,7 +96,7 @@ public class EmailService : IEmailService
private (string subject, string htmlContent) GetPendingModerationTemplate(string userName, string pageTitle, string? previewUrl) private (string subject, string htmlContent) GetPendingModerationTemplate(string userName, string pageTitle, string? previewUrl)
{ {
var subject = $"📋 Sua página está sendo analisada - {_tenant.SiteName}"; var subject = "📋 Sua página está sendo analisada - bcards.site";
var previewButton = !string.IsNullOrEmpty(previewUrl) var previewButton = !string.IsNullOrEmpty(previewUrl)
? $"<p><a href='{previewUrl}' style='background: #28a745; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; display: inline-block;'>Ver Preview</a></p>" ? $"<p><a href='{previewUrl}' style='background: #28a745; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; display: inline-block;'>Ver Preview</a></p>"
: ""; : "";
@ -131,7 +126,7 @@ public class EmailService : IEmailService
private (string subject, string htmlContent) GetApprovedTemplate(string userName, string pageTitle) private (string subject, string htmlContent) GetApprovedTemplate(string userName, string pageTitle)
{ {
var subject = $"✅ Sua página foi aprovada! - {_tenant.SiteName}"; var subject = "✅ Sua página foi aprovada! - bcards.site";
var htmlContent = $@" var htmlContent = $@"
<div style='font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;'> <div style='font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;'>
<h2 style='color: #28a745;'>Parabéns {userName}! 🎉</h2> <h2 style='color: #28a745;'>Parabéns {userName}! 🎉</h2>
@ -162,7 +157,7 @@ public class EmailService : IEmailService
private (string subject, string htmlContent) GetRejectedTemplate(string userName, string pageTitle, string? reason) private (string subject, string htmlContent) GetRejectedTemplate(string userName, string pageTitle, string? reason)
{ {
var subject = $"⚠️ Sua página precisa de ajustes - {_tenant.SiteName}"; var subject = "⚠️ Sua página precisa de ajustes - bcards.site";
var reasonText = !string.IsNullOrEmpty(reason) ? $"<p><strong>Motivo:</strong> {reason}</p>" : ""; var reasonText = !string.IsNullOrEmpty(reason) ? $"<p><strong>Motivo:</strong> {reason}</p>" : "";
var htmlContent = $@" var htmlContent = $@"

View File

@ -0,0 +1,58 @@
using System.ComponentModel.DataAnnotations;
namespace BCards.Web.ViewModels;
public class CreatePageViewModel
{
[Required(ErrorMessage = "Nome é obrigatório")]
[StringLength(50, ErrorMessage = "Nome deve ter no máximo 50 caracteres")]
public string DisplayName { get; set; } = string.Empty;
[Required(ErrorMessage = "Categoria é obrigatória")]
public string Category { get; set; } = string.Empty;
[Required(ErrorMessage = "Tipo de negócio é obrigatório")]
public string BusinessType { get; set; } = "individual";
[StringLength(200, ErrorMessage = "Bio deve ter no máximo 200 caracteres")]
public string Bio { get; set; } = string.Empty;
[Required(ErrorMessage = "Tema é obrigatório")]
public string SelectedTheme { get; set; } = "minimalist";
public string WhatsAppNumber { get; set; } = string.Empty;
public string FacebookUrl { get; set; } = string.Empty;
public string TwitterUrl { get; set; } = string.Empty;
public string InstagramUrl { get; set; } = string.Empty;
public string TiktokUrl { get; set; } = string.Empty;
public string PinterestUrl { get; set; } = string.Empty;
public string DiscordUrl { get; set; } = string.Empty;
public string KawaiUrl { get; set; } = string.Empty;
public List<CreateLinkViewModel> Links { get; set; } = new();
public string Slug { get; set; } = string.Empty;
}
public class CreateLinkViewModel
{
[Required(ErrorMessage = "Título é obrigatório")]
[StringLength(50, ErrorMessage = "Título deve ter no máximo 50 caracteres")]
public string Title { get; set; } = string.Empty;
[Required(ErrorMessage = "URL é obrigatória")]
[Url(ErrorMessage = "URL inválida")]
public string Url { get; set; } = string.Empty;
[StringLength(100, ErrorMessage = "Descrição deve ter no máximo 100 caracteres")]
public string Description { get; set; } = string.Empty;
public string Icon { get; set; } = string.Empty;
}

View File

@ -18,7 +18,7 @@ public class ManagePageViewModel
[Required(ErrorMessage = "Tipo de negócio é obrigatório")] [Required(ErrorMessage = "Tipo de negócio é obrigatório")]
public string BusinessType { get; set; } = "individual"; public string BusinessType { get; set; } = "individual";
[StringLength(3000, ErrorMessage = "Bio deve ter no máximo 3000 caracteres")] [StringLength(200, ErrorMessage = "Bio deve ter no máximo 200 caracteres")]
public string Bio { get; set; } = string.Empty; public string Bio { get; set; } = string.Empty;
public string Slug { get; set; } = string.Empty; public string Slug { get; set; } = string.Empty;

View File

@ -0,0 +1,618 @@
@model BCards.Web.ViewModels.CreatePageViewModel
@{
ViewData["Title"] = "Criar Página";
Layout = "_Layout";
}
<div class="container-fluid">
<div class="row">
<div class="col-12 col-lg-8 mx-auto">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">
<i class="fas fa-magic"></i>
Criar Sua Página de Links
</h4>
</div>
<div class="card-body">
<!-- Progress Bar -->
<div class="progress mb-4" style="height: 8px;">
<div class="progress-bar" role="progressbar" style="width: 20%" id="wizardProgress"></div>
</div>
<form asp-action="CreatePage" method="post" id="createPageForm">
<!-- Step 1: Informações Básicas -->
<div class="wizard-step" id="step1">
<h5 class="step-title">
<span class="step-number">1</span>
Informações Básicas
</h5>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label asp-for="DisplayName" class="form-label">Nome da Página</label>
<input asp-for="DisplayName" class="form-control" placeholder="Ex: João Silva">
<span asp-validation-for="DisplayName" class="text-danger"></span>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label asp-for="Category" class="form-label">Categoria</label>
<select asp-for="Category" class="form-select">
<option value="">Selecione uma categoria</option>
@foreach (var category in ViewBag.Categories as List<BCards.Web.Models.Category> ?? new List<BCards.Web.Models.Category>())
{
<option value="@category.Name">@category.Name</option>
}
</select>
<span asp-validation-for="Category" class="text-danger"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label asp-for="BusinessType" class="form-label">Tipo</label>
<select asp-for="BusinessType" class="form-select">
<option value="individual">Pessoa Física</option>
<option value="company">Empresa</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="slugPreview" class="form-label">URL da Página</label>
<div class="input-group">
<span class="input-group-text">page/</span>
<span class="input-group-text" id="categorySlug">categoria</span>
<span class="input-group-text">/</span>
<input type="text" class="form-control" id="slugPreview" readonly>
<input asp-for="Slug" type="hidden">
</div>
<small class="form-text text-muted">URL gerada automaticamente</small>
</div>
</div>
</div>
<div class="mb-3">
<label asp-for="Bio" class="form-label">Bio/Descrição</label>
<textarea asp-for="Bio" class="form-control" rows="3" placeholder="Uma breve descrição sobre você ou sua empresa..."></textarea>
<span asp-validation-for="Bio" class="text-danger"></span>
</div>
</div>
<!-- Step 2: Seleção de Tema -->
<div class="wizard-step d-none" id="step2">
<h5 class="step-title">
<span class="step-number">2</span>
Escolha Seu Tema Visual
</h5>
<div class="row">
@foreach (var theme in ViewBag.Themes as List<BCards.Web.Models.PageTheme> ?? new List<BCards.Web.Models.PageTheme>())
{
<div class="col-md-4 mb-3">
<div class="theme-card" data-theme="@theme.Name.ToLower()">
<div class="theme-preview" style="background: @theme.BackgroundColor; color: @theme.TextColor;">
<div class="theme-header" style="background-color: @theme.PrimaryColor;">
<div class="theme-avatar"></div>
<h6>@theme.Name</h6>
</div>
<div class="theme-links">
<div class="theme-link" style="background-color: @theme.PrimaryColor;"></div>
<div class="theme-link" style="background-color: @theme.SecondaryColor;"></div>
</div>
</div>
<div class="theme-name">
@theme.Name
@if (theme.IsPremium)
{
<span class="badge bg-warning">Premium</span>
}
</div>
</div>
</div>
}
</div>
<input asp-for="SelectedTheme" type="hidden">
</div>
<!-- Step 3: Links Principais -->
<div class="wizard-step d-none" id="step3">
<h5 class="step-title">
<span class="step-number">3</span>
Links Principais
</h5>
<div id="linksContainer">
<!-- Links will be added dynamically -->
</div>
<button type="button" class="btn btn-outline-primary" id="addLinkBtn">
<i class="fas fa-plus"></i> Adicionar Link
</button>
</div>
<!-- Step 4: Redes Sociais -->
<div class="wizard-step d-none" id="step4">
<h5 class="step-title">
<span class="step-number">4</span>
Redes Sociais
</h5>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label asp-for="WhatsAppNumber" class="form-label">
<i class="fab fa-whatsapp text-success"></i>
WhatsApp
</label>
<input asp-for="WhatsAppNumber" class="form-control" placeholder="+55 11 99999-9999">
<span asp-validation-for="WhatsAppNumber" class="text-danger"></span>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label asp-for="FacebookUrl" class="form-label">
<i class="fab fa-facebook text-primary"></i>
Facebook
</label>
<input asp-for="FacebookUrl" class="form-control" placeholder="https://facebook.com/seu-perfil">
<span asp-validation-for="FacebookUrl" class="text-danger"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label asp-for="TwitterUrl" class="form-label">
<i class="fab fa-x-twitter"></i>
X / Twitter
</label>
<input asp-for="TwitterUrl" class="form-control" placeholder="https://x.com/seu-perfil">
<span asp-validation-for="TwitterUrl" class="text-danger"></span>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label asp-for="InstagramUrl" class="form-label">
<i class="fab fa-instagram text-danger"></i>
Instagram
</label>
<input asp-for="InstagramUrl" class="form-control" placeholder="https://instagram.com/seu-perfil">
<span asp-validation-for="InstagramUrl" class="text-danger"></span>
</div>
</div>
</div>
</div>
<!-- Step 5: Preview e Finalização -->
<div class="wizard-step d-none" id="step5">
<h5 class="step-title">
<span class="step-number">5</span>
Preview e Finalização
</h5>
<div class="preview-container">
<div class="preview-phone">
<div class="preview-screen" id="previewScreen">
<!-- Preview will be generated here -->
</div>
</div>
</div>
<div class="text-center mt-4">
<p class="text-muted">Sua página estará disponível em:</p>
<strong id="finalUrl">page/categoria/seu-slug</strong>
</div>
</div>
<!-- Navigation Buttons -->
<div class="wizard-navigation mt-4">
<button type="button" class="btn btn-secondary" id="prevBtn" style="display: none;">
<i class="fas fa-arrow-left"></i> Anterior
</button>
<button type="button" class="btn btn-primary float-end" id="nextBtn">
Próximo <i class="fas fa-arrow-right"></i>
</button>
<button type="submit" class="btn btn-success float-end d-none" id="submitBtn">
<i class="fas fa-check"></i> Criar Página
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<style>
.wizard-step {
min-height: 400px;
}
.step-title {
color: #495057;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 2px solid #e9ecef;
}
.step-number {
display: inline-block;
width: 30px;
height: 30px;
line-height: 30px;
background-color: #007bff;
color: white;
border-radius: 50%;
text-align: center;
margin-right: 0.5rem;
font-size: 0.875rem;
font-weight: 600;
}
.theme-card {
cursor: pointer;
border: 2px solid transparent;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
}
.theme-card:hover,
.theme-card.selected {
border-color: #007bff;
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
}
.theme-preview {
height: 120px;
position: relative;
padding: 1rem;
}
.theme-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
border-radius: 4px;
margin-bottom: 1rem;
}
.theme-avatar {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.3);
}
.theme-header h6 {
margin: 0;
font-size: 0.75rem;
color: white;
}
.theme-links {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.theme-link {
height: 8px;
border-radius: 4px;
opacity: 0.8;
}
.theme-name {
padding: 0.75rem;
text-align: center;
font-weight: 500;
background-color: #f8f9fa;
}
.link-input-group {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
}
.preview-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
}
.preview-phone {
width: 300px;
height: 400px;
border: 8px solid #333;
border-radius: 20px;
background-color: #000;
padding: 20px 10px;
position: relative;
}
.preview-screen {
width: 100%;
height: 100%;
background-color: #fff;
border-radius: 12px;
overflow-y: auto;
padding: 1rem;
}
.wizard-navigation {
border-top: 1px solid #dee2e6;
padding-top: 1rem;
}
</style>
<script>
let currentStep = 1;
const totalSteps = 5;
let linkCount = 0;
$(document).ready(function() {
initializeWizard();
// Generate slug when name or category changes
$('#DisplayName, #Category').on('input change', function() {
generateSlug();
});
// Theme selection
$('.theme-card').on('click', function() {
$('.theme-card').removeClass('selected');
$(this).addClass('selected');
const themeName = $(this).data('theme');
$('#SelectedTheme').val(themeName);
});
// Navigation
$('#nextBtn').on('click', function() {
if (validateCurrentStep()) {
nextStep();
}
});
$('#prevBtn').on('click', function() {
prevStep();
});
// Add link functionality
$('#addLinkBtn').on('click', function() {
addLinkInput();
});
// Form submission
$('#createPageForm').on('submit', function(e) {
generateLinksData();
});
});
function initializeWizard() {
updateProgressBar();
updateNavigationButtons();
addLinkInput(); // Add first link input
}
function nextStep() {
if (currentStep < totalSteps) {
$(`#step${currentStep}`).addClass('d-none');
currentStep++;
$(`#step${currentStep}`).removeClass('d-none');
if (currentStep === 5) {
generatePreview();
}
updateProgressBar();
updateNavigationButtons();
}
}
function prevStep() {
if (currentStep > 1) {
$(`#step${currentStep}`).addClass('d-none');
currentStep--;
$(`#step${currentStep}`).removeClass('d-none');
updateProgressBar();
updateNavigationButtons();
}
}
function updateProgressBar() {
const progress = (currentStep / totalSteps) * 100;
$('#wizardProgress').css('width', progress + '%');
}
function updateNavigationButtons() {
$('#prevBtn').toggle(currentStep > 1);
if (currentStep === totalSteps) {
$('#nextBtn').addClass('d-none');
$('#submitBtn').removeClass('d-none');
} else {
$('#nextBtn').removeClass('d-none');
$('#submitBtn').addClass('d-none');
}
}
function validateCurrentStep() {
let isValid = true;
switch (currentStep) {
case 1:
if (!$('#DisplayName').val() || !$('#Category').val()) {
alert('Por favor, preencha o nome e a categoria.');
isValid = false;
}
break;
case 2:
if (!$('#SelectedTheme').val()) {
alert('Por favor, selecione um tema.');
isValid = false;
}
break;
}
return isValid;
}
function generateSlug() {
const name = $('#DisplayName').val();
const category = $('#Category').val();
if (name && category) {
$.post('/Admin/GenerateSlug', { category: category, name: name })
.done(function(data) {
$('#Slug').val(data.slug);
$('#slugPreview').val(data.slug);
$('#categorySlug').text(category);
$('#finalUrl').text(`page/${category}/${data.slug}`);
});
}
}
function addLinkInput() {
linkCount++;
const linkHtml = `
<div class="link-input-group" data-link="${linkCount}">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Link ${linkCount}</h6>
<button type="button" class="btn btn-sm btn-outline-danger remove-link-btn">
<i class="fas fa-trash"></i>
</button>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-2">
<label class="form-label">Título</label>
<input type="text" class="form-control link-title" placeholder="Ex: Meu Site">
</div>
</div>
<div class="col-md-6">
<div class="mb-2">
<label class="form-label">URL</label>
<input type="url" class="form-control link-url" placeholder="https://exemplo.com">
</div>
</div>
</div>
<div class="mb-2">
<label class="form-label">Descrição (opcional)</label>
<input type="text" class="form-control link-description" placeholder="Breve descrição do link">
</div>
</div>
`;
$('#linksContainer').append(linkHtml);
// Add remove functionality
$('.remove-link-btn').off('click').on('click', function() {
$(this).closest('.link-input-group').remove();
});
}
function generateLinksData() {
const links = [];
$('.link-input-group').each(function() {
const title = $(this).find('.link-title').val();
const url = $(this).find('.link-url').val();
const description = $(this).find('.link-description').val();
if (title && url) {
links.push({
Title: title,
Url: url,
Description: description,
Icon: ''
});
}
});
// Remove existing hidden link inputs
$('input[name^="Links["]').remove();
// Create hidden inputs for links directly in the form
links.forEach((link, index) => {
$('#createPageForm').append(`
<input type="hidden" name="Links[${index}].Title" value="${link.Title}" />
<input type="hidden" name="Links[${index}].Url" value="${link.Url}" />
<input type="hidden" name="Links[${index}].Description" value="${link.Description}" />
<input type="hidden" name="Links[${index}].Icon" value="${link.Icon}" />
`);
});
// Debug: Log what we're sending
console.log('=== DEBUG GENERATELINKSDATA ===');
console.log('Links found:', links.length);
links.forEach((link, index) => {
console.log(`Link ${index}:`, link);
});
console.log('=== FIM DEBUG ===');
}
function generatePreview() {
const name = $('#DisplayName').val();
const bio = $('#Bio').val();
const selectedTheme = $('#SelectedTheme').val();
let previewHtml = `
<div class="text-center">
<div class="mb-3">
<div style="width: 60px; height: 60px; background-color: #ddd; border-radius: 50%; margin: 0 auto;"></div>
</div>
<h5 class="mb-2">${name}</h5>
<p class="text-muted small mb-3">${bio}</p>
<div class="d-grid gap-2">
`;
// Add links preview
$('.link-input-group').each(function() {
const title = $(this).find('.link-title').val();
if (title) {
previewHtml += `<div class="btn btn-primary btn-sm">${title}</div>`;
}
});
// Add social media preview
if ($('#WhatsAppNumber').val()) {
previewHtml += `<div class="btn btn-success btn-sm"><i class="fab fa-whatsapp"></i> WhatsApp</div>`;
}
if ($('#FacebookUrl').val()) {
previewHtml += `<div class="btn btn-primary btn-sm"><i class="fab fa-facebook"></i> Facebook</div>`;
}
if ($('#TwitterUrl').val()) {
previewHtml += `<div class="btn btn-dark btn-sm"><i class="fab fa-x-twitter"></i> X / Twitter</div>`;
}
if ($('#InstagramUrl').val()) {
previewHtml += `<div class="btn btn-danger btn-sm"><i class="fab fa-instagram"></i> Instagram</div>`;
}
previewHtml += `</div></div>`;
$('#previewScreen').html(previewHtml);
}
</script>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View File

@ -1,8 +1,6 @@
@model BCards.Web.ViewModels.DashboardViewModel @model BCards.Web.ViewModels.DashboardViewModel
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value; ViewData["Title"] = "Dashboard - BCards";
ViewData["Title"] = $"Dashboard - {tenant.SiteName}";
Layout = "_Layout"; Layout = "_Layout";
var pageInCreation = Model.UserPages.FirstOrDefault(p => (p.LastModerationStatus ?? p.Status) == BCards.Web.ViewModels.PageStatus.Creating); var pageInCreation = Model.UserPages.FirstOrDefault(p => (p.LastModerationStatus ?? p.Status) == BCards.Web.ViewModels.PageStatus.Creating);
} }

View File

@ -1,17 +1,8 @@
@using BCards.Web.Utils @using BCards.Web.Utils
@model BCards.Web.ViewModels.ManagePageViewModel @model BCards.Web.ViewModels.ManagePageViewModel
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
ViewData["Title"] = Model.IsNewPage ? "Criar Página" : "Editar Página"; ViewData["Title"] = Model.IsNewPage ? "Criar Página" : "Editar Página";
Layout = "_Layout"; Layout = "_Layout";
var tenant = TenantConfig.Value;
var linkTypesJson = System.Text.Json.JsonSerializer.Serialize(
tenant.AllowedLinkTypes.ToDictionary(
lt => lt.Icon,
lt => new { prefix = lt.Prefix, visualPrefix = lt.VisualPrefix, placeholder = lt.Placeholder, instructions = lt.Instructions, color = lt.Color }
),
new System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase }
);
} }
<div class="container-fluid"> <div class="container-fluid">
@ -115,54 +106,9 @@
<div class="mb-3"> <div class="mb-3">
<label asp-for="Bio" class="form-label">Bio/Descrição</label> <label asp-for="Bio" class="form-label">Bio/Descrição</label>
<div class="md-toolbar border rounded-top border-bottom-0 bg-light px-2 py-1 d-flex gap-1 flex-wrap position-relative"> <textarea asp-for="Bio" class="form-control" rows="3" placeholder="Uma breve descrição sobre você ou sua empresa..."></textarea>
<button type="button" class="btn btn-sm btn-outline-secondary md-btn" data-target="Bio" data-wrap="**" title="Negrito"><b>B</b></button>
<button type="button" class="btn btn-sm btn-outline-secondary md-btn" data-target="Bio" data-wrap="*" title="Itálico"><i>I</i></button>
<button type="button" class="btn btn-sm btn-outline-secondary md-list-btn" data-target="Bio" title="Lista">&#8226; Lista</button>
<button type="button" class="btn btn-sm btn-outline-secondary md-link-btn" data-target="Bio" title="Link">&#128279; Link</button>
<button type="button" class="btn btn-sm btn-outline-secondary md-icon-picker-btn" data-target="Bio" title="Inserir ícone">&#9881; Ícone</button>
<div class="md-icon-picker-panel shadow border rounded bg-white p-2" style="display:none; position:absolute; top:100%; left:0; z-index:9999; width:300px; max-height:260px; overflow-y:auto;">
<div class="md-icon-section mb-2">
<div class="text-muted small mb-1 fw-semibold">Status</div>
<div class="d-flex flex-wrap gap-1">
<span class="md-icon-item" title="OK">✅</span><span class="md-icon-item" title="Erro">❌</span><span class="md-icon-item" title="Atenção">⚠️</span><span class="md-icon-item" title="Info"></span><span class="md-icon-item" title="Check">✔</span><span class="md-icon-item" title="X">✘</span><span class="md-icon-item" title="Seta">➤</span><span class="md-icon-item" title="Seta direita">→</span>
</div>
</div>
<div class="md-icon-section mb-2">
<div class="text-muted small mb-1 fw-semibold">Formas &amp; Bullets</div>
<div class="d-flex flex-wrap gap-1">
<span class="md-icon-item" title="Bullet">•</span><span class="md-icon-item" title="Círculo vazio">○</span><span class="md-icon-item" title="Círculo cheio">●</span><span class="md-icon-item" title="Quadrado cheio">■</span><span class="md-icon-item" title="Quadrado vazio">□</span><span class="md-icon-item" title="Quadrado com check">☑</span><span class="md-icon-item" title="Losango cheio">◆</span><span class="md-icon-item" title="Losango vazio">◇</span><span class="md-icon-item" title="Triângulo">▶</span><span class="md-icon-item" title="Estrela">★</span><span class="md-icon-item" title="Estrela vazia">☆</span>
</div>
</div>
<div class="md-icon-section mb-2">
<div class="text-muted small mb-1 fw-semibold">Círculos coloridos</div>
<div class="d-flex flex-wrap gap-1">
<span class="md-icon-item" title="Vermelho">🔴</span><span class="md-icon-item" title="Laranja">🟠</span><span class="md-icon-item" title="Amarelo">🟡</span><span class="md-icon-item" title="Verde">🟢</span><span class="md-icon-item" title="Azul">🔵</span><span class="md-icon-item" title="Roxo">🟣</span><span class="md-icon-item" title="Preto">⚫</span><span class="md-icon-item" title="Branco">⚪</span>
</div>
</div>
<div class="md-icon-section mb-2">
<div class="text-muted small mb-1 fw-semibold">Negócios &amp; Escritório</div>
<div class="d-flex flex-wrap gap-1">
<span class="md-icon-item" title="Gráfico barras">📊</span><span class="md-icon-item" title="Gráfico alta">📈</span><span class="md-icon-item" title="Gráfico baixa">📉</span><span class="md-icon-item" title="Prancheta">📋</span><span class="md-icon-item" title="Pin">📌</span><span class="md-icon-item" title="Pasta">📁</span><span class="md-icon-item" title="Maleta">💼</span><span class="md-icon-item" title="Nota">📝</span><span class="md-icon-item" title="Email">📧</span><span class="md-icon-item" title="Telefone">📞</span><span class="md-icon-item" title="Alvo">🎯</span><span class="md-icon-item" title="Troféu">🏆</span>
</div>
</div>
<div class="md-icon-section mb-2">
<div class="text-muted small mb-1 fw-semibold">Tecnologia</div>
<div class="d-flex flex-wrap gap-1">
<span class="md-icon-item" title="Notebook">💻</span><span class="md-icon-item" title="Monitor">🖥️</span><span class="md-icon-item" title="Celular">📱</span><span class="md-icon-item" title="Teclado">⌨️</span><span class="md-icon-item" title="Impressora">🖨️</span><span class="md-icon-item" title="Engrenagem">⚙️</span><span class="md-icon-item" title="Lupa">🔍</span><span class="md-icon-item" title="Link">🔗</span>
</div>
</div>
<div class="md-icon-section">
<div class="text-muted small mb-1 fw-semibold">Finanças &amp; Outros</div>
<div class="d-flex flex-wrap gap-1">
<span class="md-icon-item" title="Dinheiro">💰</span><span class="md-icon-item" title="Cartão">💳</span><span class="md-icon-item" title="Banco">🏦</span><span class="md-icon-item" title="Chave">🔑</span><span class="md-icon-item" title="Cadeado">🔒</span><span class="md-icon-item" title="Sino">🔔</span><span class="md-icon-item" title="Lâmpada">💡</span><span class="md-icon-item" title="Raio">⚡</span><span class="md-icon-item" title="Pessoa">👤</span><span class="md-icon-item" title="Pessoas">👥</span>
</div>
</div>
</div>
</div>
<textarea asp-for="Bio" id="Bio" class="form-control rounded-0 rounded-bottom" rows="5" maxlength="3000" placeholder="Uma breve descrição sobre você ou sua empresa..." style="font-family: monospace; font-size: 0.9rem;"></textarea>
<span asp-validation-for="Bio" class="text-danger"></span> <span asp-validation-for="Bio" class="text-danger"></span>
<div class="form-text">Máximo 3000 caracteres. Use **negrito**, *itálico*, - item para listas.</div> <div class="form-text">Máximo 200 caracteres</div>
</div> </div>
<!-- Profile Image Upload --> <!-- Profile Image Upload -->
@ -910,10 +856,18 @@
<label for="linkIcon" class="form-label">Tipo de Link <span class="text-danger">*</span></label> <label for="linkIcon" class="form-label">Tipo de Link <span class="text-danger">*</span></label>
<select class="form-select" id="linkIcon" required> <select class="form-select" id="linkIcon" required>
<option value="">Selecione o tipo de link</option> <option value="">Selecione o tipo de link</option>
@foreach (var lt in tenant.AllowedLinkTypes) <option value="fas fa-globe">🌐 Site Geral</option>
{ <option value="fas fa-shopping-cart">🛒 Loja/E-commerce</option>
<option value="@lt.Icon">@lt.Label</option> <option value="fas fa-briefcase">💼 Portfólio</option>
} <option value="fas fa-envelope">✉️ Email</option>
<option value="fas fa-phone">📞 Telefone</option>
<option value="fas fa-map-marker-alt">📍 Localização</option>
<option value="fab fa-youtube">📺 YouTube</option>
<option value="fab fa-linkedin">💼 LinkedIn</option>
<option value="fab fa-github">💻 GitHub</option>
<option value="fas fa-download">⬇️ Download</option>
<option value="fas fa-calendar">📅 Agenda</option>
<option value="fas fa-heart">❤️ Favorito</option>
</select> </select>
<div class="form-text">Escolha o tipo para obter instruções específicas</div> <div class="form-text">Escolha o tipo para obter instruções específicas</div>
</div> </div>
@ -1255,17 +1209,14 @@
@section Scripts { @section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");} @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script> <script>
const LINK_TYPES_CONFIG = @Html.Raw(linkTypesJson);
let linkCount = @Model.Links.Count; let linkCount = @Model.Links.Count;
let documentCount = @(Model.Documents?.Count ?? 0); let documentCount = @(Model.Documents?.Count ?? 0);
let currentStep = 1; let currentStep = 1;
const totalSteps = 5; const totalSteps = 5;
$(document).ready(function() { $(document).ready(function() {
// Initialize Markdown toolbar
initMarkdownToolbar();
// Initialize social media fields // Initialize social media fields
initializeSocialMedia(); initializeSocialMedia();
@ -1384,11 +1335,11 @@
updateLinkNumbers(); updateLinkNumbers();
}); });
// Loading state — só desabilita se a validação JS não bloqueou o submit // Form validation
$('#managePageForm').on('submit', function(e) { $('#managePageForm').on('submit', function(e) {
if (!e.isDefaultPrevented()) { console.log('Form submitted');
$(this).find('button[type="submit"]').prop('disabled', true).html('<i class="fas fa-spinner fa-spin me-2"></i>Salvando...'); // Allow submission but add loading state
} $(this).find('button[type="submit"]').prop('disabled', true).html('<i class="fas fa-spinner fa-spin me-2"></i>Criando...');
}); });
}); });
@ -1990,84 +1941,6 @@
}, 7000); }, 7000);
} }
// Markdown Toolbar
function initMarkdownToolbar() {
// Icon picker toggle
document.querySelectorAll('.md-icon-picker-btn').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
var panel = this.parentElement.querySelector('.md-icon-picker-panel');
var isVisible = panel.style.display !== 'none';
document.querySelectorAll('.md-icon-picker-panel').forEach(function(p) { p.style.display = 'none'; });
if (!isVisible) panel.style.display = 'block';
});
});
// Insert icon on click
document.querySelectorAll('.md-icon-item').forEach(function(item) {
item.addEventListener('click', function(e) {
e.stopPropagation();
var panel = this.closest('.md-icon-picker-panel');
var btn = panel.parentElement.querySelector('.md-icon-picker-btn');
var targetId = btn ? btn.dataset.target : 'Bio';
var ta = document.getElementById(targetId);
var icon = this.textContent;
var start = ta.selectionStart, end = ta.selectionEnd;
ta.value = ta.value.substring(0, start) + icon + ta.value.substring(end);
ta.selectionStart = ta.selectionEnd = start + icon.length;
ta.focus();
panel.style.display = 'none';
});
});
// Close picker on outside click
document.addEventListener('click', function() {
document.querySelectorAll('.md-icon-picker-panel').forEach(function(p) { p.style.display = 'none'; });
});
document.querySelectorAll('.md-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var targetId = this.dataset.target;
var wrap = this.dataset.wrap;
var ta = document.getElementById(targetId);
var start = ta.selectionStart, end = ta.selectionEnd;
var sel = ta.value.substring(start, end) || 'texto';
var before = ta.value.substring(0, start);
var after = ta.value.substring(end);
ta.value = before + wrap + sel + wrap + after;
ta.selectionStart = start + wrap.length;
ta.selectionEnd = start + wrap.length + sel.length;
ta.focus();
});
});
document.querySelectorAll('.md-list-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var targetId = this.dataset.target;
var ta = document.getElementById(targetId);
var start = ta.selectionStart;
var lineStart = ta.value.lastIndexOf('\n', start - 1) + 1;
var before = ta.value.substring(0, lineStart);
var after = ta.value.substring(lineStart);
ta.value = before + '- ' + after;
ta.selectionStart = ta.selectionEnd = start + 2;
ta.focus();
});
});
document.querySelectorAll('.md-link-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var targetId = this.dataset.target;
var ta = document.getElementById(targetId);
var start = ta.selectionStart, end = ta.selectionEnd;
var sel = ta.value.substring(start, end) || 'texto do link';
var url = prompt('URL do link:') || 'https://';
var before = ta.value.substring(0, start);
var after = ta.value.substring(end);
var md = '[' + sel + '](' + url + ')';
ta.value = before + md + after;
ta.selectionStart = ta.selectionEnd = start + md.length;
ta.focus();
});
});
}
// Validation Error Handling // Validation Error Handling
function checkValidationErrors() { function checkValidationErrors() {
// Só verificar erros se estamos em um POST-back (ou seja, se ModelState foi validado) // Só verificar erros se estamos em um POST-back (ou seja, se ModelState foi validado)
@ -2387,10 +2260,8 @@
} }
// Atualizar campo hidden - SEMPRE string, nunca null // Atualizar campo hidden - SEMPRE string, nunca null
// WhatsApp: armazena só o número (servidor adiciona https://wa.me/ ao salvar)
// Outros: armazena URL completa (servidor usa diretamente)
if (value) { if (value) {
hiddenField.val(isWhatsApp ? value : prefix + value); hiddenField.val(prefix + value);
} else { } else {
hiddenField.val(' '); // Espaço em branco para evitar null hiddenField.val(' '); // Espaço em branco para evitar null
} }
@ -2443,8 +2314,82 @@
const $input = $('#linkUrlInput'); const $input = $('#linkUrlInput');
const $instructions = $('#urlInstructions'); const $instructions = $('#urlInstructions');
// Configurações por tipo de ícone — carregadas do appsettings via LINK_TYPES_CONFIG // Configurações por tipo de ícone
const linkTypes = LINK_TYPES_CONFIG; const linkTypes = {
'fas fa-envelope': {
prefix: 'mailto:',
placeholder: 'seuemail@@exemplo.com',
instructions: 'Digite apenas o email (sem mailto:)',
color: 'bg-success'
},
'fas fa-phone': {
prefix: 'tel:',
placeholder: '5511999999999',
instructions: 'Digite o telefone com código do país e DDD (apenas números)',
color: 'bg-success'
},
'fab fa-youtube': {
prefix: 'https://youtube.com/',
placeholder: 'watch?v=VIDEO_ID ou @@usuario ou c/CANAL',
instructions: 'Digite o ID do vídeo, @@usuário ou c/canal',
color: 'bg-danger'
},
'fab fa-linkedin': {
prefix: 'https://linkedin.com/in/',
placeholder: 'seu-perfil-linkedin',
instructions: 'Digite apenas seu nome de usuário do LinkedIn',
color: 'bg-primary'
},
'fab fa-github': {
prefix: 'https://github.com/',
placeholder: 'usuario ou usuario/repositorio',
instructions: 'Digite seu usuário ou usuário/repositório',
color: 'bg-dark'
},
'fas fa-map-marker-alt': {
prefix: 'https://maps.google.com/?q=',
visualPrefix: '📍 Maps:',
placeholder: 'Rua das Flores, 123 - São Paulo, SP',
instructions: 'Digite o endereço completo (acentos e espaços serão codificados automaticamente)',
color: 'bg-warning'
},
'fas fa-globe': {
prefix: 'https://',
placeholder: 'exemplo.com',
instructions: 'Digite apenas o domínio e caminho (sem https://)',
color: 'bg-primary'
},
'fas fa-shopping-cart': {
prefix: 'https://',
placeholder: 'minhaloja.com/produto',
instructions: 'Digite apenas o domínio e caminho da sua loja',
color: 'bg-success'
},
'fas fa-briefcase': {
prefix: 'https://',
placeholder: 'meuportifolio.com',
instructions: 'Digite apenas o domínio do seu portfólio',
color: 'bg-info'
},
'fas fa-download': {
prefix: 'https://',
placeholder: 'exemplo.com/arquivo.pdf',
instructions: 'Digite o link direto para download',
color: 'bg-secondary'
},
'fas fa-calendar': {
prefix: 'https://',
placeholder: 'calendly.com/seunome',
instructions: 'Digite o link do seu calendário (Calendly, etc.)',
color: 'bg-info'
},
'fas fa-heart': {
prefix: 'https://',
placeholder: 'exemplo.com',
instructions: 'Digite qualquer link especial',
color: 'bg-danger'
}
};
if (iconValue && linkTypes[iconValue]) { if (iconValue && linkTypes[iconValue]) {
const config = linkTypes[iconValue]; const config = linkTypes[iconValue];
@ -2512,7 +2457,14 @@
const selectedIcon = $('#linkIcon').val(); const selectedIcon = $('#linkIcon').val();
let realPrefix = 'https://'; let realPrefix = 'https://';
const linkTypes = LINK_TYPES_CONFIG; const linkTypes = {
'fas fa-envelope': { prefix: 'mailto:' },
'fas fa-phone': { prefix: 'tel:' },
'fas fa-map-marker-alt': { prefix: 'https://maps.google.com/?q=' },
'fab fa-youtube': { prefix: 'https://youtube.com/' },
'fab fa-linkedin': { prefix: 'https://linkedin.com/in/' },
'fab fa-github': { prefix: 'https://github.com/' }
};
if (selectedIcon && linkTypes[selectedIcon]) { if (selectedIcon && linkTypes[selectedIcon]) {
realPrefix = linkTypes[selectedIcon].prefix; realPrefix = linkTypes[selectedIcon].prefix;
@ -2733,16 +2685,6 @@
@section Styles { @section Styles {
<style> <style>
.md-icon-item {
cursor: pointer;
font-size: 1.2rem;
padding: 2px 4px;
border-radius: 4px;
line-height: 1.4;
transition: background 0.1s;
}
.md-icon-item:hover { background: #e9ecef; }
/* Estilo customizado para o scroll dos temas */ /* Estilo customizado para o scroll dos temas */
.themes-container { .themes-container {
scrollbar-width: thin; scrollbar-width: thin;

View File

@ -1,32 +0,0 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
ViewData["Title"] = "Acesso Negado";
Layout = null;
}
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@tenant.SiteName — Acesso Negado</title>
<link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.min.css" />
<style>
body {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
</style>
</head>
<body>
<div class="text-center p-5">
<h1 class="fs-1 mb-3">🚫</h1>
<h2>Acesso não permitido</h2>
<p class="text-white-50">Este site é exclusivo para maiores de 18 anos.</p>
</div>
</body>
</html>

View File

@ -1,73 +0,0 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
ViewData["Title"] = "Verificação de Idade";
Layout = null;
}
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@tenant.SiteName — Verificação de Idade</title>
<link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.min.css" />
<style>
body {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.age-gate-card {
max-width: 440px;
width: 100%;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.15);
border-radius: 16px;
backdrop-filter: blur(10px);
color: #fff;
}
.btn-confirm {
background: linear-gradient(135deg, #e94560, #c23152);
border: none;
padding: 12px 32px;
font-size: 1.1rem;
border-radius: 8px;
color: #fff;
width: 100%;
}
.btn-confirm:hover { opacity: 0.9; color: #fff; }
.btn-deny { color: rgba(255,255,255,0.5); background: none; border: none; width: 100%; }
.btn-deny:hover { color: rgba(255,255,255,0.8); }
</style>
</head>
<body>
<div class="age-gate-card p-5 text-center mx-3">
<h1 class="fs-1 mb-2">🔞</h1>
<h2 class="fw-bold mb-1">@tenant.SiteName</h2>
<p class="text-white-50 mb-4">Este site contém conteúdo exclusivo para adultos.</p>
<p class="mb-4">
Você confirma que tem <strong>18 anos ou mais</strong> e concorda com os
<a href="/Legal/Terms" class="text-white-50">Termos de Uso</a>?
</p>
<form asp-controller="AgeGate" asp-action="Confirm" method="post">
@Html.AntiForgeryToken()
<input type="hidden" name="returnUrl" value="@ViewBag.ReturnUrl" />
<button type="submit" class="btn btn-confirm mb-3">
✅ Sim, tenho 18 anos ou mais
</button>
</form>
<form asp-controller="AgeGate" asp-action="Deny" method="post">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-deny">
Não, tenho menos de 18 anos
</button>
</form>
</div>
<script src="~/lib/bootstrap/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@ -1,7 +1,5 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value; ViewData["Title"] = "Login - BCards";
ViewData["Title"] = $"Login - {tenant.SiteName}";
var returnUrl = ViewBag.ReturnUrl as string; var returnUrl = ViewBag.ReturnUrl as string;
var isPreview = ViewBag.IsPreview as bool? ?? false; var isPreview = ViewBag.IsPreview as bool? ?? false;
Layout = isPreview ? "_Layout" : "_UserPageLayout"; Layout = isPreview ? "_Layout" : "_UserPageLayout";
@ -13,7 +11,7 @@
<div class="card shadow"> <div class="card shadow">
<div class="card-body p-4"> <div class="card-body p-4">
<div class="text-center mb-4"> <div class="text-center mb-4">
<h2 class="text-primary fw-bold">@tenant.SiteName</h2> <h2 class="text-primary fw-bold">BCards</h2>
<p class="text-muted">Entre na sua conta</p> <p class="text-muted">Entre na sua conta</p>
</div> </div>

View File

@ -1,22 +1,23 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value; //var isPreview = ViewBag.IsPreview as bool? ?? false;
ViewData["Title"] = $"{tenant.SiteName} - {tenant.Tagline}"; ViewData["Title"] = "BCards - Crie sua bio / links Profissional";
var categories = ViewBag.Categories as List<BCards.Web.Models.Category> ?? new List<BCards.Web.Models.Category>(); var categories = ViewBag.Categories as List<BCards.Web.Models.Category> ?? new List<BCards.Web.Models.Category>();
var recentPages = ViewBag.RecentPages as List<BCards.Web.Models.UserPage> ?? new List<BCards.Web.Models.UserPage>(); var recentPages = ViewBag.RecentPages as List<BCards.Web.Models.UserPage> ?? new List<BCards.Web.Models.UserPage>();
//Layout = isPreview ? "_Layout" : "_UserPageLayout";
Layout = "_Layout"; Layout = "_Layout";
var featuresHeadline = tenant.FeaturesHeadline.Replace("{SiteName}", tenant.SiteName);
} }
<div class="hero-section text-white py-5 mb-5"> <div class="hero-section bg-primary bg-gradient text-white py-5 mb-5">
<div class="container"> <div class="container">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col-lg-6"> <div class="col-lg-6">
<h1 class="display-4 fw-bold mb-4"> <h1 class="display-4 fw-bold mb-4">
@tenant.HeroHeadline Crie sua página profissional em minutos
</h1> </h1>
<p class="lead mb-4"> <p class="lead mb-4">
@tenant.HeroDescription A melhor alternativa ao para ter uma página de links simples.
Criada para profissionais e empresas no Brasil.
Organize todos os seus links em uma página única e profissional.
</p> </p>
<div class="d-flex gap-3 flex-wrap mb-4 mb-lg-0"> <div class="d-flex gap-3 flex-wrap mb-4 mb-lg-0">
@if (User.Identity?.IsAuthenticated == true) @if (User.Identity?.IsAuthenticated == true)
@ -28,7 +29,7 @@
else else
{ {
<a asp-controller="Auth" asp-action="Login" class="btn btn-light btn-lg px-4"> <a asp-controller="Auth" asp-action="Login" class="btn btn-light btn-lg px-4">
@tenant.HeroCtaText Começar Grátis
</a> </a>
} }
<a asp-controller="Home" asp-action="Pricing" class="btn btn-outline-light btn-lg px-4"> <a asp-controller="Home" asp-action="Pricing" class="btn btn-outline-light btn-lg px-4">
@ -37,7 +38,7 @@
</div> </div>
</div> </div>
<div class="col-lg-6 text-center"> <div class="col-lg-6 text-center">
<img src="~/images/hero-mockup.svg" alt="Exemplo de página @tenant.SiteName" class="img-fluid rounded shadow-lg" style="max-height: 400px;"> <img src="~/images/hero-mockup.svg" alt="Exemplo de página BCards" class="img-fluid rounded shadow-lg" style="max-height: 400px;">
</div> </div>
</div> </div>
</div> </div>
@ -53,7 +54,7 @@
@foreach (var category in categories.Take(8)) @foreach (var category in categories.Take(8))
{ {
<div class="col-6 col-md-3"> <div class="col-6 col-md-3">
<a href="@Url.Action("Category", "Home", new { categorySlug = category.Slug })" <a href="@Url.Action("Category", "Home", new { categorySlug = category.Slug })"
class="text-decoration-none"> class="text-decoration-none">
<div class="card h-100 border-0 shadow-sm hover-card"> <div class="card h-100 border-0 shadow-sm hover-card">
<div class="card-body text-center"> <div class="card-body text-center">
@ -69,32 +70,44 @@
} }
<!-- Funcionalidades --> <!-- Funcionalidades -->
@if (tenant.Features.Any()) <section class="mb-5">
{ <h2 class="text-center mb-4">Por que escolher o BCards?</h2>
<section class="mb-5"> <div class="row g-4">
<h2 class="text-center mb-4">@featuresHeadline</h2> <div class="col-md-4">
<div class="row g-4"> <div class="text-center">
@foreach (var feature in tenant.Features) <div class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
{ <i class="fs-2 text-primary">🎨</i>
<div class="col-md-4">
<div class="text-center">
<div class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
<i class="fs-2 text-primary">@feature.Icon</i>
</div>
<h5>@feature.Title</h5>
<p class="text-muted">@feature.Description</p>
</div>
</div> </div>
} <h5>Temas Profissionais</h5>
<p class="text-muted">Escolha entre diversos temas profissionais ou personalize as cores da sua página.</p>
</div>
</div> </div>
</section> <div class="col-md-4">
} <div class="text-center">
<div class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
<i class="fs-2 text-primary">📊</i>
</div>
<h5>Analytics Avançado</h5>
<p class="text-muted">Acompanhe quantas pessoas visitaram sua página e clicaram nos seus links.</p>
</div>
</div>
<div class="col-md-4">
<div class="text-center">
<div class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
<i class="fs-2 text-primary">🔗</i>
</div>
<h5>URLs Organizadas</h5>
<p class="text-muted">Suas URLs são organizadas por categoria: bcards.site/corretor/seu-nome</p>
</div>
</div>
</div>
</section>
<!-- Páginas Recentes --> <!-- Páginas Recentes -->
@if (recentPages.Any()) @if (recentPages.Any())
{ {
<section class="mb-5"> <section class="mb-5">
<h2 class="text-center mb-4">Quem já usa o @tenant.SiteName</h2> <h2 class="text-center mb-4">Profissionais que confiam no BCards</h2>
<div class="row g-3"> <div class="row g-3">
@foreach (var page in recentPages) @foreach (var page in recentPages)
{ {
@ -103,12 +116,12 @@
<div class="card-body text-center"> <div class="card-body text-center">
@if (!string.IsNullOrEmpty(page.ProfileImageId)) @if (!string.IsNullOrEmpty(page.ProfileImageId))
{ {
<img src="@(page.ProfileImageUrl)" alt="@(page.DisplayName)" <img src="@(page.ProfileImageUrl)" alt="@(page.DisplayName)"
class="rounded-circle mb-3" style="width: 60px; height: 60px; object-fit: cover;"> class="rounded-circle mb-3" style="width: 60px; height: 60px; object-fit: cover;">
} }
else else
{ {
<div class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" <div class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3"
style="width: 60px; height: 60px;"> style="width: 60px; height: 60px;">
<i class="fas fa-id-card text-primary"></i> <i class="fas fa-id-card text-primary"></i>
</div> </div>
@ -116,7 +129,7 @@
<h6 class="card-title">@(page.DisplayName)</h6> <h6 class="card-title">@(page.DisplayName)</h6>
<small class="text-muted text-capitalize">@(page.Category)</small> <small class="text-muted text-capitalize">@(page.Category)</small>
<div class="mt-2"> <div class="mt-2">
<a href="~/@(page.Category)/@(page.Slug)" target="_blank" <a href="~/@(page.Category)/@(page.Slug)" target="_blank"
class="btn btn-sm btn-outline-primary"> class="btn btn-sm btn-outline-primary">
Ver Página Ver Página
</a> </a>
@ -132,20 +145,20 @@
<!-- CTA Final --> <!-- CTA Final -->
<section class="text-center py-5 bg-light rounded-3 mb-5"> <section class="text-center py-5 bg-light rounded-3 mb-5">
<div class="container"> <div class="container">
<h2 class="mb-3">@tenant.CtaHeadline</h2> <h2 class="mb-3">Pronto para começar?</h2>
<p class="lead mb-4 text-muted"> <p class="lead mb-4 text-muted">
@tenant.CtaDescription Crie sua página profissional agora mesmo e comece a organizar seus links.
</p> </p>
@if (User.Identity?.IsAuthenticated == true) @if (User.Identity?.IsAuthenticated == true)
{ {
<a asp-controller="Admin" asp-action="Dashboard" class="btn btn-primary btn-lg"> <a asp-controller="Admin" asp-action="Dashboard" class="btn btn-primary btn-lg">
@tenant.CtaButtonText Criar Minha Página
</a> </a>
} }
else else
{ {
<a asp-controller="Auth" asp-action="Login" class="btn btn-primary btn-lg"> <a asp-controller="Auth" asp-action="Login" class="btn btn-primary btn-lg">
@tenant.CtaButtonText Começar Grátis
</a> </a>
} }
</div> </div>
@ -157,13 +170,13 @@
.hero-section { .hero-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
} }
.hover-card { .hover-card {
transition: transform 0.2s ease-in-out; transition: transform 0.2s ease-in-out;
} }
.hover-card:hover { .hover-card:hover {
transform: translateY(-5px); transform: translateY(-5px);
} }
</style> </style>
} }

View File

@ -1,81 +1,33 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value; ViewData["Title"] = "Planos e Preços - BCards";
ViewData["Title"] = $"Planos e Preços - {tenant.SiteName}";
//var isPreview = ViewBag.IsPreview as bool? ?? false; //var isPreview = ViewBag.IsPreview as bool? ?? false;
//Layout = isPreview ? "_Layout" : "_UserPageLayout"; //Layout = isPreview ? "_Layout" : "_UserPageLayout";
Layout = "_Layout"; Layout = "_Layout";
} }
<div class="container-fluid px-3 px-lg-5 py-5"> <div class="container py-5">
<div class="text-center mb-5"> <div class="text-center mb-5">
<h1 class="display-5 fw-bold mb-3">Escolha o plano ideal para você</h1> <h1 class="display-5 fw-bold mb-3">Escolha o plano ideal para você</h1>
<p class="lead text-muted">Comece grátis e faça upgrade quando precisar de mais recursos</p> <p class="lead text-muted">Comece grátis e faça upgrade quando precisar de mais recursos</p>
<!-- Toggle Mensal/Anual --> <!-- Toggle Mensal/Anual -->
<div class="d-flex justify-content-center mb-4 mt-3"> <div class="d-flex justify-content-center mb-4">
<div class="pricing-toggle-wrapper"> <div class="btn-group" role="group" aria-label="Período de cobrança">
<span class="pricing-savings-chip">🎁 2 meses grátis</span> <input type="radio" class="btn-check" name="billingPeriod" id="monthly" autocomplete="off" checked>
<div class="pricing-pill"> <label class="btn btn-outline-primary" for="monthly">Mensal</label>
<button type="button" class="pill-btn pill-active" id="btnMonthly">Mensal</button>
<button type="button" class="pill-btn" id="btnYearly">Anual</button> <input type="radio" class="btn-check" name="billingPeriod" id="yearly" autocomplete="off">
</div> <label class="btn btn-outline-primary" for="yearly">
Anual
<span class="badge bg-success ms-1">2 meses grátis</span>
</label>
</div> </div>
</div> </div>
<style>
.pricing-toggle-wrapper {
position: relative;
display: inline-block;
}
.pricing-savings-chip {
position: absolute;
top: -24px;
right: -4px;
background: #198754;
color: #fff;
font-size: 0.68rem;
font-weight: 700;
padding: 2px 9px;
border-radius: 20px;
white-space: nowrap;
letter-spacing: 0.02em;
}
.pricing-pill {
display: inline-flex;
background: #ededf3;
border-radius: 50px;
padding: 4px;
gap: 2px;
}
.pill-btn {
padding: 8px 32px;
border-radius: 50px;
border: none;
background: transparent;
color: #555;
font-weight: 600;
font-size: 0.95rem;
cursor: pointer;
transition: background 0.18s, color 0.18s, box-shadow 0.18s;
white-space: nowrap;
}
.pill-btn.pill-active {
background: var(--tenant-primary, #667eea);
color: #fff;
box-shadow: 0 2px 10px rgba(0,0,0,.18);
}
.pricing-cards .card-body {
font-size: 0.875rem;
}
.pricing-cards .card-header h5 {
font-size: 0.95rem;
}
</style>
</div> </div>
<div class="row g-4 row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-5 pricing-cards"> <div class="row g-3 justify-content-center pricing-cards">
<!-- Plano Trial --> <!-- Plano Trial -->
<div class="col"> <div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-0 shadow-sm"> <div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-success bg-opacity-10 text-center py-4"> <div class="card-header bg-success bg-opacity-10 text-center py-4">
<h5 class="mb-0">Trial Gratuito</h5> <h5 class="mb-0">Trial Gratuito</h5>
@ -111,7 +63,7 @@
<div class="card-footer bg-transparent p-4"> <div class="card-footer bg-transparent p-4">
@if (User.Identity?.IsAuthenticated == true) @if (User.Identity?.IsAuthenticated == true)
{ {
<a asp-controller="Admin" asp-action="ManagePage" asp-route-id="new" class="btn btn-success w-100">Começar Grátis</a> <a asp-controller="Admin" asp-action="CreatePage" class="btn btn-success w-100">Começar Grátis</a>
} }
else else
{ {
@ -122,20 +74,20 @@
</div> </div>
<!-- Plano Básico --> <!-- Plano Básico -->
<div class="col"> <div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-0 shadow-sm"> <div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-light text-center py-4"> <div class="card-header bg-light text-center py-4">
<h5 class="mb-0">Básico</h5> <h5 class="mb-0">Básico</h5>
<div class="mt-3"> <div class="mt-3">
<div class="pricing-monthly"> <div class="pricing-monthly">
<span class="display-5 fw-bold text-primary">R$ 12,90</span> <span class="display-5 fw-bold text-primary">R$ 12,90</span>
<span class="text-muted">/mês</span> <span class="text-muted">/mês</span>
</div> </div>
<div class="pricing-yearly d-none"> <div class="pricing-yearly d-none">
<span class="display-5 fw-bold text-primary">R$ 129,00</span> <span class="display-5 fw-bold text-primary">R$ 129,00</span>
<span class="text-muted">/ano</span> <span class="text-muted">/ano</span>
<br> <br>
<small class="text-success">Economize R$ 25,80 (2 meses grátis)</small> <small class="text-success">Economize R$ 25,80 (2 meses grátis)</small>
</div> </div>
</div> </div>
</div> </div>
@ -196,20 +148,20 @@
</div> </div>
<!-- Plano Profissional (Decoy) --> <!-- Plano Profissional (Decoy) -->
<div class="col"> <div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-0 shadow-sm"> <div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-warning bg-opacity-10 text-center py-4"> <div class="card-header bg-warning bg-opacity-10 text-center py-4">
<h5 class="mb-0">Profissional</h5> <h5 class="mb-0">Profissional</h5>
<div class="mt-3"> <div class="mt-3">
<div class="pricing-monthly"> <div class="pricing-monthly">
<span class="display-5 fw-bold text-warning">R$ 25,90</span> <span class="display-5 fw-bold text-warning">R$ 25,90</span>
<span class="text-muted">/mês</span> <span class="text-muted">/mês</span>
</div> </div>
<div class="pricing-yearly d-none"> <div class="pricing-yearly d-none">
<span class="display-5 fw-bold text-warning">R$ 259,00</span> <span class="display-5 fw-bold text-warning">R$ 259,00</span>
<span class="text-muted">/ano</span> <span class="text-muted">/ano</span>
<br> <br>
<small class="text-success">Economize R$ 51,80 (2 meses grátis)</small> <small class="text-success">Economize R$ 51,80 (2 meses grátis)</small>
</div> </div>
</div> </div>
</div> </div>
@ -270,21 +222,23 @@
</div> </div>
<!-- Plano Premium (Mais Popular) --> <!-- Plano Premium (Mais Popular) -->
<div class="col"> <div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-primary shadow"> <div class="card h-100 border-primary shadow position-relative">
<div class="card-header bg-primary text-white text-center pt-3 pb-4"> <div class="position-absolute top-0 start-50 translate-middle">
<span class="badge bg-white text-primary px-3 py-1 mb-2 d-inline-block" style="font-size:0.7rem;letter-spacing:.04em;">⭐ Mais Popular</span> <span class="badge bg-primary px-3 py-2">Mais Popular</span>
</div>
<div class="card-header bg-primary text-white text-center py-4">
<h5 class="mb-0">Premium</h5> <h5 class="mb-0">Premium</h5>
<div class="mt-3"> <div class="mt-3">
<div class="pricing-monthly"> <div class="pricing-monthly">
<span class="display-5 fw-bold">R$ 29,90</span> <span class="display-5 fw-bold">R$ 29,90</span>
<span class="opacity-75">/mês</span> <span class="opacity-75">/mês</span>
</div> </div>
<div class="pricing-yearly d-none"> <div class="pricing-yearly d-none">
<span class="display-5 fw-bold">R$ 299,00</span> <span class="display-5 fw-bold">R$ 299,00</span>
<span class="opacity-75">/ano</span> <span class="opacity-75">/ano</span>
<br> <br>
<small class="text-light">Economize R$ 59,80 (2 meses grátis)</small> <small class="text-light">Economize R$ 59,80 (2 meses grátis)</small>
</div> </div>
</div> </div>
<small class="opacity-75">Melhor custo-benefício!</small> <small class="opacity-75">Melhor custo-benefício!</small>
@ -311,18 +265,18 @@
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
URL personalizada URL personalizada
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
Suporte prioritário Suporte prioritário
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
Upload de PDFs (até 5 arquivos) Upload de PDFs (até 5 arquivos)
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-muted me-2">✗</i> <i class="text-muted me-2">✗</i>
<span class="text-muted">Links de produto</span> <span class="text-muted">Links de produto</span>
</li> </li>
</ul> </ul>
<div class="mt-3"> <div class="mt-3">
<small class="text-muted">* 20 temas básicos + 20 temas premium exclusivos</small> <small class="text-muted">* 20 temas básicos + 20 temas premium exclusivos</small>
@ -353,21 +307,23 @@
</div> </div>
<!-- Plano Premium + Afiliados --> <!-- Plano Premium + Afiliados -->
<div class="col"> <div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-success shadow"> <div class="card h-100 border-success shadow">
<div class="card-header bg-success text-white text-center pt-3 pb-4"> <div class="position-absolute top-0 start-50 translate-middle pricing-premium-badge">
<span class="badge bg-white text-success px-3 py-1 mb-2 d-inline-block" style="font-size:0.7rem;letter-spacing:.04em;">🆕 Novo!</span> <span class="badge bg-success px-3 py-2">Novo!</span>
</div>
<div class="card-header bg-success text-white text-center py-4">
<h5 class="mb-0">Premium + Afiliados</h5> <h5 class="mb-0">Premium + Afiliados</h5>
<div class="mt-3"> <div class="mt-3">
<div class="pricing-monthly"> <div class="pricing-monthly">
<span class="display-5 fw-bold">R$ 34,90</span> <span class="display-5 fw-bold">R$ 34,90</span>
<span class="opacity-75">/mês</span> <span class="opacity-75">/mês</span>
</div> </div>
<div class="pricing-yearly d-none"> <div class="pricing-yearly d-none">
<span class="display-5 fw-bold">R$ 349,00</span> <span class="display-5 fw-bold">R$ 349,00</span>
<span class="opacity-75">/ano</span> <span class="opacity-75">/ano</span>
<br> <br>
<small class="text-light">Economize R$ 69,80 (2 meses grátis)</small> <small class="text-light">Economize R$ 69,80 (2 meses grátis)</small>
</div> </div>
</div> </div>
<small class="opacity-75">Para monetização!</small> <small class="opacity-75">Para monetização!</small>
@ -394,19 +350,19 @@
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
Moderação plus Moderação plus
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
Suporte prioritário Suporte prioritário
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
10 links afiliados 10 links afiliados
</li> </li>
<li class="mb-3"> <li class="mb-3">
<i class="text-success me-2">✓</i> <i class="text-success me-2">✓</i>
Upload de PDFs (até 10 arquivos) Upload de PDFs (até 10 arquivos)
</li> </li>
</ul> </ul>
<div class="mt-3"> <div class="mt-3">
<small class="text-muted">* 20 temas básicos + 20 temas premium exclusivos</small> <small class="text-muted">* 20 temas básicos + 20 temas premium exclusivos</small>
</div> </div>
@ -436,7 +392,6 @@
</div> </div>
</div> </div>
<div class="container">
<!-- Comparação de recursos --> <!-- Comparação de recursos -->
<div class="mt-5 pt-5"> <div class="mt-5 pt-5">
<h2 class="text-center mb-4">Compare todos os recursos</h2> <h2 class="text-center mb-4">Compare todos os recursos</h2>
@ -578,7 +533,6 @@
</div> </div>
</div> </div>
</div> </div>
</div><!-- /container inner -->
</div> </div>
@if (TempData["Success"] != null) @if (TempData["Success"] != null)
@ -631,20 +585,22 @@
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const btnMonthly = document.getElementById('btnMonthly'); const monthlyRadio = document.getElementById('monthly');
const btnYearly = document.getElementById('btnYearly'); const yearlyRadio = document.getElementById('yearly');
const monthlyElements = document.querySelectorAll('.pricing-monthly'); const monthlyElements = document.querySelectorAll('.pricing-monthly');
const yearlyElements = document.querySelectorAll('.pricing-yearly'); const yearlyElements = document.querySelectorAll('.pricing-yearly');
function setPricing(period) { function togglePricing() {
const isYearly = period === 'yearly'; if (yearlyRadio.checked) {
btnMonthly.classList.toggle('pill-active', !isYearly); monthlyElements.forEach(el => el.classList.add('d-none'));
btnYearly.classList.toggle('pill-active', isYearly); yearlyElements.forEach(el => el.classList.remove('d-none'));
monthlyElements.forEach(el => el.classList.toggle('d-none', isYearly)); } else {
yearlyElements.forEach(el => el.classList.toggle('d-none', !isYearly)); monthlyElements.forEach(el => el.classList.remove('d-none'));
yearlyElements.forEach(el => el.classList.add('d-none'));
}
} }
btnMonthly.addEventListener('click', () => setPricing('monthly')); monthlyRadio.addEventListener('change', togglePricing);
btnYearly.addEventListener('click', () => setPricing('yearly')); yearlyRadio.addEventListener('change', togglePricing);
}); });
</script> </script>

View File

@ -1,6 +1,4 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value;
ViewData["Title"] = "Diretrizes da Comunidade"; ViewData["Title"] = "Diretrizes da Comunidade";
Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/Views/Shared/_Layout.cshtml";
} }
@ -10,13 +8,13 @@
<div class="col-lg-10"> <div class="col-lg-10">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-body p-4 p-md-5"> <div class="card-body p-4 p-md-5">
<h1 class="card-title text-primary fw-bold">Diretrizes da Comunidade @tenant.SiteName</h1> <h1 class="card-title text-primary fw-bold">Diretrizes da Comunidade BCards</h1>
<p class="text-muted">Última atualização: 31 de agosto de 2025</p> <p class="text-muted">Última atualização: 31 de agosto de 2025</p>
<hr class="my-4"> <hr class="my-4">
<p>O @tenant.SiteName se dedica a manter uma comunidade segura, profissional e respeitosa. Todas as páginas e conteúdos criados em nossa plataforma devem seguir estas regras. O descumprimento pode levar à remoção do conteúdo, suspensão da página ou banimento da conta.</p> <p>O BCards se dedica a manter uma comunidade segura, profissional e respeitosa. Todas as páginas e conteúdos criados em nossa plataforma devem seguir estas regras. O descumprimento pode levar à remoção do conteúdo, suspensão da página ou banimento da conta.</p>
<p>Nosso objetivo é capacitar criadores e profissionais. Estas regras nos ajudam a garantir que o @tenant.SiteName continue sendo uma plataforma confiável e valiosa para todos.</p> <p>Nosso objetivo é capacitar criadores e profissionais. Estas regras nos ajudam a garantir que o BCards continue sendo uma plataforma confiável e valiosa para todos.</p>
<h4 class="mt-5 fw-bold text-danger">1. Conteúdo Estritamente Proibido</h4> <h4 class="mt-5 fw-bold text-danger">1. Conteúdo Estritamente Proibido</h4>
<p>O seguinte conteúdo será removido imediatamente e pode resultar no banimento da conta sem aviso prévio:</p> <p>O seguinte conteúdo será removido imediatamente e pode resultar no banimento da conta sem aviso prévio:</p>
@ -50,7 +48,7 @@
<div class="alert alert-info mt-5" role="alert"> <div class="alert alert-info mt-5" role="alert">
<h5 class="alert-heading">Como Denunciar</h5> <h5 class="alert-heading">Como Denunciar</h5>
<p>Se você encontrar conteúdo que viola estas diretrizes, por favor, denuncie através do link "Denunciar" presente no rodapé de todas as páginas de usuário ou envie um e-mail para <a href="mailto:@tenant.SupportEmail">@tenant.SupportEmail</a>. Levamos todas as denúncias a sério.</p> <p>Se você encontrar conteúdo que viola estas diretrizes, por favor, denuncie através do link "Denunciar" presente no rodapé de todas as páginas de usuário ou envie um e-mail para <a href="mailto:suporte@bcards.site">suporte@bcards.site</a>. Levamos todas as denúncias a sério.</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,8 +1,4 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value;
var supportParts = tenant.SupportEmail.Split('@');
var dpoParts = tenant.DpoEmail.Split('@');
ViewData["Title"] = "Política de Privacidade"; ViewData["Title"] = "Política de Privacidade";
Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/Views/Shared/_Layout.cshtml";
} }
@ -17,22 +13,22 @@
<hr class="my-4"> <hr class="my-4">
<p>Bem-vindo à Política de Privacidade do <strong>@tenant.SiteName</strong>. Sua privacidade é de extrema importância para nós. Este documento explica como coletamos, usamos, compartilhamos e protegemos suas informações pessoais, em conformidade com a Lei Geral de Proteção de Dados (LGPD), Lei nº 13.709/2018, do Brasil, e outras legislações aplicáveis nos países em que atuamos.</p> <p>Bem-vindo à Política de Privacidade do <strong>BCards</strong>. Sua privacidade é de extrema importância para nós. Este documento explica como coletamos, usamos, compartilhamos e protegemos suas informações pessoais, em conformidade com a Lei Geral de Proteção de Dados (LGPD), Lei nº 13.709/2018, do Brasil, e outras legislações aplicáveis nos países em que atuamos.</p>
<h4 class="mt-5 fw-bold">1. Dados Pessoais Coletados e a Finalidade</h4> <h4 class="mt-5 fw-bold">1. Dados Pessoais Coletados e a Finalidade</h4>
<p>Coletamos diferentes tipos de informações para fornecer e melhorar nossos serviços para você:</p> <p>Coletamos diferentes tipos de informações para fornecer e melhorar nossos serviços para você:</p>
<ul> <ul>
<li><strong>Dados de Cadastro e Autenticação:</strong> Ao se registrar usando Google ou Microsoft (OAuth), coletamos seu nome, endereço de e-mail e foto de perfil. Usamos esses dados para criar e gerenciar sua conta, permitir seu acesso à plataforma e nos comunicarmos com você.</li> <li><strong>Dados de Cadastro e Autenticação:</strong> Ao se registrar usando Google ou Microsoft (OAuth), coletamos seu nome, endereço de e-mail e foto de perfil. Usamos esses dados para criar e gerenciar sua conta, permitir seu acesso à plataforma e nos comunicarmos com você.</li>
<li><strong>Conteúdo da Página Pública:</strong> Coletamos as informações que você insere em sua página @tenant.SiteName, como título, biografia, links, categoria de negócio e imagens. A finalidade é exibir publicamente sua página conforme sua configuração.</li> <li><strong>Conteúdo da Página Pública:</strong> Coletamos as informações que você insere em sua página BCards, como título, biografia, links, categoria de negócio e imagens. A finalidade é exibir publicamente sua página conforme sua configuração.</li>
<li><strong>Dados de Pagamento:</strong> Para nossos planos pagos, utilizamos o Stripe como processador de pagamentos. Não armazenamos os dados do seu cartão de crédito. O Stripe coleta as informações necessárias para processar a transação de forma segura.</li> <li><strong>Dados de Pagamento:</strong> Para nossos planos pagos, utilizamos o Stripe como processador de pagamentos. Não armazenamos os dados do seu cartão de crédito. O Stripe coleta as informações necessárias para processar a transação de forma segura.</li>
<li><strong>Dados de Análise e Uso (Analytics):</strong> Coletamos dados sobre como os visitantes e você interagem com as páginas @tenant.SiteName. Isso inclui visualizações de página, cliques em links, endereço IP (anonimizado sempre que possível), tipo de dispositivo, sistema operacional e navegador. Usamos esses dados para fornecer estatísticas, entender o uso da plataforma e melhorar nossos serviços.</li> <li><strong>Dados de Análise e Uso (Analytics):</strong> Coletamos dados sobre como os visitantes e você interagem com as páginas BCards. Isso inclui visualizações de página, cliques em links, endereço IP (anonimizado sempre que possível), tipo de dispositivo, sistema operacional e navegador. Usamos esses dados para fornecer estatísticas, entender o uso da plataforma e melhorar nossos serviços.</li>
<li><strong>Cookies e Tecnologias Semelhantes:</strong> Usamos cookies essenciais para o funcionamento da plataforma (ex: manter sua sessão ativa) e cookies de análise. Para mais detalhes, consulte nossa seção sobre Cookies.</li> <li><strong>Cookies e Tecnologias Semelhantes:</strong> Usamos cookies essenciais para o funcionamento da plataforma (ex: manter sua sessão ativa) e cookies de análise. Para mais detalhes, consulte nossa seção sobre Cookies.</li>
</ul> </ul>
<h4 class="mt-5 fw-bold">2. Base Legal para o Tratamento de Dados (LGPD)</h4> <h4 class="mt-5 fw-bold">2. Base Legal para o Tratamento de Dados (LGPD)</h4>
<p>O tratamento de seus dados pessoais é realizado com base nas seguintes hipóteses legais previstas no Art. 7º da LGPD:</p> <p>O tratamento de seus dados pessoais é realizado com base nas seguintes hipóteses legais previstas no Art. 7º da LGPD:</p>
<ul> <ul>
<li><strong>Execução de Contrato (Inciso V):</strong> A maior parte da nossa coleta e processamento de dados é necessária para executar o contrato de serviço que você aceita ao criar uma conta no @tenant.SiteName.</li> <li><strong>Execução de Contrato (Inciso V):</strong> A maior parte da nossa coleta e processamento de dados é necessária para executar o contrato de serviço que você aceita ao criar uma conta no BCards.</li>
<li><strong>Consentimento (Inciso I):</strong> Para o uso de cookies não essenciais e para o envio de comunicações de marketing, solicitaremos seu consentimento explícito.</li> <li><strong>Consentimento (Inciso I):</strong> Para o uso de cookies não essenciais e para o envio de comunicações de marketing, solicitaremos seu consentimento explícito.</li>
<li><strong>Legítimo Interesse (Inciso IX):</strong> Para análises de uso da plataforma e prevenção a fraudes, tratamos os dados com base em nosso legítimo interesse, sempre balanceando com seus direitos e liberdades.</li> <li><strong>Legítimo Interesse (Inciso IX):</strong> Para análises de uso da plataforma e prevenção a fraudes, tratamos os dados com base em nosso legítimo interesse, sempre balanceando com seus direitos e liberdades.</li>
<li><strong>Cumprimento de Obrigação Legal (Inciso II):</strong> Podemos tratar dados para cumprir obrigações legais, como a emissão de notas fiscais ou ordens judiciais.</li> <li><strong>Cumprimento de Obrigação Legal (Inciso II):</strong> Podemos tratar dados para cumprir obrigações legais, como a emissão de notas fiscais ou ordens judiciais.</li>
@ -59,7 +55,7 @@
<li><strong>Informação sobre Compartilhamento:</strong> O direito de saber com quais entidades públicas e privadas compartilhamos seus dados.</li> <li><strong>Informação sobre Compartilhamento:</strong> O direito de saber com quais entidades públicas e privadas compartilhamos seus dados.</li>
<li><strong>Revogação do Consentimento:</strong> O direito de revogar seu consentimento a qualquer momento.</li> <li><strong>Revogação do Consentimento:</strong> O direito de revogar seu consentimento a qualquer momento.</li>
</ul> </ul>
<p>Para exercer seus direitos, entre em contato com nosso Encarregado de Proteção de Dados (DPO) através do e-mail <a class="email-obfuscated" data-user="@dpoParts[0]" data-domain="@dpoParts[1]">[carregando e-mail...]</a>. O prazo para resposta é de até 15 dias, conforme a legislação.</p> <p>Para exercer seus direitos, entre em contato com nosso Encarregado de Proteção de Dados (DPO) através do e-mail <a class="email-obfuscated" data-user="dpo" data-domain="bcards.site">[carregando e-mail...]</a>. O prazo para resposta é de até 15 dias, conforme a legislação.</p>
<h4 class="mt-5 fw-bold">5. Cookies e Tecnologias de Rastreamento</h4> <h4 class="mt-5 fw-bold">5. Cookies e Tecnologias de Rastreamento</h4>
<p>Utilizamos cookies para melhorar sua experiência. Cookies são pequenos arquivos de texto armazenados em seu dispositivo. Você pode gerenciar suas preferências de cookies através do nosso banner de consentimento ou nas configurações do seu navegador.</p> <p>Utilizamos cookies para melhorar sua experiência. Cookies são pequenos arquivos de texto armazenados em seu dispositivo. Você pode gerenciar suas preferências de cookies através do nosso banner de consentimento ou nas configurações do seu navegador.</p>
@ -77,7 +73,7 @@
<h4 class="mt-5 fw-bold">8. Contato do Encarregado de Proteção de Dados (DPO)</h4> <h4 class="mt-5 fw-bold">8. Contato do Encarregado de Proteção de Dados (DPO)</h4>
<p>Para qualquer dúvida sobre esta Política de Privacidade ou para exercer seus direitos, entre em contato com nosso DPO:</p> <p>Para qualquer dúvida sobre esta Política de Privacidade ou para exercer seus direitos, entre em contato com nosso DPO:</p>
<p><strong>E-mail:</strong> <a href="mailto:@tenant.DpoEmail" class="fw-bold">@tenant.DpoEmail</a></p> <p><strong>E-mail:</strong> <a href="mailto:dpo@bcards.site" class="fw-bold">dpo@bcards.site</a></p>
<h4 class="mt-5 fw-bold">9. Alterações a esta Política</h4> <h4 class="mt-5 fw-bold">9. Alterações a esta Política</h4>
<p>Podemos atualizar esta Política de Privacidade periodicamente. Notificaremos você sobre quaisquer alterações significativas através de um aviso em nosso site ou por e-mail.</p> <p>Podemos atualizar esta Política de Privacidade periodicamente. Notificaremos você sobre quaisquer alterações significativas através de um aviso em nosso site ou por e-mail.</p>

View File

@ -1,7 +1,4 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value;
var dpoParts = tenant.DpoEmail.Split('@');
ViewData["Title"] = "Política de Privacidad"; ViewData["Title"] = "Política de Privacidad";
Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/Views/Shared/_Layout.cshtml";
} }
@ -16,15 +13,15 @@
<hr class="my-4"> <hr class="my-4">
<p>Bienvenido a la Política de Privacidad de <strong>@tenant.SiteName</strong>. Su privacidad es de suma importancia para nosotros. Este documento explica cómo recopilamos, usamos, compartimos y protegemos su información personal, en conformidad con las leyes de protección de datos de los países en los que operamos, incluyendo la Ley 1581 de 2012 de Colombia, la Ley 19.628 de Chile, y la Ley Federal de Protección de Datos Personales en Posesión de los Particulares de México.</p> <p>Bienvenido a la Política de Privacidad de <strong>BCards</strong>. Su privacidad es de suma importancia para nosotros. Este documento explica cómo recopilamos, usamos, compartimos y protegemos su información personal, en conformidad con las leyes de protección de datos de los países en los que operamos, incluyendo la Ley 1581 de 2012 de Colombia, la Ley 19.628 de Chile, y la Ley Federal de Protección de Datos Personales en Posesión de los Particulares de México.</p>
<h4 class="mt-5 fw-bold">1. Datos Personales Recopilados y su Finalidad</h4> <h4 class="mt-5 fw-bold">1. Datos Personales Recopilados y su Finalidad</h4>
<p>Recopilamos diferentes tipos de información para proporcionar y mejorar nuestros servicios para usted:</p> <p>Recopilamos diferentes tipos de información para proporcionar y mejorar nuestros servicios para usted:</p>
<ul> <ul>
<li><strong>Datos de Registro y Autenticación:</strong> Al registrarse usando Google o Microsoft (OAuth), recopilamos su nombre, dirección de correo electrónico y foto de perfil. Usamos estos datos para crear y administrar su cuenta, permitir su acceso a la plataforma y comunicarnos con usted.</li> <li><strong>Datos de Registro y Autenticación:</strong> Al registrarse usando Google o Microsoft (OAuth), recopilamos su nombre, dirección de correo electrónico y foto de perfil. Usamos estos datos para crear y administrar su cuenta, permitir su acceso a la plataforma y comunicarnos con usted.</li>
<li><strong>Contenido de la Página Pública:</strong> Recopilamos la información que usted introduce en su página @tenant.SiteName, como título, biografía, enlaces, categoría de negocio e imágenes. La finalidad es mostrar públicamente su página según su configuración.</li> <li><strong>Contenido de la Página Pública:</strong> Recopilamos la información que usted introduce en su página BCards, como título, biografía, enlaces, categoría de negocio e imágenes. La finalidad es mostrar públicamente su página según su configuración.</li>
<li><strong>Datos de Pago:</strong> Para nuestros planes de pago, utilizamos Stripe como procesador de pagos. No almacenamos los datos de su tarjeta de crédito. Stripe recopila la información necesaria para procesar la transacción de forma segura.</li> <li><strong>Datos de Pago:</strong> Para nuestros planes de pago, utilizamos Stripe como procesador de pagos. No almacenamos los datos de su tarjeta de crédito. Stripe recopila la información necesaria para procesar la transacción de forma segura.</li>
<li><strong>Datos de Análisis y Uso (Analytics):</strong> Recopilamos datos sobre cómo los visitantes y usted interactúan con las páginas @tenant.SiteName. Esto incluye vistas de página, clics en enlaces, dirección IP (anonimizada siempre que sea posible), tipo de dispositivo, sistema operativo y navegador. Usamos estos datos para proporcionar estadísticas, entender el uso de la plataforma y mejorar nuestros servicios.</li> <li><strong>Datos de Análisis y Uso (Analytics):</strong> Recopilamos datos sobre cómo los visitantes y usted interactúan con las páginas BCards. Esto incluye vistas de página, clics en enlaces, dirección IP (anonimizada siempre que sea posible), tipo de dispositivo, sistema operativo y navegador. Usamos estos datos para proporcionar estadísticas, entender el uso de la plataforma y mejorar nuestros servicios.</li>
<li><strong>Cookies y Tecnologías Similares:</strong> Usamos cookies esenciales para el funcionamiento de la plataforma (ej: mantener su sesión activa) y cookies de análisis.</li> <li><strong>Cookies y Tecnologías Similares:</strong> Usamos cookies esenciales para el funcionamiento de la plataforma (ej: mantener su sesión activa) y cookies de análisis.</li>
</ul> </ul>
@ -50,14 +47,14 @@
<li><strong>Oposición:</strong> El derecho a oponerse al tratamiento de sus datos para ciertos fines.</li> <li><strong>Oposición:</strong> El derecho a oponerse al tratamiento de sus datos para ciertos fines.</li>
<li><strong>Portabilidad:</strong> El derecho a recibir sus datos en un formato estructurado.</li> <li><strong>Portabilidad:</strong> El derecho a recibir sus datos en un formato estructurado.</li>
</ul> </ul>
<p>Para ejercer sus derechos (conocidos como derechos ARCO en México y Chile), por favor contacte a nuestro Oficial de Protección de Datos (DPO) a través del correo electrónico <a class="email-obfuscated" data-user="@dpoParts[0]" data-domain="@dpoParts[1]">[cargando email...]</a>.</p> <p>Para ejercer sus derechos (conocidos como derechos ARCO en México y Chile), por favor contacte a nuestro Oficial de Protección de Datos (DPO) a través del correo electrónico <a class="email-obfuscated" data-user="dpo" data-domain="bcards.site">[cargando email...]</a>.</p>
<h4 class="mt-5 fw-bold">5. Retención de Datos</h4> <h4 class="mt-5 fw-bold">5. Retención de Datos</h4>
<p>Mantendremos sus datos personales mientras su cuenta esté activa. Si su cuenta es desactivada o permanece inactiva por más de 12 meses, sus datos serán anonimizados o eliminados, excepto aquellos que necesitemos retener para cumplir con obligaciones legales.</p> <p>Mantendremos sus datos personales mientras su cuenta esté activa. Si su cuenta es desactivada o permanece inactiva por más de 12 meses, sus datos serán anonimizados o eliminados, excepto aquellos que necesitemos retener para cumplir con obligaciones legales.</p>
<h4 class="mt-5 fw-bold">6. Contacto del Oficial de Protección de Datos (DPO)</h4> <h4 class="mt-5 fw-bold">6. Contacto del Oficial de Protección de Datos (DPO)</h4>
<p>Para cualquier pregunta sobre esta Política de Privacidad o para ejercer sus derechos, contacte a nuestro DPO:</p> <p>Para cualquier pregunta sobre esta Política de Privacidad o para ejercer sus derechos, contacte a nuestro DPO:</p>
<p><strong>Correo Electrónico:</strong> <a href="mailto:@tenant.DpoEmail" class="fw-bold">@tenant.DpoEmail</a></p> <p><strong>Correo Electrónico:</strong> <a href="mailto:dpo@bcards.site" class="fw-bold">dpo@bcards.site</a></p>
<h4 class="mt-5 fw-bold">7. Cambios a esta Política</h4> <h4 class="mt-5 fw-bold">7. Cambios a esta Política</h4>
<p>Podemos actualizar esta Política de Privacidad periódicamente. Le notificaremos sobre cualquier cambio significativo a través de un aviso en nuestro sitio web o por correo electrónico.</p> <p>Podemos actualizar esta Política de Privacidad periódicamente. Le notificaremos sobre cualquier cambio significativo a través de un aviso en nuestro sitio web o por correo electrónico.</p>

View File

@ -1,7 +1,4 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value;
var dpoParts = tenant.DpoEmail.Split('@');
ViewData["Title"] = "Direitos do Titular de Dados"; ViewData["Title"] = "Direitos do Titular de Dados";
Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/Views/Shared/_Layout.cshtml";
} }
@ -16,7 +13,7 @@
<hr class="my-4"> <hr class="my-4">
<p>Em conformidade com a Lei Geral de Proteção de Dados (LGPD) e outras legislações de privacidade, o <strong>@tenant.SiteName</strong> garante a você o controle sobre seus dados pessoais. Você pode solicitar acesso, correção, portabilidade ou exclusão de suas informações a qualquer momento.</p> <p>Em conformidade com a Lei Geral de Proteção de Dados (LGPD) e outras legislações de privacidade, o <strong>BCards</strong> garante a você o controle sobre seus dados pessoais. Você pode solicitar acesso, correção, portabilidade ou exclusão de suas informações a qualquer momento.</p>
<h4 class="mt-5 fw-bold">Como Fazer uma Solicitação</h4> <h4 class="mt-5 fw-bold">Como Fazer uma Solicitação</h4>
<p>Para garantir a segurança do processo e a correta identificação do titular, todas as solicitações devem ser enviadas para nosso Encarregado de Proteção de Dados (DPO) através do seguinte canal:</p> <p>Para garantir a segurança do processo e a correta identificação do titular, todas as solicitações devem ser enviadas para nosso Encarregado de Proteção de Dados (DPO) através do seguinte canal:</p>
@ -24,13 +21,13 @@
<div class="alert alert-secondary text-center"> <div class="alert alert-secondary text-center">
<h5 class="alert-heading">Canal de Atendimento ao Titular</h5> <h5 class="alert-heading">Canal de Atendimento ao Titular</h5>
<p class="mb-0">Envie um e-mail para:</p> <p class="mb-0">Envie um e-mail para:</p>
<a class="email-obfuscated fs-5 fw-bold" data-user="@dpoParts[0]" data-domain="@dpoParts[1]">[carregando e-mail...]</a> <a class="email-obfuscated fs-5 fw-bold" data-user="dpo" data-domain="bcards.site">[carregando e-mail...]</a>
</div> </div>
<p>No seu e-mail, por favor, inclua:</p> <p>No seu e-mail, por favor, inclua:</p>
<ul> <ul>
<li><strong>Nome Completo:</strong> O nome associado à sua conta.</li> <li><strong>Nome Completo:</strong> O nome associado à sua conta.</li>
<li><strong>E-mail de Cadastro:</strong> O e-mail que você usou para se registrar no @tenant.SiteName.</li> <li><strong>E-mail de Cadastro:</strong> O e-mail que você usou para se registrar no BCards.</li>
<li><strong>Tipo de Solicitação:</strong> Especifique o que você deseja (ex: "Solicitação de Acesso aos Dados", "Pedido de Exclusão de Conta e Dados", "Correção de Informações").</li> <li><strong>Tipo de Solicitação:</strong> Especifique o que você deseja (ex: "Solicitação de Acesso aos Dados", "Pedido de Exclusão de Conta e Dados", "Correção de Informações").</li>
<li><strong>Detalhes da Solicitação:</strong> Forneça qualquer detalhe adicional que possa nos ajudar a atender seu pedido.</li> <li><strong>Detalhes da Solicitação:</strong> Forneça qualquer detalhe adicional que possa nos ajudar a atender seu pedido.</li>
</ul> </ul>
@ -39,7 +36,7 @@
<p>Após o recebimento da sua solicitação, nossa equipe poderá entrar em contato para validar sua identidade. O prazo legal para resposta a solicitações de confirmação e acesso é de até <strong>15 dias</strong>. Para outras solicitações, responderemos o mais breve possível, respeitando os prazos definidos na legislação aplicável.</p> <p>Após o recebimento da sua solicitação, nossa equipe poderá entrar em contato para validar sua identidade. O prazo legal para resposta a solicitações de confirmação e acesso é de até <strong>15 dias</strong>. Para outras solicitações, responderemos o mais breve possível, respeitando os prazos definidos na legislação aplicável.</p>
<div class="alert alert-info mt-5"> <div class="alert alert-info mt-5">
<strong>Nota:</strong> A exclusão de dados é um processo irreversível e resultará na perda permanente de sua conta e de todas as suas páginas @tenant.SiteName. <strong>Nota:</strong> A exclusão de dados é um processo irreversível e resultará na perda permanente de sua conta e de todas as suas páginas BCards.
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,7 +1,4 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value;
var supportParts = tenant.SupportEmail.Split('@');
ViewData["Title"] = "Termos de Uso"; ViewData["Title"] = "Termos de Uso";
Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/Views/Shared/_Layout.cshtml";
} }
@ -16,20 +13,20 @@
<hr class="my-4"> <hr class="my-4">
<p>Bem-vindo ao <strong>@tenant.SiteName</strong>. Ao acessar ou usar nossa plataforma, você concorda em cumprir e estar vinculado a estes Termos de Uso. Por favor, leia-os com atenção.</p> <p>Bem-vindo ao <strong>BCards</strong>. Ao acessar ou usar nossa plataforma, você concorda em cumprir e estar vinculado a estes Termos de Uso. Por favor, leia-os com atenção.</p>
<h4 class="mt-5 fw-bold">1. Aceitação dos Termos</h4> <h4 class="mt-5 fw-bold">1. Aceitação dos Termos</h4>
<p>Ao criar uma conta ou usar os serviços do @tenant.SiteName, você celebra um contrato legalmente vinculativo conosco e concorda com estes Termos, nossa <a asp-action="Privacy">Política de Privacidade</a> e nossas <a asp-action="CommunityGuidelines">Diretrizes da Comunidade</a>.</p> <p>Ao criar uma conta ou usar os serviços do BCards, você celebra um contrato legalmente vinculativo conosco e concorda com estes Termos, nossa <a asp-action="Privacy">Política de Privacidade</a> e nossas <a asp-action="CommunityGuidelines">Diretrizes da Comunidade</a>.</p>
<h4 class="mt-5 fw-bold">2. Descrição do Serviço</h4> <h4 class="mt-5 fw-bold">2. Descrição do Serviço</h4>
<p>O @tenant.SiteName é uma plataforma que permite a indivíduos e empresas criar e gerenciar uma página pública personalizada contendo links para seus sites e redes sociais. Oferecemos planos gratuitos e pagos com diferentes níveis de funcionalidade.</p> <p>O BCards é uma plataforma que permite a indivíduos e empresas criar e gerenciar uma página pública personalizada contendo links para seus sites e redes sociais. Oferecemos planos gratuitos e pagos com diferentes níveis de funcionalidade.</p>
<h4 class="mt-5 fw-bold">3. Responsabilidades do Usuário</h4> <h4 class="mt-5 fw-bold">3. Responsabilidades do Usuário</h4>
<p>Você é o único responsável por todo o conteúdo que publica em sua página @tenant.SiteName e por garantir que ele cumpra todas as leis aplicáveis e nossas diretrizes. Você concorda em:</p> <p>Você é o único responsável por todo o conteúdo que publica em sua página BCards e por garantir que ele cumpra todas as leis aplicáveis e nossas diretrizes. Você concorda em:</p>
<ul> <ul>
<li>Fornecer informações de registro precisas e mantê-las atualizadas.</li> <li>Fornecer informações de registro precisas e mantê-las atualizadas.</li>
<li>Manter a segurança de sua senha e conta. Você é responsável por todas as atividades que ocorrem em sua conta.</li> <li>Manter a segurança de sua senha e conta. Você é responsável por todas as atividades que ocorrem em sua conta.</li>
<li>Não usar o @tenant.SiteName para qualquer finalidade ilegal ou não autorizada.</li> <li>Não usar o BCards para qualquer finalidade ilegal ou não autorizada.</li>
<li>Não violar nossas <a asp-action="CommunityGuidelines">Diretrizes da Comunidade</a>, que proíbem conteúdo de ódio, violência, spam, nudez, entre outros.</li> <li>Não violar nossas <a asp-action="CommunityGuidelines">Diretrizes da Comunidade</a>, que proíbem conteúdo de ódio, violência, spam, nudez, entre outros.</li>
<li>Possuir os direitos ou as permissões necessárias para todo o conteúdo que você publica.</li> <li>Possuir os direitos ou as permissões necessárias para todo o conteúdo que você publica.</li>
</ul> </ul>
@ -46,12 +43,12 @@
<h4 class="mt-5 fw-bold">6. Propriedade Intelectual</h4> <h4 class="mt-5 fw-bold">6. Propriedade Intelectual</h4>
<ul> <ul>
<li><strong>Seu Conteúdo:</strong> Você retém todos os direitos de propriedade intelectual sobre o conteúdo que publica em sua página @tenant.SiteName. No entanto, você nos concede uma licença mundial, não exclusiva e isenta de royalties para hospedar, exibir, reproduzir e distribuir seu conteúdo publicamente como parte da prestação do serviço.</li> <li><strong>Seu Conteúdo:</strong> Você retém todos os direitos de propriedade intelectual sobre o conteúdo que publica em sua página BCards. No entanto, você nos concede uma licença mundial, não exclusiva e isenta de royalties para hospedar, exibir, reproduzir e distribuir seu conteúdo publicamente como parte da prestação do serviço.</li>
<li><strong>Nossa Plataforma:</strong> O @tenant.SiteName e todo o seu conteúdo original, recursos e funcionalidades (incluindo, mas não se limitando a, software, texto, gráficos e logotipos) são de propriedade exclusiva do @tenant.SiteName e seus licenciadores e são protegidos por leis de direitos autorais e outras leis de propriedade intelectual.</li> <li><strong>Nossa Plataforma:</strong> O BCards e todo o seu conteúdo original, recursos e funcionalidades (incluindo, mas não se limitando a, software, texto, gráficos e logotipos) são de propriedade exclusiva do BCards e seus licenciadores e são protegidos por leis de direitos autorais e outras leis de propriedade intelectual.</li>
</ul> </ul>
<h4 class="mt-5 fw-bold">7. Limitação de Responsabilidade</h4> <h4 class="mt-5 fw-bold">7. Limitação de Responsabilidade</h4>
<p>NA MÁXIMA EXTENSÃO PERMITIDA PELA LEI, O @tenant.SiteName.ToUpper() E SEUS DIRETORES, FUNCIONÁRIOS E AFILIADOS NÃO SERÃO RESPONSÁVEIS POR QUAISQUER DANOS INDIRETOS, INCIDENTAIS, ESPECIAIS, CONSEQUENCIAIS OU PUNITIVOS, OU QUALQUER PERDA DE LUCROS OU RECEITAS, SEJA INCORRIDA DIRETA OU INDIRETAMENTE, OU QUALQUER PERDA DE DADOS, USO, BOA VONTADE OU OUTRAS PERDAS INTANGÍVEIS, RESULTANTES DE:</p> <p>NA MÁXIMA EXTENSÃO PERMITIDA PELA LEI, O BCARDS E SEUS DIRETORES, FUNCIONÁRIOS E AFILIADOS NÃO SERÃO RESPONSÁVEIS POR QUAISQUER DANOS INDIRETOS, INCIDENTAIS, ESPECIAIS, CONSEQUENCIAIS OU PUNITIVOS, OU QUALQUER PERDA DE LUCROS OU RECEITAS, SEJA INCORRIDA DIRETA OU INDIRETAMENTE, OU QUALQUER PERDA DE DADOS, USO, BOA VONTADE OU OUTRAS PERDAS INTANGÍVEIS, RESULTANTES DE:</p>
<ul> <ul>
<li>(a) SEU ACESSO OU USO OU INCAPACIDADE DE ACESSAR OU USAR O SERVIÇO;</li> <li>(a) SEU ACESSO OU USO OU INCAPACIDADE DE ACESSAR OU USAR O SERVIÇO;</li>
<li>(b) QUALQUER CONDUTA OU CONTEÚDO DE TERCEIROS NO SERVIÇO;</li> <li>(b) QUALQUER CONDUTA OU CONTEÚDO DE TERCEIROS NO SERVIÇO;</li>
@ -66,8 +63,8 @@
<h4 class="mt-5 fw-bold">9. Disposições Gerais</h4> <h4 class="mt-5 fw-bold">9. Disposições Gerais</h4>
<ul> <ul>
<li><strong>Legislação Aplicável:</strong> Estes Termos serão regidos e interpretados de acordo com as leis da República Federativa do Brasil, sem consideração com o conflito de disposições legais.</li> <li><strong>Legislação Aplicável:</strong> Estes Termos serão regidos e interpretados de acordo com as leis da República Federativa do Brasil, sem consideração com o conflito de disposições legais.</li>
<li><strong>Alterações nos Termos:</strong> Podemos modificar estes Termos a qualquer momento. Se fizermos alterações materiais, forneceremos um aviso com antecedência razoável. Ao continuar a usar o @tenant.SiteName após as alterações entrarem em vigor, você concorda em ficar vinculado aos termos revisados.</li> <li><strong>Alterações nos Termos:</strong> Podemos modificar estes Termos a qualquer momento. Se fizermos alterações materiais, forneceremos um aviso com antecedência razoável. Ao continuar a usar o BCards após as alterações entrarem em vigor, você concorda em ficar vinculado aos termos revisados.</li>
<li><strong>Contato:</strong> Para qualquer dúvida sobre estes Termos, entre em contato conosco pelo e-mail <a class="email-obfuscated" data-user="@supportParts[0]" data-domain="@supportParts[1]">[carregando e-mail...]</a>.</li> <li><strong>Contato:</strong> Para qualquer dúvida sobre estes Termos, entre em contato conosco pelo e-mail <a class="email-obfuscated" data-user="suporte" data-domain="bcards.site">[carregando e-mail...]</a>.</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -1,7 +1,4 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value;
var supportParts = tenant.SupportEmail.Split('@');
ViewData["Title"] = "Términos de Uso"; ViewData["Title"] = "Términos de Uso";
Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/Views/Shared/_Layout.cshtml";
} }
@ -16,20 +13,20 @@
<hr class="my-4"> <hr class="my-4">
<p>Bienvenido a <strong>@tenant.SiteName</strong>. Al acceder o utilizar nuestra plataforma, usted acepta cumplir y estar sujeto a estos Términos de Uso. Por favor, léalos con atención.</p> <p>Bienvenido a <strong>BCards</strong>. Al acceder o utilizar nuestra plataforma, usted acepta cumplir y estar sujeto a estos Términos de Uso. Por favor, léalos con atención.</p>
<h4 class="mt-5 fw-bold">1. Aceptación de los Términos</h4> <h4 class="mt-5 fw-bold">1. Aceptación de los Términos</h4>
<p>Al crear una cuenta o usar los servicios de @tenant.SiteName, usted celebra un contrato legalmente vinculante con nosotros y acepta estos Términos, nuestra <a asp-action="PrivacyES">Política de Privacidad</a> y nuestras <a asp-action="CommunityGuidelines">Directrices de la Comunidad</a>.</p> <p>Al crear una cuenta o usar los servicios de BCards, usted celebra un contrato legalmente vinculante con nosotros y acepta estos Términos, nuestra <a asp-action="PrivacyES">Política de Privacidad</a> y nuestras <a asp-action="CommunityGuidelines">Directrices de la Comunidad</a>.</p>
<h4 class="mt-5 fw-bold">2. Descripción del Servicio</h4> <h4 class="mt-5 fw-bold">2. Descripción del Servicio</h4>
<p>@tenant.SiteName es una plataforma que permite a individuos y empresas crear y gestionar una página pública personalizada que contiene enlaces a sus sitios web y redes sociales. Ofrecemos planes gratuitos y de pago con diferentes niveles de funcionalidad.</p> <p>BCards es una plataforma que permite a individuos y empresas crear y gestionar una página pública personalizada que contiene enlaces a sus sitios web y redes sociales. Ofrecemos planes gratuitos y de pago con diferentes niveles de funcionalidad.</p>
<h4 class="mt-5 fw-bold">3. Responsabilidades del Usuario</h4> <h4 class="mt-5 fw-bold">3. Responsabilidades del Usuario</h4>
<p>Usted es el único responsable de todo el contenido que publica en su página de @tenant.SiteName y de garantizar que cumpla con todas las leyes aplicables y nuestras directrices. Usted se compromete a:</p> <p>Usted es el único responsable de todo el contenido que publica en su página de BCards y de garantizar que cumpla con todas las leyes aplicables y nuestras directrices. Usted se compromete a:</p>
<ul> <ul>
<li>Proporcionar información de registro precisa y mantenerla actualizada.</li> <li>Proporcionar información de registro precisa y mantenerla actualizada.</li>
<li>Mantener la seguridad de su contraseña y cuenta.</li> <li>Mantener la seguridad de su contraseña y cuenta.</li>
<li>No utilizar @tenant.SiteName para ningún propósito ilegal o no autorizado.</li> <li>No utilizar BCards para ningún propósito ilegal o no autorizado.</li>
<li>No violar nuestras <a asp-action="CommunityGuidelines">Directrices de la Comunidad</a>.</li> <li>No violar nuestras <a asp-action="CommunityGuidelines">Directrices de la Comunidad</a>.</li>
<li>Poseer los derechos o permisos necesarios para todo el contenido que publique.</li> <li>Poseer los derechos o permisos necesarios para todo el contenido que publique.</li>
</ul> </ul>
@ -47,11 +44,11 @@
<h4 class="mt-5 fw-bold">6. Propiedad Intelectual</h4> <h4 class="mt-5 fw-bold">6. Propiedad Intelectual</h4>
<ul> <ul>
<li><strong>Su Contenido:</strong> Usted retiene todos los derechos de propiedad intelectual sobre su contenido. Sin embargo, nos otorga una licencia mundial, no exclusiva y libre de regalías para alojar, mostrar y distribuir su contenido como parte del servicio.</li> <li><strong>Su Contenido:</strong> Usted retiene todos los derechos de propiedad intelectual sobre su contenido. Sin embargo, nos otorga una licencia mundial, no exclusiva y libre de regalías para alojar, mostrar y distribuir su contenido como parte del servicio.</li>
<li><strong>Nuestra Plataforma:</strong> @tenant.SiteName y todo su contenido original, características y funcionalidades son propiedad exclusiva de @tenant.SiteName y están protegidos por leyes de derechos de autor.</li> <li><strong>Nuestra Plataforma:</strong> BCards y todo su contenido original, características y funcionalidades son propiedad exclusiva de BCards y están protegidos por leyes de derechos de autor.</li>
</ul> </ul>
<h4 class="mt-5 fw-bold">7. Limitación de Responsabilidad</h4> <h4 class="mt-5 fw-bold">7. Limitación de Responsabilidad</h4>
<p>EN LA MÁXIMA MEDIDA PERMITIDA POR LA LEY, @tenant.SiteName.ToUpper() NO SERÁ RESPONSABLE DE NINGÚN DAÑO INDIRECTO, INCIDENTAL, ESPECIAL, CONSECUENTE O PUNITIVO. Nuestra responsabilidad total agregada para todos los reclamos relacionados con el servicio no excederá el mayor de veinticinco dólares estadounidenses (USD $25.00) o el monto que nos haya pagado en los últimos 12 meses.</p> <p>EN LA MÁXIMA MEDIDA PERMITIDA POR LA LEY, BCARDS NO SERÁ RESPONSABLE DE NINGÚN DAÑO INDIRECTO, INCIDENTAL, ESPECIAL, CONSECUENTE O PUNITIVO. Nuestra responsabilidad total agregada para todos los reclamos relacionados con el servicio no excederá el mayor de veinticinco dólares estadounidenses (USD $25.00) o el monto que nos haya pagado en los últimos 12 meses.</p>
<h4 class="mt-5 fw-bold">8. Cancelación y Terminación</h4> <h4 class="mt-5 fw-bold">8. Cancelación y Terminación</h4>
<p>Puede dejar de usar nuestros servicios y eliminar su cuenta en cualquier momento. También nos reservamos el derecho de suspender o cancelar su cuenta en cualquier momento, con o sin previo aviso, por violación de estos Términos.</p> <p>Puede dejar de usar nuestros servicios y eliminar su cuenta en cualquier momento. También nos reservamos el derecho de suspender o cancelar su cuenta en cualquier momento, con o sin previo aviso, por violación de estos Términos.</p>
@ -60,7 +57,7 @@
<ul> <ul>
<li><strong>Legislación Aplicable:</strong> Estos Términos se regirán por las leyes de Brasil para todos los usuarios. Para disputas específicas en Colombia, Chile o México, se pueden considerar las leyes locales.</li> <li><strong>Legislación Aplicable:</strong> Estos Términos se regirán por las leyes de Brasil para todos los usuarios. Para disputas específicas en Colombia, Chile o México, se pueden considerar las leyes locales.</li>
<li><strong>Cambios en los Términos:</strong> Podemos modificar estos Términos en cualquier momento. Le notificaremos con antelación.</li> <li><strong>Cambios en los Términos:</strong> Podemos modificar estos Términos en cualquier momento. Le notificaremos con antelación.</li>
<li><strong>Contacto:</strong> Para cualquier pregunta sobre estos Términos, contáctenos en <a class="email-obfuscated" data-user="@supportParts[0]" data-domain="@supportParts[1]">[cargando email...]</a>.</li> <li><strong>Contacto:</strong> Para cualquier pregunta sobre estos Términos, contáctenos en <a class="email-obfuscated" data-user="suporte" data-domain="bcards.site">[cargando email...]</a>.</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -103,11 +103,9 @@
<div class="card-body"> <div class="card-body">
@if (!string.IsNullOrEmpty(Model.Page.Bio)) @if (!string.IsNullOrEmpty(Model.Page.Bio))
{ {
var bioPipeline = new Markdig.MarkdownPipelineBuilder().UseAutoLinks().DisableHtml().Build();
var bioHtml = Markdig.Markdown.ToHtml(Model.Page.Bio, bioPipeline);
<div class="mb-3"> <div class="mb-3">
<strong>Biografia:</strong> <strong>Biografia:</strong>
<div>@Html.Raw(bioHtml)</div> <p>@Model.Page.Bio</p>
</div> </div>
} }

View File

@ -1,6 +1,4 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value;
ViewData["Title"] = "Erro"; ViewData["Title"] = "Erro";
} }
@ -9,7 +7,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Erro - @tenant.SiteName</title> <title>Erro - BCards</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -1,11 +1,9 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ var tenant = TenantConfig.Value; }
<!DOCTYPE html> <!DOCTYPE html>
<html lang="pt-BR"> <html lang="pt-BR">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@(ViewData["Title"] ?? $"{tenant.SiteName} - {tenant.Tagline}")</title> <title>@(ViewData["Title"] ?? "BCards - Crie sua bios / links Profissional")</title>
@if (ViewBag.SeoSettings != null) @if (ViewBag.SeoSettings != null)
{ {
@ -29,8 +27,8 @@
} }
else else
{ {
<meta name="description" content="@tenant.SiteDescription" /> <meta name="description" content="Crie sua página profissional com links organizados. A melhor alternativa para ter sua bio / links. Criada para profissionais e empresas no Brasil." />
<meta name="keywords" content="@tenant.MetaKeywords" /> <meta name="keywords" content="linktree, links, página profissional, perfil, redes sociais, cartão digital, bio, links, página simples" />
} }
@await RenderSectionAsync("Head", required: false) @await RenderSectionAsync("Head", required: false)
@ -43,28 +41,7 @@
<link rel="icon" type="image/x-icon" href="~/favicon.ico" /> <link rel="icon" type="image/x-icon" href="~/favicon.ico" />
@await RenderSectionAsync("Styles", required: false) @await RenderSectionAsync("Styles", required: false)
<style>
:root {
--tenant-primary: @tenant.PrimaryColor;
--tenant-primary-dark: @tenant.PrimaryColorDark;
--tenant-gradient: @tenant.HeroGradient;
}
.hero-section { background: var(--tenant-gradient) !important; }
.btn-primary { background-color: var(--tenant-primary) !important; border-color: var(--tenant-primary) !important; }
.btn-primary:hover { background-color: var(--tenant-primary-dark) !important; border-color: var(--tenant-primary-dark) !important; }
.btn-outline-primary { color: var(--tenant-primary) !important; border-color: var(--tenant-primary) !important; }
.btn-outline-primary:hover { background-color: var(--tenant-primary) !important; color: #fff !important; }
.text-primary { color: var(--tenant-primary) !important; }
.bg-primary { background-color: var(--tenant-primary) !important; }
.border-primary { border-color: var(--tenant-primary) !important; }
.bg-primary.bg-opacity-10 { background-color: color-mix(in srgb, var(--tenant-primary) 10%, transparent) !important; }
#loading-bar { background: linear-gradient(90deg, var(--tenant-primary), var(--tenant-primary-dark), var(--tenant-primary)) !important; }
.nav-link.active { background-color: color-mix(in srgb, var(--tenant-primary) 10%, transparent) !important; }
.form-control:focus { border-color: var(--tenant-primary); box-shadow: 0 0 0 0.2rem color-mix(in srgb, var(--tenant-primary) 25%, transparent); }
.profile-image-placeholder { border-color: var(--tenant-primary) !important; }
</style>
<script type="text/javascript"> <script type="text/javascript">
(function(c,l,a,r,i,t,y){ (function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)}; c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
@ -99,14 +76,14 @@
100% { background-position: 200% 0; } 100% { background-position: 200% 0; }
} }
/* Destacar item ativo do menu — usa cor do tenant */ /* Destacar item ativo do menu */
.nav-link.active { .nav-link.active {
background-color: color-mix(in srgb, var(--tenant-primary, #0d6efd) 10%, transparent) !important; background-color: rgba(0, 123, 255, 0.1) !important;
border-radius: 6px !important; border-radius: 6px !important;
font-weight: 600 !important; font-weight: 600 !important;
} }
/* Para homepage (fundo colorido) */ /* Para homepage (fundo azul) */
.bg-home-blue .nav-link.active { .bg-home-blue .nav-link.active {
background-color: rgba(255, 255, 255, 0.2) !important; background-color: rgba(255, 255, 255, 0.2) !important;
} }
@ -157,12 +134,11 @@
<div id="loading-bar"></div> <div id="loading-bar"></div>
<header> <header>
<nav class="navbar navbar-expand-lg navbar-toggleable-sm navbar-light fixed-top @(ViewBag.IsHomePage == true ? "bg-home-blue" : "bg-dashboard")" id="mainNavbar" <nav class="navbar navbar-expand-lg navbar-toggleable-sm navbar-light fixed-top @(ViewBag.IsHomePage == true ? "bg-home-blue" : "bg-dashboard")" id="mainNavbar">
style="@(ViewBag.IsHomePage == true ? $"background-color: {tenant.PrimaryColor} !important;" : "")">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand fw-bold @(ViewBag.IsHomePage == true ? "text-white" : "text-primary")" <a class="navbar-brand fw-bold @(ViewBag.IsHomePage == true ? "text-white" : "text-primary")"
asp-area="" asp-controller="Home" asp-action="Index"> asp-area="" asp-controller="Home" asp-action="Index">
@tenant.SiteName BCards
</a> </a>
<button class="navbar-toggler @(ViewBag.IsHomePage == true ? "navbar-dark" : "")" <button class="navbar-toggler @(ViewBag.IsHomePage == true ? "navbar-dark" : "")"
@ -192,7 +168,7 @@
@* Menu Artigos - Condicional (só aparece se existir ao menos 1 artigo) *@ @* Menu Artigos - Condicional (só aparece se existir ao menos 1 artigo) *@
@{ @{
var artigosPath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "Content", "Tenants", tenant.ContentFolder, "Artigos"); var artigosPath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "Content", "Artigos");
var hasArtigos = System.IO.Directory.Exists(artigosPath) && var hasArtigos = System.IO.Directory.Exists(artigosPath) &&
System.IO.Directory.GetFiles(artigosPath, "*.pt-BR.md").Any(); System.IO.Directory.GetFiles(artigosPath, "*.pt-BR.md").Any();
} }
@ -267,7 +243,7 @@
<div class="container py-4"> <div class="container py-4">
<div class="row"> <div class="row">
<div class="col-lg-4 mb-3 mb-lg-0"> <div class="col-lg-4 mb-3 mb-lg-0">
<h5 class="fw-bold">@tenant.SiteName</h5> <h5 class="fw-bold">BCards</h5>
<p class="small text-muted">&copy; @DateTime.Now.Year - Todos os direitos reservados.</p> <p class="small text-muted">&copy; @DateTime.Now.Year - Todos os direitos reservados.</p>
</div> </div>
<div class="col-6 col-lg-2 offset-lg-1 mb-3"> <div class="col-6 col-lg-2 offset-lg-1 mb-3">
@ -282,12 +258,12 @@
<h5 class="fw-bold">Empresa</h5> <h5 class="fw-bold">Empresa</h5>
<ul class="list-unstyled"> <ul class="list-unstyled">
<li><a href="#" class="text-muted text-decoration-none">Sobre nós</a></li> <li><a href="#" class="text-muted text-decoration-none">Sobre nós</a></li>
<li><a href="mailto:@tenant.SupportEmail" class="text-muted text-decoration-none">Contato</a></li> <li><a href="mailto:suporte@bcards.site" class="text-muted text-decoration-none">Contato</a></li>
</ul> </ul>
</div> </div>
<div class="col-lg-3"> <div class="col-lg-3">
<h5 class="fw-bold">Crie sua página</h5> <h5 class="fw-bold">Crie sua página</h5>
<p class="small text-muted">@tenant.FooterTagline</p> <p class="small text-muted">Sua presença online, simplificada. Crie sua página profissional em minutos.</p>
@if (User.Identity?.IsAuthenticated == true) @if (User.Identity?.IsAuthenticated == true)
{ {
<a asp-controller="Admin" asp-action="Dashboard" class="btn btn-primary btn-sm">Comece Agora</a> <a asp-controller="Admin" asp-action="Dashboard" class="btn btn-primary btn-sm">Comece Agora</a>

View File

@ -176,16 +176,7 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
/* Seta de expansão e Botão de copiar */ /* Seta de expansão */
.link-actions-container {
display: flex;
align-items: center;
gap: 0.5rem;
margin-left: 0.5rem;
flex-shrink: 0;
}
.copy-link-btn,
.expand-arrow { .expand-arrow {
background: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.2);
border: none; border: none;
@ -198,15 +189,15 @@
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
flex-shrink: 0;
margin-left: 0.5rem;
} }
.copy-link-btn:hover,
.expand-arrow:hover { .expand-arrow:hover {
background: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.3);
transform: scale(1.05); transform: scale(1.05);
} }
.copy-link-btn i,
.expand-arrow i { .expand-arrow i {
font-size: 0.9rem; font-size: 0.9rem;
transition: transform 0.3s ease; transition: transform 0.3s ease;
@ -291,20 +282,6 @@
gap: 0.25rem; gap: 0.25rem;
} }
.expanded-link {
color: var(--primary-color) !important;
text-decoration: underline !important;
display: flex;
align-items: center;
gap: 0.5rem;
transition: opacity 0.2s ease;
}
.expanded-link:hover {
opacity: 0.8;
text-decoration: none !important;
}
/* ========== FOOTER ========== */ /* ========== FOOTER ========== */
.profile-footer { .profile-footer {
margin-top: 2rem; margin-top: 2rem;

View File

@ -1,6 +1,4 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value;
var seo = ViewBag.SeoSettings as BCards.Web.Models.SeoSettings; var seo = ViewBag.SeoSettings as BCards.Web.Models.SeoSettings;
} }
<!DOCTYPE html> <!DOCTYPE html>
@ -22,7 +20,7 @@
<meta property="og:image" content="@seo.OgImage" /> <meta property="og:image" content="@seo.OgImage" />
<meta property="og:url" content="@seo.CanonicalUrl" /> <meta property="og:url" content="@seo.CanonicalUrl" />
<meta property="og:type" content="profile" /> <meta property="og:type" content="profile" />
<meta property="og:site_name" content="@tenant.SiteName" /> <meta property="og:site_name" content="BCards" />
<!-- Twitter Card --> <!-- Twitter Card -->
<meta name="twitter:card" content="@seo.TwitterCard" /> <meta name="twitter:card" content="@seo.TwitterCard" />

View File

@ -1,7 +1,5 @@
@model BCards.Web.Models.IPageDisplay @model BCards.Web.Models.IPageDisplay
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{ @{
var tenant = TenantConfig.Value;
var seo = ViewBag.SeoSettings as BCards.Web.Models.SeoSettings; var seo = ViewBag.SeoSettings as BCards.Web.Models.SeoSettings;
var category = ViewBag.Category as BCards.Web.Models.Category; var category = ViewBag.Category as BCards.Web.Models.Category;
var isPreview = ViewBag.IsPreview as bool? ?? false; var isPreview = ViewBag.IsPreview as bool? ?? false;
@ -209,37 +207,31 @@
.qrcode-toggle { .qrcode-toggle {
width: 100%; width: 100%;
background-color: var(--primary-color); background: rgba(255, 255, 255, 0.1);
color: white !important; border: 1px solid rgba(255, 255, 255, 0.2);
border: none;
border-radius: 12px; border-radius: 12px;
padding: 0.75rem 1.25rem; padding: 1rem;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
color: inherit;
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 500;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
} }
.qrcode-toggle:hover { .qrcode-toggle:hover {
background-color: var(--secondary-color); background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.3);
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
} }
.qrcode-toggle i { .qrcode-toggle i {
transition: transform 0.3s ease; transition: transform 0.3s ease;
} }
.qrcode-toggle div {
display: flex;
align-items: center;
gap: 0.75rem;
}
.qrcode-container { .qrcode-container {
margin-top: 1rem; margin-top: 1rem;
padding: 1.5rem; padding: 1.5rem;
@ -367,9 +359,7 @@
@if (!string.IsNullOrEmpty(Model.Bio)) @if (!string.IsNullOrEmpty(Model.Bio))
{ {
var bioPipeline = new Markdig.MarkdownPipelineBuilder().UseAutoLinks().DisableHtml().Build(); <p class="profile-bio">@Model.Bio</p>
var bioHtml = Markdig.Markdown.ToHtml(Model.Bio, bioPipeline);
<div class="profile-bio">@Html.Raw(bioHtml)</div>
} }
<!-- Links Container --> <!-- Links Container -->
@ -384,7 +374,7 @@
var hasExpandableContent = (!string.IsNullOrEmpty(link.Description) || var hasExpandableContent = (!string.IsNullOrEmpty(link.Description) ||
(link.Type == BCards.Web.Models.LinkType.Product && !string.IsNullOrEmpty(link.ProductDescription))); (link.Type == BCards.Web.Models.LinkType.Product && !string.IsNullOrEmpty(link.ProductDescription)));
<div class="universal-link" id="link-@i" data-link-id="@i"> <div class="universal-link" data-link-id="@i">
<a href="@NormalizeSocialUrl(link.Url, link.Icon)" <a href="@NormalizeSocialUrl(link.Url, link.Icon)"
class="universal-link-header" class="universal-link-header"
onclick="recordClick('@Model.Id', @i)" onclick="recordClick('@Model.Id', @i)"
@ -431,23 +421,15 @@
} }
</div> </div>
</div> </div>
<div class="link-actions-container"> @if (hasExpandableContent)
<button class="copy-link-btn" {
type="button" <button class="expand-arrow"
title="Copiar link" type="button"
onclick="event.preventDefault(); event.stopPropagation(); copyAnchorLink('link-@i', this)"> onclick="event.preventDefault(); event.stopPropagation(); toggleLinkDetails(@i)">
<i class="fas fa-copy"></i> <i class="fas fa-chevron-down"></i>
</button> </button>
@if (hasExpandableContent) }
{
<button class="expand-arrow"
type="button"
onclick="event.preventDefault(); event.stopPropagation(); toggleLinkDetails(@i)">
<i class="fas fa-chevron-down"></i>
</button>
}
</div>
</a> </a>
@if (hasExpandableContent) @if (hasExpandableContent)
@ -479,10 +461,8 @@
} }
} }
<div class="expanded-action"> <div class="expanded-action">
<a href="@NormalizeSocialUrl(link.Url, link.Icon)" target="_blank" rel="noopener noreferrer" class="expanded-link"> <i class="fas fa-external-link-alt"></i>
<i class="fas fa-external-link-alt"></i> Clique no título acima para abrir
Clique aqui para abrir o link
</a>
</div> </div>
</div> </div>
} }
@ -535,10 +515,8 @@
<div class="universal-link-details" id="details-@uniqueId"> <div class="universal-link-details" id="details-@uniqueId">
<div class="expanded-description">@document.Description</div> <div class="expanded-description">@document.Description</div>
<div class="expanded-action"> <div class="expanded-action">
<a href="/api/document/@document.FileId" target="_blank" rel="noopener noreferrer" class="expanded-link"> <i class="fas fa-external-link-alt"></i>
<i class="fas fa-external-link-alt"></i> Clique no título acima para abrir o PDF
Clique aqui para abrir o PDF
</a>
</div> </div>
</div> </div>
} }
@ -558,13 +536,13 @@
<div class="qrcode-section mt-4"> <div class="qrcode-section mt-4">
<button class="qrcode-toggle" onclick="toggleQRCode()" type="button"> <button class="qrcode-toggle" onclick="toggleQRCode()" type="button">
<i class="fas fa-qrcode me-2"></i> <i class="fas fa-qrcode me-2"></i>
QR Code <span id="qrToggleText">Ocultar QR Code</span>
<i class="fas fa-chevron-up ms-auto" id="qrToggleIcon"></i> <i class="fas fa-chevron-up ms-auto" id="qrToggleIcon"></i>
</button> </button>
<div class="qrcode-container" id="qrcodeContainer" style="display: block;"> <div class="qrcode-container" id="qrcodeContainer" style="display: block;">
<div class="qrcode-canvas" id="qrcode"></div> <div class="qrcode-canvas" id="qrcode"></div>
<p class="qrcode-hint">Escaneie o QR Code para abrir no celular</p> <p class="qrcode-hint">Escaneie para compartilhar esta página</p>
<button class="btn-download-qr" onclick="downloadQR()" type="button"> <button class="btn-download-qr" onclick="downloadQR()" type="button">
<i class="fas fa-download me-1"></i> Baixar QR Code <i class="fas fa-download me-1"></i> Baixar QR Code
</button> </button>
@ -579,7 +557,7 @@
<i class="fas fa-chevron-down"></i> <i class="fas fa-chevron-down"></i>
</div> </div>
<div class="footer-promo-content"> <div class="footer-promo-content">
Crie a sua própria página personalizada com <strong>@tenant.SiteName</strong>! Crie a sua própria página personalizada com <strong>BCards</strong>!
É rápido, fácil e profissional. Compartilhe todos os seus links em um só lugar. É rápido, fácil e profissional. Compartilhe todos os seus links em um só lugar.
<div class="mt-2"> <div class="mt-2">
<a href="@Url.Action("Index", "Home")" class="footer-promo-button"> <a href="@Url.Action("Index", "Home")" class="footer-promo-button">
@ -590,7 +568,7 @@
</div> </div>
</div> </div>
<div class="footer-credits"> <div class="footer-credits">
Criado com <a href="@Url.Action("Index", "Home")">@tenant.SiteName</a> Criado com <a href="@Url.Action("Index", "Home")">BCards</a>
</div> </div>
</div> </div>
@ -602,8 +580,8 @@
</div> </div>
@section Scripts { @section Scripts {
<!-- QR Code Library - Usando bwip-js para maior compatibilidade --> <!-- QRCode.js Library - Load FIRST -->
<script src="https://cdn.jsdelivr.net/npm/bwip-js@3.4.1/dist/bwip-js-min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script> <script>
function recordClick(pageId, linkIndex) { function recordClick(pageId, linkIndex) {
@ -673,129 +651,72 @@
// Generate QR Code on page load // Generate QR Code on page load
generateQRCode(); generateQRCode();
// Auto-open link from hash
if (window.location.hash) {
const targetId = window.location.hash.substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
// Small delay to ensure everything is rendered
setTimeout(() => {
targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
// If it has a link-id, try to open the details
const linkId = targetElement.getAttribute('data-link-id');
const docId = targetElement.getAttribute('data-document-id');
if (linkId !== null) {
toggleLinkDetails(linkId);
} else if (docId !== null) {
toggleLinkDetails(docId);
}
}, 500);
}
}
}); });
function copyAnchorLink(id, btn) {
// Preserva a URL completa incluindo query strings (importante para o preview token)
// e apenas substitui/adiciona o hash
const url = new URL(window.location.href);
url.hash = id;
const fullUrl = url.toString();
navigator.clipboard.writeText(fullUrl).then(() => {
const icon = btn.querySelector('i');
const originalClass = icon.className;
icon.className = 'fas fa-check text-success';
btn.classList.add('copied');
setTimeout(() => {
icon.className = originalClass;
btn.classList.remove('copied');
}, 2000);
}).catch(err => {
console.error('Erro ao copiar link:', err);
});
}
// QR Code Functions // QR Code Functions
let qrCodeGenerated = false; let qrCodeGenerated = false;
function toggleQRCode() { function toggleQRCode() {
const container = document.getElementById('qrcodeContainer'); const container = document.getElementById('qrcodeContainer');
const icon = document.getElementById('qrToggleIcon'); const icon = document.getElementById('qrToggleIcon');
const text = document.getElementById('qrToggleText');
if (container.style.display === 'block') { if (container.style.display === 'block') {
// Close // Close
container.style.display = 'none'; container.style.display = 'none';
icon.classList.remove('fa-chevron-up'); icon.classList.remove('fa-chevron-up');
icon.classList.add('fa-chevron-down'); icon.classList.add('fa-chevron-down');
text.textContent = 'Mostrar QR Code';
} else { } else {
// Open // Open
container.style.display = 'block'; container.style.display = 'block';
icon.classList.remove('fa-chevron-down'); icon.classList.remove('fa-chevron-down');
icon.classList.add('fa-chevron-up'); icon.classList.add('fa-chevron-up');
text.textContent = 'Ocultar QR Code';
} }
} }
function generateQRCode() { function generateQRCode() {
if (qrCodeGenerated) { if (qrCodeGenerated) {
console.log('[QR] QR Code already generated, skipping...'); console.log('QR Code already generated, skipping...');
return; return;
} }
const qrcodeElement = document.getElementById("qrcode"); const qrcodeElement = document.getElementById("qrcode");
if (!qrcodeElement) { if (!qrcodeElement) {
console.error('[QR] QR Code container not found'); console.error('QR Code container not found');
return; return;
} }
if (typeof bwipjs === 'undefined') { // Check if already has content (double-call prevention)
console.error('[QR] bwip-js library is not loaded'); if (qrcodeElement.querySelector('canvas')) {
qrcodeElement.innerHTML = '<p class="text-danger small">Erro ao carregar gerador de QR Code. Verifique sua conexão ou bloqueador de anúncios.</p>'; console.log('Canvas already exists, skipping generation');
return;
}
// Construir a URL final (LivePage)
const baseUrl = window.location.origin;
const categoryName = '@Html.Raw(category?.Name ?? "")'.trim();
const slug = '@Html.Raw(Model.Slug ?? "")'.trim();
// Se não houver categoria, usa 'geral' ou remove a barra extra
const categoryPart = categoryName ? encodeURIComponent(categoryName) : "page";
const pageUrl = `${baseUrl}/page/${categoryPart}/${encodeURIComponent(slug)}`;
console.log('[QR] Generating QR for URL:', pageUrl);
// Criar um elemento canvas dinamicamente
const canvas = document.createElement('canvas');
canvas.style.maxWidth = '100%';
canvas.style.height = 'auto';
try {
// bwip-js usa um canvas para renderizar
bwipjs.toCanvas(canvas, {
bcid: 'qrcode', // Tipo de código de barras
text: pageUrl, // Texto/URL
scale: 3, // Escala (resolução)
height: 50, // Altura (para QR é proporcional)
width: 50,
includetext: false, // Não mostrar o texto embaixo
textxalign: 'center',
});
// Limpar e adicionar o canvas ao container
qrcodeElement.innerHTML = '';
qrcodeElement.appendChild(canvas);
qrCodeGenerated = true; qrCodeGenerated = true;
console.log('[QR] QR Code generated successfully with bwip-js'); return;
}
// Mark as generated BEFORE creating to prevent race conditions
qrCodeGenerated = true;
// Clear any existing content
qrcodeElement.innerHTML = '';
const pageUrl = window.location.href.split('?')[0]; // Remove query params
try {
new QRCode(qrcodeElement, {
text: pageUrl,
width: 200,
height: 200,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.H
});
console.log('QR Code generated successfully for:', pageUrl);
} catch (error) { } catch (error) {
console.error('[QR] Error generating QR Code with bwip-js:', error); console.error('Error generating QR Code:', error);
qrcodeElement.innerHTML = '<p class="text-danger small">Erro ao gerar QR Code. Consulte o console para detalhes.</p>'; qrcodeElement.innerHTML = '<p class="text-danger small">Erro ao gerar QR Code</p>';
qrCodeGenerated = false; qrCodeGenerated = false; // Reset on error so it can retry
} }
} }

View File

@ -1,4 +1,3 @@
@using BCards.Web @using BCards.Web
@using BCards.Web.Models @using BCards.Web.Models
@using Markdig
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -1,25 +1,25 @@
{ {
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Debug", "Default": "Debug",
"System": "Information", "System": "Information",
"Microsoft": "Information", "Microsoft": "Information",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"Stripe": { "Stripe": {
"PublishableKey": "pk_test_51RjUmIBMIadsOxJVP4bWc54pHEOSf5km1hpOkOBSoGVoKxI46N4KSWtevpXCSq68OjFazBuXmPJGBwZ1KDN5MNJy003lj1YmAS", "PublishableKey": "pk_test_51RjUmIBMIadsOxJVP4bWc54pHEOSf5km1hpOkOBSoGVoKxI46N4KSWtevpXCSq68OjFazBuXmPJGBwZ1KDN5MNJy003lj1YmAS",
"SecretKey": "sk_test_51RjUmIBMIadsOxJVeqsMFxnZ8ePR7d8IbnaF4sAwBVJv9rrfODPEQ2C9fF3beoABpITdfzEk0ZDzGTTQfvKv63xI00PeZoABGO", "SecretKey": "sk_test_51RjUmIBMIadsOxJVeqsMFxnZ8ePR7d8IbnaF4sAwBVJv9rrfODPEQ2C9fF3beoABpITdfzEk0ZDzGTTQfvKv63xI00PeZoABGO",
"WebhookSecret": "whsec_8d189c137ff170ab5e62498003512b9d073e2db50c50ed7d8712b7ef11a37543", "WebhookSecret": "whsec_8d189c137ff170ab5e62498003512b9d073e2db50c50ed7d8712b7ef11a37543",
"Environment": "test" "Environment": "test"
}, },
"Serilog": { "Serilog": {
"OpenSearchUrl": "http://192.168.0.100:9200" "OpenSearchUrl": "http://192.168.0.100:9200",
}, },
"DetailedErrors": true, "DetailedErrors": true,
"MongoDb": { "MongoDb": {
"ConnectionString": "mongodb://localhost:27017", "ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "BCardsDB_Dev" "DatabaseName": "BCardsDB_Dev"
}, },
"BaseUrl": "https://localhost:49178" "BaseUrl": "https://localhost:49178"
} }

View File

@ -1,71 +0,0 @@
{
"BaseUrl": "https://localhost:49184",
"Stripe": {
"PublishableKey": "pk_test_51RjUmIBMIadsOxJVP4bWc54pHEOSf5km1hpOkOBSoGVoKxI46N4KSWtevpXCSq68OjFazBuXmPJGBwZ1KDN5MNJy003lj1YmAS",
"SecretKey": "sk_test_51RjUmIBMIadsOxJVeqsMFxnZ8ePR7d8IbnaF4sAwBVJv9rrfODPEQ2C9fF3beoABpITdfzEk0ZDzGTTQfvKv63xI00PeZoABGO",
"WebhookSecret": "whsec_8d189c137ff170ab5e62498003512b9d073e2db50c50ed7d8712b7ef11a37543",
"Environment": "test"
},
"Serilog": {
"OpenSearchUrl": "http://192.168.0.100:9200"
},
"MongoDb": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "LuzLinksDB"
},
"Tenant": {
"SiteName": "LuzLinks",
"SiteDescription": "A plataforma para pastores, padres, líderes religiosos e ministérios. Reúna seus estudos bíblicos, eventos, lives e canal em uma única página de fé.",
"Tagline": "Conecte sua comunidade em um único link",
"SupportEmail": "suporte@luzlinks.site",
"ContentFolder": "luzlinks",
"AgeGated": false,
"UrlExample": "luzlinks.site/pastor/seu-nome",
"DpoEmail": "dpo@luzlinks.site",
"HeroHeadline": "Conecte sua comunidade em um único link",
"HeroDescription": "A plataforma ideal para pastores, padres, líderes e ministérios. Reúna seus estudos bíblicos, agenda de cultos, canal e dízimos em uma só página.",
"HeroCtaText": "Criar Minha Bio de Fé",
"FeaturesHeadline": "Por que líderes religiosos usam o {SiteName}?",
"Features": [
{ "Icon": "📖", "Title": "Conteúdo Espiritual Organizado", "Description": "Concentre seus estudos bíblicos, séries de pregações, agenda de cultos e canal do YouTube em um só link." },
{ "Icon": "🙏", "Title": "Facilite Dízimos e Ofertas", "Description": "Link direto para doações do seu ministério. Simplifique as ofertas e dízimos da sua congregação." },
{ "Icon": "📅", "Title": "Agenda e Eventos", "Description": "Compartilhe retiros, cultos especiais e eventos com toda a comunidade de forma simples e organizada." }
],
"CtaHeadline": "Compartilhe sua mensagem com o mundo",
"CtaDescription": "Líderes de toda denominação já usam o LuzLinks para alcançar mais pessoas com sua mensagem de fé.",
"CtaButtonText": "Criar Minha Bio de Fé",
"MetaKeywords": "bio links pastor, página ministério, linktree cristão, links religiosos, página iglesia, bio pastor, links igreja",
"FooterTagline": "Conectando fé e comunidade.",
"HeroGradient": "linear-gradient(135deg, #5b9bd5 0%, #1a5276 100%)",
"PrimaryColor": "#2471a3",
"PrimaryColorDark": "#1a5276",
"DefaultCategories": [
{ "Icon": "🙏", "Name": "Pastores", "Slug": "pastor", "Description": "Pastores evangélicos, protestantes e pentecostais", "SeoKeywords": [ "pastor", "evangélico", "protestante", "pentecostal", "pregador" ] },
{ "Icon": "✝️", "Name": "Padres", "Slug": "padre", "Description": "Sacerdotes, padres e religiosos da Igreja Católica", "SeoKeywords": [ "padre", "sacerdote", "católico", "pároco", "religioso" ] },
{ "Icon": "⛪", "Name": "Igrejas", "Slug": "igreja", "Description": "Congregações, comunidades de fé e denominações", "SeoKeywords": [ "igreja", "congregação", "comunidade", "denominação", "templo" ] },
{ "Icon": "🌟", "Name": "Ministérios", "Slug": "ministerio", "Description": "Ministérios, organizações e missões cristãs", "SeoKeywords": [ "ministério", "missão", "organização cristã", "obra" ] },
{ "Icon": "🎵", "Name": "Louvor e Adoração", "Slug": "louvor", "Description": "Ministérios de louvor, bandas gospel e cantores cristãos", "SeoKeywords": [ "louvor", "adoração", "gospel", "banda", "música cristã" ] },
{ "Icon": "👨‍👩‍👧", "Name": "Família e Jovens", "Slug": "familia", "Description": "Líderes de grupos de jovens, casais e família", "SeoKeywords": [ "jovens", "família", "casais", "célula", "grupo" ] },
{ "Icon": "📖", "Name": "Estudos Bíblicos", "Slug": "estudos", "Description": "Mestres, professores bíblicos e teólogos", "SeoKeywords": [ "estudo bíblico", "teologia", "mestre", "professor", "bíblia" ] },
{ "Icon": "🌍", "Name": "Missionários", "Slug": "missionario","Description": "Missionários e evangelistas nacionais e internacionais", "SeoKeywords": [ "missionário", "evangelista", "evangelismo", "missões" ] },
{ "Icon": "📻", "Name": "Mídia Cristã", "Slug": "midia", "Description": "Podcasts, canais, rádios e comunicação cristã", "SeoKeywords": [ "podcast", "canal cristão", "rádio evangélica", "mídia" ] },
{ "Icon": "🤝", "Name": "Assistência Social", "Slug": "assistencia","Description": "Projetos sociais, pastorais de assistência e ONGs cristãs","SeoKeywords": [ "assistência social", "projeto social", "ONG", "pastoral" ] }
],
"AllowedLinkTypes": [
{ "Icon": "fas fa-globe", "Label": "🌐 Site / Ministério", "Prefix": "https://", "Placeholder": "ministerio.com.br", "Instructions": "Digite o domínio do site", "Color": "bg-primary" },
{ "Icon": "fas fa-envelope", "Label": "✉️ Email", "Prefix": "mailto:", "Placeholder": "contato@ministerio.com", "Instructions": "Digite apenas o email", "Color": "bg-success" },
{ "Icon": "fas fa-phone", "Label": "📞 Telefone / WhatsApp","Prefix": "tel:", "Placeholder": "5511999999999", "Instructions": "Número com código do país", "Color": "bg-success" },
{ "Icon": "fab fa-youtube", "Label": "📺 YouTube", "Prefix": "https://youtube.com/", "Placeholder": "@canal ou c/CANAL", "Instructions": "Digite o canal ou @usuário", "Color": "bg-danger" },
{ "Icon": "fab fa-instagram", "Label": "📸 Instagram", "Prefix": "https://instagram.com/", "Placeholder": "seu.usuario", "Instructions": "Digite apenas seu usuário", "Color": "bg-danger" },
{ "Icon": "fas fa-book", "Label": "📖 Estudo / Série", "Prefix": "https://", "Placeholder": "link-do-estudo.com", "Instructions": "Link para estudo bíblico ou série", "Color": "bg-info" },
{ "Icon": "fas fa-calendar", "Label": "📅 Agenda / Eventos", "Prefix": "https://", "Placeholder": "calendly.com/seunome", "Instructions": "Link para agenda ou evento", "Color": "bg-warning" },
{ "Icon": "fas fa-donate", "Label": "🙏 Dízimos / Ofertas", "Prefix": "https://", "Placeholder": "pix.com.br/ministerio", "Instructions": "Link para doações ou dízimos", "Color": "bg-success" },
{ "Icon": "fas fa-map-marker-alt","Label": "📍 Localização", "Prefix": "https://maps.google.com/?q=", "VisualPrefix": "📍 Maps:", "Placeholder": "Rua da Igreja, 123", "Instructions": "Endereço da igreja/ministério", "Color": "bg-warning" },
{ "Icon": "fas fa-download", "Label": "⬇️ Material / Apostila","Prefix": "https://", "Placeholder": "drive.google.com/...", "Instructions": "Link para download de material", "Color": "bg-secondary" }
]
},
"SendGrid": {
"FromEmail": "noreply@luzlinks.site",
"FromName": "LuzLinks"
}
}

View File

@ -1,184 +0,0 @@
{
"BaseUrl": "https://localhost:49182",
"Stripe": {
"PublishableKey": "pk_test_51RjUmIBMIadsOxJVP4bWc54pHEOSf5km1hpOkOBSoGVoKxI46N4KSWtevpXCSq68OjFazBuXmPJGBwZ1KDN5MNJy003lj1YmAS",
"SecretKey": "sk_test_51RjUmIBMIadsOxJVeqsMFxnZ8ePR7d8IbnaF4sAwBVJv9rrfODPEQ2C9fF3beoABpITdfzEk0ZDzGTTQfvKv63xI00PeZoABGO",
"WebhookSecret": "whsec_8d189c137ff170ab5e62498003512b9d073e2db50c50ed7d8712b7ef11a37543",
"Environment": "test"
},
"MongoDb": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "SpicyLinksDB"
},
"Serilog": {
"OpenSearchUrl": "http://192.168.0.100:9200"
},
"Tenant": {
"SiteName": "SpicyLinks",
"SiteDescription": "A plataforma discreta e segura para criadores de conteúdo adulto. Reúna suas assinaturas, lista de desejos, redes sociais e conteúdo exclusivo em uma única bio.",
"Tagline": "Seu conteúdo exclusivo, um link só",
"SupportEmail": "suporte@spicylinks.site",
"ContentFolder": "spicylinks",
"AgeGated": true,
"UrlExample": "spicylinks.site/modelo/seu-nome",
"DpoEmail": "dpo@spicylinks.site",
"HeroHeadline": "Seu conteúdo exclusivo, um link só",
"HeroDescription": "A plataforma discreta e segura para criadores de conteúdo adulto. Reúna suas assinaturas, lista de desejos, redes e conteúdo exclusivo em uma bio elegante.",
"HeroCtaText": "Criar Minha Bio",
"FeaturesHeadline": "Por que criadores escolhem o {SiteName}?",
"Features": [
{
"Icon": "❤️",
"Title": "Tudo num Só Link",
"Description": "Instagram, Twitter/X, OnlyFans, lista de desejos e mais — tudo em uma bio única, elegante e fácil de compartilhar."
},
{
"Icon": "🔒",
"Title": "Verificação de Idade",
"Description": "Acesso protegido com verificação de idade automática. Plataforma segura, discreta e responsável."
},
{
"Icon": "📊",
"Title": "Saiba Quem Te Visita",
"Description": "Analytics detalhado de cliques, visualizações e origem do tráfego para otimizar suas conversões."
}
],
"CtaHeadline": "Pronta para monetizar seu conteúdo?",
"CtaDescription": "Milhares de criadoras já centralizam seus links e aumentam suas conversões com o SpicyLinks.",
"CtaButtonText": "Criar Minha Bio",
"MetaKeywords": "bio links criadora, creator bio, linktree conteudo adulto, links onlyfans, bio instagram criadora",
"FooterTagline": "Seu conteúdo, sua identidade.",
"HeroGradient": "linear-gradient(135deg, #ff416c 0%, #c0392b 100%)",
"PrimaryColor": "#e63946",
"PrimaryColorDark": "#c1121f",
"DefaultCategories": [
{
"Icon": "📸",
"Name": "Modelos",
"Slug": "modelos",
"Description": "Modelos e criadores de conteúdo visual",
"SeoKeywords": [ "modelo", "fotografia", "conteúdo", "criadora" ]
},
{
"Icon": "⭐",
"Name": "Influencers",
"Slug": "influencers",
"Description": "Influencers e personalidades digitais",
"SeoKeywords": [ "influencer", "digital", "social media" ]
},
{
"Icon": "💪",
"Name": "Fitness",
"Slug": "fitness",
"Description": "Criadores de conteúdo fitness e lifestyle",
"SeoKeywords": [ "fitness", "academia", "saúde", "corpo" ]
},
{
"Icon": "🎨",
"Name": "Arte",
"Slug": "arte",
"Description": "Artistas e criadores de conteúdo visual",
"SeoKeywords": [ "arte", "ilustração", "design", "criativo" ]
},
{
"Icon": "🎵",
"Name": "Música",
"Slug": "musica",
"Description": "Músicos e cantores independentes",
"SeoKeywords": [ "música", "cantor", "artista", "show" ]
},
{
"Icon": "🎮",
"Name": "Gaming",
"Slug": "gaming",
"Description": "Streamers e criadores de conteúdo gamer",
"SeoKeywords": [ "gaming", "streamer", "games", "twitch" ]
},
{
"Icon": "🦸",
"Name": "Cosplay",
"Slug": "cosplay",
"Description": "Cosplayers e criadores de fantasia",
"SeoKeywords": [ "cosplay", "anime", "fantasia", "cosplayer" ]
},
{
"Icon": "💋",
"Name": "Lifestyle",
"Slug": "lifestyle",
"Description": "Criadores de conteúdo lifestyle e entretenimento",
"SeoKeywords": [ "lifestyle", "entretenimento", "diversão" ]
}
],
"AllowedLinkTypes": [
{
"Icon": "fas fa-globe",
"Label": "🌐 Site Geral",
"Prefix": "https://",
"Placeholder": "exemplo.com",
"Instructions": "Digite o domínio e caminho",
"Color": "bg-primary"
},
{
"Icon": "fas fa-envelope",
"Label": "✉️ Email",
"Prefix": "mailto:",
"Placeholder": "seuemail@exemplo.com",
"Instructions": "Digite apenas o email",
"Color": "bg-success"
},
{
"Icon": "fas fa-phone",
"Label": "📞 Telefone",
"Prefix": "tel:",
"Placeholder": "5511999999999",
"Instructions": "Número com código do país",
"Color": "bg-success"
},
{
"Icon": "fab fa-instagram",
"Label": "📸 Instagram",
"Prefix": "https://instagram.com/",
"Placeholder": "seu.usuario",
"Instructions": "Digite apenas seu usuário",
"Color": "bg-danger"
},
{
"Icon": "fab fa-twitter",
"Label": "🐦 Twitter/X",
"Prefix": "https://x.com/",
"Placeholder": "seu_usuario",
"Instructions": "Digite apenas seu usuário",
"Color": "bg-dark"
},
{
"Icon": "fab fa-tiktok",
"Label": "🎵 TikTok",
"Prefix": "https://tiktok.com/@",
"Placeholder": "seu.usuario",
"Instructions": "Digite apenas seu usuário",
"Color": "bg-dark"
},
{
"Icon": "fas fa-shopping-cart",
"Label": "🛒 Lista de Desejos",
"Prefix": "https://",
"Placeholder": "wishlist.com/...",
"Instructions": "Link para lista de desejos",
"Color": "bg-warning"
},
{
"Icon": "fas fa-heart",
"Label": "❤️ Assinatura",
"Prefix": "https://",
"Placeholder": "plataforma.com/...",
"Instructions": "Link para plataforma paga",
"Color": "bg-danger"
}
]
},
"SendGrid": {
"FromEmail": "noreply@spicylinks.site",
"FromName": "SpicyLinks"
}
}

View File

@ -19,122 +19,122 @@
"Environment": "test" "Environment": "test"
}, },
"Plans": { "Plans": {
"Basic": { "Basic": {
"Name": "Básico", "Name": "Básico",
"PriceId": "price_1TR10MBk8jHwC3c0iey23Ghb", "PriceId": "price_1RycPaBMIadsOxJVKioZZofK",
"Price": 12.90, "Price": 12.90,
"MaxPages": 3, "MaxPages": 3,
"MaxLinks": 8, "MaxLinks": 8,
"AllowPremiumThemes": false, "AllowPremiumThemes": false,
"AllowProductLinks": false, "AllowProductLinks": false,
"AllowAnalytics": true, "AllowAnalytics": true,
"AllowDocumentUpload": false, "AllowDocumentUpload": false,
"MaxDocuments": 0, "MaxDocuments": 0,
"Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ], "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ],
"Interval": "month" "Interval": "month"
}, },
"Professional": { "Professional": {
"Name": "Profissional", "Name": "Profissional",
"PriceId": "price_1TR10NBk8jHwC3c0yqmy8soD", "PriceId": "price_1RycQmBMIadsOxJVGqjVMaOj",
"Price": 25.90, "Price": 25.90,
"MaxPages": 5, "MaxPages": 5,
"MaxLinks": 20, "MaxLinks": 20,
"AllowPremiumThemes": false, "AllowPremiumThemes": false,
"AllowProductLinks": false, "AllowProductLinks": false,
"AllowAnalytics": true, "AllowAnalytics": true,
"AllowDocumentUpload": false, "AllowDocumentUpload": false,
"MaxDocuments": 0, "MaxDocuments": 0,
"Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ], "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ],
"Interval": "month" "Interval": "month"
}, },
"Premium": { "Premium": {
"Name": "Premium", "Name": "Premium",
"PriceId": "price_1TR10OBk8jHwC3c0eZa77y31", "PriceId": "price_1RycRUBMIadsOxJVkxGOh3uu",
"Price": 29.90, "Price": 29.90,
"MaxPages": 15, "MaxPages": 15,
"MaxLinks": -1, "MaxLinks": -1,
"AllowPremiumThemes": true, "AllowPremiumThemes": true,
"AllowProductLinks": false, "AllowProductLinks": false,
"AllowAnalytics": true, "AllowAnalytics": true,
"SpecialModeration": false, "SpecialModeration": false,
"AllowDocumentUpload": true, "AllowDocumentUpload": true,
"MaxDocuments": 5, "MaxDocuments": 5,
"Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados", "Upload de PDFs (até 5 arquivos)" ], "Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados", "Upload de PDFs (até 5 arquivos)" ],
"Interval": "month" "Interval": "month"
}, },
"PremiumAffiliate": { "PremiumAffiliate": {
"Name": "Premium+Afiliados", "Name": "Premium+Afiliados",
"PriceId": "price_1TR10PBk8jHwC3c0B1oIvvYY", "PriceId": "price_1RycTaBMIadsOxJVeDLseXQq",
"Price": 34.90, "Price": 34.90,
"MaxPages": 15, "MaxPages": 15,
"MaxLinks": -1, "MaxLinks": -1,
"AllowPremiumThemes": true, "AllowPremiumThemes": true,
"AllowProductLinks": true, "AllowProductLinks": true,
"AllowAnalytics": true, "AllowAnalytics": true,
"SpecialModeration": true, "SpecialModeration": true,
"AllowDocumentUpload": true, "AllowDocumentUpload": true,
"MaxDocuments": 10, "MaxDocuments": 10,
"Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Upload de PDFs (até 10 arquivos)" ], "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Upload de PDFs (até 10 arquivos)" ],
"Interval": "month" "Interval": "month"
}, },
"BasicYearly": { "BasicYearly": {
"Name": "Básico Anual", "Name": "Básico Anual",
"PriceId": "price_1TR10NBk8jHwC3c0L4SDaWe9", "PriceId": "price_1RycWgBMIadsOxJVGdtEeoMS",
"Price": 129.00, "Price": 129.00,
"MaxPages": 3, "MaxPages": 3,
"MaxLinks": 8, "MaxLinks": 8,
"AllowPremiumThemes": false, "AllowPremiumThemes": false,
"AllowProductLinks": false, "AllowProductLinks": false,
"AllowAnalytics": true, "AllowAnalytics": true,
"AllowDocumentUpload": false, "AllowDocumentUpload": false,
"MaxDocuments": 0, "MaxDocuments": 0,
"Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ], "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ],
"Interval": "year" "Interval": "year"
}, },
"ProfessionalYearly": { "ProfessionalYearly": {
"Name": "Profissional Anual", "Name": "Profissional Anual",
"PriceId": "price_1TR10OBk8jHwC3c0IuyvrvRf", "PriceId": "price_1RycXdBMIadsOxJV5cNX7dHm",
"Price": 259.00, "Price": 259.00,
"MaxPages": 5, "MaxPages": 5,
"MaxLinks": 20, "MaxLinks": 20,
"AllowPremiumThemes": false, "AllowPremiumThemes": false,
"AllowProductLinks": false, "AllowProductLinks": false,
"AllowAnalytics": true, "AllowAnalytics": true,
"AllowDocumentUpload": false, "AllowDocumentUpload": false,
"MaxDocuments": 0, "MaxDocuments": 0,
"Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ], "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ],
"Interval": "year" "Interval": "year"
}, },
"PremiumYearly": { "PremiumYearly": {
"Name": "Premium Anual", "Name": "Premium Anual",
"PriceId": "price_1TR10PBk8jHwC3c0qngPYMUN", "PriceId": "price_1RycYnBMIadsOxJVPdKmzy4m",
"Price": 299.00, "Price": 299.00,
"MaxPages": 15, "MaxPages": 15,
"MaxLinks": -1, "MaxLinks": -1,
"AllowPremiumThemes": true, "AllowPremiumThemes": true,
"AllowProductLinks": false, "AllowProductLinks": false,
"AllowAnalytics": true, "AllowAnalytics": true,
"SpecialModeration": false, "SpecialModeration": false,
"AllowDocumentUpload": true, "AllowDocumentUpload": true,
"MaxDocuments": 5, "MaxDocuments": 5,
"Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados", "Upload de PDFs (até 5 arquivos)", "Economize R$ 39,80 (2 meses grátis)" ], "Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados", "Upload de PDFs (até 5 arquivos)", "Economize R$ 39,80 (2 meses grátis)" ],
"Interval": "year" "Interval": "year"
}, },
"PremiumAffiliateYearly": { "PremiumAffiliateYearly": {
"Name": "Premium+Afiliados Anual", "Name": "Premium+Afiliados Anual",
"PriceId": "price_1TR10QBk8jHwC3c0f8CBaD1n", "PriceId": "price_1RycaEBMIadsOxJVEhsdB2Y1",
"Price": 349.00, "Price": 349.00,
"MaxPages": 15, "MaxPages": 15,
"MaxLinks": -1, "MaxLinks": -1,
"AllowPremiumThemes": true, "AllowPremiumThemes": true,
"AllowProductLinks": true, "AllowProductLinks": true,
"AllowAnalytics": true, "AllowAnalytics": true,
"SpecialModeration": true, "SpecialModeration": true,
"AllowDocumentUpload": true, "AllowDocumentUpload": true,
"MaxDocuments": 10, "MaxDocuments": 10,
"Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Upload de PDFs (até 10 arquivos)", "Economize R$ 59,80 (2 meses grátis)" ], "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Upload de PDFs (até 10 arquivos)", "Economize R$ 59,80 (2 meses grátis)" ],
"Interval": "year" "Interval": "year"
} }
}, },
"MongoDb": { "MongoDb": {
"ConnectionString": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/BCardsDB?replicaSet=rs0&authSource=admin", "ConnectionString": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/BCardsDB?replicaSet=rs0&authSource=admin",
@ -147,7 +147,7 @@
}, },
"Microsoft": { "Microsoft": {
"ClientId": "b411606a-e574-4f59-b7cd-10dd941b9fa3", "ClientId": "b411606a-e574-4f59-b7cd-10dd941b9fa3",
"ClientSecret": "bff10c42-f1e5-487b-bacb-16b1b691aa7d" "ClientSecret": "T0.8Q~an.51iW1H0DVjL2i1bmSK_qTgVQOuEmapK"
} }
}, },
"Moderation": { "Moderation": {
@ -170,47 +170,6 @@
"FromName": "Ricardo Carneiro" "FromName": "Ricardo Carneiro"
}, },
"BaseUrl": "https://bcards.site", "BaseUrl": "https://bcards.site",
"Tenant": {
"SiteName": "BCards",
"SiteDescription": "Crie sua página profissional com links organizados. A melhor alternativa para ter sua bio / links. Criada para profissionais e empresas no Brasil.",
"Tagline": "Sua bio de links profissional",
"SupportEmail": "suporte@bcards.site",
"ContentFolder": "bcards",
"AgeGated": false,
"UrlExample": "bcards.site/corretor/seu-nome",
"DpoEmail": "dpo@bcards.site",
"HeroHeadline": "Sua presença digital profissional em um só lugar",
"HeroDescription": "Organize seus links, portfólio, contatos e redes sociais em uma página única e elegante. Criado para profissionais e empresas brasileiras que querem ser encontrados.",
"HeroCtaText": "Criar Minha Página Grátis",
"FeaturesHeadline": "Por que profissionais escolhem o {SiteName}?",
"Features": [
{ "Icon": "🎨", "Title": "Temas Profissionais", "Description": "Mais de 40 temas para sua área: corretores, advogados, médicos, consultores e muito mais." },
{ "Icon": "📊", "Title": "Analytics de Verdade", "Description": "Saiba quantas pessoas acessaram sua página, quais links clicaram e de onde vieram." },
{ "Icon": "🔗", "Title": "URLs com Credibilidade", "Description": "Sua URL tem contexto profissional: bcards.site/corretor/seu-nome — transmite autoridade instantânea." }
],
"CtaHeadline": "Pronto para se destacar?",
"CtaDescription": "Junte-se a milhares de profissionais que já têm sua presença digital organizada no BCards.",
"CtaButtonText": "Criar Minha Página Grátis",
"MetaKeywords": "cartão digital, página de links, bio links, linktree brasil, página profissional, corretor, advogado, médico, consultor",
"FooterTagline": "Sua presença digital profissional, simplificada.",
"HeroGradient": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
"PrimaryColor": "#667eea",
"PrimaryColorDark": "#5a6fd6",
"AllowedLinkTypes": [
{ "Icon": "fas fa-globe", "Label": "🌐 Site Geral", "Prefix": "https://", "Placeholder": "exemplo.com", "Instructions": "Digite apenas o domínio e caminho (sem https://)", "Color": "bg-primary" },
{ "Icon": "fas fa-shopping-cart", "Label": "🛒 Loja/E-commerce", "Prefix": "https://", "Placeholder": "minhaloja.com/produto", "Instructions": "Digite apenas o domínio e caminho da sua loja", "Color": "bg-success" },
{ "Icon": "fas fa-briefcase", "Label": "💼 Portfólio", "Prefix": "https://", "Placeholder": "meuportifolio.com", "Instructions": "Digite apenas o domínio do seu portfólio", "Color": "bg-info" },
{ "Icon": "fas fa-envelope", "Label": "✉️ Email", "Prefix": "mailto:", "Placeholder": "seuemail@exemplo.com", "Instructions": "Digite apenas o email (sem mailto:)", "Color": "bg-success" },
{ "Icon": "fas fa-phone", "Label": "📞 Telefone", "Prefix": "tel:", "Placeholder": "5511999999999", "Instructions": "Digite o telefone com código do país e DDD (apenas números)", "Color": "bg-success" },
{ "Icon": "fas fa-map-marker-alt", "Label": "📍 Localização", "Prefix": "https://maps.google.com/?q=", "VisualPrefix": "📍 Maps:", "Placeholder": "Rua das Flores, 123 - São Paulo, SP", "Instructions": "Digite o endereço completo (acentos e espaços serão codificados automaticamente)", "Color": "bg-warning" },
{ "Icon": "fab fa-youtube", "Label": "📺 YouTube", "Prefix": "https://youtube.com/", "Placeholder": "watch?v=VIDEO_ID ou @usuario ou c/CANAL","Instructions": "Digite o ID do vídeo, @usuário ou c/canal", "Color": "bg-danger" },
{ "Icon": "fab fa-linkedin", "Label": "💼 LinkedIn", "Prefix": "https://linkedin.com/in/", "Placeholder": "seu-perfil-linkedin", "Instructions": "Digite apenas seu nome de usuário do LinkedIn", "Color": "bg-primary" },
{ "Icon": "fab fa-github", "Label": "💻 GitHub", "Prefix": "https://github.com/", "Placeholder": "usuario ou usuario/repositorio", "Instructions": "Digite seu usuário ou usuário/repositório", "Color": "bg-dark" },
{ "Icon": "fas fa-download", "Label": "⬇️ Download", "Prefix": "https://", "Placeholder": "exemplo.com/arquivo.pdf", "Instructions": "Digite o link direto para download", "Color": "bg-secondary" },
{ "Icon": "fas fa-calendar", "Label": "📅 Agenda", "Prefix": "https://", "Placeholder": "calendly.com/seunome", "Instructions": "Digite o link do seu calendário (Calendly, etc.)", "Color": "bg-info" },
{ "Icon": "fas fa-heart", "Label": "❤️ Favorito", "Prefix": "https://", "Placeholder": "exemplo.com", "Instructions": "Digite qualquer link especial", "Color": "bg-danger" }
]
},
"Support": { "Support": {
"TelegramUrl": "https://t.me/jobmakerbr", "TelegramUrl": "https://t.me/jobmakerbr",
"FormspreeUrl": "https://formspree.io/f/xpwynqpj", "FormspreeUrl": "https://formspree.io/f/xpwynqpj",
@ -218,4 +177,4 @@
"EnableFormForPlans": [ "Basic", "Professional", "Premium", "PremiumAffiliate" ], "EnableFormForPlans": [ "Basic", "Professional", "Premium", "PremiumAffiliate" ],
"EnableRatingForAllUsers": true "EnableRatingForAllUsers": true
} }
} }

View File

@ -143,9 +143,9 @@ body > .container-fluid {
transition: all 0.3s ease; transition: all 0.3s ease;
} }
/* Menu Home — cor sólida primária do tenant (gradiente em elemento estreito parece diferente do hero) */ /* Menu Home (gradiente) */
.bg-home-blue { .bg-home-blue {
background-color: var(--tenant-primary, #667eea) !important; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
} }
.bg-home-blue .navbar-brand, .bg-home-blue .navbar-brand,
@ -185,6 +185,6 @@ body > .container-fluid {
box-shadow: 0 4px 15px rgba(0,0,0,0.1); box-shadow: 0 4px 15px rgba(0,0,0,0.1);
} }
.bg-home-blue .navbar-collapse { .bg-home-blue .navbar-collapse {
background-color: color-mix(in srgb, var(--tenant-primary, #764ba2) 85%, black); background-color: rgba(118, 75, 162, 0.95); /* Cor do gradiente para o menu recolhido */
} }
} }