Compare commits
No commits in common. "acb5bbf74bcff6b5c5ba6f29db4dcc20974450ee" and "dca698e4c4c4c79fe40ecfd1548c2c220bc71b02" have entirely different histories.
acb5bbf74b
...
dca698e4c4
@ -53,9 +53,7 @@
|
|||||||
"Bash(git log:*)",
|
"Bash(git log:*)",
|
||||||
"Bash(git mv:*)",
|
"Bash(git mv:*)",
|
||||||
"Bash(python3 -c \"import sys,json; [print\\(json.loads\\(l\\).get\\('MessageTemplate',''\\) + ' ' + str\\(json.loads\\(l\\).get\\('Level',''\\)\\)\\) for l in sys.stdin if 'categ' in l.lower\\(\\) or 'Error' in l or 'error' in l.lower\\(\\)]\")",
|
"Bash(python3 -c \"import sys,json; [print\\(json.loads\\(l\\).get\\('MessageTemplate',''\\) + ' ' + str\\(json.loads\\(l\\).get\\('Level',''\\)\\)\\) for l in sys.stdin if 'categ' in l.lower\\(\\) or 'Error' in l or 'error' in l.lower\\(\\)]\")",
|
||||||
"Bash(python3 -c \" import sys, json for line in sys.stdin: line = line.strip\\(\\).rstrip\\(','\\) try: obj = json.loads\\(line\\) lvl = obj.get\\('Level',''\\) msg = obj.get\\('MessageTemplate',''\\) or obj.get\\('message',''\\) if lvl in \\('Error','Warning','Fatal'\\) or 'categ' in msg.lower\\(\\) or 'initial' in msg.lower\\(\\): print\\(f'[{lvl}] {msg}'\\) except: pass \")",
|
"Bash(python3 -c \" import sys, json for line in sys.stdin: line = line.strip\\(\\).rstrip\\(','\\) try: obj = json.loads\\(line\\) lvl = obj.get\\('Level',''\\) msg = obj.get\\('MessageTemplate',''\\) or obj.get\\('message',''\\) if lvl in \\('Error','Warning','Fatal'\\) or 'categ' in msg.lower\\(\\) or 'initial' in msg.lower\\(\\): print\\(f'[{lvl}] {msg}'\\) except: pass \")"
|
||||||
"Bash(grep -E \"\\\\.\\(cs|csproj\\)$\")",
|
|
||||||
"Bash(git -C /c/vscode/bcards log --oneline --follow src/BCards.Web/Views/Admin/CreatePage.cshtml)"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"enableAllProjectMcpServers": false
|
"enableAllProjectMcpServers": false
|
||||||
|
|||||||
@ -482,10 +482,182 @@ public class AdminController : Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
[Route("CreatePage")]
|
[Route("CreatePage")]
|
||||||
public IActionResult CreatePage()
|
public async Task<IActionResult> CreatePage(CreatePageViewModel model)
|
||||||
{
|
{
|
||||||
return RedirectToAction("ManagePage", new { id = "new" });
|
var user = await _authService.GetCurrentUserAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
return RedirectToAction("Login", "Auth");
|
||||||
|
|
||||||
|
// Check if user already has a page
|
||||||
|
var existingPage = await _userPageService.GetUserPageAsync(user.Id);
|
||||||
|
if (existingPage != null)
|
||||||
|
return RedirectToAction("EditPage");
|
||||||
|
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
var categories = await _categoryService.GetAllCategoriesAsync();
|
||||||
|
var themes = await _themeService.GetAvailableThemesAsync();
|
||||||
|
ViewBag.Categories = categories;
|
||||||
|
ViewBag.Themes = themes;
|
||||||
|
return View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate slug if not provided
|
||||||
|
if (string.IsNullOrEmpty(model.Slug))
|
||||||
|
{
|
||||||
|
model.Slug = await _userPageService.GenerateSlugAsync(model.Category, model.DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if slug is available
|
||||||
|
if (!await _userPageService.ValidateSlugAsync(model.Category, model.Slug))
|
||||||
|
{
|
||||||
|
ModelState.AddModelError("Slug", "Esta URL já está em uso. Tente outra.");
|
||||||
|
var categories = await _categoryService.GetAllCategoriesAsync();
|
||||||
|
var themes = await _themeService.GetAvailableThemesAsync();
|
||||||
|
ViewBag.Categories = categories;
|
||||||
|
ViewBag.Themes = themes;
|
||||||
|
return View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user can create the requested number of links
|
||||||
|
var activeLinksCount = model.Links?.Count ?? 0;
|
||||||
|
if (!await _userPageService.CanCreateLinksAsync(user.Id, activeLinksCount))
|
||||||
|
{
|
||||||
|
ModelState.AddModelError("", "Você excedeu o limite de links do seu plano atual.");
|
||||||
|
var categories = await _categoryService.GetAllCategoriesAsync();
|
||||||
|
var themes = await _themeService.GetAvailableThemesAsync();
|
||||||
|
ViewBag.Categories = categories;
|
||||||
|
ViewBag.Themes = themes;
|
||||||
|
return View(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert ViewModel to UserPage
|
||||||
|
var userPage = new UserPage
|
||||||
|
{
|
||||||
|
UserId = user.Id,
|
||||||
|
DisplayName = model.DisplayName,
|
||||||
|
Category = model.Category,
|
||||||
|
BusinessType = model.BusinessType,
|
||||||
|
Bio = model.Bio,
|
||||||
|
Slug = model.Slug,
|
||||||
|
Theme = await _themeService.GetThemeByNameAsync(model.SelectedTheme) ?? _themeService.GetDefaultTheme(),
|
||||||
|
Links = model.Links?.Select(l => new LinkItem
|
||||||
|
{
|
||||||
|
Title = l.Title,
|
||||||
|
Url = l.Url,
|
||||||
|
Description = l.Description,
|
||||||
|
Icon = l.Icon,
|
||||||
|
IsActive = true,
|
||||||
|
Order = model.Links.IndexOf(l)
|
||||||
|
}).ToList() ?? new List<LinkItem>()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add social media links
|
||||||
|
var socialLinks = new List<LinkItem>();
|
||||||
|
if (!string.IsNullOrEmpty(model.WhatsAppNumber))
|
||||||
|
{
|
||||||
|
socialLinks.Add(new LinkItem
|
||||||
|
{
|
||||||
|
Title = "WhatsApp",
|
||||||
|
Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}",
|
||||||
|
Icon = "fab fa-whatsapp",
|
||||||
|
IsActive = true,
|
||||||
|
Order = userPage.Links.Count + socialLinks.Count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(model.FacebookUrl))
|
||||||
|
{
|
||||||
|
socialLinks.Add(new LinkItem
|
||||||
|
{
|
||||||
|
Title = "Facebook",
|
||||||
|
Url = model.FacebookUrl,
|
||||||
|
Icon = "fab fa-facebook",
|
||||||
|
IsActive = true,
|
||||||
|
Order = userPage.Links.Count + socialLinks.Count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(model.TwitterUrl))
|
||||||
|
{
|
||||||
|
socialLinks.Add(new LinkItem
|
||||||
|
{
|
||||||
|
Title = "X / Twitter",
|
||||||
|
Url = model.TwitterUrl,
|
||||||
|
Icon = "fab fa-x-twitter",
|
||||||
|
IsActive = true,
|
||||||
|
Order = userPage.Links.Count + socialLinks.Count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(model.InstagramUrl))
|
||||||
|
{
|
||||||
|
socialLinks.Add(new LinkItem
|
||||||
|
{
|
||||||
|
Title = "Instagram",
|
||||||
|
Url = model.InstagramUrl,
|
||||||
|
Icon = "fab fa-instagram",
|
||||||
|
IsActive = true,
|
||||||
|
Order = userPage.Links.Count + socialLinks.Count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(model.TiktokUrl))
|
||||||
|
{
|
||||||
|
socialLinks.Add(new LinkItem
|
||||||
|
{
|
||||||
|
Title = "TikTok",
|
||||||
|
Url = model.TiktokUrl,
|
||||||
|
Icon = "fab fa-tiktok",
|
||||||
|
IsActive = true,
|
||||||
|
Order = userPage.Links.Count + socialLinks.Count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(model.PinterestUrl))
|
||||||
|
{
|
||||||
|
socialLinks.Add(new LinkItem
|
||||||
|
{
|
||||||
|
Title = "Pinterest",
|
||||||
|
Url = model.PinterestUrl,
|
||||||
|
Icon = "fab fa-pinterest",
|
||||||
|
IsActive = true,
|
||||||
|
Order = userPage.Links.Count + socialLinks.Count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(model.DiscordUrl))
|
||||||
|
{
|
||||||
|
socialLinks.Add(new LinkItem
|
||||||
|
{
|
||||||
|
Title = "Discord",
|
||||||
|
Url = model.DiscordUrl,
|
||||||
|
Icon = "fab fa-discord",
|
||||||
|
IsActive = true,
|
||||||
|
Order = userPage.Links.Count + socialLinks.Count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(model.KawaiUrl))
|
||||||
|
{
|
||||||
|
socialLinks.Add(new LinkItem
|
||||||
|
{
|
||||||
|
Title = "Kawai",
|
||||||
|
Url = model.KawaiUrl,
|
||||||
|
Icon = "fas fa-heart",
|
||||||
|
IsActive = true,
|
||||||
|
Order = userPage.Links.Count + socialLinks.Count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
userPage.Links.AddRange(socialLinks);
|
||||||
|
|
||||||
|
await _userPageService.CreatePageAsync(userPage);
|
||||||
|
|
||||||
|
TempData["Success"] = "Página criada com sucesso!";
|
||||||
|
return RedirectToAction("Dashboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -665,16 +837,6 @@ public class AdminController : Controller
|
|||||||
FileSize = d.FileSize,
|
FileSize = d.FileSize,
|
||||||
UploadedAt = d.UploadedAt
|
UploadedAt = d.UploadedAt
|
||||||
}).ToList() ?? new List<ManageDocumentViewModel>(),
|
}).ToList() ?? new List<ManageDocumentViewModel>(),
|
||||||
// Social media fields — extracted from Links so the edit form pre-fills correctly
|
|
||||||
WhatsAppNumber = page.Links?.FirstOrDefault(l => l.Icon?.Contains("whatsapp") == true)?.Url
|
|
||||||
?.Replace("https://wa.me/", "").Replace("whatsapp://", "") ?? string.Empty,
|
|
||||||
FacebookUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("facebook") == true)?.Url ?? string.Empty,
|
|
||||||
InstagramUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("instagram") == true)?.Url ?? string.Empty,
|
|
||||||
TwitterUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("twitter") == true)?.Url ?? string.Empty,
|
|
||||||
TiktokUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("tiktok") == true)?.Url ?? string.Empty,
|
|
||||||
PinterestUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("pinterest") == true)?.Url ?? string.Empty,
|
|
||||||
DiscordUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("discord") == true)?.Url ?? string.Empty,
|
|
||||||
KawaiUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("kawai") == true)?.Url ?? string.Empty,
|
|
||||||
AvailableCategories = categories,
|
AvailableCategories = categories,
|
||||||
AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(),
|
AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(),
|
||||||
MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(),
|
MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(),
|
||||||
@ -730,13 +892,10 @@ public class AdminController : Controller
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(model.WhatsAppNumber))
|
if (!string.IsNullOrEmpty(model.WhatsAppNumber))
|
||||||
{
|
{
|
||||||
var whatsappDigits = model.WhatsAppNumber
|
|
||||||
.Replace("https://wa.me/", "").Replace("whatsapp://", "")
|
|
||||||
.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "");
|
|
||||||
socialLinks.Add(new LinkItem
|
socialLinks.Add(new LinkItem
|
||||||
{
|
{
|
||||||
Title = "WhatsApp",
|
Title = "WhatsApp",
|
||||||
Url = $"https://wa.me/{whatsappDigits}",
|
Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}",
|
||||||
Icon = "fab fa-whatsapp",
|
Icon = "fab fa-whatsapp",
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
Order = currentOrder++
|
Order = currentOrder++
|
||||||
@ -1082,13 +1241,10 @@ public class AdminController : Controller
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(model.WhatsAppNumber))
|
if (!string.IsNullOrEmpty(model.WhatsAppNumber))
|
||||||
{
|
{
|
||||||
var whatsappDigits = model.WhatsAppNumber
|
|
||||||
.Replace("https://wa.me/", "").Replace("whatsapp://", "")
|
|
||||||
.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "");
|
|
||||||
socialLinks.Add(new LinkItem
|
socialLinks.Add(new LinkItem
|
||||||
{
|
{
|
||||||
Title = "WhatsApp",
|
Title = "WhatsApp",
|
||||||
Url = $"https://wa.me/{whatsappDigits}",
|
Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}",
|
||||||
Icon = "fab fa-whatsapp",
|
Icon = "fab fa-whatsapp",
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
Order = currentOrder++
|
Order = currentOrder++
|
||||||
|
|||||||
58
src/BCards.Web/ViewModels/CreatePageViewModel.cs
Normal file
58
src/BCards.Web/ViewModels/CreatePageViewModel.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace BCards.Web.ViewModels;
|
||||||
|
|
||||||
|
public class CreatePageViewModel
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "Nome é obrigatório")]
|
||||||
|
[StringLength(50, ErrorMessage = "Nome deve ter no máximo 50 caracteres")]
|
||||||
|
public string DisplayName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "Categoria é obrigatória")]
|
||||||
|
public string Category { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "Tipo de negócio é obrigatório")]
|
||||||
|
public string BusinessType { get; set; } = "individual";
|
||||||
|
|
||||||
|
[StringLength(3000, ErrorMessage = "Bio deve ter no máximo 3000 caracteres")]
|
||||||
|
public string Bio { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "Tema é obrigatório")]
|
||||||
|
public string SelectedTheme { get; set; } = "minimalist";
|
||||||
|
|
||||||
|
public string WhatsAppNumber { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string FacebookUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string TwitterUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string InstagramUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string TiktokUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string PinterestUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string DiscordUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string KawaiUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public List<CreateLinkViewModel> Links { get; set; } = new();
|
||||||
|
|
||||||
|
public string Slug { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CreateLinkViewModel
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "Título é obrigatório")]
|
||||||
|
[StringLength(50, ErrorMessage = "Título deve ter no máximo 50 caracteres")]
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "URL é obrigatória")]
|
||||||
|
[Url(ErrorMessage = "URL inválida")]
|
||||||
|
public string Url { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[StringLength(100, ErrorMessage = "Descrição deve ter no máximo 100 caracteres")]
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Icon { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
619
src/BCards.Web/Views/Admin/CreatePage.cshtml
Normal file
619
src/BCards.Web/Views/Admin/CreatePage.cshtml
Normal file
@ -0,0 +1,619 @@
|
|||||||
|
@model BCards.Web.ViewModels.CreatePageViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Criar Página";
|
||||||
|
Layout = "_Layout";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-8 mx-auto">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h4 class="mb-0">
|
||||||
|
<i class="fas fa-magic"></i>
|
||||||
|
Criar Sua Página de Links
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Progress Bar -->
|
||||||
|
<div class="progress mb-4" style="height: 8px;">
|
||||||
|
<div class="progress-bar" role="progressbar" style="width: 20%" id="wizardProgress"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-action="CreatePage" method="post" id="createPageForm">
|
||||||
|
|
||||||
|
<!-- Step 1: Informações Básicas -->
|
||||||
|
<div class="wizard-step" id="step1">
|
||||||
|
<h5 class="step-title">
|
||||||
|
<span class="step-number">1</span>
|
||||||
|
Informações Básicas
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label asp-for="DisplayName" class="form-label">Nome da Página</label>
|
||||||
|
<input asp-for="DisplayName" class="form-control" placeholder="Ex: João Silva">
|
||||||
|
<span asp-validation-for="DisplayName" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label asp-for="Category" class="form-label">Categoria</label>
|
||||||
|
<select asp-for="Category" class="form-select">
|
||||||
|
<option value="">Selecione uma categoria</option>
|
||||||
|
@foreach (var category in ViewBag.Categories as List<BCards.Web.Models.Category> ?? new List<BCards.Web.Models.Category>())
|
||||||
|
{
|
||||||
|
<option value="@category.Name">@category.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<span asp-validation-for="Category" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label asp-for="BusinessType" class="form-label">Tipo</label>
|
||||||
|
<select asp-for="BusinessType" class="form-select">
|
||||||
|
<option value="individual">Pessoa Física</option>
|
||||||
|
<option value="company">Empresa</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="slugPreview" class="form-label">URL da Página</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">page/</span>
|
||||||
|
<span class="input-group-text" id="categorySlug">categoria</span>
|
||||||
|
<span class="input-group-text">/</span>
|
||||||
|
<input type="text" class="form-control" id="slugPreview" readonly>
|
||||||
|
<input asp-for="Slug" type="hidden">
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">URL gerada automaticamente</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label asp-for="Bio" class="form-label">Bio/Descrição</label>
|
||||||
|
<textarea asp-for="Bio" class="form-control" rows="8" maxlength="3000" placeholder="Uma breve descrição sobre você ou sua empresa..."></textarea>
|
||||||
|
<span asp-validation-for="Bio" class="text-danger"></span>
|
||||||
|
<div class="form-text">Máximo 3000 caracteres</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 2: Seleção de Tema -->
|
||||||
|
<div class="wizard-step d-none" id="step2">
|
||||||
|
<h5 class="step-title">
|
||||||
|
<span class="step-number">2</span>
|
||||||
|
Escolha Seu Tema Visual
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var theme in ViewBag.Themes as List<BCards.Web.Models.PageTheme> ?? new List<BCards.Web.Models.PageTheme>())
|
||||||
|
{
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="theme-card" data-theme="@theme.Name.ToLower()">
|
||||||
|
<div class="theme-preview" style="background: @theme.BackgroundColor; color: @theme.TextColor;">
|
||||||
|
<div class="theme-header" style="background-color: @theme.PrimaryColor;">
|
||||||
|
<div class="theme-avatar"></div>
|
||||||
|
<h6>@theme.Name</h6>
|
||||||
|
</div>
|
||||||
|
<div class="theme-links">
|
||||||
|
<div class="theme-link" style="background-color: @theme.PrimaryColor;"></div>
|
||||||
|
<div class="theme-link" style="background-color: @theme.SecondaryColor;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="theme-name">
|
||||||
|
@theme.Name
|
||||||
|
@if (theme.IsPremium)
|
||||||
|
{
|
||||||
|
<span class="badge bg-warning">Premium</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input asp-for="SelectedTheme" type="hidden">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 3: Links Principais -->
|
||||||
|
<div class="wizard-step d-none" id="step3">
|
||||||
|
<h5 class="step-title">
|
||||||
|
<span class="step-number">3</span>
|
||||||
|
Links Principais
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div id="linksContainer">
|
||||||
|
<!-- Links will be added dynamically -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-outline-primary" id="addLinkBtn">
|
||||||
|
<i class="fas fa-plus"></i> Adicionar Link
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 4: Redes Sociais -->
|
||||||
|
<div class="wizard-step d-none" id="step4">
|
||||||
|
<h5 class="step-title">
|
||||||
|
<span class="step-number">4</span>
|
||||||
|
Redes Sociais
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label asp-for="WhatsAppNumber" class="form-label">
|
||||||
|
<i class="fab fa-whatsapp text-success"></i>
|
||||||
|
WhatsApp
|
||||||
|
</label>
|
||||||
|
<input asp-for="WhatsAppNumber" class="form-control" placeholder="+55 11 99999-9999">
|
||||||
|
<span asp-validation-for="WhatsAppNumber" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label asp-for="FacebookUrl" class="form-label">
|
||||||
|
<i class="fab fa-facebook text-primary"></i>
|
||||||
|
Facebook
|
||||||
|
</label>
|
||||||
|
<input asp-for="FacebookUrl" class="form-control" placeholder="https://facebook.com/seu-perfil">
|
||||||
|
<span asp-validation-for="FacebookUrl" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label asp-for="TwitterUrl" class="form-label">
|
||||||
|
<i class="fab fa-x-twitter"></i>
|
||||||
|
X / Twitter
|
||||||
|
</label>
|
||||||
|
<input asp-for="TwitterUrl" class="form-control" placeholder="https://x.com/seu-perfil">
|
||||||
|
<span asp-validation-for="TwitterUrl" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label asp-for="InstagramUrl" class="form-label">
|
||||||
|
<i class="fab fa-instagram text-danger"></i>
|
||||||
|
Instagram
|
||||||
|
</label>
|
||||||
|
<input asp-for="InstagramUrl" class="form-control" placeholder="https://instagram.com/seu-perfil">
|
||||||
|
<span asp-validation-for="InstagramUrl" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 5: Preview e Finalização -->
|
||||||
|
<div class="wizard-step d-none" id="step5">
|
||||||
|
<h5 class="step-title">
|
||||||
|
<span class="step-number">5</span>
|
||||||
|
Preview e Finalização
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div class="preview-container">
|
||||||
|
<div class="preview-phone">
|
||||||
|
<div class="preview-screen" id="previewScreen">
|
||||||
|
<!-- Preview will be generated here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mt-4">
|
||||||
|
<p class="text-muted">Sua página estará disponível em:</p>
|
||||||
|
<strong id="finalUrl">page/categoria/seu-slug</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Buttons -->
|
||||||
|
<div class="wizard-navigation mt-4">
|
||||||
|
<button type="button" class="btn btn-secondary" id="prevBtn" style="display: none;">
|
||||||
|
<i class="fas fa-arrow-left"></i> Anterior
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-primary float-end" id="nextBtn">
|
||||||
|
Próximo <i class="fas fa-arrow-right"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-success float-end d-none" id="submitBtn">
|
||||||
|
<i class="fas fa-check"></i> Criar Página
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wizard-step {
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-title {
|
||||||
|
color: #495057;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 2px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-number {
|
||||||
|
display: inline-block;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
text-align: center;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-card {
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-card:hover,
|
||||||
|
.theme-card.selected {
|
||||||
|
border-color: #007bff;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview {
|
||||||
|
height: 120px;
|
||||||
|
position: relative;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-avatar {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-header h6 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-links {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-link {
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-name {
|
||||||
|
padding: 0.75rem;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-input-group {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-phone {
|
||||||
|
width: 300px;
|
||||||
|
height: 400px;
|
||||||
|
border: 8px solid #333;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: #000;
|
||||||
|
padding: 20px 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-screen {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wizard-navigation {
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let currentStep = 1;
|
||||||
|
const totalSteps = 5;
|
||||||
|
let linkCount = 0;
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
initializeWizard();
|
||||||
|
|
||||||
|
// Generate slug when name or category changes
|
||||||
|
$('#DisplayName, #Category').on('input change', function() {
|
||||||
|
generateSlug();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Theme selection
|
||||||
|
$('.theme-card').on('click', function() {
|
||||||
|
$('.theme-card').removeClass('selected');
|
||||||
|
$(this).addClass('selected');
|
||||||
|
const themeName = $(this).data('theme');
|
||||||
|
$('#SelectedTheme').val(themeName);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
$('#nextBtn').on('click', function() {
|
||||||
|
if (validateCurrentStep()) {
|
||||||
|
nextStep();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#prevBtn').on('click', function() {
|
||||||
|
prevStep();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add link functionality
|
||||||
|
$('#addLinkBtn').on('click', function() {
|
||||||
|
addLinkInput();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Form submission
|
||||||
|
$('#createPageForm').on('submit', function(e) {
|
||||||
|
generateLinksData();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function initializeWizard() {
|
||||||
|
updateProgressBar();
|
||||||
|
updateNavigationButtons();
|
||||||
|
addLinkInput(); // Add first link input
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextStep() {
|
||||||
|
if (currentStep < totalSteps) {
|
||||||
|
$(`#step${currentStep}`).addClass('d-none');
|
||||||
|
currentStep++;
|
||||||
|
$(`#step${currentStep}`).removeClass('d-none');
|
||||||
|
|
||||||
|
if (currentStep === 5) {
|
||||||
|
generatePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgressBar();
|
||||||
|
updateNavigationButtons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevStep() {
|
||||||
|
if (currentStep > 1) {
|
||||||
|
$(`#step${currentStep}`).addClass('d-none');
|
||||||
|
currentStep--;
|
||||||
|
$(`#step${currentStep}`).removeClass('d-none');
|
||||||
|
updateProgressBar();
|
||||||
|
updateNavigationButtons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProgressBar() {
|
||||||
|
const progress = (currentStep / totalSteps) * 100;
|
||||||
|
$('#wizardProgress').css('width', progress + '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNavigationButtons() {
|
||||||
|
$('#prevBtn').toggle(currentStep > 1);
|
||||||
|
|
||||||
|
if (currentStep === totalSteps) {
|
||||||
|
$('#nextBtn').addClass('d-none');
|
||||||
|
$('#submitBtn').removeClass('d-none');
|
||||||
|
} else {
|
||||||
|
$('#nextBtn').removeClass('d-none');
|
||||||
|
$('#submitBtn').addClass('d-none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCurrentStep() {
|
||||||
|
let isValid = true;
|
||||||
|
|
||||||
|
switch (currentStep) {
|
||||||
|
case 1:
|
||||||
|
if (!$('#DisplayName').val() || !$('#Category').val()) {
|
||||||
|
alert('Por favor, preencha o nome e a categoria.');
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (!$('#SelectedTheme').val()) {
|
||||||
|
alert('Por favor, selecione um tema.');
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSlug() {
|
||||||
|
const name = $('#DisplayName').val();
|
||||||
|
const category = $('#Category').val();
|
||||||
|
|
||||||
|
if (name && category) {
|
||||||
|
$.post('/Admin/GenerateSlug', { category: category, name: name })
|
||||||
|
.done(function(data) {
|
||||||
|
$('#Slug').val(data.slug);
|
||||||
|
$('#slugPreview').val(data.slug);
|
||||||
|
$('#categorySlug').text(category);
|
||||||
|
$('#finalUrl').text(`page/${category}/${data.slug}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLinkInput() {
|
||||||
|
linkCount++;
|
||||||
|
const linkHtml = `
|
||||||
|
<div class="link-input-group" data-link="${linkCount}">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<h6 class="mb-0">Link ${linkCount}</h6>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-danger remove-link-btn">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Título</label>
|
||||||
|
<input type="text" class="form-control link-title" placeholder="Ex: Meu Site">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">URL</label>
|
||||||
|
<input type="url" class="form-control link-url" placeholder="https://exemplo.com">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Descrição (opcional)</label>
|
||||||
|
<input type="text" class="form-control link-description" placeholder="Breve descrição do link">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
$('#linksContainer').append(linkHtml);
|
||||||
|
|
||||||
|
// Add remove functionality
|
||||||
|
$('.remove-link-btn').off('click').on('click', function() {
|
||||||
|
$(this).closest('.link-input-group').remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateLinksData() {
|
||||||
|
const links = [];
|
||||||
|
$('.link-input-group').each(function() {
|
||||||
|
const title = $(this).find('.link-title').val();
|
||||||
|
const url = $(this).find('.link-url').val();
|
||||||
|
const description = $(this).find('.link-description').val();
|
||||||
|
|
||||||
|
if (title && url) {
|
||||||
|
links.push({
|
||||||
|
Title: title,
|
||||||
|
Url: url,
|
||||||
|
Description: description,
|
||||||
|
Icon: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove existing hidden link inputs
|
||||||
|
$('input[name^="Links["]').remove();
|
||||||
|
|
||||||
|
// Create hidden inputs for links directly in the form
|
||||||
|
links.forEach((link, index) => {
|
||||||
|
$('#createPageForm').append(`
|
||||||
|
<input type="hidden" name="Links[${index}].Title" value="${link.Title}" />
|
||||||
|
<input type="hidden" name="Links[${index}].Url" value="${link.Url}" />
|
||||||
|
<input type="hidden" name="Links[${index}].Description" value="${link.Description}" />
|
||||||
|
<input type="hidden" name="Links[${index}].Icon" value="${link.Icon}" />
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Debug: Log what we're sending
|
||||||
|
console.log('=== DEBUG GENERATELINKSDATA ===');
|
||||||
|
console.log('Links found:', links.length);
|
||||||
|
links.forEach((link, index) => {
|
||||||
|
console.log(`Link ${index}:`, link);
|
||||||
|
});
|
||||||
|
console.log('=== FIM DEBUG ===');
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePreview() {
|
||||||
|
const name = $('#DisplayName').val();
|
||||||
|
const bio = $('#Bio').val();
|
||||||
|
const selectedTheme = $('#SelectedTheme').val();
|
||||||
|
|
||||||
|
let previewHtml = `
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="mb-3">
|
||||||
|
<div style="width: 60px; height: 60px; background-color: #ddd; border-radius: 50%; margin: 0 auto;"></div>
|
||||||
|
</div>
|
||||||
|
<h5 class="mb-2">${name}</h5>
|
||||||
|
<p class="text-muted small mb-3">${bio}</p>
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add links preview
|
||||||
|
$('.link-input-group').each(function() {
|
||||||
|
const title = $(this).find('.link-title').val();
|
||||||
|
if (title) {
|
||||||
|
previewHtml += `<div class="btn btn-primary btn-sm">${title}</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add social media preview
|
||||||
|
if ($('#WhatsAppNumber').val()) {
|
||||||
|
previewHtml += `<div class="btn btn-success btn-sm"><i class="fab fa-whatsapp"></i> WhatsApp</div>`;
|
||||||
|
}
|
||||||
|
if ($('#FacebookUrl').val()) {
|
||||||
|
previewHtml += `<div class="btn btn-primary btn-sm"><i class="fab fa-facebook"></i> Facebook</div>`;
|
||||||
|
}
|
||||||
|
if ($('#TwitterUrl').val()) {
|
||||||
|
previewHtml += `<div class="btn btn-dark btn-sm"><i class="fab fa-x-twitter"></i> X / Twitter</div>`;
|
||||||
|
}
|
||||||
|
if ($('#InstagramUrl').val()) {
|
||||||
|
previewHtml += `<div class="btn btn-danger btn-sm"><i class="fab fa-instagram"></i> Instagram</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewHtml += `</div></div>`;
|
||||||
|
|
||||||
|
$('#previewScreen').html(previewHtml);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||||
|
}
|
||||||
@ -115,54 +115,9 @@
|
|||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label asp-for="Bio" class="form-label">Bio/Descrição</label>
|
<label asp-for="Bio" class="form-label">Bio/Descrição</label>
|
||||||
<div class="md-toolbar border rounded-top border-bottom-0 bg-light px-2 py-1 d-flex gap-1 flex-wrap position-relative">
|
<textarea asp-for="Bio" class="form-control" rows="8" maxlength="3000" placeholder="Uma breve descrição sobre você ou sua empresa..."></textarea>
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary md-btn" data-target="Bio" data-wrap="**" title="Negrito"><b>B</b></button>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary md-btn" data-target="Bio" data-wrap="*" title="Itálico"><i>I</i></button>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary md-list-btn" data-target="Bio" title="Lista">• Lista</button>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary md-link-btn" data-target="Bio" title="Link">🔗 Link</button>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary md-icon-picker-btn" data-target="Bio" title="Inserir ícone">⚙ Ícone</button>
|
|
||||||
<div class="md-icon-picker-panel shadow border rounded bg-white p-2" style="display:none; position:absolute; top:100%; left:0; z-index:9999; width:300px; max-height:260px; overflow-y:auto;">
|
|
||||||
<div class="md-icon-section mb-2">
|
|
||||||
<div class="text-muted small mb-1 fw-semibold">Status</div>
|
|
||||||
<div class="d-flex flex-wrap gap-1">
|
|
||||||
<span class="md-icon-item" title="OK">✅</span><span class="md-icon-item" title="Erro">❌</span><span class="md-icon-item" title="Atenção">⚠️</span><span class="md-icon-item" title="Info">ℹ️</span><span class="md-icon-item" title="Check">✔</span><span class="md-icon-item" title="X">✘</span><span class="md-icon-item" title="Seta">➤</span><span class="md-icon-item" title="Seta direita">→</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="md-icon-section mb-2">
|
|
||||||
<div class="text-muted small mb-1 fw-semibold">Formas & Bullets</div>
|
|
||||||
<div class="d-flex flex-wrap gap-1">
|
|
||||||
<span class="md-icon-item" title="Bullet">•</span><span class="md-icon-item" title="Círculo vazio">○</span><span class="md-icon-item" title="Círculo cheio">●</span><span class="md-icon-item" title="Quadrado cheio">■</span><span class="md-icon-item" title="Quadrado vazio">□</span><span class="md-icon-item" title="Quadrado com check">☑</span><span class="md-icon-item" title="Losango cheio">◆</span><span class="md-icon-item" title="Losango vazio">◇</span><span class="md-icon-item" title="Triângulo">▶</span><span class="md-icon-item" title="Estrela">★</span><span class="md-icon-item" title="Estrela vazia">☆</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="md-icon-section mb-2">
|
|
||||||
<div class="text-muted small mb-1 fw-semibold">Círculos coloridos</div>
|
|
||||||
<div class="d-flex flex-wrap gap-1">
|
|
||||||
<span class="md-icon-item" title="Vermelho">🔴</span><span class="md-icon-item" title="Laranja">🟠</span><span class="md-icon-item" title="Amarelo">🟡</span><span class="md-icon-item" title="Verde">🟢</span><span class="md-icon-item" title="Azul">🔵</span><span class="md-icon-item" title="Roxo">🟣</span><span class="md-icon-item" title="Preto">⚫</span><span class="md-icon-item" title="Branco">⚪</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="md-icon-section mb-2">
|
|
||||||
<div class="text-muted small mb-1 fw-semibold">Negócios & Escritório</div>
|
|
||||||
<div class="d-flex flex-wrap gap-1">
|
|
||||||
<span class="md-icon-item" title="Gráfico barras">📊</span><span class="md-icon-item" title="Gráfico alta">📈</span><span class="md-icon-item" title="Gráfico baixa">📉</span><span class="md-icon-item" title="Prancheta">📋</span><span class="md-icon-item" title="Pin">📌</span><span class="md-icon-item" title="Pasta">📁</span><span class="md-icon-item" title="Maleta">💼</span><span class="md-icon-item" title="Nota">📝</span><span class="md-icon-item" title="Email">📧</span><span class="md-icon-item" title="Telefone">📞</span><span class="md-icon-item" title="Alvo">🎯</span><span class="md-icon-item" title="Troféu">🏆</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="md-icon-section mb-2">
|
|
||||||
<div class="text-muted small mb-1 fw-semibold">Tecnologia</div>
|
|
||||||
<div class="d-flex flex-wrap gap-1">
|
|
||||||
<span class="md-icon-item" title="Notebook">💻</span><span class="md-icon-item" title="Monitor">🖥️</span><span class="md-icon-item" title="Celular">📱</span><span class="md-icon-item" title="Teclado">⌨️</span><span class="md-icon-item" title="Impressora">🖨️</span><span class="md-icon-item" title="Engrenagem">⚙️</span><span class="md-icon-item" title="Lupa">🔍</span><span class="md-icon-item" title="Link">🔗</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="md-icon-section">
|
|
||||||
<div class="text-muted small mb-1 fw-semibold">Finanças & Outros</div>
|
|
||||||
<div class="d-flex flex-wrap gap-1">
|
|
||||||
<span class="md-icon-item" title="Dinheiro">💰</span><span class="md-icon-item" title="Cartão">💳</span><span class="md-icon-item" title="Banco">🏦</span><span class="md-icon-item" title="Chave">🔑</span><span class="md-icon-item" title="Cadeado">🔒</span><span class="md-icon-item" title="Sino">🔔</span><span class="md-icon-item" title="Lâmpada">💡</span><span class="md-icon-item" title="Raio">⚡</span><span class="md-icon-item" title="Pessoa">👤</span><span class="md-icon-item" title="Pessoas">👥</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea asp-for="Bio" id="Bio" class="form-control rounded-0 rounded-bottom" rows="5" maxlength="3000" placeholder="Uma breve descrição sobre você ou sua empresa..." style="font-family: monospace; font-size: 0.9rem;"></textarea>
|
|
||||||
<span asp-validation-for="Bio" class="text-danger"></span>
|
<span asp-validation-for="Bio" class="text-danger"></span>
|
||||||
<div class="form-text">Máximo 3000 caracteres. Use **negrito**, *itálico*, - item para listas.</div>
|
<div class="form-text">Máximo 3000 caracteres</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Profile Image Upload -->
|
<!-- Profile Image Upload -->
|
||||||
@ -1255,6 +1210,7 @@
|
|||||||
|
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const LINK_TYPES_CONFIG = @Html.Raw(linkTypesJson);
|
const LINK_TYPES_CONFIG = @Html.Raw(linkTypesJson);
|
||||||
let linkCount = @Model.Links.Count;
|
let linkCount = @Model.Links.Count;
|
||||||
@ -1263,9 +1219,6 @@
|
|||||||
const totalSteps = 5;
|
const totalSteps = 5;
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// Initialize Markdown toolbar
|
|
||||||
initMarkdownToolbar();
|
|
||||||
|
|
||||||
// Initialize social media fields
|
// Initialize social media fields
|
||||||
initializeSocialMedia();
|
initializeSocialMedia();
|
||||||
|
|
||||||
@ -1384,11 +1337,11 @@
|
|||||||
updateLinkNumbers();
|
updateLinkNumbers();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Loading state — só desabilita se a validação JS não bloqueou o submit
|
// Form validation
|
||||||
$('#managePageForm').on('submit', function(e) {
|
$('#managePageForm').on('submit', function(e) {
|
||||||
if (!e.isDefaultPrevented()) {
|
console.log('Form submitted');
|
||||||
$(this).find('button[type="submit"]').prop('disabled', true).html('<i class="fas fa-spinner fa-spin me-2"></i>Salvando...');
|
// Allow submission but add loading state
|
||||||
}
|
$(this).find('button[type="submit"]').prop('disabled', true).html('<i class="fas fa-spinner fa-spin me-2"></i>Criando...');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1990,84 +1943,6 @@
|
|||||||
}, 7000);
|
}, 7000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Markdown Toolbar
|
|
||||||
function initMarkdownToolbar() {
|
|
||||||
// Icon picker toggle
|
|
||||||
document.querySelectorAll('.md-icon-picker-btn').forEach(function(btn) {
|
|
||||||
btn.addEventListener('click', function(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
var panel = this.parentElement.querySelector('.md-icon-picker-panel');
|
|
||||||
var isVisible = panel.style.display !== 'none';
|
|
||||||
document.querySelectorAll('.md-icon-picker-panel').forEach(function(p) { p.style.display = 'none'; });
|
|
||||||
if (!isVisible) panel.style.display = 'block';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// Insert icon on click
|
|
||||||
document.querySelectorAll('.md-icon-item').forEach(function(item) {
|
|
||||||
item.addEventListener('click', function(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
var panel = this.closest('.md-icon-picker-panel');
|
|
||||||
var btn = panel.parentElement.querySelector('.md-icon-picker-btn');
|
|
||||||
var targetId = btn ? btn.dataset.target : 'Bio';
|
|
||||||
var ta = document.getElementById(targetId);
|
|
||||||
var icon = this.textContent;
|
|
||||||
var start = ta.selectionStart, end = ta.selectionEnd;
|
|
||||||
ta.value = ta.value.substring(0, start) + icon + ta.value.substring(end);
|
|
||||||
ta.selectionStart = ta.selectionEnd = start + icon.length;
|
|
||||||
ta.focus();
|
|
||||||
panel.style.display = 'none';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// Close picker on outside click
|
|
||||||
document.addEventListener('click', function() {
|
|
||||||
document.querySelectorAll('.md-icon-picker-panel').forEach(function(p) { p.style.display = 'none'; });
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelectorAll('.md-btn').forEach(function(btn) {
|
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
var targetId = this.dataset.target;
|
|
||||||
var wrap = this.dataset.wrap;
|
|
||||||
var ta = document.getElementById(targetId);
|
|
||||||
var start = ta.selectionStart, end = ta.selectionEnd;
|
|
||||||
var sel = ta.value.substring(start, end) || 'texto';
|
|
||||||
var before = ta.value.substring(0, start);
|
|
||||||
var after = ta.value.substring(end);
|
|
||||||
ta.value = before + wrap + sel + wrap + after;
|
|
||||||
ta.selectionStart = start + wrap.length;
|
|
||||||
ta.selectionEnd = start + wrap.length + sel.length;
|
|
||||||
ta.focus();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
document.querySelectorAll('.md-list-btn').forEach(function(btn) {
|
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
var targetId = this.dataset.target;
|
|
||||||
var ta = document.getElementById(targetId);
|
|
||||||
var start = ta.selectionStart;
|
|
||||||
var lineStart = ta.value.lastIndexOf('\n', start - 1) + 1;
|
|
||||||
var before = ta.value.substring(0, lineStart);
|
|
||||||
var after = ta.value.substring(lineStart);
|
|
||||||
ta.value = before + '- ' + after;
|
|
||||||
ta.selectionStart = ta.selectionEnd = start + 2;
|
|
||||||
ta.focus();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
document.querySelectorAll('.md-link-btn').forEach(function(btn) {
|
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
var targetId = this.dataset.target;
|
|
||||||
var ta = document.getElementById(targetId);
|
|
||||||
var start = ta.selectionStart, end = ta.selectionEnd;
|
|
||||||
var sel = ta.value.substring(start, end) || 'texto do link';
|
|
||||||
var url = prompt('URL do link:') || 'https://';
|
|
||||||
var before = ta.value.substring(0, start);
|
|
||||||
var after = ta.value.substring(end);
|
|
||||||
var md = '[' + sel + '](' + url + ')';
|
|
||||||
ta.value = before + md + after;
|
|
||||||
ta.selectionStart = ta.selectionEnd = start + md.length;
|
|
||||||
ta.focus();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation Error Handling
|
// Validation Error Handling
|
||||||
function checkValidationErrors() {
|
function checkValidationErrors() {
|
||||||
// Só verificar erros se estamos em um POST-back (ou seja, se ModelState foi validado)
|
// Só verificar erros se estamos em um POST-back (ou seja, se ModelState foi validado)
|
||||||
@ -2387,10 +2262,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Atualizar campo hidden - SEMPRE string, nunca null
|
// Atualizar campo hidden - SEMPRE string, nunca null
|
||||||
// WhatsApp: armazena só o número (servidor adiciona https://wa.me/ ao salvar)
|
|
||||||
// Outros: armazena URL completa (servidor usa diretamente)
|
|
||||||
if (value) {
|
if (value) {
|
||||||
hiddenField.val(isWhatsApp ? value : prefix + value);
|
hiddenField.val(prefix + value);
|
||||||
} else {
|
} else {
|
||||||
hiddenField.val(' '); // Espaço em branco para evitar null
|
hiddenField.val(' '); // Espaço em branco para evitar null
|
||||||
}
|
}
|
||||||
@ -2733,16 +2606,6 @@
|
|||||||
|
|
||||||
@section Styles {
|
@section Styles {
|
||||||
<style>
|
<style>
|
||||||
.md-icon-item {
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
line-height: 1.4;
|
|
||||||
transition: background 0.1s;
|
|
||||||
}
|
|
||||||
.md-icon-item:hover { background: #e9ecef; }
|
|
||||||
|
|
||||||
/* Estilo customizado para o scroll dos temas */
|
/* Estilo customizado para o scroll dos temas */
|
||||||
.themes-container {
|
.themes-container {
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
|
|||||||
@ -111,7 +111,7 @@
|
|||||||
<div class="card-footer bg-transparent p-4">
|
<div class="card-footer bg-transparent p-4">
|
||||||
@if (User.Identity?.IsAuthenticated == true)
|
@if (User.Identity?.IsAuthenticated == true)
|
||||||
{
|
{
|
||||||
<a asp-controller="Admin" asp-action="ManagePage" asp-route-id="new" class="btn btn-success w-100">Começar Grátis</a>
|
<a asp-controller="Admin" asp-action="CreatePage" class="btn btn-success w-100">Começar Grátis</a>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@ -103,11 +103,9 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@if (!string.IsNullOrEmpty(Model.Page.Bio))
|
@if (!string.IsNullOrEmpty(Model.Page.Bio))
|
||||||
{
|
{
|
||||||
var bioPipeline = new Markdig.MarkdownPipelineBuilder().UseAutoLinks().DisableHtml().Build();
|
|
||||||
var bioHtml = Markdig.Markdown.ToHtml(Model.Page.Bio, bioPipeline);
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<strong>Biografia:</strong>
|
<strong>Biografia:</strong>
|
||||||
<div>@Html.Raw(bioHtml)</div>
|
<p>@Model.Page.Bio</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -367,9 +367,7 @@
|
|||||||
|
|
||||||
@if (!string.IsNullOrEmpty(Model.Bio))
|
@if (!string.IsNullOrEmpty(Model.Bio))
|
||||||
{
|
{
|
||||||
var bioPipeline = new Markdig.MarkdownPipelineBuilder().UseAutoLinks().DisableHtml().Build();
|
<p class="profile-bio">@Model.Bio</p>
|
||||||
var bioHtml = Markdig.Markdown.ToHtml(Model.Bio, bioPipeline);
|
|
||||||
<div class="profile-bio">@Html.Raw(bioHtml)</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Links Container -->
|
<!-- Links Container -->
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
@using BCards.Web
|
@using BCards.Web
|
||||||
@using BCards.Web.Models
|
@using BCards.Web.Models
|
||||||
@using Markdig
|
|
||||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
Loading…
Reference in New Issue
Block a user