diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 7674f52..a5c5d8d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -53,7 +53,9 @@ "Bash(git log:*)", "Bash(git mv:*)", "Bash(python3 -c \"import sys,json; [print\\(json.loads\\(l\\).get\\('MessageTemplate',''\\) + ' ' + str\\(json.loads\\(l\\).get\\('Level',''\\)\\)\\) for l in sys.stdin if 'categ' in l.lower\\(\\) or 'Error' in l or 'error' in l.lower\\(\\)]\")", - "Bash(python3 -c \" import sys, json for line in sys.stdin: line = line.strip\\(\\).rstrip\\(','\\) try: obj = json.loads\\(line\\) lvl = obj.get\\('Level',''\\) msg = obj.get\\('MessageTemplate',''\\) or obj.get\\('message',''\\) if lvl in \\('Error','Warning','Fatal'\\) or 'categ' in msg.lower\\(\\) or 'initial' in msg.lower\\(\\): print\\(f'[{lvl}] {msg}'\\) except: pass \")" + "Bash(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 diff --git a/src/BCards.Web/Controllers/AdminController.cs b/src/BCards.Web/Controllers/AdminController.cs index a35abde..489406b 100644 --- a/src/BCards.Web/Controllers/AdminController.cs +++ b/src/BCards.Web/Controllers/AdminController.cs @@ -482,185 +482,10 @@ public class AdminController : Controller } } - [HttpPost] [Route("CreatePage")] - public async Task CreatePage(CreatePageViewModel model) + public IActionResult CreatePage() { - 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() - }; - - // Add social media links - var socialLinks = new List(); - if (!string.IsNullOrEmpty(model.WhatsAppNumber)) - { - var whatsappDigits = model.WhatsAppNumber - .Replace("https://wa.me/", "").Replace("whatsapp://", "") - .Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", ""); - socialLinks.Add(new LinkItem - { - Title = "WhatsApp", - Url = $"https://wa.me/{whatsappDigits}", - 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"); + return RedirectToAction("ManagePage", new { id = "new" }); } [HttpGet] diff --git a/src/BCards.Web/ViewModels/CreatePageViewModel.cs b/src/BCards.Web/ViewModels/CreatePageViewModel.cs deleted file mode 100644 index 48b6b9a..0000000 --- a/src/BCards.Web/ViewModels/CreatePageViewModel.cs +++ /dev/null @@ -1,58 +0,0 @@ -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 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; -} \ No newline at end of file diff --git a/src/BCards.Web/Views/Admin/CreatePage.cshtml b/src/BCards.Web/Views/Admin/CreatePage.cshtml deleted file mode 100644 index a88a0b2..0000000 --- a/src/BCards.Web/Views/Admin/CreatePage.cshtml +++ /dev/null @@ -1,672 +0,0 @@ -@model BCards.Web.ViewModels.CreatePageViewModel -@{ - ViewData["Title"] = "Criar Página"; - Layout = "_Layout"; -} - -
-
-
-
-
-

- - Criar Sua Página de Links -

-
- -
- -
-
-
- -
- - -
-
- 1 - Informações Básicas -
- -
-
-
- - - -
-
- -
-
- - - -
-
-
- -
-
-
- - -
-
- -
-
- -
- page/ - categoria - / - - -
- URL gerada automaticamente -
-
-
- -
- -
- - - - -
- - -
Máximo 3000 caracteres. Use **negrito**, *itálico*, - item para listas.
-
-
- - -
-
- 2 - Escolha Seu Tema Visual -
- -
- @foreach (var theme in ViewBag.Themes as List ?? new List()) - { -
-
-
-
-
-
@theme.Name
-
- -
-
- @theme.Name - @if (theme.IsPremium) - { - Premium - } -
-
-
- } -
- - -
- - -
-
- 3 - Links Principais -
- - - - -
- - -
-
- 4 - Redes Sociais -
- -
-
-
- - - -
-
- -
-
- - - -
-
-
- -
-
-
- - - -
-
- -
-
- - - -
-
-
-
- - -
-
- 5 - Preview e Finalização -
- -
-
-
- -
-
-
- -
-

Sua página estará disponível em:

- page/categoria/seu-slug -
-
- - -
- - - - - -
-
-
-
-
-
-
- - - - - -@section Scripts { - @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} - -} \ No newline at end of file diff --git a/src/BCards.Web/Views/Admin/ManagePage.cshtml b/src/BCards.Web/Views/Admin/ManagePage.cshtml index 36c302e..a14ea71 100644 --- a/src/BCards.Web/Views/Admin/ManagePage.cshtml +++ b/src/BCards.Web/Views/Admin/ManagePage.cshtml @@ -115,11 +115,50 @@
-
+
- + + +
@@ -1953,6 +1992,37 @@ // 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; @@ -2663,6 +2733,16 @@ @section Styles {