diff --git a/.claude/napkin.md b/.claude/napkin.md new file mode 100644 index 0000000..da06465 --- /dev/null +++ b/.claude/napkin.md @@ -0,0 +1,48 @@ +# 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. diff --git a/.gitea/workflows/deploy-bcards.yml b/.gitea/workflows/deploy-bcards.yml index f6cc88d..b7bf6cf 100644 --- a/.gitea/workflows/deploy-bcards.yml +++ b/.gitea/workflows/deploy-bcards.yml @@ -1,11 +1,34 @@ -name: BCards Deployment Pipeline +name: BCards Multi-Tenant Deployment Pipeline + +# ─── Required Gitea Secrets ──────────────────────────────────────────────── +# secrets.SSH_PRIVATE_KEY – SSH key for ubuntu@ on both OCI nodes +# secrets.STRIPE_SECRET_KEY – Shared Stripe secret key +# secrets.STRIPE_WEBHOOK_SECRET – Shared Stripe webhook secret +# secrets.GOOGLE_CLIENT_SECRET – Google OAuth client secret +# secrets.MICROSOFT_CLIENT_SECRET – Microsoft OAuth client secret +# secrets.SENDGRID_API_KEY – SendGrid API key +# +# ─── Required Gitea Variables (vars.*) ──────────────────────────────────── +# vars.STRIPE_PUBLISHABLE_KEY +# vars.STRIPE_ENVIRONMENT (default: test) +# vars.GOOGLE_CLIENT_ID +# vars.MICROSOFT_CLIENT_ID +# vars.OPENSEARCH_URL (default: http://localhost:9201) +# vars.MODERATOR_EMAIL +# vars.MODERATOR_EMAIL_1 +# vars.MODERATOR_EMAIL_2 +# +# ─── Per-Tenant Variables (optional, have defaults) ─────────────────────── +# vars.SPICYLINKS_FROM_EMAIL (default: noreply@spicylinks.site) +# vars.SPICYLINKS_FROM_NAME (default: SpicyLinks) +# vars.LUSLINKS_FROM_EMAIL (default: noreply@luslinks.site) +# vars.LUSLINKS_FROM_NAME (default: LusLinks) on: push: - branches: + branches: - main - 'Release/*' - # PRs apenas validam, não fazem deploy pull_request: branches: [ main ] types: [opened, synchronize, reopened] @@ -13,22 +36,20 @@ on: env: REGISTRY: registry.redecarneir.us IMAGE_NAME: bcards - MONGODB_HOST: 192.168.0.100:27017 + SWARM_MANAGER: 141.148.162.114 + SWARM_WORKER: 129.146.116.218 jobs: + # ─── Tests ──────────────────────────────────────────────────────────────── test: name: Run Tests runs-on: ubuntu-latest - + steps: - name: Test info run: | echo "🧪 Executando testes para ${{ github.ref_name }}" - echo "🎯 Trigger: ${{ github.event_name }}" - - # Verificar se deve pular testes SKIP_TESTS="${{ github.event.inputs.skip_tests || vars.SKIP_TESTS }}" - if [ "$SKIP_TESTS" == "true" ]; then echo "⚠️ Testes PULADOS" echo "TESTS_SKIPPED=true" >> $GITHUB_ENV @@ -36,17 +57,17 @@ jobs: echo "✅ Executando testes" echo "TESTS_SKIPPED=false" >> $GITHUB_ENV fi - + - name: Checkout code if: env.TESTS_SKIPPED == 'false' uses: actions/checkout@v4 - + - name: Setup .NET 8 if: env.TESTS_SKIPPED == 'false' uses: actions/setup-dotnet@v4 with: dotnet-version: '8.0.x' - + - name: Cache dependencies if: env.TESTS_SKIPPED == 'false' uses: actions/cache@v3 @@ -55,126 +76,87 @@ jobs: key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} restore-keys: | ${{ runner.os }}-nuget- - + - name: Restore dependencies if: env.TESTS_SKIPPED == 'false' run: dotnet restore - + - name: Build solution if: env.TESTS_SKIPPED == 'false' run: dotnet build --no-restore --configuration Release - + - name: Run unit tests if: env.TESTS_SKIPPED == 'false' run: dotnet test --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage" - # Job específico para validação de PRs (sem deploy) + # ─── PR Validation (no deploy) ──────────────────────────────────────────── pr-validation: name: PR Validation runs-on: ubuntu-latest needs: [test] if: github.event_name == 'pull_request' - + steps: - name: PR Validation Summary run: | echo "✅ Pull Request Validation Summary" - echo "🎯 Target Branch: ${{ github.base_ref }}" - echo "📂 Source Branch: ${{ github.head_ref }}" + echo "🎯 Target: ${{ github.base_ref }}" + echo "📂 Source: ${{ github.head_ref }}" echo "🧪 Tests: ${{ needs.test.result }}" - echo "👤 Author: ${{ github.event.pull_request.user.login }}" - echo "📝 Title: ${{ github.event.pull_request.title }}" - echo "" echo "✨ PR está pronto para merge!" + # ─── Build & Push (single image, all tenants share it) ─────────────────── build-and-push: name: Build and Push Image runs-on: [self-hosted, arm64, bcards] needs: [test] - # Só faz build/push em push (não em PR) if: github.event_name == 'push' && (needs.test.result == 'success' || needs.test.result == 'skipped') - + steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: platforms: linux/arm64 - + - name: Determine build settings id: settings run: | BRANCH_NAME="${{ github.ref_name }}" - if [ "$BRANCH_NAME" = "main" ]; then - # Main = Produção (ARM64) - usando Dockerfile simples echo "tag=latest" >> $GITHUB_OUTPUT - echo "platform=linux/arm64" >> $GITHUB_OUTPUT echo "environment=Production" >> $GITHUB_OUTPUT - echo "dockerfile=Dockerfile" >> $GITHUB_OUTPUT echo "deploy_target=production" >> $GITHUB_OUTPUT elif [[ "$BRANCH_NAME" == Release/* ]]; then - # Release = Swarm tests (Orange Pi arm64) - usando Dockerfile simples também VERSION_RAW=${BRANCH_NAME#Release/} - # Only remove V/v if it's at the start and followed by a number (like v1.0.0) VERSION=$(echo "$VERSION_RAW" | sed 's/^[Vv]\([0-9]\)/\1/') [ -z "$VERSION" ] && VERSION="0.0.1" - echo "tag=$VERSION" >> $GITHUB_OUTPUT - echo "platform=linux/arm64" >> $GITHUB_OUTPUT echo "environment=Testing" >> $GITHUB_OUTPUT - echo "dockerfile=Dockerfile" >> $GITHUB_OUTPUT echo "deploy_target=testing" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT fi - - COMMIT_SHA=${{ github.sha }} - SHORT_COMMIT=${COMMIT_SHA:0:7} + SHORT_COMMIT=${GITHUB_SHA:0:7} echo "commit=$SHORT_COMMIT" >> $GITHUB_OUTPUT - - echo "📦 Tag: ${{ steps.settings.outputs.tag }}" - echo "🏗️ Platform: ${{ steps.settings.outputs.platform }}" - echo "🌍 Environment: ${{ steps.settings.outputs.environment }}" - echo "🎯 Target: ${{ steps.settings.outputs.deploy_target }}" - + - name: Build and push image run: | - echo "🏗️ Building image for ${{ steps.settings.outputs.deploy_target }}..." - - # Debug das variáveis - echo "Platform: ${{ steps.settings.outputs.platform }}" - echo "Dockerfile: ${{ steps.settings.outputs.dockerfile }}" - echo "Tag: ${{ steps.settings.outputs.tag }}" - - # Verificar se o Dockerfile existe - if [ ! -f "${{ steps.settings.outputs.dockerfile }}" ]; then - echo "❌ Dockerfile não encontrado: ${{ steps.settings.outputs.dockerfile }}" - echo "📂 Arquivos na raiz:" - ls -la - echo "📂 Arquivos em src/BCards.Web/:" - ls -la src/BCards.Web/ || echo "Diretório não existe" - exit 1 - else - echo "✅ Dockerfile encontrado: ${{ steps.settings.outputs.dockerfile }}" - fi - - # Build para a plataforma correta + echo "🏗️ Building image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.settings.outputs.tag }}" + if [ "${{ steps.settings.outputs.deploy_target }}" = "production" ]; then - # Build para produção (main branch) - Usa Configuration=Release (padrão) docker buildx build \ - --platform ${{ steps.settings.outputs.platform }} \ - --file ${{ steps.settings.outputs.dockerfile }} \ + --platform linux/arm64 \ + --file Dockerfile \ --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.settings.outputs.tag }} \ --push \ --progress=plain \ . else - # Build para staging (Release branches) - Usa Configuration=Testing para habilitar código de teste docker buildx build \ - --platform ${{ steps.settings.outputs.platform }} \ - --file ${{ steps.settings.outputs.dockerfile }} \ + --platform linux/arm64 \ + --file Dockerfile \ --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.settings.outputs.tag }} \ --push \ --build-arg BUILD_CONFIGURATION=Testing \ @@ -184,8 +166,9 @@ jobs: . fi - deploy-production: - name: Deploy to Production (ARM - OCI) + # ─── Deploy: bcards.site ────────────────────────────────────────────────── + deploy-bcards: + name: Deploy bcards.site runs-on: ubuntu-latest needs: [build-and-push] if: github.ref_name == 'main' @@ -195,32 +178,15 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Create Production Configuration + - name: Generate appsettings for bcards run: | - echo "🔧 Creating appsettings.Production.json with environment variables..." - - # Cria o arquivo de configuração para produção - cat > appsettings.Production.json << 'CONFIG_EOF' + cat > appsettings.bcards.json << 'CONFIG_EOF' { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", - "Microsoft.EntityFrameworkCore": "Warning", - "BCards": "Information", - "BCards.Web.Services.GridFSImageStorage": "Debug" - }, - "Console": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Information" - } - }, - "File": { - "Path": "/app/logs/bcards-{Date}.log", - "LogLevel": { - "Default": "Information" - } + "BCards": "Information" } }, "AllowedHosts": "*", @@ -246,249 +212,396 @@ jobs: "FromName": "${{ vars.SENDGRID_FROM_NAME || 'Ricardo Carneiro' }}" }, "Plans": { - "Basic": { - "Name": "Básico", - "PriceId": "price_1RycPaBMIadsOxJVKioZZofK", - "Price": 5.90, - "MaxPages": 3, - "MaxLinks": 8, - "AllowPremiumThemes": false, - "AllowProductLinks": false, - "AllowAnalytics": true, - "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ], - "Interval": "month" - }, - "Professional": { - "Name": "Profissional", - "PriceId": "price_1RycQmBMIadsOxJVGqjVMaOj", - "Price": 12.90, - "MaxPages": 5, - "MaxLinks": 20, - "AllowPremiumThemes": false, - "AllowProductLinks": false, - "AllowAnalytics": true, - "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ], - "Interval": "month" - }, - "Premium": { - "Name": "Premium", - "PriceId": "price_1RycRUBMIadsOxJVkxGOh3uu", - "Price": 19.90, - "MaxPages": 15, - "MaxLinks": -1, - "AllowPremiumThemes": true, - "AllowProductLinks": false, - "AllowAnalytics": true, - "SpecialModeration": false, - "Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados" ], - "Interval": "month" - }, - "PremiumAffiliate": { - "Name": "Premium+Afiliados", - "PriceId": "price_1RycTaBMIadsOxJVeDLseXQq", - "Price": 29.90, - "MaxPages": 15, - "MaxLinks": -1, - "AllowPremiumThemes": true, - "AllowProductLinks": true, - "AllowAnalytics": true, - "SpecialModeration": true, - "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados" ], - "Interval": "month" - }, - "BasicYearly": { - "Name": "Básico Anual", - "PriceId": "price_1RycWgBMIadsOxJVGdtEeoMS", - "Price": 59.00, - "MaxPages": 3, - "MaxLinks": 8, - "AllowPremiumThemes": false, - "AllowProductLinks": false, - "AllowAnalytics": true, - "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ], - "Interval": "year" - }, - "ProfessionalYearly": { - "Name": "Profissional Anual", - "PriceId": "price_1RycXdBMIadsOxJV5cNX7dHm", - "Price": 129.00, - "MaxPages": 5, - "MaxLinks": 20, - "AllowPremiumThemes": false, - "AllowProductLinks": false, - "AllowAnalytics": true, - "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ], - "Interval": "year" - }, - "PremiumYearly": { - "Name": "Premium Anual", - "PriceId": "price_1RycYnBMIadsOxJVPdKmzy4m", - "Price": 199.00, - "MaxPages": 15, - "MaxLinks": -1, - "AllowPremiumThemes": true, - "AllowProductLinks": false, - "AllowAnalytics": true, - "SpecialModeration": false, - "Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados", "Economize R$ 39,80 (2 meses grátis)" ], - "Interval": "year" - }, - "PremiumAffiliateYearly": { - "Name": "Premium+Afiliados Anual", - "PriceId": "price_1RycaEBMIadsOxJVEhsdB2Y1", - "Price": 299.00, - "MaxPages": 15, - "MaxLinks": -1, - "AllowPremiumThemes": true, - "AllowProductLinks": true, - "AllowAnalytics": true, - "SpecialModeration": true, - "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Economize R$ 59,80 (2 meses grátis)" ], - "Interval": "year" - } + "Basic": { "Name": "Básico", "PriceId": "price_1RycPaBMIadsOxJVKioZZofK", "Price": 12.90, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ], "Interval": "month" }, + "Professional": { "Name": "Profissional", "PriceId": "price_1RycQmBMIadsOxJVGqjVMaOj", "Price": 25.90, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ], "Interval": "month" }, + "Premium": { "Name": "Premium", "PriceId": "price_1RycRUBMIadsOxJVkxGOh3uu", "Price": 29.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados", "Upload de PDFs (até 5 arquivos)" ], "Interval": "month" }, + "PremiumAffiliate": { "Name": "Premium+Afiliados", "PriceId": "price_1RycTaBMIadsOxJVeDLseXQq", "Price": 34.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Upload de PDFs (até 10 arquivos)" ], "Interval": "month" }, + "BasicYearly": { "Name": "Básico Anual", "PriceId": "price_1RycWgBMIadsOxJVGdtEeoMS", "Price": 129.00, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ], "Interval": "year" }, + "ProfessionalYearly": { "Name": "Profissional Anual", "PriceId": "price_1RycXdBMIadsOxJV5cNX7dHm", "Price": 259.00, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ], "Interval": "year" }, + "PremiumYearly": { "Name": "Premium Anual", "PriceId": "price_1RycYnBMIadsOxJVPdKmzy4m", "Price": 299.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "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)" ], "Interval": "year" }, + "PremiumAffiliateYearly": { "Name": "Premium+Afiliados Anual", "PriceId": "price_1RycaEBMIadsOxJVEhsdB2Y1", "Price": 349.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "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)" ], "Interval": "year" } }, "Moderation": { - "PriorityTimeframes": { - "Trial": "7.00:00:00", - "Basic": "7.00:00:00", - "Professional": "3.00:00:00", - "Premium": "1.00:00:00" - }, + "PriorityTimeframes": { "Trial": "7.00:00:00", "Basic": "7.00:00:00", "Professional": "3.00:00:00", "Premium": "1.00:00:00" }, "MaxAttempts": 3, "ModeratorEmail": "${{ vars.MODERATOR_EMAIL || 'ricardo.carneiro@jobmaker.com.br' }}", - "ModeratorEmails": [ - "${{ vars.MODERATOR_EMAIL_1 || 'rrcgoncalves@gmail.com' }}", - "${{ vars.MODERATOR_EMAIL_2 || 'rirocarneiro@gmail.com' }}" - ] + "ModeratorEmails": [ "${{ vars.MODERATOR_EMAIL_1 || 'rrcgoncalves@gmail.com' }}", "${{ vars.MODERATOR_EMAIL_2 || 'rirocarneiro@gmail.com' }}" ] }, "MongoDb": { "ConnectionString": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/BCardsDB?replicaSet=rs0&authSource=admin", - "DatabaseName": "BCardsDB", - "MaxConnectionPoolSize": 100, - "ConnectTimeout": "30s", - "ServerSelectionTimeout": "30s", - "SocketTimeout": "30s" + "DatabaseName": "BCardsDB" }, "BaseUrl": "https://bcards.site", - "Environment": { - "Name": "Production", - "IsStagingEnvironment": false, - "AllowTestData": false, - "EnableDetailedErrors": false + "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." }, - "Performance": { - "EnableCaching": true, - "CacheExpirationMinutes": 30, - "EnableCompression": true, - "EnableResponseCaching": true - }, - "Security": { - "EnableHttpsRedirection": true, - "EnableHsts": true, - "RequireHttpsMetadata": true - }, - "HealthChecks": { - "Enabled": true, - "Endpoints": { - "Health": "/health", - "Ready": "/ready", - "Live": "/live" - }, - "MongoDb": { - "Enabled": true, - "Timeout": "10s" - } - }, - "Features": { - "EnablePreviewMode": true, - "EnableModerationWorkflow": true, - "EnableAnalytics": true, - "EnableFileUploads": true, - "MaxFileUploadSize": "5MB" + "Support": { + "TelegramUrl": "https://t.me/jobmakerbr", + "FormspreeUrl": "https://formspree.io/f/xpwynqpj", + "EnableTelegramForPlans": [ "Premium", "PremiumAffiliate" ], + "EnableFormForPlans": [ "Basic", "Professional", "Premium", "PremiumAffiliate" ], + "EnableRatingForAllUsers": true }, "Serilog": { - "OpenSearchUrl": "${{ vars.OPENSEARCH_URL || 'http://localhost:9201' }}" + "OpenSearchUrl": "${{ vars.OPENSEARCH_URL || 'http://141.148.162.114:19201' }}" } } CONFIG_EOF - - echo "✅ Configuration file created!" - echo "🔍 File content (sensitive data masked):" - cat appsettings.Production.json | sed 's/"[^"]*_[0-9A-Za-z_]*"/"***MASKED***"/g' - - - name: Deploy to Production Swarm - run: | - echo "🚀 Deploying to production Docker Swarm (ARM64)..." + echo "✅ appsettings.bcards.json gerado" - # Configura SSH + - name: Deploy bcards stack + run: | mkdir -p ~/.ssh echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa + ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null + ssh-keyscan -H ${{ env.SWARM_WORKER }} >> ~/.ssh/known_hosts 2>/dev/null - # Adiciona hosts conhecidos - ssh-keyscan -H 141.148.162.114 >> ~/.ssh/known_hosts - ssh-keyscan -H 129.146.116.218 >> ~/.ssh/known_hosts + # ── Sync Content to both nodes ────────────────────────────────────── + for NODE in ${{ env.SWARM_MANAGER }} ${{ env.SWARM_WORKER }}; do + echo "📂 Syncing content to $NODE..." + ssh -o StrictHostKeyChecking=no ubuntu@$NODE 'mkdir -p /opt/bcards-content/bcards' + scp -o StrictHostKeyChecking=no -r src/BCards.Web/Content/Tenants/bcards/. ubuntu@$NODE:/opt/bcards-content/bcards/ + done - # Testa a chave SSH - ssh-add ~/.ssh/id_rsa 2>/dev/null || echo "SSH key loaded" + # ── Deploy stack on manager ───────────────────────────────────────── + scp -o StrictHostKeyChecking=no appsettings.bcards.json ubuntu@${{ env.SWARM_MANAGER }}:/tmp/ + scp -o StrictHostKeyChecking=no deploy/docker-stack-bcards.yml ubuntu@${{ env.SWARM_MANAGER }}:/tmp/ - # Upload configuration and stack file to swarm manager - echo "📤 Uploading files to Swarm manager..." - scp -o StrictHostKeyChecking=no appsettings.Production.json ubuntu@141.148.162.114:/tmp/ - scp -o StrictHostKeyChecking=no deploy/docker-stack.yml ubuntu@141.148.162.114:/tmp/ - - # Deploy to Docker Swarm - ssh -o StrictHostKeyChecking=no ubuntu@141.148.162.114 << 'EOF' + ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} << 'EOF' set -e + echo "🔄 Updating bcards stack..." - echo "🔄 Updating Docker Swarm stack..." - - # Update Docker config with new appsettings - echo "📝 Updating bcards-appsettings config..." - - # Remove old config (will fail if in use, that's ok - swarm will use it until update) - docker config rm bcards-appsettings 2>/dev/null || echo "Config in use or doesn't exist, will create new one" - - # Create new config with timestamp to force update + docker config rm bcards-appsettings 2>/dev/null || true CONFIG_NAME="bcards-appsettings-$(date +%s)" - docker config create ${CONFIG_NAME} /tmp/appsettings.Production.json + docker config create ${CONFIG_NAME} /tmp/appsettings.bcards.json - # Update stack file to use new config name - sed "s/bcards-appsettings/${CONFIG_NAME}/g" /tmp/docker-stack.yml > /tmp/docker-stack-updated.yml + sed "s/bcards-appsettings/${CONFIG_NAME}/g" /tmp/docker-stack-bcards.yml > /tmp/docker-stack-bcards-final.yml - echo "🐳 Deploying stack to Swarm (rolling update, zero downtime)..." - docker stack deploy -c /tmp/docker-stack-updated.yml bcards --with-registry-auth + docker stack deploy -c /tmp/docker-stack-bcards-final.yml bcards --with-registry-auth - echo "⏳ Waiting for service to update..." - sleep 10 - - # Show service status - docker service ls --filter name=bcards_bcards-app - docker service ps bcards_bcards-app --no-trunc --filter "desired-state=running" | head -10 - - echo "🧹 Cleaning up standalone containers if they exist..." - docker stop bcards-prod 2>/dev/null || echo "No standalone container to stop" - docker rm bcards-prod 2>/dev/null || echo "No standalone container to remove" - - # Clean up temp files - rm -f /tmp/appsettings.Production.json /tmp/docker-stack.yml /tmp/docker-stack-updated.yml - - echo "✅ Swarm stack updated successfully!" + rm -f /tmp/appsettings.bcards.json /tmp/docker-stack-bcards.yml /tmp/docker-stack-bcards-final.yml + echo "✅ bcards stack atualizado!" EOF - - name: Health Check Production + - name: Health Check bcards run: | - echo "🏥 Verificando saúde dos servidores de produção..." sleep 30 - - # Verifica Servidor 1 - echo "Verificando Servidor 1 (ARM)..." - ssh -o StrictHostKeyChecking=no ubuntu@141.148.162.114 'curl -f http://localhost:8080/health || echo "⚠️ Servidor 1 pode não estar respondendo"' - - # Verifica Servidor 2 - echo "Verificando Servidor 2 (ARM)..." - ssh -o StrictHostKeyChecking=no ubuntu@129.146.116.218 'curl -f http://localhost:8080/health || echo "⚠️ Servidor 2 pode não estar respondendo"' + ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} \ + 'curl -sf http://localhost:8080/health && echo "✅ bcards healthy" || echo "⚠️ bcards health check failed"' + # ─── Deploy: spicylinks.site ────────────────────────────────────────────── + deploy-spicylinks: + name: Deploy spicylinks.site + runs-on: ubuntu-latest + needs: [build-and-push] + if: github.ref_name == 'main' + environment: production + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Generate appsettings for spicylinks + run: | + cat > appsettings.spicylinks.json << 'CONFIG_EOF' + { + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "BCards": "Information" + } + }, + "AllowedHosts": "*", + "Stripe": { + "PublishableKey": "${{ vars.STRIPE_PUBLISHABLE_KEY }}", + "SecretKey": "${{ secrets.STRIPE_SECRET_KEY }}", + "WebhookSecret": "${{ secrets.STRIPE_WEBHOOK_SECRET }}", + "Environment": "${{ vars.STRIPE_ENVIRONMENT || 'test' }}" + }, + "Authentication": { + "Google": { + "ClientId": "${{ vars.GOOGLE_CLIENT_ID }}", + "ClientSecret": "${{ secrets.GOOGLE_CLIENT_SECRET }}" + }, + "Microsoft": { + "ClientId": "${{ vars.MICROSOFT_CLIENT_ID }}", + "ClientSecret": "${{ secrets.MICROSOFT_CLIENT_SECRET }}" + } + }, + "SendGrid": { + "ApiKey": "${{ secrets.SENDGRID_API_KEY }}", + "FromEmail": "${{ vars.SPICYLINKS_FROM_EMAIL || 'noreply@spicylinks.site' }}", + "FromName": "${{ vars.SPICYLINKS_FROM_NAME || 'SpicyLinks' }}" + }, + "Plans": { + "Basic": { "Name": "Básico", "PriceId": "price_1RycPaBMIadsOxJVKioZZofK", "Price": 12.90, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ], "Interval": "month" }, + "Professional": { "Name": "Profissional", "PriceId": "price_1RycQmBMIadsOxJVGqjVMaOj", "Price": 25.90, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ], "Interval": "month" }, + "Premium": { "Name": "Premium", "PriceId": "price_1RycRUBMIadsOxJVkxGOh3uu", "Price": 29.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados" ], "Interval": "month" }, + "PremiumAffiliate": { "Name": "Premium+Afiliados", "PriceId": "price_1RycTaBMIadsOxJVeDLseXQq", "Price": 34.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados" ], "Interval": "month" }, + "BasicYearly": { "Name": "Básico Anual", "PriceId": "price_1RycWgBMIadsOxJVGdtEeoMS", "Price": 129.00, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ], "Interval": "year" }, + "ProfessionalYearly": { "Name": "Profissional Anual", "PriceId": "price_1RycXdBMIadsOxJV5cNX7dHm", "Price": 259.00, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ], "Interval": "year" }, + "PremiumYearly": { "Name": "Premium Anual", "PriceId": "price_1RycYnBMIadsOxJVPdKmzy4m", "Price": 299.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas", "Suporte prioritário", "Links ilimitados", "Economize R$ 39,80 (2 meses grátis)" ], "Interval": "year" }, + "PremiumAffiliateYearly": { "Name": "Premium+Afiliados Anual", "PriceId": "price_1RycaEBMIadsOxJVEhsdB2Y1", "Price": 349.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Economize R$ 59,80 (2 meses grátis)" ], "Interval": "year" } + }, + "Moderation": { + "PriorityTimeframes": { "Trial": "7.00:00:00", "Basic": "7.00:00:00", "Professional": "3.00:00:00", "Premium": "1.00:00:00" }, + "MaxAttempts": 3, + "ModeratorEmail": "${{ vars.MODERATOR_EMAIL || 'ricardo.carneiro@jobmaker.com.br' }}", + "ModeratorEmails": [ "${{ vars.MODERATOR_EMAIL_1 || 'rrcgoncalves@gmail.com' }}", "${{ vars.MODERATOR_EMAIL_2 || 'rirocarneiro@gmail.com' }}" ] + }, + "MongoDb": { + "ConnectionString": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/SpicyLinksDB?replicaSet=rs0&authSource=admin", + "DatabaseName": "SpicyLinksDB" + }, + "BaseUrl": "https://spicylinks.site", + "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." + }, + "Support": { + "TelegramUrl": "https://t.me/jobmakerbr", + "FormspreeUrl": "https://formspree.io/f/xpwynqpj", + "EnableTelegramForPlans": [ "Premium", "PremiumAffiliate" ], + "EnableFormForPlans": [ "Basic", "Professional", "Premium", "PremiumAffiliate" ], + "EnableRatingForAllUsers": true + }, + "Serilog": { + "OpenSearchUrl": "${{ vars.OPENSEARCH_URL || 'http://141.148.162.114:19201' }}" + } + } + CONFIG_EOF + echo "✅ appsettings.spicylinks.json gerado" + + - name: Deploy spicylinks stack + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null + ssh-keyscan -H ${{ env.SWARM_WORKER }} >> ~/.ssh/known_hosts 2>/dev/null + + # ── Sync Content to both nodes ────────────────────────────────────── + for NODE in ${{ env.SWARM_MANAGER }} ${{ env.SWARM_WORKER }}; do + echo "📂 Syncing spicylinks content to $NODE..." + ssh -o StrictHostKeyChecking=no ubuntu@$NODE 'mkdir -p /opt/bcards-content/spicylinks' + scp -o StrictHostKeyChecking=no -r src/BCards.Web/Content/Tenants/spicylinks/. ubuntu@$NODE:/opt/bcards-content/spicylinks/ + done + + # ── Deploy stack on manager ───────────────────────────────────────── + scp -o StrictHostKeyChecking=no appsettings.spicylinks.json ubuntu@${{ env.SWARM_MANAGER }}:/tmp/ + scp -o StrictHostKeyChecking=no deploy/docker-stack-spicylinks.yml ubuntu@${{ env.SWARM_MANAGER }}:/tmp/ + + ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} << 'EOF' + set -e + echo "🔄 Updating spicylinks stack..." + + docker config rm spicylinks-appsettings 2>/dev/null || true + CONFIG_NAME="spicylinks-appsettings-$(date +%s)" + docker config create ${CONFIG_NAME} /tmp/appsettings.spicylinks.json + + sed "s/spicylinks-appsettings/${CONFIG_NAME}/g" /tmp/docker-stack-spicylinks.yml > /tmp/docker-stack-spicylinks-final.yml + + docker stack deploy -c /tmp/docker-stack-spicylinks-final.yml spicylinks --with-registry-auth + + rm -f /tmp/appsettings.spicylinks.json /tmp/docker-stack-spicylinks.yml /tmp/docker-stack-spicylinks-final.yml + echo "✅ spicylinks stack atualizado!" + EOF + + - name: Health Check spicylinks + run: | + sleep 30 + ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} \ + 'curl -sf http://localhost:8082/health && echo "✅ spicylinks healthy" || echo "⚠️ spicylinks health check failed"' + + # ─── Deploy: luslinks.site ──────────────────────────────────────────────── + deploy-luslinks: + name: Deploy luslinks.site + runs-on: ubuntu-latest + needs: [build-and-push] + if: github.ref_name == 'main' + environment: production + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Generate appsettings for luslinks + run: | + cat > appsettings.luslinks.json << 'CONFIG_EOF' + { + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "BCards": "Information" + } + }, + "AllowedHosts": "*", + "Stripe": { + "PublishableKey": "${{ vars.STRIPE_PUBLISHABLE_KEY }}", + "SecretKey": "${{ secrets.STRIPE_SECRET_KEY }}", + "WebhookSecret": "${{ secrets.STRIPE_WEBHOOK_SECRET }}", + "Environment": "${{ vars.STRIPE_ENVIRONMENT || 'test' }}" + }, + "Authentication": { + "Google": { + "ClientId": "${{ vars.GOOGLE_CLIENT_ID }}", + "ClientSecret": "${{ secrets.GOOGLE_CLIENT_SECRET }}" + }, + "Microsoft": { + "ClientId": "${{ vars.MICROSOFT_CLIENT_ID }}", + "ClientSecret": "${{ secrets.MICROSOFT_CLIENT_SECRET }}" + } + }, + "SendGrid": { + "ApiKey": "${{ secrets.SENDGRID_API_KEY }}", + "FromEmail": "${{ vars.LUSLINKS_FROM_EMAIL || 'noreply@luslinks.site' }}", + "FromName": "${{ vars.LUSLINKS_FROM_NAME || 'LusLinks' }}" + }, + "Plans": { + "Basic": { "Name": "Básico", "PriceId": "price_1RycPaBMIadsOxJVKioZZofK", "Price": 12.90, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ], "Interval": "month" }, + "Professional": { "Name": "Profissional", "PriceId": "price_1RycQmBMIadsOxJVGqjVMaOj", "Price": 25.90, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ], "Interval": "month" }, + "Premium": { "Name": "Premium", "PriceId": "price_1RycRUBMIadsOxJVkxGOh3uu", "Price": 29.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas", "Suporte prioritário", "Links ilimitados" ], "Interval": "month" }, + "PremiumAffiliate": { "Name": "Premium+Afiliados", "PriceId": "price_1RycTaBMIadsOxJVeDLseXQq", "Price": 34.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados" ], "Interval": "month" }, + "BasicYearly": { "Name": "Básico Anual", "PriceId": "price_1RycWgBMIadsOxJVGdtEeoMS", "Price": 129.00, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ], "Interval": "year" }, + "ProfessionalYearly": { "Name": "Profissional Anual", "PriceId": "price_1RycXdBMIadsOxJV5cNX7dHm", "Price": 259.00, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ], "Interval": "year" }, + "PremiumYearly": { "Name": "Premium Anual", "PriceId": "price_1RycYnBMIadsOxJVPdKmzy4m", "Price": 299.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas", "Suporte prioritário", "Links ilimitados", "Economize R$ 39,80 (2 meses grátis)" ], "Interval": "year" }, + "PremiumAffiliateYearly": { "Name": "Premium+Afiliados Anual", "PriceId": "price_1RycaEBMIadsOxJVEhsdB2Y1", "Price": 349.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Economize R$ 59,80 (2 meses grátis)" ], "Interval": "year" } + }, + "Moderation": { + "PriorityTimeframes": { "Trial": "7.00:00:00", "Basic": "7.00:00:00", "Professional": "3.00:00:00", "Premium": "1.00:00:00" }, + "MaxAttempts": 3, + "ModeratorEmail": "${{ vars.MODERATOR_EMAIL || 'ricardo.carneiro@jobmaker.com.br' }}", + "ModeratorEmails": [ "${{ vars.MODERATOR_EMAIL_1 || 'rrcgoncalves@gmail.com' }}", "${{ vars.MODERATOR_EMAIL_2 || 'rirocarneiro@gmail.com' }}" ] + }, + "MongoDb": { + "ConnectionString": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/LusLinksDB?replicaSet=rs0&authSource=admin", + "DatabaseName": "LusLinksDB" + }, + "BaseUrl": "https://luslinks.site", + "Tenant": { + "SiteName": "LusLinks", + "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@luslinks.site", + "ContentFolder": "luslinks", + "AgeGated": false, + "UrlExample": "luslinks.site/pastor/seu-nome", + "DpoEmail": "dpo@luslinks.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 LusLinks 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." + }, + "Support": { + "TelegramUrl": "https://t.me/jobmakerbr", + "FormspreeUrl": "https://formspree.io/f/xpwynqpj", + "EnableTelegramForPlans": [ "Premium", "PremiumAffiliate" ], + "EnableFormForPlans": [ "Basic", "Professional", "Premium", "PremiumAffiliate" ], + "EnableRatingForAllUsers": true + }, + "Serilog": { + "OpenSearchUrl": "${{ vars.OPENSEARCH_URL || 'http://141.148.162.114:19201' }}" + } + } + CONFIG_EOF + echo "✅ appsettings.luslinks.json gerado" + + - name: Deploy luslinks stack + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null + ssh-keyscan -H ${{ env.SWARM_WORKER }} >> ~/.ssh/known_hosts 2>/dev/null + + # ── Sync Content to both nodes ────────────────────────────────────── + for NODE in ${{ env.SWARM_MANAGER }} ${{ env.SWARM_WORKER }}; do + echo "📂 Syncing luslinks content to $NODE..." + ssh -o StrictHostKeyChecking=no ubuntu@$NODE 'mkdir -p /opt/bcards-content/luslinks' + scp -o StrictHostKeyChecking=no -r src/BCards.Web/Content/Tenants/luslinks/. ubuntu@$NODE:/opt/bcards-content/luslinks/ + done + + # ── Deploy stack on manager ───────────────────────────────────────── + scp -o StrictHostKeyChecking=no appsettings.luslinks.json ubuntu@${{ env.SWARM_MANAGER }}:/tmp/ + scp -o StrictHostKeyChecking=no deploy/docker-stack-luslinks.yml ubuntu@${{ env.SWARM_MANAGER }}:/tmp/ + + ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} << 'EOF' + set -e + echo "🔄 Updating luslinks stack..." + + docker config rm luslinks-appsettings 2>/dev/null || true + CONFIG_NAME="luslinks-appsettings-$(date +%s)" + docker config create ${CONFIG_NAME} /tmp/appsettings.luslinks.json + + sed "s/luslinks-appsettings/${CONFIG_NAME}/g" /tmp/docker-stack-luslinks.yml > /tmp/docker-stack-luslinks-final.yml + + docker stack deploy -c /tmp/docker-stack-luslinks-final.yml luslinks --with-registry-auth + + rm -f /tmp/appsettings.luslinks.json /tmp/docker-stack-luslinks.yml /tmp/docker-stack-luslinks-final.yml + echo "✅ luslinks stack atualizado!" + EOF + + - name: Health Check luslinks + run: | + sleep 30 + ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} \ + 'curl -sf http://localhost:8083/health && echo "✅ luslinks healthy" || echo "⚠️ luslinks health check failed"' + + # ─── Release branch deploy (test swarm) ─────────────────────────────────── deploy-test: name: Deploy to Release Swarm (ARM) runs-on: [self-hosted, arm64, bcards] @@ -504,7 +617,6 @@ jobs: run: | BRANCH_NAME="${{ github.ref_name }}" VERSION_RAW=${BRANCH_NAME#Release/} - # Only remove V/v if it's at the start and followed by a number (like v1.0.0) VERSION=$(echo "$VERSION_RAW" | sed 's/^[Vv]\([0-9]\)/\1/') [ -z "$VERSION" ] && VERSION="0.0.1" echo "version=$VERSION" >> $GITHUB_OUTPUT @@ -514,17 +626,11 @@ jobs: run: | mkdir -p artifacts BCARDS_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }} - - # Replace ${BCARDS_IMAGE} with actual image name using sed sed "s|\${BCARDS_IMAGE}|${BCARDS_IMAGE}|g" deploy/docker-stack.release.yml > artifacts/docker-stack.release.yml - echo "🔧 Generated manifest with image: ${BCARDS_IMAGE}" - echo "📄 Manifest content:" - head -10 artifacts/docker-stack.release.yml - name: Deploy to release swarm run: | - echo "🚀 Deploying release stack to Orange Pi swarm..." docker stack deploy -c artifacts/docker-stack.release.yml bcards-release - name: Await release service readiness @@ -541,54 +647,59 @@ jobs: sleep 5 ATTEMPTS=$((ATTEMPTS-1)) done - if [ "$REPLICAS" != "1/1" ]; then echo "❌ Serviço não atingiu 1/1 réplica" docker service ps bcards-release_bcards-release exit 1 fi - docker service ps bcards-release_bcards-release + # ─── Cleanup ────────────────────────────────────────────────────────────── cleanup: name: Cleanup Old Resources runs-on: ubuntu-latest - needs: [deploy-production, deploy-test] - if: always() && (needs.deploy-production.result == 'success' || needs.deploy-test.result == 'success') - + needs: [deploy-bcards, deploy-spicylinks, deploy-luslinks, deploy-test] + if: always() && (needs.deploy-bcards.result == 'success' || needs.deploy-spicylinks.result == 'success' || needs.deploy-luslinks.result == 'success' || needs.deploy-test.result == 'success') + steps: - name: Cleanup containers and images run: | echo "🧹 Limpando recursos antigos..." - + if [ "${{ github.ref_name }}" = "main" ]; then mkdir -p ~/.ssh echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa - ssh-keyscan -H 141.148.162.114 >> ~/.ssh/known_hosts - ssh-keyscan -H 129.146.116.218 >> ~/.ssh/known_hosts - ssh-add ~/.ssh/id_rsa 2>/dev/null || echo "SSH key loaded" + ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null + ssh-keyscan -H ${{ env.SWARM_WORKER }} >> ~/.ssh/known_hosts 2>/dev/null - for server in 141.148.162.114 129.146.116.218; do - echo "🧹 Limpando servidor $server..." - ssh -o StrictHostKeyChecking=no ubuntu@$server << 'EOF' + for SERVER in ${{ env.SWARM_MANAGER }} ${{ env.SWARM_WORKER }}; do + echo "🧹 Limpando $SERVER..." + ssh -o StrictHostKeyChecking=no ubuntu@$SERVER << 'EOF' docker container prune -f docker image prune -f docker network prune -f + # Remove stale swarm configs (keep last 3 per tenant) + for TENANT in bcards spicylinks luslinks; do + docker config ls --filter "name=${TENANT}-appsettings" --format "{{.ID}} {{.Name}}" \ + | sort -k2 | head -n -3 | awk '{print $1}' \ + | xargs -r docker config rm 2>/dev/null || true + done EOF done else - echo "ℹ️ Release branch: limpeza remota ignorada (Swarm gerencia recursos)." + echo "ℹ️ Release branch: limpeza remota ignorada." fi - + echo "✅ Limpeza concluída!" + # ─── Summary ────────────────────────────────────────────────────────────── deployment-summary: name: Deployment Summary runs-on: ubuntu-latest - needs: [deploy-production, deploy-test] + needs: [deploy-bcards, deploy-spicylinks, deploy-luslinks, deploy-test] if: always() - + steps: - name: Summary run: | @@ -597,18 +708,19 @@ jobs: echo "🎯 Branch: ${{ github.ref_name }}" echo "🔑 Commit: ${{ github.sha }}" echo "🏗️ Registry: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" - + if [ "${{ github.ref_name }}" = "main" ]; then - echo "🌍 Environment: Production (Swarm ARM)" - echo "🖥️ Servers: 141.148.162.114, 129.146.116.218" - echo "📦 Tag: latest" - echo "🔗 Status: ${{ needs.deploy-production.result }}" + echo "🌍 Environment: Production (Multi-Tenant)" + echo "🖥️ Manager: ${{ env.SWARM_MANAGER }}" + echo "🖥️ Worker: ${{ env.SWARM_WORKER }}" + echo "" + echo " bcards.site → :8080 [${{ needs.deploy-bcards.result }}]" + echo " spicylinks.site → :8082 [${{ needs.deploy-spicylinks.result }}]" + echo " luslinks.site → :8083 [${{ needs.deploy-luslinks.result }}]" else - echo "🌍 Environment: Release (Swarm ARM)" - echo "🖥️ Servers: 141.148.162.114, 129.146.116.218" - echo "📦 Branch Tag: ${{ github.ref_name }}" + echo "🌍 Environment: Release (Test Swarm)" echo "🔗 Status: ${{ needs.deploy-test.result }}" fi - + echo "====================" echo "✅ Pipeline completed!" diff --git a/BCards.sln b/BCards.sln index af5c4e7..bae87da 100644 --- a/BCards.sln +++ b/BCards.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 +# Visual Studio Version 18 +VisualStudioVersion = 18.2.11415.280 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BCards.Web", "src\BCards.Web\BCards.Web.csproj", "{2E8F4B5C-9B3A-4F8E-8C7D-1A2B3C4D5E6F}" EndProject @@ -21,6 +21,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}" ProjectSection(SolutionItems) = preProject Conexoes.txt = Conexoes.txt + deploy\docker-stack.release.yml = deploy\docker-stack.release.yml README.md = README.md EndProjectSection EndProject diff --git a/deploy/docker-stack-bcards.yml b/deploy/docker-stack-bcards.yml new file mode 100644 index 0000000..d7116eb --- /dev/null +++ b/deploy/docker-stack-bcards.yml @@ -0,0 +1,57 @@ +version: '3.8' + +configs: + bcards-appsettings: + external: true + +services: + 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 diff --git a/deploy/docker-stack-luslinks.yml b/deploy/docker-stack-luslinks.yml new file mode 100644 index 0000000..4a39516 --- /dev/null +++ b/deploy/docker-stack-luslinks.yml @@ -0,0 +1,57 @@ +version: '3.8' + +configs: + luslinks-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: luslinks-appsettings + target: /app/appsettings.Production.json + mode: 0444 + volumes: + - type: bind + source: /opt/bcards-content/luslinks + target: /app/Content/Tenants/luslinks + 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/LusLinksDB?replicaSet=rs0&authSource=admin + MongoDb__DatabaseName: LusLinksDB + 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 diff --git a/deploy/docker-stack-spicylinks.yml b/deploy/docker-stack-spicylinks.yml new file mode 100644 index 0000000..588f19a --- /dev/null +++ b/deploy/docker-stack-spicylinks.yml @@ -0,0 +1,57 @@ +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 diff --git a/deploy/nginx/tenants.conf b/deploy/nginx/tenants.conf new file mode 100644 index 0000000..348f445 --- /dev/null +++ b/deploy/nginx/tenants.conf @@ -0,0 +1,116 @@ +# 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 luslinks.site -d www.luslinks.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; + } +} + +# ─── luslinks.site → :8083 ───────────────────────────────────────────────── + +upstream luslinks { + server 127.0.0.1:8083; + keepalive 32; +} + +server { + listen 80; + server_name luslinks.site www.luslinks.site; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + server_name luslinks.site www.luslinks.site; + + ssl_certificate /etc/letsencrypt/live/luslinks.site/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/luslinks.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://luslinks; + 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; + } +} diff --git a/src/BCards.Web/Areas/Tutoriais/Services/MarkdownService.cs b/src/BCards.Web/Areas/Tutoriais/Services/MarkdownService.cs index d01e1c0..6cb7982 100644 --- a/src/BCards.Web/Areas/Tutoriais/Services/MarkdownService.cs +++ b/src/BCards.Web/Areas/Tutoriais/Services/MarkdownService.cs @@ -1,7 +1,9 @@ using BCards.Web.Areas.Tutoriais.Models; using BCards.Web.Areas.Tutoriais.Models.ViewModels; +using BCards.Web.Configuration; using Markdig; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -18,11 +20,13 @@ public class MarkdownService : IMarkdownService public MarkdownService( IMemoryCache cache, ILogger logger, - IWebHostEnvironment environment) + IWebHostEnvironment environment, + IOptions tenantSettings) { _cache = cache; _logger = logger; - _contentBasePath = Path.Combine(environment.ContentRootPath, "Content"); + _contentBasePath = Path.Combine( + environment.ContentRootPath, "Content", "Tenants", tenantSettings.Value.ContentFolder); // Pipeline Markdig com extensões avançadas _markdownPipeline = new MarkdownPipelineBuilder() diff --git a/src/BCards.Web/Configuration/LinkTypeConfig.cs b/src/BCards.Web/Configuration/LinkTypeConfig.cs new file mode 100644 index 0000000..b5fa778 --- /dev/null +++ b/src/BCards.Web/Configuration/LinkTypeConfig.cs @@ -0,0 +1,12 @@ +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"; +} diff --git a/src/BCards.Web/Configuration/TenantFeature.cs b/src/BCards.Web/Configuration/TenantFeature.cs new file mode 100644 index 0000000..2a644bd --- /dev/null +++ b/src/BCards.Web/Configuration/TenantFeature.cs @@ -0,0 +1,8 @@ +namespace BCards.Web.Configuration; + +public class TenantFeature +{ + public string Icon { get; set; } = ""; + public string Title { get; set; } = ""; + public string Description { get; set; } = ""; +} diff --git a/src/BCards.Web/Configuration/TenantSettings.cs b/src/BCards.Web/Configuration/TenantSettings.cs new file mode 100644 index 0000000..3f7ae1d --- /dev/null +++ b/src/BCards.Web/Configuration/TenantSettings.cs @@ -0,0 +1,32 @@ +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 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 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."; +} diff --git a/src/BCards.Web/Content/Tenants/bcards/Artigos/bcards-para-corretores-de-imoveis.pt-BR.md b/src/BCards.Web/Content/Tenants/bcards/Artigos/bcards-para-corretores-de-imoveis.pt-BR.md new file mode 100644 index 0000000..e2628d9 --- /dev/null +++ b/src/BCards.Web/Content/Tenants/bcards/Artigos/bcards-para-corretores-de-imoveis.pt-BR.md @@ -0,0 +1,255 @@ +--- +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 diff --git a/src/BCards.Web/Content/Artigos/bcards-vs-linktree.pt-BR.md b/src/BCards.Web/Content/Tenants/bcards/Artigos/bcards-vs-linktree.pt-BR.md similarity index 100% rename from src/BCards.Web/Content/Artigos/bcards-vs-linktree.pt-BR.md rename to src/BCards.Web/Content/Tenants/bcards/Artigos/bcards-vs-linktree.pt-BR.md diff --git a/src/BCards.Web/Content/Artigos/transformacao-digital-pequenos-negocios.pt-BR.md b/src/BCards.Web/Content/Tenants/bcards/Artigos/transformacao-digital-pequenos-negocios.pt-BR.md similarity index 100% rename from src/BCards.Web/Content/Artigos/transformacao-digital-pequenos-negocios.pt-BR.md rename to src/BCards.Web/Content/Tenants/bcards/Artigos/transformacao-digital-pequenos-negocios.pt-BR.md diff --git a/src/BCards.Web/Content/ModerationRules.md b/src/BCards.Web/Content/Tenants/bcards/ModerationRules.md similarity index 100% rename from src/BCards.Web/Content/ModerationRules.md rename to src/BCards.Web/Content/Tenants/bcards/ModerationRules.md diff --git a/src/BCards.Web/Content/Tenants/bcards/Tutoriais/advocacia/bcards-para-escritorios-de-advocacia.pt-BR.md b/src/BCards.Web/Content/Tenants/bcards/Tutoriais/advocacia/bcards-para-escritorios-de-advocacia.pt-BR.md new file mode 100644 index 0000000..f440f7a --- /dev/null +++ b/src/BCards.Web/Content/Tenants/bcards/Tutoriais/advocacia/bcards-para-escritorios-de-advocacia.pt-BR.md @@ -0,0 +1,269 @@ +--- +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 diff --git a/src/BCards.Web/Content/Tutoriais/advocacia/como-advogados-podem-usar-bcards.pt-BR.md b/src/BCards.Web/Content/Tenants/bcards/Tutoriais/advocacia/como-advogados-podem-usar-bcards.pt-BR.md similarity index 100% rename from src/BCards.Web/Content/Tutoriais/advocacia/como-advogados-podem-usar-bcards.pt-BR.md rename to src/BCards.Web/Content/Tenants/bcards/Tutoriais/advocacia/como-advogados-podem-usar-bcards.pt-BR.md diff --git a/src/BCards.Web/Content/Tenants/bcards/Tutoriais/tecnologia/bcards-para-freelancers-de-ti.pt-BR.md b/src/BCards.Web/Content/Tenants/bcards/Tutoriais/tecnologia/bcards-para-freelancers-de-ti.pt-BR.md new file mode 100644 index 0000000..ed27350 --- /dev/null +++ b/src/BCards.Web/Content/Tenants/bcards/Tutoriais/tecnologia/bcards-para-freelancers-de-ti.pt-BR.md @@ -0,0 +1,364 @@ +--- +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 diff --git a/src/BCards.Web/Content/Tutoriais/tecnologia/como-criar-um-bcard.pt-BR.md b/src/BCards.Web/Content/Tenants/bcards/Tutoriais/tecnologia/como-criar-um-bcard.pt-BR.md similarity index 100% rename from src/BCards.Web/Content/Tutoriais/tecnologia/como-criar-um-bcard.pt-BR.md rename to src/BCards.Web/Content/Tenants/bcards/Tutoriais/tecnologia/como-criar-um-bcard.pt-BR.md diff --git a/src/BCards.Web/Content/Tenants/luslinks/.gitkeep b/src/BCards.Web/Content/Tenants/luslinks/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/BCards.Web/Content/Tenants/luslinks/Artigos/como-a-tecnologia-esta-transformando-o-ministerio-moderno.pt-BR.md b/src/BCards.Web/Content/Tenants/luslinks/Artigos/como-a-tecnologia-esta-transformando-o-ministerio-moderno.pt-BR.md new file mode 100644 index 0000000..de8c31b --- /dev/null +++ b/src/BCards.Web/Content/Tenants/luslinks/Artigos/como-a-tecnologia-esta-transformando-o-ministerio-moderno.pt-BR.md @@ -0,0 +1,160 @@ +--- +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 LusLinks" +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 LusLinks, 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 LusLinks e ofereça à sua congregação uma porta de entrada organizada para tudo que você produz. + +[Criar minha página no LusLinks →](https://luslinks.site/) + +--- + +**Última atualização:** Janeiro 2026 diff --git a/src/BCards.Web/Content/Tenants/luslinks/Tutoriais/ministerio/como-criar-sua-pagina-no-luslinks.pt-BR.md b/src/BCards.Web/Content/Tenants/luslinks/Tutoriais/ministerio/como-criar-sua-pagina-no-luslinks.pt-BR.md new file mode 100644 index 0000000..3b35b23 --- /dev/null +++ b/src/BCards.Web/Content/Tenants/luslinks/Tutoriais/ministerio/como-criar-sua-pagina-no-luslinks.pt-BR.md @@ -0,0 +1,287 @@ +--- +title: "Como Criar sua Página no LusLinks: Guia Completo para Líderes Religiosos" +description: "Passo a passo para pastores, padres, líderes e ministérios criarem sua página profissional no LusLinks e alcançarem mais pessoas com sua mensagem de fé." +keywords: "luslinks tutorial, página pastor, bio links ministerio, presença digital iglesia, como criar página pastor" +author: "Equipe LusLinks" +date: 2026-01-25 +lastMod: 2026-01-25 +image: "/images/tutoriais/pastor-luslinks.jpg" +culture: "pt-BR" +category: "ministerio" +--- + +# Como Criar sua Página no LusLinks: 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 LusLinks 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 LusLinks + +### 1.1. Acesse o Site + +Vá para [luslinks.site](https://luslinks.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 LusLinks seguirá o padrão: +``` +luslinks.site/ministerio/seu-slug +``` + +Boas opções: +``` +pr-joao-silva → luslinks.site/ministerio/pr-joao-silva +igreja-graca-viva → luslinks.site/ministerio/igreja-graca-viva +ministerio-agape → luslinks.site/ministerio/ministerio-agape +pe-carlos-mendes → luslinks.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 LusLinks 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: +luslinks.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: luslinks.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: +luslinks.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: +luslinks.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 LusLinks é 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 LusLinks é 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://luslinks.site/) + +--- + +**Última atualização:** Janeiro 2026 diff --git a/src/BCards.Web/Content/Tenants/spicylinks/.gitkeep b/src/BCards.Web/Content/Tenants/spicylinks/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/BCards.Web/Content/Tenants/spicylinks/Artigos/como-monetizar-seu-conteudo-com-uma-bio-profissional.pt-BR.md b/src/BCards.Web/Content/Tenants/spicylinks/Artigos/como-monetizar-seu-conteudo-com-uma-bio-profissional.pt-BR.md new file mode 100644 index 0000000..3960ff7 --- /dev/null +++ b/src/BCards.Web/Content/Tenants/spicylinks/Artigos/como-monetizar-seu-conteudo-com-uma-bio-profissional.pt-BR.md @@ -0,0 +1,217 @@ +--- +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 diff --git a/src/BCards.Web/Content/Tenants/spicylinks/Tutoriais/criadores/como-configurar-seu-spicylinks.pt-BR.md b/src/BCards.Web/Content/Tenants/spicylinks/Tutoriais/criadores/como-configurar-seu-spicylinks.pt-BR.md new file mode 100644 index 0000000..4179f86 --- /dev/null +++ b/src/BCards.Web/Content/Tenants/spicylinks/Tutoriais/criadores/como-configurar-seu-spicylinks.pt-BR.md @@ -0,0 +1,334 @@ +--- +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 diff --git a/src/BCards.Web/Controllers/AgeGateController.cs b/src/BCards.Web/Controllers/AgeGateController.cs new file mode 100644 index 0000000..5f0311e --- /dev/null +++ b/src/BCards.Web/Controllers/AgeGateController.cs @@ -0,0 +1,50 @@ +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) + { + _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 : "/"; +} diff --git a/src/BCards.Web/Controllers/SitemapController.cs b/src/BCards.Web/Controllers/SitemapController.cs index 7a3e977..8a465ef 100644 --- a/src/BCards.Web/Controllers/SitemapController.cs +++ b/src/BCards.Web/Controllers/SitemapController.cs @@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Mvc; using System.Text; using System.Xml.Linq; using BCards.Web.Services; +using BCards.Web.Areas.Tutoriais.Services; +using BCards.Web.Repositories; namespace BCards.Web.Controllers; @@ -9,15 +11,21 @@ public class SitemapController : Controller { private readonly IUserPageService _userPageService; private readonly ILivePageService _livePageService; + private readonly IMarkdownService _markdownService; + private readonly ICategoryRepository _categoryRepository; private readonly ILogger _logger; public SitemapController( - IUserPageService userPageService, + IUserPageService userPageService, ILivePageService livePageService, + IMarkdownService markdownService, + ICategoryRepository categoryRepository, ILogger logger) { _userPageService = userPageService; _livePageService = livePageService; + _markdownService = markdownService; + _categoryRepository = categoryRepository; _logger = logger; } @@ -27,14 +35,31 @@ public class SitemapController : Controller { try { - // 🔥 NOVA FUNCIONALIDADE: Usar LivePages em vez de UserPages - var livePages = await _livePageService.GetAllActiveAsync(); - - // Define namespace corretamente para evitar conflitos XNamespace ns = "http://www.sitemaps.org/schemas/sitemap/0.9"; - - // Construir URLs das páginas dinâmicas separadamente para evitar problemas - var dynamicUrls = livePages.Select(page => + + var livePages = await _livePageService.GetAllActiveAsync(); + + // Artigos + var artigos = await _markdownService.GetAllArticlesAsync("Artigos", "pt-BR"); + + // Tutoriais por categoria + var categories = await _categoryRepository.GetAllActiveAsync(); + var tutorialUrls = new List(); + 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 + "loc", $"{Request.Scheme}://{Request.Host}/page/{page.Category?.Replace(" ", "-")?.ToLower()}/{page.Slug}"), new XElement(ns + "lastmod", page.LastSyncAt.ToString("yyyy-MM-dd")), @@ -42,11 +67,19 @@ public class SitemapController : Controller new XElement(ns + "priority", "0.8") ) ).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( new XDeclaration("1.0", "utf-8", "yes"), new XElement(ns + "urlset", - // Add static pages new XElement(ns + "url", new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/"), new XElement(ns + "lastmod", DateTime.UtcNow.ToString("yyyy-MM-dd")), @@ -59,14 +92,27 @@ public class SitemapController : Controller new XElement(ns + "changefreq", "weekly"), new XElement(ns + "priority", "0.9") ), - - // Add live pages (SEO-optimized URLs only) + new XElement(ns + "url", + new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/artigos"), + 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 ) ); - - _logger.LogInformation($"Generated sitemap with {livePages.Count} live pages"); - + + _logger.LogInformation("Generated sitemap with {LivePages} live pages, {Artigos} artigos, {Tutoriais} tutoriais", + livePages.Count, artigos.Count, tutorialUrls.Count); + return Content(sitemap.ToString(SaveOptions.DisableFormatting), "application/xml", Encoding.UTF8); } catch (Exception ex) diff --git a/src/BCards.Web/Middleware/AgeGateMiddleware.cs b/src/BCards.Web/Middleware/AgeGateMiddleware.cs new file mode 100644 index 0000000..1d735d4 --- /dev/null +++ b/src/BCards.Web/Middleware/AgeGateMiddleware.cs @@ -0,0 +1,58 @@ +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 _excludedPrefixes = new(StringComparer.OrdinalIgnoreCase) + { + "/age-gate", + "/auth", + "/admin", + "/webhook", + "/health", + "/ready", + "/live", + "/lib", + "/css", + "/js", + "/images", + "/favicon" + }; + + public AgeGateMiddleware(RequestDelegate next, IOptions 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; + } +} diff --git a/src/BCards.Web/Program.cs b/src/BCards.Web/Program.cs index 22c7a54..d490012 100644 --- a/src/BCards.Web/Program.cs +++ b/src/BCards.Web/Program.cs @@ -72,31 +72,26 @@ if (isDevelopment) try { - // OpenSearch configurado para ser MUITO agressivo no envio loggerConfig.WriteTo.Async(a => a.OpenSearch(new OpenSearchSinkOptions(new Uri(openSearchUrl)) { IndexFormat = indexFormat, - AutoRegisterTemplate = true, - BufferBaseFilename = "./logs/opensearch-buffer", // Buffer em disco + AutoRegisterTemplate = false, // Não faz GET / no startup ModifyConnectionSettings = conn => conn - .RequestTimeout(TimeSpan.FromSeconds(8)) - .PingTimeout(TimeSpan.FromSeconds(4)), + .RequestTimeout(TimeSpan.FromSeconds(5)) + .PingTimeout(TimeSpan.FromSeconds(3)), MinimumLogEventLevel = LogEventLevel.Debug, EmitEventFailure = EmitEventFailureHandling.WriteToSelfLog, RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway, - BatchPostingLimit = 10, // Lotes pequenos = envio mais frequente - Period = TimeSpan.FromSeconds(2), // Envia a cada 2 segundos - // Configurações para máxima persistência - BufferRetainedInvalidPayloadsLimitBytes = 100 * 1024 * 1024, // 100MB buffer - BufferLogShippingInterval = TimeSpan.FromSeconds(1), // Tenta reenviar rapidamente + BatchPostingLimit = 10, + Period = TimeSpan.FromSeconds(2), TemplateCustomSettings = new Dictionary { {"number_of_shards", "1"}, {"number_of_replicas", "0"} } }), - bufferSize: 10000, // Buffer grande na memória - blockWhenFull: false); // Nunca bloquear aplicação + bufferSize: 10000, + blockWhenFull: false); } catch (Exception) { @@ -262,6 +257,9 @@ else } // Stripe Configuration with validation +builder.Services.Configure( + builder.Configuration.GetSection("Tenant")); + builder.Services.Configure( builder.Configuration.GetSection("Stripe")); @@ -602,11 +600,9 @@ if (!app.Environment.IsDevelopment()) { context.Request.Scheme = "https"; - if (context.Request.Host.Host == "bcards.site") - { - // Fix para Cloudflare - não especificar porta explícita - context.Request.Host = new HostString("bcards.site"); - } + // Fix para Cloudflare - remover porta 443 explícita em qualquer domínio + if (context.Request.Host.Port == 443) + context.Request.Host = new HostString(context.Request.Host.Host); await next(); }); @@ -740,6 +736,7 @@ app.Use(async (context, next) => app.UseMiddleware(); app.UseMiddleware(); +app.UseMiddleware(); app.UseMiddleware(); app.UseMiddleware(); app.UseMiddleware(); diff --git a/src/BCards.Web/Properties/launchSettings.json b/src/BCards.Web/Properties/launchSettings.json index d8bc651..58a8396 100644 --- a/src/BCards.Web/Properties/launchSettings.json +++ b/src/BCards.Web/Properties/launchSettings.json @@ -15,6 +15,22 @@ "ASPNETCORE_ENVIRONMENT": "Testing" }, "applicationUrl": "https://localhost:49178;http://localhost:49179" + }, + "SpicyLinks": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Spicylinks" + }, + "applicationUrl": "https://localhost:49182;http://localhost:49183" + }, + "LusLinks": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Luslinks" + }, + "applicationUrl": "https://localhost:49184;http://localhost:49185" } } } \ No newline at end of file diff --git a/src/BCards.Web/Services/EmailService.cs b/src/BCards.Web/Services/EmailService.cs index f072c6c..25da09d 100644 --- a/src/BCards.Web/Services/EmailService.cs +++ b/src/BCards.Web/Services/EmailService.cs @@ -1,3 +1,5 @@ +using BCards.Web.Configuration; +using Microsoft.Extensions.Options; using SendGrid; using SendGrid.Helpers.Mail; @@ -8,15 +10,18 @@ public class EmailService : IEmailService private readonly ISendGridClient _sendGridClient; private readonly IConfiguration _configuration; private readonly ILogger _logger; + private readonly TenantSettings _tenant; public EmailService( ISendGridClient sendGridClient, IConfiguration configuration, - ILogger logger) + ILogger logger, + IOptions tenantSettings) { _sendGridClient = sendGridClient; _configuration = configuration; _logger = logger; + _tenant = tenantSettings.Value; } public async Task SendModerationStatusAsync(string userEmail, string userName, string pageTitle, string status, string? reason = null, string? previewUrl = null) @@ -96,7 +101,7 @@ public class EmailService : IEmailService private (string subject, string htmlContent) GetPendingModerationTemplate(string userName, string pageTitle, string? previewUrl) { - var subject = "📋 Sua página está sendo analisada - bcards.site"; + var subject = $"📋 Sua página está sendo analisada - {_tenant.SiteName}"; var previewButton = !string.IsNullOrEmpty(previewUrl) ? $"

Ver Preview

" : ""; @@ -126,7 +131,7 @@ public class EmailService : IEmailService private (string subject, string htmlContent) GetApprovedTemplate(string userName, string pageTitle) { - var subject = "✅ Sua página foi aprovada! - bcards.site"; + var subject = $"✅ Sua página foi aprovada! - {_tenant.SiteName}"; var htmlContent = $@"

Parabéns {userName}! 🎉

@@ -157,7 +162,7 @@ public class EmailService : IEmailService private (string subject, string htmlContent) GetRejectedTemplate(string userName, string pageTitle, string? reason) { - var subject = "⚠️ Sua página precisa de ajustes - bcards.site"; + var subject = $"⚠️ Sua página precisa de ajustes - {_tenant.SiteName}"; var reasonText = !string.IsNullOrEmpty(reason) ? $"

Motivo: {reason}

" : ""; var htmlContent = $@" diff --git a/src/BCards.Web/Views/Admin/Dashboard.cshtml b/src/BCards.Web/Views/Admin/Dashboard.cshtml index 436230e..f4cceeb 100644 --- a/src/BCards.Web/Views/Admin/Dashboard.cshtml +++ b/src/BCards.Web/Views/Admin/Dashboard.cshtml @@ -1,6 +1,8 @@ @model BCards.Web.ViewModels.DashboardViewModel +@inject Microsoft.Extensions.Options.IOptions TenantConfig @{ - ViewData["Title"] = "Dashboard - BCards"; + var tenant = TenantConfig.Value; + ViewData["Title"] = $"Dashboard - {tenant.SiteName}"; Layout = "_Layout"; var pageInCreation = Model.UserPages.FirstOrDefault(p => (p.LastModerationStatus ?? p.Status) == BCards.Web.ViewModels.PageStatus.Creating); } diff --git a/src/BCards.Web/Views/Admin/ManagePage.cshtml b/src/BCards.Web/Views/Admin/ManagePage.cshtml index d8b9511..07c8dfc 100644 --- a/src/BCards.Web/Views/Admin/ManagePage.cshtml +++ b/src/BCards.Web/Views/Admin/ManagePage.cshtml @@ -1,8 +1,17 @@ @using BCards.Web.Utils @model BCards.Web.ViewModels.ManagePageViewModel +@inject Microsoft.Extensions.Options.IOptions TenantConfig @{ ViewData["Title"] = Model.IsNewPage ? "Criar Página" : "Editar Página"; 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 } + ); }
@@ -856,18 +865,10 @@
Escolha o tipo para obter instruções específicas
@@ -1211,6 +1212,7 @@ @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} + + diff --git a/src/BCards.Web/Views/Auth/Login.cshtml b/src/BCards.Web/Views/Auth/Login.cshtml index 41b64de..94dc9a4 100644 --- a/src/BCards.Web/Views/Auth/Login.cshtml +++ b/src/BCards.Web/Views/Auth/Login.cshtml @@ -1,5 +1,7 @@ +@inject Microsoft.Extensions.Options.IOptions TenantConfig @{ - ViewData["Title"] = "Login - BCards"; + var tenant = TenantConfig.Value; + ViewData["Title"] = $"Login - {tenant.SiteName}"; var returnUrl = ViewBag.ReturnUrl as string; var isPreview = ViewBag.IsPreview as bool? ?? false; Layout = isPreview ? "_Layout" : "_UserPageLayout"; @@ -11,7 +13,7 @@
-

BCards

+

@tenant.SiteName

Entre na sua conta

diff --git a/src/BCards.Web/Views/Home/Index.cshtml b/src/BCards.Web/Views/Home/Index.cshtml index 6370a40..7929a36 100644 --- a/src/BCards.Web/Views/Home/Index.cshtml +++ b/src/BCards.Web/Views/Home/Index.cshtml @@ -1,23 +1,22 @@ +@inject Microsoft.Extensions.Options.IOptions TenantConfig @{ - //var isPreview = ViewBag.IsPreview as bool? ?? false; - ViewData["Title"] = "BCards - Crie sua bio / links Profissional"; + var tenant = TenantConfig.Value; + ViewData["Title"] = $"{tenant.SiteName} - {tenant.Tagline}"; var categories = ViewBag.Categories as List ?? new List(); var recentPages = ViewBag.RecentPages as List ?? new List(); - //Layout = isPreview ? "_Layout" : "_UserPageLayout"; Layout = "_Layout"; + var featuresHeadline = tenant.FeaturesHeadline.Replace("{SiteName}", tenant.SiteName); } -
+

- Crie sua página profissional em minutos + @tenant.HeroHeadline

- 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. + @tenant.HeroDescription

@if (User.Identity?.IsAuthenticated == true) @@ -29,7 +28,7 @@ else { - Começar Grátis + @tenant.HeroCtaText } @@ -38,7 +37,7 @@
- Exemplo de página BCards + Exemplo de página @tenant.SiteName
@@ -54,7 +53,7 @@ @foreach (var category in categories.Take(8)) {
-
@@ -70,44 +69,32 @@ } -
-

Por que escolher o BCards?

-
-
-
-
- 🎨 + @if (tenant.Features.Any()) + { +
+

@featuresHeadline

+
+ @foreach (var feature in tenant.Features) + { +
+
+
+ @feature.Icon +
+
@feature.Title
+

@feature.Description

+
-
Temas Profissionais
-

Escolha entre diversos temas profissionais ou personalize as cores da sua página.

-
+ }
-
-
-
- 📊 -
-
Analytics Avançado
-

Acompanhe quantas pessoas visitaram sua página e clicaram nos seus links.

-
-
-
-
-
- 🔗 -
-
URLs Organizadas
-

Suas URLs são organizadas por categoria: bcards.site/corretor/seu-nome

-
-
-
-
+ + } @if (recentPages.Any()) {
-

Profissionais que confiam no BCards

+

Quem já usa o @tenant.SiteName

@foreach (var page in recentPages) { @@ -116,12 +103,12 @@
@if (!string.IsNullOrEmpty(page.ProfileImageId)) { - @(page.DisplayName) } else { -
@@ -129,7 +116,7 @@
@(page.DisplayName)
@(page.Category)
- Ver Página @@ -145,20 +132,20 @@
-

Pronto para começar?

+

@tenant.CtaHeadline

- Crie sua página profissional agora mesmo e comece a organizar seus links. + @tenant.CtaDescription

@if (User.Identity?.IsAuthenticated == true) { - Criar Minha Página + @tenant.CtaButtonText } else { - Começar Grátis + @tenant.CtaButtonText }
@@ -170,13 +157,13 @@ .hero-section { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } - + .hover-card { transition: transform 0.2s ease-in-out; } - + .hover-card:hover { transform: translateY(-5px); } -} \ No newline at end of file +} diff --git a/src/BCards.Web/Views/Home/Pricing.cshtml b/src/BCards.Web/Views/Home/Pricing.cshtml index ac5c944..694daec 100644 --- a/src/BCards.Web/Views/Home/Pricing.cshtml +++ b/src/BCards.Web/Views/Home/Pricing.cshtml @@ -1,5 +1,7 @@ +@inject Microsoft.Extensions.Options.IOptions TenantConfig @{ - ViewData["Title"] = "Planos e Preços - BCards"; + var tenant = TenantConfig.Value; + ViewData["Title"] = $"Planos e Preços - {tenant.SiteName}"; //var isPreview = ViewBag.IsPreview as bool? ?? false; //Layout = isPreview ? "_Layout" : "_UserPageLayout"; Layout = "_Layout"; @@ -79,15 +81,15 @@
Básico
-
- R$ 12,90 - /mês -
-
- R$ 129,00 - /ano -
- Economize R$ 25,80 (2 meses grátis) +
+ R$ 12,90 + /mês +
+
+ R$ 129,00 + /ano +
+ Economize R$ 25,80 (2 meses grátis)
@@ -153,15 +155,15 @@
Profissional
-
- R$ 25,90 - /mês -
-
- R$ 259,00 - /ano -
- Economize R$ 51,80 (2 meses grátis) +
+ R$ 25,90 + /mês +
+
+ R$ 259,00 + /ano +
+ Economize R$ 51,80 (2 meses grátis)
@@ -230,15 +232,15 @@
Premium
-
- R$ 29,90 - /mês -
-
- R$ 299,00 - /ano -
- Economize R$ 59,80 (2 meses grátis) +
+ R$ 29,90 + /mês +
+
+ R$ 299,00 + /ano +
+ Economize R$ 59,80 (2 meses grátis)
Melhor custo-benefício! @@ -265,18 +267,18 @@ URL personalizada -
  • - - Suporte prioritário -
  • -
  • - - Upload de PDFs (até 5 arquivos) -
  • -
  • - - Links de produto -
  • +
  • + + Suporte prioritário +
  • +
  • + + Upload de PDFs (até 5 arquivos) +
  • +
  • + + Links de produto +
  • * 20 temas básicos + 20 temas premium exclusivos @@ -315,15 +317,15 @@
    Premium + Afiliados
    -
    - R$ 34,90 - /mês -
    -
    - R$ 349,00 - /ano -
    - Economize R$ 69,80 (2 meses grátis) +
    + R$ 34,90 + /mês +
    +
    + R$ 349,00 + /ano +
    + Economize R$ 69,80 (2 meses grátis)
    Para monetização! @@ -350,19 +352,19 @@ Moderação plus -
  • - - Suporte prioritário -
  • -
  • - - 10 links afiliados -
  • -
  • - - Upload de PDFs (até 10 arquivos) -
  • - +
  • + + Suporte prioritário +
  • +
  • + + 10 links afiliados +
  • +
  • + + Upload de PDFs (até 10 arquivos) +
  • +
    * 20 temas básicos + 20 temas premium exclusivos
    @@ -603,4 +605,4 @@ document.addEventListener('DOMContentLoaded', function() { monthlyRadio.addEventListener('change', togglePricing); yearlyRadio.addEventListener('change', togglePricing); }); - + diff --git a/src/BCards.Web/Views/Legal/CommunityGuidelines.cshtml b/src/BCards.Web/Views/Legal/CommunityGuidelines.cshtml index 327e97e..515997f 100644 --- a/src/BCards.Web/Views/Legal/CommunityGuidelines.cshtml +++ b/src/BCards.Web/Views/Legal/CommunityGuidelines.cshtml @@ -1,4 +1,6 @@ +@inject Microsoft.Extensions.Options.IOptions TenantConfig @{ + var tenant = TenantConfig.Value; ViewData["Title"] = "Diretrizes da Comunidade"; Layout = "~/Views/Shared/_Layout.cshtml"; } @@ -8,13 +10,13 @@
    -

    Diretrizes da Comunidade BCards

    +

    Diretrizes da Comunidade @tenant.SiteName

    Última atualização: 31 de agosto de 2025


    -

    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.

    -

    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.

    +

    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.

    +

    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.

    1. Conteúdo Estritamente Proibido

    O seguinte conteúdo será removido imediatamente e pode resultar no banimento da conta sem aviso prévio:

    @@ -48,7 +50,7 @@
    diff --git a/src/BCards.Web/Views/Legal/Privacy.cshtml b/src/BCards.Web/Views/Legal/Privacy.cshtml index 8524e01..d5d49e1 100644 --- a/src/BCards.Web/Views/Legal/Privacy.cshtml +++ b/src/BCards.Web/Views/Legal/Privacy.cshtml @@ -1,4 +1,8 @@ +@inject Microsoft.Extensions.Options.IOptions TenantConfig @{ + var tenant = TenantConfig.Value; + var supportParts = tenant.SupportEmail.Split('@'); + var dpoParts = tenant.DpoEmail.Split('@'); ViewData["Title"] = "Política de Privacidade"; Layout = "~/Views/Shared/_Layout.cshtml"; } @@ -13,22 +17,22 @@
    -

    Bem-vindo à Política de Privacidade do BCards. 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.

    +

    Bem-vindo à Política de Privacidade do @tenant.SiteName. 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.

    1. Dados Pessoais Coletados e a Finalidade

    Coletamos diferentes tipos de informações para fornecer e melhorar nossos serviços para você:

    • Dados de Cadastro e Autenticação: 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ê.
    • -
    • Conteúdo da Página Pública: 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.
    • +
    • Conteúdo da Página Pública: 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.
    • Dados de Pagamento: 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.
    • -
    • Dados de Análise e Uso (Analytics): 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.
    • +
    • Dados de Análise e Uso (Analytics): 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.
    • Cookies e Tecnologias Semelhantes: 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.

    2. Base Legal para o Tratamento de Dados (LGPD)

    O tratamento de seus dados pessoais é realizado com base nas seguintes hipóteses legais previstas no Art. 7º da LGPD:

      -
    • Execução de Contrato (Inciso V): 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.
    • +
    • Execução de Contrato (Inciso V): 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.
    • Consentimento (Inciso I): Para o uso de cookies não essenciais e para o envio de comunicações de marketing, solicitaremos seu consentimento explícito.
    • Legítimo Interesse (Inciso IX): 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.
    • Cumprimento de Obrigação Legal (Inciso II): Podemos tratar dados para cumprir obrigações legais, como a emissão de notas fiscais ou ordens judiciais.
    • @@ -55,7 +59,7 @@
    • Informação sobre Compartilhamento: O direito de saber com quais entidades públicas e privadas compartilhamos seus dados.
    • Revogação do Consentimento: O direito de revogar seu consentimento a qualquer momento.
    -

    Para exercer seus direitos, entre em contato com nosso Encarregado de Proteção de Dados (DPO) através do e-mail . O prazo para resposta é de até 15 dias, conforme a legislação.

    +

    Para exercer seus direitos, entre em contato com nosso Encarregado de Proteção de Dados (DPO) através do e-mail . O prazo para resposta é de até 15 dias, conforme a legislação.

    5. Cookies e Tecnologias de Rastreamento

    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.

    @@ -73,7 +77,7 @@

    8. Contato do Encarregado de Proteção de Dados (DPO)

    Para qualquer dúvida sobre esta Política de Privacidade ou para exercer seus direitos, entre em contato com nosso DPO:

    -

    E-mail: dpo@bcards.site

    +

    E-mail: @tenant.DpoEmail

    9. Alterações a esta Política

    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.

    diff --git a/src/BCards.Web/Views/Legal/PrivacyES.cshtml b/src/BCards.Web/Views/Legal/PrivacyES.cshtml index f51cc22..edc5035 100644 --- a/src/BCards.Web/Views/Legal/PrivacyES.cshtml +++ b/src/BCards.Web/Views/Legal/PrivacyES.cshtml @@ -1,4 +1,7 @@ +@inject Microsoft.Extensions.Options.IOptions TenantConfig @{ + var tenant = TenantConfig.Value; + var dpoParts = tenant.DpoEmail.Split('@'); ViewData["Title"] = "Política de Privacidad"; Layout = "~/Views/Shared/_Layout.cshtml"; } @@ -13,15 +16,15 @@
    -

    Bienvenido a la Política de Privacidad de BCards. 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.

    +

    Bienvenido a la Política de Privacidad de @tenant.SiteName. 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.

    1. Datos Personales Recopilados y su Finalidad

    Recopilamos diferentes tipos de información para proporcionar y mejorar nuestros servicios para usted:

    • Datos de Registro y Autenticación: 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.
    • -
    • Contenido de la Página Pública: 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.
    • +
    • Contenido de la Página Pública: 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.
    • Datos de Pago: 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.
    • -
    • Datos de Análisis y Uso (Analytics): 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.
    • +
    • Datos de Análisis y Uso (Analytics): 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.
    • Cookies y Tecnologías Similares: Usamos cookies esenciales para el funcionamiento de la plataforma (ej: mantener su sesión activa) y cookies de análisis.
    @@ -47,14 +50,14 @@
  • Oposición: El derecho a oponerse al tratamiento de sus datos para ciertos fines.
  • Portabilidad: El derecho a recibir sus datos en un formato estructurado.
  • -

    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 .

    +

    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 .

    5. Retención de Datos

    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.

    6. Contacto del Oficial de Protección de Datos (DPO)

    Para cualquier pregunta sobre esta Política de Privacidad o para ejercer sus derechos, contacte a nuestro DPO:

    -

    Correo Electrónico: dpo@bcards.site

    +

    Correo Electrónico: @tenant.DpoEmail

    7. Cambios a esta Política

    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.

    diff --git a/src/BCards.Web/Views/Legal/RequestData.cshtml b/src/BCards.Web/Views/Legal/RequestData.cshtml index b1d9d5e..21969fa 100644 --- a/src/BCards.Web/Views/Legal/RequestData.cshtml +++ b/src/BCards.Web/Views/Legal/RequestData.cshtml @@ -1,4 +1,7 @@ +@inject Microsoft.Extensions.Options.IOptions TenantConfig @{ + var tenant = TenantConfig.Value; + var dpoParts = tenant.DpoEmail.Split('@'); ViewData["Title"] = "Direitos do Titular de Dados"; Layout = "~/Views/Shared/_Layout.cshtml"; } @@ -13,7 +16,7 @@
    -

    Em conformidade com a Lei Geral de Proteção de Dados (LGPD) e outras legislações de privacidade, o BCards 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.

    +

    Em conformidade com a Lei Geral de Proteção de Dados (LGPD) e outras legislações de privacidade, o @tenant.SiteName 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.

    Como Fazer uma Solicitação

    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:

    @@ -21,13 +24,13 @@
    Canal de Atendimento ao Titular

    Envie um e-mail para:

    - +

    No seu e-mail, por favor, inclua:

    • Nome Completo: O nome associado à sua conta.
    • -
    • E-mail de Cadastro: O e-mail que você usou para se registrar no BCards.
    • +
    • E-mail de Cadastro: O e-mail que você usou para se registrar no @tenant.SiteName.
    • Tipo de Solicitação: 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").
    • Detalhes da Solicitação: Forneça qualquer detalhe adicional que possa nos ajudar a atender seu pedido.
    @@ -36,7 +39,7 @@

    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é 15 dias. Para outras solicitações, responderemos o mais breve possível, respeitando os prazos definidos na legislação aplicável.

    - Nota: 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. + Nota: 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.
    diff --git a/src/BCards.Web/Views/Legal/Terms.cshtml b/src/BCards.Web/Views/Legal/Terms.cshtml index aaaaf9b..ceca534 100644 --- a/src/BCards.Web/Views/Legal/Terms.cshtml +++ b/src/BCards.Web/Views/Legal/Terms.cshtml @@ -1,4 +1,7 @@ +@inject Microsoft.Extensions.Options.IOptions TenantConfig @{ + var tenant = TenantConfig.Value; + var supportParts = tenant.SupportEmail.Split('@'); ViewData["Title"] = "Termos de Uso"; Layout = "~/Views/Shared/_Layout.cshtml"; } @@ -13,20 +16,20 @@
    -

    Bem-vindo ao BCards. 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.

    +

    Bem-vindo ao @tenant.SiteName. 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.

    1. Aceitação dos Termos

    -

    Ao criar uma conta ou usar os serviços do BCards, você celebra um contrato legalmente vinculativo conosco e concorda com estes Termos, nossa Política de Privacidade e nossas Diretrizes da Comunidade.

    +

    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 Política de Privacidade e nossas Diretrizes da Comunidade.

    2. Descrição do Serviço

    -

    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.

    +

    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.

    3. Responsabilidades do Usuário

    -

    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:

    +

    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:

    • Fornecer informações de registro precisas e mantê-las atualizadas.
    • Manter a segurança de sua senha e conta. Você é responsável por todas as atividades que ocorrem em sua conta.
    • -
    • Não usar o BCards para qualquer finalidade ilegal ou não autorizada.
    • +
    • Não usar o @tenant.SiteName para qualquer finalidade ilegal ou não autorizada.
    • Não violar nossas Diretrizes da Comunidade, que proíbem conteúdo de ódio, violência, spam, nudez, entre outros.
    • Possuir os direitos ou as permissões necessárias para todo o conteúdo que você publica.
    @@ -43,12 +46,12 @@

    6. Propriedade Intelectual

      -
    • Seu Conteúdo: 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.
    • -
    • Nossa Plataforma: 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.
    • +
    • Seu Conteúdo: 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.
    • +
    • Nossa Plataforma: 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.

    7. Limitação de Responsabilidade

    -

    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:

    +

    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:

    • (a) SEU ACESSO OU USO OU INCAPACIDADE DE ACESSAR OU USAR O SERVIÇO;
    • (b) QUALQUER CONDUTA OU CONTEÚDO DE TERCEIROS NO SERVIÇO;
    • @@ -63,8 +66,8 @@

      9. Disposições Gerais

      • Legislação Aplicável: 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.
      • -
      • Alterações nos Termos: 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.
      • -
      • Contato: Para qualquer dúvida sobre estes Termos, entre em contato conosco pelo e-mail .
      • +
      • Alterações nos Termos: 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.
      • +
      • Contato: Para qualquer dúvida sobre estes Termos, entre em contato conosco pelo e-mail .
    diff --git a/src/BCards.Web/Views/Legal/TermsES.cshtml b/src/BCards.Web/Views/Legal/TermsES.cshtml index 016baa7..a4cba9e 100644 --- a/src/BCards.Web/Views/Legal/TermsES.cshtml +++ b/src/BCards.Web/Views/Legal/TermsES.cshtml @@ -1,4 +1,7 @@ +@inject Microsoft.Extensions.Options.IOptions TenantConfig @{ + var tenant = TenantConfig.Value; + var supportParts = tenant.SupportEmail.Split('@'); ViewData["Title"] = "Términos de Uso"; Layout = "~/Views/Shared/_Layout.cshtml"; } @@ -13,20 +16,20 @@
    -

    Bienvenido a BCards. 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.

    +

    Bienvenido a @tenant.SiteName. 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.

    1. Aceptación de los Términos

    -

    Al crear una cuenta o usar los servicios de BCards, usted celebra un contrato legalmente vinculante con nosotros y acepta estos Términos, nuestra Política de Privacidad y nuestras Directrices de la Comunidad.

    +

    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 Política de Privacidad y nuestras Directrices de la Comunidad.

    2. Descripción del Servicio

    -

    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.

    +

    @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.

    3. Responsabilidades del Usuario

    -

    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:

    +

    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:

    • Proporcionar información de registro precisa y mantenerla actualizada.
    • Mantener la seguridad de su contraseña y cuenta.
    • -
    • No utilizar BCards para ningún propósito ilegal o no autorizado.
    • +
    • No utilizar @tenant.SiteName para ningún propósito ilegal o no autorizado.
    • No violar nuestras Directrices de la Comunidad.
    • Poseer los derechos o permisos necesarios para todo el contenido que publique.
    @@ -44,11 +47,11 @@

    6. Propiedad Intelectual

    • Su Contenido: 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.
    • -
    • Nuestra Plataforma: 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.
    • +
    • Nuestra Plataforma: @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.

    7. Limitación de Responsabilidad

    -

    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.

    +

    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.

    8. Cancelación y Terminación

    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.

    @@ -57,7 +60,7 @@
    • Legislación Aplicable: 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.
    • Cambios en los Términos: Podemos modificar estos Términos en cualquier momento. Le notificaremos con antelación.
    • -
    • Contacto: Para cualquier pregunta sobre estos Términos, contáctenos en .
    • +
    • Contacto: Para cualquier pregunta sobre estos Términos, contáctenos en .
    diff --git a/src/BCards.Web/Views/Shared/Error.cshtml b/src/BCards.Web/Views/Shared/Error.cshtml index 24bd346..7a2a49d 100644 --- a/src/BCards.Web/Views/Shared/Error.cshtml +++ b/src/BCards.Web/Views/Shared/Error.cshtml @@ -1,4 +1,6 @@ +@inject Microsoft.Extensions.Options.IOptions TenantConfig @{ + var tenant = TenantConfig.Value; ViewData["Title"] = "Erro"; } @@ -7,7 +9,7 @@ - Erro - BCards + Erro - @tenant.SiteName diff --git a/src/BCards.Web/Views/Shared/_Layout.cshtml b/src/BCards.Web/Views/Shared/_Layout.cshtml index 0819c9e..e31f696 100644 --- a/src/BCards.Web/Views/Shared/_Layout.cshtml +++ b/src/BCards.Web/Views/Shared/_Layout.cshtml @@ -1,9 +1,11 @@ +@inject Microsoft.Extensions.Options.IOptions TenantConfig +@{ var tenant = TenantConfig.Value; } - @(ViewData["Title"] ?? "BCards - Crie sua bios / links Profissional") + @(ViewData["Title"] ?? $"{tenant.SiteName} - {tenant.Tagline}") @if (ViewBag.SeoSettings != null) { @@ -27,8 +29,8 @@ } else { - - + + } @await RenderSectionAsync("Head", required: false) @@ -136,9 +138,9 @@