feat: tela para cadastrar nova postagem

This commit is contained in:
Ricardo Carneiro 2025-01-29 21:25:16 -03:00
parent 4b04639ad7
commit 955a131fec
21 changed files with 936 additions and 33 deletions

BIN
LogoPoost.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@ -0,0 +1,17 @@
// Models/FacebookTokenResponse.cs
using System.Text.Json.Serialization;
namespace Postall.Domain.Dtos.Responses
{
public class FacebookTokenResponse
{
[JsonPropertyName("access_token")]
public string AccessToken { get; set; }
[JsonPropertyName("token_type")]
public string TokenType { get; set; }
[JsonPropertyName("expires_in")]
public long ExpiresIn { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Postall.Domain.Entities
{
public class FacebookToken
{
public string AccessToken { get; set; }
public DateTime ExpiresAt { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Postall.Domain.Entities
{
public class UserSocialData
{
[BsonId]
public ObjectId Id { get; set; }
public string UserId { get; set; }
public string GoogleToken { get; set; }
public FacebookToken FacebookToken { get; set; }
}
}

View File

@ -7,6 +7,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Bson" Version="3.1.0" />
<PackageReference Include="MongoDB.Driver" Version="3.1.0" />
<PackageReference Include="Serilog" Version="4.0.2" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Serilog.Enrichers.Context" Version="4.6.5" />
@ -15,7 +17,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\vcart.me\vcart.back\Struct.ValueObjects\BaseDomain.csproj" />
<ProjectReference Include="..\BaseDomain\BaseDomain.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Postall.Domain.Services
{
public interface IFacebookServices
{
Task<string> GetLongLivedToken(string shortLivedToken);
Task SaveFacebookToken(string userId, string token);
}
}

View File

@ -7,8 +7,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Bson" Version="2.28.0" />
<PackageReference Include="MongoDB.Driver" Version="2.28.0" />
<PackageReference Include="MongoDB.Bson" Version="3.1.0" />
<PackageReference Include="MongoDB.Driver" Version="3.1.0" />
<PackageReference Include="Serilog" Version="4.0.2" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Serilog.Enrichers.Context" Version="4.6.5" />
@ -16,4 +16,8 @@
<PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Postall.Domain\Postall.Domain.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,49 @@
using Microsoft.Extensions.Configuration;
using MongoDB.Driver;
using Postall.Domain.Dtos.Responses;
using Postall.Domain.Entities;
using Postall.Domain.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Json;
using System.Text;
using System.Threading.Tasks;
namespace Postall.Infra.Services
{
public class FacebookTokenService: IFacebookServices
{
private readonly IMongoCollection<UserSocialData> _tokens;
private readonly IConfiguration _config;
private readonly HttpClient _httpClient;
public async Task<string> GetLongLivedToken(string shortLivedToken)
{
var appId = _config["Authentication:Facebook:AppId"];
var appSecret = _config["Authentication:Facebook:AppSecret"];
var response = await _httpClient.GetFromJsonAsync<FacebookTokenResponse>(
$"https://graph.facebook.com/oauth/access_token?" +
$"grant_type=fb_exchange_token&" +
$"client_id={appId}&" +
$"client_secret={appSecret}&" +
$"fb_exchange_token={shortLivedToken}");
return response.AccessToken;
}
public async Task SaveFacebookToken(string userId, string token)
{
var update = Builders<UserSocialData>.Update
.Set(x => x.FacebookToken.AccessToken, token)
.Set(x => x.FacebookToken.ExpiresAt, DateTime.UtcNow.AddDays(60));
await _tokens.UpdateOneAsync(
x => x.UserId == userId,
update,
new UpdateOptions { IsUpsert = true }
);
}
}
}

View File

@ -2,11 +2,19 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Facebook;
using Microsoft.AspNetCore.Mvc;
using Postall.Domain.Services;
using System.Security.Claims;
namespace Postall.Controllers
{
public class OtherLoginsController : Controller
{
private readonly IFacebookServices _facebookServices;
public OtherLoginsController(IFacebookServices facebookServices)
{
this._facebookServices = facebookServices;
}
[HttpGet]
public IActionResult Index()
{
@ -24,21 +32,13 @@ namespace Postall.Controllers
public async Task<IActionResult> FacebookResponse()
{
var result = await HttpContext.AuthenticateAsync(FacebookDefaults.AuthenticationScheme);
if (!result.Succeeded) return RedirectToAction("Login");
if (!result.Succeeded)
return RedirectToAction("Login");
var accessToken = result.Properties.GetTokenValue("access_token");
var longLivedToken = await _facebookServices.GetLongLivedToken(accessToken);
var claims = result.Principal.Identities.FirstOrDefault()
.Claims.Select(claim => new
{
claim.Issuer,
claim.OriginalIssuer,
claim.Type,
claim.Value
});
// Aqui você pode implementar sua lógica de login
// Por exemplo, criar ou atualizar o usuário no banco de dados
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
await _facebookServices.SaveFacebookToken(userId, longLivedToken);
return RedirectToAction("Index", "Home");
}

View File

@ -0,0 +1,91 @@
using Microsoft.AspNetCore.Mvc;
using Postall.Models;
namespace Postall.Controllers
{
public class SocialMediaController : Controller
{
[HttpGet]
public IActionResult Index()
{
// Implementar lógica para buscar lista de posts
return View();
}
[HttpGet]
public JsonResult GetPostDetails(int postId)
{
try
{
// Implementar lógica para buscar detalhes do post
return Json(new { success = true });
}
catch (Exception ex)
{
return Json(new { success = false, message = ex.Message });
}
}
[HttpGet]
public JsonResult GetSocialMediaStatus(int postId)
{
try
{
// Implementar lógica para buscar status das redes sociais
return Json(new { success = true });
}
catch (Exception ex)
{
return Json(new { success = false, message = ex.Message });
}
}
[HttpGet]
public IActionResult Post()
{
return View();
}
[HttpPost]
public JsonResult SaveDraft([FromBody] PostViewModel model)
{
try
{
// Lógica para salvar rascunho
return Json(new { success = true });
}
catch (Exception ex)
{
return Json(new { success = false, message = ex.Message });
}
}
[HttpPost]
public JsonResult PublishPost([FromBody] PostViewModel model)
{
try
{
// Lógica para publicar post
return Json(new { success = true });
}
catch (Exception ex)
{
return Json(new { success = false, message = ex.Message });
}
}
[HttpPost]
public JsonResult SchedulePost([FromBody] PostScheduleViewModel model)
{
try
{
// Lógica para agendar post
return Json(new { success = true });
}
catch (Exception ex)
{
return Json(new { success = false, message = ex.Message });
}
}
}
}

View File

@ -0,0 +1,12 @@
namespace Postall.Models
{
public class PostListViewModel
{
public int Id { get; set; }
public string Channel { get; set; }
public string Title { get; set; }
public DateTime? NextScheduledDate { get; set; }
public DateTime LastUpdate { get; set; }
public List<SocialMediaStatusViewModel> SocialMediaStatus { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace Postall.Models
{
public class PostScheduleViewModel
{
public bool IsManual { get; set; }
public DayOfWeek? WeekDay { get; set; }
public TimeSpan? Time { get; set; }
}
}

View File

@ -0,0 +1,10 @@
namespace Postall.Models
{
public class PostViewModel
{
public string Title { get; set; }
public string Content { get; set; }
public string ImageUrl { get; set; }
public List<string> SelectedPlatforms { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace Postall.Models
{
public class SocialMediaStatusViewModel
{
public string Platform { get; set; }
public DateTime? NextScheduledDate { get; set; }
public string Status { get; set; }
}
}

View File

@ -32,6 +32,11 @@
<Folder Include="wwwroot\img\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Postall.Domain\Postall.Domain.csproj" />
<ProjectReference Include="..\Postall.Infra\Postall.Infra.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resource.Designer.cs">
<DesignTime>True</DesignTime>

View File

@ -4,6 +4,8 @@ using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Options;
using Postall.Domain.Services;
using Postall.Infra.Services;
using Serilog;
using Serilog.Sinks.Grafana.Loki;
using Stripe;
@ -86,6 +88,8 @@ builder.Services.AddControllersWithViews();
builder.Services.AddHttpClient();
builder.Services.AddSerilog();
builder.Services.AddScoped<IFacebookServices, FacebookTokenService>();
var app = builder.Build();
var locOptions = app.Services.GetService<IOptions<RequestLocalizationOptions>>();

View File

@ -1,11 +1,18 @@
@{
ViewData["Title"] = "Site";
ViewData["Title"] = "Sites";
}
<div class="text-center">
<h1 class="display-4">Login</h1>
<div class="row justify-content-center">
<div class="col-md-4">
<div class="container">
<div class="row justify-content-center align-items-center min-vh-100">
<div class="col-md-6">
<div class="card shadow-lg">
<div class="card-body text-center p-5">
<h2 class="card-title mb-4">Bem-vindo</h2>
<p class="card-text text-muted mb-4">
Faça login com sua conta Microsoft para acessar o chat.
</p>
<form asp-action="FacebookLogin" method="get">
<button type="submit" class="btn btn-primary btn-block">
<i class="fab fa-facebook"></i> Login com Facebook
@ -14,3 +21,34 @@
</div>
</div>
</div>
</div>
</div>
@section Styles {
<style>
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
.card {
border: none;
border-radius: 15px;
}
.btn-primary {
background-color: #2f2f2f;
border: none;
padding: 12px;
transition: all 0.3s;
}
.btn-primary:hover {
background-color: #404040;
transform: translateY(-2px);
}
</style>
}
@section Scripts {
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
}

View File

@ -32,6 +32,15 @@
<li class="nav-item">
<a class="nav-link text-white" asp-area="" asp-controller="Plans" asp-action="Index">Planos</a>
</li>
@if (User!=null && User.Identity!=null && User.Identity.IsAuthenticated)
{
<li class="nav-item">
<a class="nav-link text-white" asp-area="" asp-controller="OtherLogins" asp-action="Index">Sites</a>
</li>
<li class="nav-item">
<a class="nav-link text-white" asp-area="" asp-controller="SocialMedia" asp-action="Index">Postagens</a>
</li>
}
</ul>
@if (User!=null && User.Identity!=null && !User.Identity.IsAuthenticated)
{
@ -44,11 +53,6 @@
}
else
{
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link text-white" asp-area="" asp-controller="OtherLogins" asp-action="Index">Sites</a>
</li>
</ul>
<ul class="navbar-nav ml-auto">
<partial name="_Language"/>
<li class="nav-item dropdown" style="margin-right: 10px">

View File

@ -0,0 +1,254 @@
@using Postall.Models
<!-- Views/SocialMedia/Index.cshtml -->
@{
ViewData["Title"] = "Gerenciador de Postagens";
}
<div class="container-fluid mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Gerenciador de Postagens</h2>
<a href="@Url.Action("Post", "SocialMedia")" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Nova Postagem
</a>
</div>
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover" id="postsTable">
<thead>
<tr>
<th></th>
<th>Canal</th>
<th>Título</th>
<th>Próxima Data</th>
<th>Última Atualização</th>
</tr>
</thead>
<tbody>
<!-- Os dados serão preenchidos via JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Template para detalhes expandidos -->
<template id="detailsTemplate">
<div class="expanded-details p-3">
<div class="row">
<div class="col-md-8">
<h5 class="mb-3">Status das Redes Sociais</h5>
<div class="social-media-grid">
<!-- Facebook -->
<div class="social-media-item">
<div class="d-flex align-items-center mb-2">
<i class="bi bi-facebook me-2"></i>
<span class="platform-name">Facebook</span>
</div>
<div class="status-badge">
<span class="badge rounded-pill status-placeholder">Status</span>
</div>
<div class="next-date small text-muted mt-1">
Próxima data: <span class="date-placeholder">--/--/----</span>
</div>
</div>
<!-- Instagram -->
<div class="social-media-item">
<div class="d-flex align-items-center mb-2">
<i class="bi bi-instagram me-2"></i>
<span class="platform-name">Instagram</span>
</div>
<div class="status-badge">
<span class="badge rounded-pill status-placeholder">Status</span>
</div>
<div class="next-date small text-muted mt-1">
Próxima data: <span class="date-placeholder">--/--/----</span>
</div>
</div>
<!-- Twitter/X -->
<div class="social-media-item">
<div class="d-flex align-items-center mb-2">
<i class="bi bi-twitter-x me-2"></i>
<span class="platform-name">Twitter/X</span>
</div>
<div class="status-badge">
<span class="badge rounded-pill status-placeholder">Status</span>
</div>
<div class="next-date small text-muted mt-1">
Próxima data: <span class="date-placeholder">--/--/----</span>
</div>
</div>
<!-- WhatsApp -->
<div class="social-media-item">
<div class="d-flex align-items-center mb-2">
<i class="bi bi-whatsapp me-2"></i>
<span class="platform-name">WhatsApp</span>
</div>
<div class="status-badge">
<span class="badge rounded-pill status-placeholder">Status</span>
</div>
<div class="next-date small text-muted mt-1">
Próxima data: <span class="date-placeholder">--/--/----</span>
</div>
</div>
<!-- Telegram -->
<div class="social-media-item">
<div class="d-flex align-items-center mb-2">
<i class="bi bi-telegram me-2"></i>
<span class="platform-name">Telegram</span>
</div>
<div class="status-badge">
<span class="badge rounded-pill status-placeholder">Status</span>
</div>
<div class="next-date small text-muted mt-1">
Próxima data: <span class="date-placeholder">--/--/----</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
@section Styles {
<style>
.social-media-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
.social-media-item {
padding: 1rem;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
background-color: #f8f9fa;
}
.status-badge .badge {
font-size: 0.875rem;
}
.badge.status-publicado { background-color: #198754; }
.badge.status-nao-selecionado { background-color: #6c757d; }
.badge.status-gerado { background-color: #0dcaf0; }
.badge.status-agendado { background-color: #ffc107; color: #000; }
.badge.status-automatizado { background-color: #0d6efd; }
tr.expanded {
background-color: #f8f9fa;
}
.expanded-details {
background-color: #f8f9fa;
border-top: 1px solid #dee2e6;
}
@@media (max-width: 768px) {
.social-media-grid {
grid-template-columns: 1fr;
}
}
</style>
}
@section Scripts {
<script>
$(document).ready(function() {
loadPosts();
});
function loadPosts() {
$.get('@Url.Action("GetPostDetails", "SocialMedia")', function(data) {
if (data.success) {
renderPosts(data.posts);
}
});
}
function renderPosts(posts) {
const tbody = $('#postsTable tbody');
tbody.empty();
posts.forEach(post => {
const row = $(`
<tr data-post-id="${post.id}">
<td>
<button class="btn btn-sm btn-link expand-btn">
<i class="bi bi-chevron-right"></i>
</button>
</td>
<td>${post.channel}</td>
<td>${post.title}</td>
<td>${formatDate(post.nextScheduledDate)}</td>
<td>${formatDate(post.lastUpdate)}</td>
</tr>
`);
tbody.append(row);
});
}
function formatDate(dateString) {
if (!dateString) return '--/--/----';
const date = new Date(dateString);
return date.toLocaleDateString('pt-BR');
}
function getStatusBadgeClass(status) {
const statusMap = {
'publicado': 'status-publicado',
'não selecionado': 'status-nao-selecionado',
'gerado': 'status-gerado',
'agendado': 'status-agendado',
'automatizado': 'status-automatizado'
};
return statusMap[status.toLowerCase()] || '';
}
$(document).on('click', '.expand-btn', function() {
const row = $(this).closest('tr');
const postId = row.data('post-id');
const icon = $(this).find('i');
if (row.next().hasClass('details-row')) {
// Fechar detalhes
row.next().remove();
row.removeClass('expanded');
icon.removeClass('bi-chevron-down').addClass('bi-chevron-right');
} else {
// Abrir detalhes
$.get(`@Url.Action("GetSocialMediaStatus", "SocialMedia")?postId=${postId}`, function(data) {
if (data.success) {
const template = document.getElementById('detailsTemplate');
const detailsContent = template.content.cloneNode(true);
// Preencher os status
data.socialMediaStatus.forEach(status => {
const platformElement = $(detailsContent).find(`[data-platform="${status.platform}"]`);
platformElement.find('.status-placeholder')
.text(status.status)
.addClass(getStatusBadgeClass(status.status));
platformElement.find('.date-placeholder').text(formatDate(status.nextScheduledDate));
});
const detailsRow = $('<tr class="details-row">').append(
$('<td colspan="5">').append(detailsContent)
);
row.addClass('expanded');
row.after(detailsRow);
icon.removeClass('bi-chevron-right').addClass('bi-chevron-down');
}
});
}
});
</script>
}

View File

@ -0,0 +1,347 @@
@using Postall.Models
@model PostViewModel
@{
ViewData["Title"] = "Gerenciar Postagens";
}
<div class="container-fluid">
<div class="row">
<!-- Menu lateral em desktop / Menu superior em mobile -->
<div class="col-md-3 col-12 mb-3">
<div class="card">
<div class="card-header">
<h5>Plataformas</h5>
</div>
<div class="card-body">
<div class="d-flex flex-wrap gap-2">
<button type="button" class="btn btn-outline-primary platform-btn" data-platform="facebook">
Facebook
</button>
<button type="button" class="btn btn-outline-primary platform-btn" data-platform="instagram">
Instagram
</button>
<button type="button" class="btn btn-outline-primary platform-btn" data-platform="twitter">
Twitter/X
</button>
<button type="button" class="btn btn-outline-primary platform-btn" data-platform="whatsapp">
WhatsApp
</button>
<button type="button" class="btn btn-outline-primary platform-btn" data-platform="telegram">
Telegram
</button>
</div>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h5>Agendamento</h5>
</div>
<div class="card-body">
<div class="form-check mb-2">
<input class="form-check-input" type="radio" name="schedule" id="manual" checked>
<label class="form-check-label" for="manual">Manual</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="radio" name="schedule" id="weekly">
<label class="form-check-label" for="weekly">Semanal</label>
</div>
<div id="weeklyOptions" class="d-none">
<div class="mb-2">
<div class="form-check">
<input class="form-check-input weekday-check" type="checkbox" value="1" id="monday">
<label class="form-check-label" for="monday">Segunda-feira</label>
</div>
<div class="form-check">
<input class="form-check-input weekday-check" type="checkbox" value="2" id="tuesday">
<label class="form-check-label" for="tuesday">Terça-feira</label>
</div>
<div class="form-check">
<input class="form-check-input weekday-check" type="checkbox" value="3" id="wednesday">
<label class="form-check-label" for="wednesday">Quarta-feira</label>
</div>
<div class="form-check">
<input class="form-check-input weekday-check" type="checkbox" value="4" id="thursday">
<label class="form-check-label" for="thursday">Quinta-feira</label>
</div>
<div class="form-check">
<input class="form-check-input weekday-check" type="checkbox" value="5" id="friday">
<label class="form-check-label" for="friday">Sexta-feira</label>
</div>
<div class="form-check">
<input class="form-check-input weekday-check" type="checkbox" value="6" id="saturday">
<label class="form-check-label" for="saturday">Sábado</label>
</div>
<div class="form-check">
<input class="form-check-input weekday-check" type="checkbox" value="0" id="sunday">
<label class="form-check-label" for="sunday">Domingo</label>
</div>
</div>
<input type="time" class="form-control" id="scheduleTime">
</div>
</div>
</div>
</div>
<!-- Preview lateral -->
<div class="col-md-6 col-12">
<div class="card">
<div class="card-header">
<h5>Video</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Canal</label>
<input type="text" class="form-control" id="previewChannel" readonly>
</div>
<div class="mb-3">
<label class="form-label">URL do Vídeo</label>
<input type="text" class="form-control" id="previewVideoUrl">
</div>
<div class="preview-content">
<h6>Título</h6>
<p id="previewTitle"></p>
<h6>Conteúdo</h6>
<p id="previewContent"></p>
<div id="previewImage" class="mt-2"></div>
</div>
</div>
<div class="card-footer">
<button type="button" class="btn btn-secondary me-2" onclick="savePlatformDraft()">Gerar com IA</button>
</div>
</div>
<div class="card">
<div class="card-header">
<h5>Nova Postagem</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="postTitle" class="form-label">Título</label>
<input type="text" class="form-control" id="postTitle">
</div>
<div class="mb-3">
<label for="postContent" class="form-label">Conteúdo</label>
<textarea class="form-control" id="postContent" rows="5"></textarea>
</div>
<div class="mb-3">
<label for="postImage" class="form-label">Imagem</label>
<input type="file" class="form-control" id="postImage" accept="image/*">
</div>
<div class="preview-image mb-3 d-none">
<img id="imagePreview" src="#" alt="Preview" class="img-fluid">
</div>
</div>
<div class="card-footer">
<button type="button" class="btn btn-secondary me-2" onclick="saveDraft()">Salvar Rascunho</button>
<button type="button" class="btn btn-primary" onclick="publishPost()">Publicar</button>
</div>
</div>
</div>
</div>
</div>
@section Scripts {
<script>
let selectedPlatform = null;
// Gestão das plataformas
document.querySelectorAll('.platform-btn').forEach(btn => {
btn.addEventListener('click', async function() {
const platform = this.dataset.platform;
// Reset outros botões
document.querySelectorAll('.platform-btn').forEach(b =>
b.classList.replace('btn-primary', 'btn-outline-primary'));
// Ativar botão selecionado
this.classList.replace('btn-outline-primary', 'btn-primary');
selectedPlatform = platform;
// Carregar dados da plataforma
await loadPlatformData(platform);
});
});
async function loadPlatformData(platform) {
try {
const response = await fetch(`/api/socialMedia/platformData/${platform}`);
const data = await response.json();
// Atualizar preview
document.getElementById('previewChannel').value = data.channel;
document.getElementById('previewVideoUrl').value = data.videoUrl;
document.getElementById('previewTitle').textContent = data.title;
document.getElementById('previewContent').textContent = data.content;
if (data.imageUrl) {
document.getElementById('previewImage').innerHTML =
`<img src="${data.imageUrl}" class="img-fluid" alt="Preview">`;
}
} catch (error) {
console.error('Erro ao carregar dados da plataforma:', error);
}
}
// Mostrar/ocultar opções de agendamento semanal
document.getElementById('weekly').addEventListener('change', function() {
document.getElementById('weeklyOptions').classList.remove('d-none');
});
document.getElementById('manual').addEventListener('change', function() {
document.getElementById('weeklyOptions').classList.add('d-none');
});
// Preview da imagem
document.getElementById('postImage').addEventListener('change', function(e) {
if (e.target.files && e.target.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('imagePreview').src = e.target.result;
document.querySelector('.preview-image').classList.remove('d-none');
};
reader.readAsDataURL(e.target.files[0]);
}
});
function getSelectedPlatform() {
return selectedPlatform;
}
function getSelectedWeekDays() {
return Array.from(document.querySelectorAll('.weekday-check:checked'))
.map(checkbox => parseInt(checkbox.value));
}
async function generateAIContent() {
if (!selectedPlatform) {
alert('Selecione uma plataforma primeiro!');
return;
}
try {
const response = await fetch('/api/socialMedia/generateContent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
platform: selectedPlatform,
videoUrl: document.getElementById('previewVideoUrl').value
})
});
const data = await response.json();
if (data.success) {
document.getElementById('previewTitle').textContent = data.title;
document.getElementById('previewContent').textContent = data.content;
} else {
alert('Erro ao gerar conteúdo: ' + data.message);
}
} catch (error) {
console.error('Erro ao gerar conteúdo:', error);
alert('Erro ao gerar conteúdo');
}
}
async function savePlatformDraft() {
if (!selectedPlatform) {
alert('Selecione uma plataforma primeiro!');
return;
}
const data = {
platform: selectedPlatform,
title: document.getElementById('previewTitle').textContent,
content: document.getElementById('previewContent').textContent,
videoUrl: document.getElementById('previewVideoUrl').value
};
try {
const response = await fetch('/api/socialMedia/savePlatformDraft', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
alert('Rascunho salvo com sucesso!');
} else {
alert('Erro ao salvar rascunho: ' + result.message);
}
} catch (error) {
console.error('Erro ao salvar rascunho:', error);
alert('Erro ao salvar rascunho');
}
}
function publishPost() {
const isWeekly = document.getElementById('weekly').checked;
const data = {
title: document.getElementById('postTitle').value,
content: document.getElementById('postContent').value,
platform: getSelectedPlatform()
};
if (isWeekly) {
data.isManual = false;
data.weekDays = getSelectedWeekDays();
data.time = document.getElementById('scheduleTime').value;
$.ajax({
url: '@Url.Action("SchedulePost", "SocialMedia")',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(data),
success: function(response) {
if (response.success) {
alert('Post agendado com sucesso!');
} else {
alert('Erro ao agendar post: ' + response.message);
}
}
});
} else {
$.ajax({
url: '@Url.Action("PublishPost", "SocialMedia")',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(data),
success: function(response) {
if (response.success) {
alert('Post publicado com sucesso!');
} else {
alert('Erro ao publicar post: ' + response.message);
}
}
});
}
}
</script>
}
<style>
.platform-btn {
min-width: 120px;
margin-bottom: 8px;
}
.preview-image img {
max-height: 300px;
object-fit: contain;
}
@@media (max-width: 768px) {
.container-fluid {
padding: 10px;
}
}
.card {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
</style>

View File

@ -18,8 +18,8 @@
"AppSecret": "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"
},
"Facebook": {
"AppId": "seu_app_id_aqui",
"AppSecret": "seu_app_secret_aqui"
"AppId": "963281005306692",
"AppSecret": "575839ccbb36d1457715f1f6dd0a8db9"
}
}
}