Initial commit — AsadiTools v1.0
Full ASP.NET Core 10 Razor Pages app for آساد ابزار tool repair shop in Karaj, Iran (official DeWalt representative). Features: - Homepage, Services, DeWalt page, Shop (pagination + images) - 10 brand SEO pages (/brands/*) with rich Persian content + FAQ schema - Blog engine with admin management (/blog, /Admin/Blog) - Cart, Checkout, Contact (OpenStreetMap embed) - Admin panel: Products CRUD, Orders, Blog, Change Password - Jalali date formatting, product images, SiteData centralised contact - Docker + docker-compose with healthcheck - Gitea CI/CD via .gitea/workflows/ci-cd.yml (NuGet through Nexus mirror) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Admin.Blog.AdminBlogCreateModel
|
||||
@{ ViewData["Title"] = "نوشته جدید"; Layout = "_AdminLayout"; }
|
||||
|
||||
<div class="p-6 md:p-8 max-w-3xl">
|
||||
<div class="flex items-center gap-3 mb-8">
|
||||
<a href="/Admin/Blog" class="text-gray-400 hover:text-gray-600 text-sm">← بازگشت</a>
|
||||
<h1 class="text-2xl font-extrabold text-gray-900">نوشته جدید</h1>
|
||||
</div>
|
||||
<form method="post" class="bg-white rounded-2xl border border-gray-100 p-6">
|
||||
@Html.AntiForgeryToken()
|
||||
@await Html.PartialAsync("_BlogFormFields", Model.Input)
|
||||
<div class="flex gap-3 mt-6 pt-5 border-t">
|
||||
<button type="submit" class="flex-1 bg-blue-700 text-white py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">
|
||||
ذخیره مقاله
|
||||
</button>
|
||||
<a href="/Admin/Blog" class="px-5 border border-gray-200 rounded-xl text-sm text-gray-600 hover:bg-gray-50 transition-colors flex items-center">
|
||||
انصراف
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,58 @@
|
||||
using AsadiTools.Data;
|
||||
using AsadiTools.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace AsadiTools.Pages.Admin.Blog;
|
||||
|
||||
[Authorize(AuthenticationSchemes = "AdminCookie")]
|
||||
public class AdminBlogCreateModel(AppDbContext db) : PageModel
|
||||
{
|
||||
[BindProperty] public BlogPostInput Input { get; set; } = new();
|
||||
|
||||
public void OnGet() { ViewData["Title"] = "نوشته جدید"; }
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
if (!ModelState.IsValid) return Page();
|
||||
|
||||
var slug = string.IsNullOrWhiteSpace(Input.Slug)
|
||||
? Input.Title.ToLower()
|
||||
.Replace(" ", "-")
|
||||
.Replace("،", "")
|
||||
.Replace(".", "-")
|
||||
: Input.Slug.Trim();
|
||||
|
||||
var now = DateTime.Now;
|
||||
db.BlogPosts.Add(new BlogPost
|
||||
{
|
||||
Title = Input.Title,
|
||||
Slug = slug,
|
||||
Content = Input.Content,
|
||||
Excerpt = Input.Excerpt,
|
||||
MetaDescription = Input.MetaDescription,
|
||||
FeaturedImage = string.IsNullOrWhiteSpace(Input.FeaturedImage) ? null : Input.FeaturedImage,
|
||||
Tags = Input.Tags,
|
||||
IsPublished = Input.IsPublished,
|
||||
PublishedAt = Input.IsPublished ? now : null,
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now,
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
return RedirectToPage("/Admin/Blog/Index");
|
||||
}
|
||||
}
|
||||
|
||||
public class BlogPostInput
|
||||
{
|
||||
[Required] public string Title { get; set; } = string.Empty;
|
||||
public string? Slug { get; set; }
|
||||
[Required] public string Content { get; set; } = string.Empty;
|
||||
public string? Excerpt { get; set; }
|
||||
public string? MetaDescription { get; set; }
|
||||
public string? FeaturedImage { get; set; }
|
||||
public string? Tags { get; set; }
|
||||
public bool IsPublished { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Admin.Blog.AdminBlogEditModel
|
||||
@{ Layout = "_AdminLayout"; }
|
||||
|
||||
<div class="p-6 md:p-8 max-w-3xl">
|
||||
<div class="flex items-center gap-3 mb-8">
|
||||
<a href="/Admin/Blog" class="text-gray-400 hover:text-gray-600 text-sm">← بازگشت</a>
|
||||
<h1 class="text-2xl font-extrabold text-gray-900">ویرایش مقاله</h1>
|
||||
</div>
|
||||
<form method="post" class="bg-white rounded-2xl border border-gray-100 p-6">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="id" value="@Model.PostId" />
|
||||
@await Html.PartialAsync("_BlogFormFields", Model.Input)
|
||||
<div class="flex gap-3 mt-6 pt-5 border-t">
|
||||
<button type="submit" class="flex-1 bg-blue-700 text-white py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">
|
||||
ذخیره تغییرات
|
||||
</button>
|
||||
<a href="/Admin/Blog" class="px-5 border border-gray-200 rounded-xl text-sm text-gray-600 hover:bg-gray-50 transition-colors flex items-center">
|
||||
انصراف
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,57 @@
|
||||
using AsadiTools.Data;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages.Admin.Blog;
|
||||
|
||||
[Authorize(AuthenticationSchemes = "AdminCookie")]
|
||||
public class AdminBlogEditModel(AppDbContext db) : PageModel
|
||||
{
|
||||
[BindProperty] public BlogPostInput Input { get; set; } = new();
|
||||
public int PostId { get; private set; }
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(int id)
|
||||
{
|
||||
var post = await db.BlogPosts.FindAsync(id);
|
||||
if (post is null) return NotFound();
|
||||
PostId = id;
|
||||
Input = new BlogPostInput
|
||||
{
|
||||
Title = post.Title,
|
||||
Slug = post.Slug,
|
||||
Content = post.Content,
|
||||
Excerpt = post.Excerpt,
|
||||
MetaDescription = post.MetaDescription,
|
||||
FeaturedImage = post.FeaturedImage,
|
||||
Tags = post.Tags,
|
||||
IsPublished = post.IsPublished,
|
||||
};
|
||||
ViewData["Title"] = "ویرایش: " + post.Title;
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(int id)
|
||||
{
|
||||
if (!ModelState.IsValid) { PostId = id; return Page(); }
|
||||
|
||||
var post = await db.BlogPosts.FindAsync(id);
|
||||
if (post is null) return NotFound();
|
||||
|
||||
var wasUnpublished = !post.IsPublished;
|
||||
post.Title = Input.Title;
|
||||
post.Slug = string.IsNullOrWhiteSpace(Input.Slug) ? post.Slug : Input.Slug.Trim();
|
||||
post.Content = Input.Content;
|
||||
post.Excerpt = Input.Excerpt;
|
||||
post.MetaDescription = Input.MetaDescription;
|
||||
post.FeaturedImage = string.IsNullOrWhiteSpace(Input.FeaturedImage) ? null : Input.FeaturedImage;
|
||||
post.Tags = Input.Tags;
|
||||
post.IsPublished = Input.IsPublished;
|
||||
post.UpdatedAt = DateTime.Now;
|
||||
if (Input.IsPublished && wasUnpublished)
|
||||
post.PublishedAt = DateTime.Now;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return RedirectToPage("/Admin/Blog/Index");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Admin.Blog.AdminBlogIndexModel
|
||||
@{ ViewData["Title"] = "مدیریت بلاگ"; Layout = "_AdminLayout"; }
|
||||
|
||||
<div class="p-6 md:p-8">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<h1 class="text-2xl font-extrabold text-gray-900">مدیریت بلاگ</h1>
|
||||
<a href="/Admin/Blog/Create"
|
||||
class="bg-blue-700 text-white px-5 py-2.5 rounded-xl font-bold hover:bg-blue-800 transition-colors flex items-center gap-2">
|
||||
✏️ نوشته جدید
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if (!Model.Posts.Any())
|
||||
{
|
||||
<div class="text-center py-20 text-gray-400">
|
||||
<div class="text-5xl mb-4">📝</div>
|
||||
<p>هنوز مقالهای ثبت نشده</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="bg-white rounded-2xl border border-gray-100 overflow-hidden">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 border-b">
|
||||
<tr>
|
||||
<th class="p-4 text-right font-medium text-gray-500">عنوان</th>
|
||||
<th class="p-4 text-right font-medium text-gray-500 hidden md:table-cell">برچسبها</th>
|
||||
<th class="p-4 text-right font-medium text-gray-500">وضعیت</th>
|
||||
<th class="p-4 text-right font-medium text-gray-500 hidden lg:table-cell">تاریخ</th>
|
||||
<th class="p-4 text-right font-medium text-gray-500">عملیات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-50">
|
||||
@foreach (var post in Model.Posts)
|
||||
{
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="p-4">
|
||||
<div class="font-medium text-gray-900 line-clamp-1">@post.Title</div>
|
||||
<div class="text-xs text-gray-400 font-mono mt-0.5">@post.EffectiveSlug</div>
|
||||
</td>
|
||||
<td class="p-4 hidden md:table-cell">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@foreach (var tag in post.TagList.Take(3))
|
||||
{
|
||||
<span class="text-xs bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded">@tag</span>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-4">
|
||||
<form method="post" asp-page-handler="TogglePublish" class="inline">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="id" value="@post.Id" />
|
||||
<button type="submit"
|
||||
class="text-xs px-2.5 py-1 rounded-full font-medium border transition-colors @(post.IsPublished ? "bg-green-100 text-green-700 border-green-200 hover:bg-green-200" : "bg-gray-100 text-gray-500 border-gray-200 hover:bg-gray-200")">
|
||||
@(post.IsPublished ? "✅ منتشر" : "⏸ پیشنویس")
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td class="p-4 hidden lg:table-cell text-gray-400 text-xs">
|
||||
@SiteData.ToJalali(post.CreatedAt)
|
||||
</td>
|
||||
<td class="p-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="/Admin/Blog/Edit?id=@post.Id"
|
||||
class="text-blue-600 hover:underline text-xs font-medium">ویرایش</a>
|
||||
@if (post.IsPublished)
|
||||
{
|
||||
<a href="/blog/@post.EffectiveSlug" target="_blank"
|
||||
class="text-gray-400 hover:text-gray-600 text-xs">مشاهده</a>
|
||||
}
|
||||
<form method="post" asp-page-handler="Delete"
|
||||
onsubmit="return confirm('این مقاله حذف شود؟')" class="inline">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="id" value="@post.Id" />
|
||||
<button type="submit" class="text-red-400 hover:text-red-600 text-xs">حذف</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,41 @@
|
||||
using AsadiTools.Data;
|
||||
using AsadiTools.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AsadiTools.Pages.Admin.Blog;
|
||||
|
||||
[Authorize(AuthenticationSchemes = "AdminCookie")]
|
||||
public class AdminBlogIndexModel(AppDbContext db) : PageModel
|
||||
{
|
||||
public List<BlogPost> Posts { get; private set; } = [];
|
||||
|
||||
public async Task OnGetAsync()
|
||||
{
|
||||
Posts = await db.BlogPosts.OrderByDescending(p => p.CreatedAt).ToListAsync();
|
||||
ViewData["Title"] = "مدیریت بلاگ";
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostDeleteAsync(int id)
|
||||
{
|
||||
var post = await db.BlogPosts.FindAsync(id);
|
||||
if (post is not null) { db.BlogPosts.Remove(post); await db.SaveChangesAsync(); }
|
||||
return RedirectToPage();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostTogglePublishAsync(int id)
|
||||
{
|
||||
var post = await db.BlogPosts.FindAsync(id);
|
||||
if (post is not null)
|
||||
{
|
||||
post.IsPublished = !post.IsPublished;
|
||||
if (post.IsPublished && post.PublishedAt is null)
|
||||
post.PublishedAt = DateTime.Now;
|
||||
post.UpdatedAt = DateTime.Now;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
return RedirectToPage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
@model AsadiTools.Pages.Admin.Blog.BlogPostInput
|
||||
|
||||
@{
|
||||
var cls = "w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500";
|
||||
}
|
||||
|
||||
<div class="space-y-5">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">عنوان مقاله <span class="text-red-500">*</span></label>
|
||||
<input asp-for="Title" class="@cls" placeholder="مثال: راهنمای تعمیر فرز برقی" />
|
||||
<span asp-validation-for="Title" class="text-red-500 text-xs"></span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">اسلاگ (URL)</label>
|
||||
<input asp-for="Slug" class="@cls" dir="ltr" placeholder="carbon-brush-guide (خودکار از عنوان)" />
|
||||
<p class="text-xs text-gray-400 mt-1">URL صفحه: /blog/اسلاگ — اگر خالی بماند از عنوان ساخته میشود</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">چکیده (برای لیست بلاگ)</label>
|
||||
<textarea asp-for="Excerpt" rows="2" class="@cls resize-none" placeholder="خلاصه کوتاه مقاله..."></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">توضیح متا (SEO)</label>
|
||||
<textarea asp-for="MetaDescription" rows="2" class="@cls resize-none" placeholder="توضیح برای گوگل (حداکثر ۱۶۰ کاراکتر)..."></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">آدرس تصویر شاخص (URL)</label>
|
||||
<input asp-for="FeaturedImage" class="@cls" dir="ltr" placeholder="https://..." />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">برچسبها (با کاما جدا شوند)</label>
|
||||
<input asp-for="Tags" class="@cls" placeholder="تعمیر فرز,کاربن,دیوالت" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">محتوا (HTML) <span class="text-red-500">*</span></label>
|
||||
<textarea asp-for="Content" rows="20" class="@cls resize-y font-mono text-xs leading-relaxed" dir="auto"
|
||||
placeholder="<h2>عنوان بخش</h2> <p>متن مقاله...</p>"></textarea>
|
||||
<p class="text-xs text-gray-400 mt-1">محتوا به صورت HTML نوشته شود. از تگهای h2، h3، p، ul، li، strong استفاده کنید.</p>
|
||||
<span asp-validation-for="Content" class="text-red-500 text-xs"></span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<input asp-for="IsPublished" type="checkbox" class="w-4 h-4 rounded accent-blue-600" />
|
||||
<div>
|
||||
<label asp-for="IsPublished" class="font-medium text-gray-800 text-sm cursor-pointer">انتشار فوری</label>
|
||||
<p class="text-xs text-gray-400">اگر تیک بزنید، مقاله بلافاصله در سایت نمایش داده میشود.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,50 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Admin.ChangePassword.ChangePasswordModel
|
||||
@{ ViewData["Title"] = "تغییر رمز عبور"; Layout = "_AdminLayout"; }
|
||||
|
||||
@{
|
||||
var inputCls = "w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500";
|
||||
}
|
||||
|
||||
<div class="p-6 md:p-8 max-w-lg">
|
||||
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">تغییر رمز عبور</h1>
|
||||
|
||||
@if (Model.Success)
|
||||
{
|
||||
<div class="bg-green-50 border border-green-200 text-green-700 px-5 py-4 rounded-xl mb-6 flex items-center gap-2">
|
||||
✅ رمز عبور با موفقیت تغییر یافت.
|
||||
</div>
|
||||
}
|
||||
|
||||
<form method="post" class="bg-white rounded-2xl border border-gray-100 p-6 space-y-5">
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
|
||||
{
|
||||
<div class="bg-red-50 border border-red-200 text-red-700 text-sm px-4 py-3 rounded-xl">@Model.ErrorMessage</div>
|
||||
}
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">رمز عبور فعلی <span class="text-red-500">*</span></label>
|
||||
<input asp-for="Input.CurrentPassword" type="password" class="@inputCls" />
|
||||
<span asp-validation-for="Input.CurrentPassword" class="text-red-500 text-xs"></span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">رمز عبور جدید <span class="text-red-500">*</span></label>
|
||||
<input asp-for="Input.NewPassword" type="password" class="@inputCls" />
|
||||
<span asp-validation-for="Input.NewPassword" class="text-red-500 text-xs"></span>
|
||||
<p class="text-xs text-gray-400 mt-1">حداقل ۶ کاراکتر</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">تکرار رمز عبور جدید <span class="text-red-500">*</span></label>
|
||||
<input asp-for="Input.ConfirmPassword" type="password" class="@inputCls" />
|
||||
<span asp-validation-for="Input.ConfirmPassword" class="text-red-500 text-xs"></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full bg-blue-700 text-white py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">
|
||||
🔑 تغییر رمز عبور
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,51 @@
|
||||
using AsadiTools.Data;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace AsadiTools.Pages.Admin.ChangePassword;
|
||||
|
||||
[Authorize(AuthenticationSchemes = "AdminCookie")]
|
||||
public class ChangePasswordModel(AppDbContext db) : PageModel
|
||||
{
|
||||
[BindProperty] public ChangePasswordInput Input { get; set; } = new();
|
||||
public string? ErrorMessage { get; private set; }
|
||||
public bool Success { get; private set; }
|
||||
|
||||
public void OnGet() { }
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
if (!ModelState.IsValid) return Page();
|
||||
|
||||
var userId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
var user = await db.AdminUsers.FindAsync(userId);
|
||||
if (user is null) return RedirectToPage("/Admin/Login");
|
||||
|
||||
if (!BCrypt.Net.BCrypt.Verify(Input.CurrentPassword, user.PasswordHash))
|
||||
{
|
||||
ErrorMessage = "رمز عبور فعلی اشتباه است";
|
||||
return Page();
|
||||
}
|
||||
|
||||
if (Input.NewPassword != Input.ConfirmPassword)
|
||||
{
|
||||
ErrorMessage = "رمز عبور جدید و تکرار آن یکسان نیستند";
|
||||
return Page();
|
||||
}
|
||||
|
||||
user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(Input.NewPassword);
|
||||
await db.SaveChangesAsync();
|
||||
Success = true;
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
|
||||
public class ChangePasswordInput
|
||||
{
|
||||
[Required] public string CurrentPassword { get; set; } = string.Empty;
|
||||
[Required, MinLength(6)] public string NewPassword { get; set; } = string.Empty;
|
||||
[Required] public string ConfirmPassword { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Admin.AdminIndexModel
|
||||
@{ ViewData["Title"] = "داشبورد"; Layout = "_AdminLayout"; }
|
||||
|
||||
<div class="p-6 md:p-8">
|
||||
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">داشبورد</h1>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-10">
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-5">
|
||||
<div class="w-10 h-10 rounded-xl bg-blue-100 text-blue-600 flex items-center justify-center text-xl mb-3">🛍️</div>
|
||||
<div class="text-2xl font-extrabold text-gray-900 mb-0.5">@Model.TotalOrders</div>
|
||||
<div class="text-sm text-gray-500">کل سفارشها</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-5">
|
||||
<div class="w-10 h-10 rounded-xl bg-yellow-100 text-yellow-600 flex items-center justify-center text-xl mb-3">⏳</div>
|
||||
<div class="text-2xl font-extrabold text-gray-900 mb-0.5">@Model.PendingOrders</div>
|
||||
<div class="text-sm text-gray-500">در انتظار تأیید</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-5">
|
||||
<div class="w-10 h-10 rounded-xl bg-green-100 text-green-600 flex items-center justify-center text-xl mb-3">💰</div>
|
||||
<div class="text-lg font-extrabold text-gray-900 mb-0.5">@SiteData.FormatPrice(Model.TotalRevenue)</div>
|
||||
<div class="text-sm text-gray-500">درآمد کل</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-5">
|
||||
<div class="w-10 h-10 rounded-xl bg-purple-100 text-purple-600 flex items-center justify-center text-xl mb-3">📦</div>
|
||||
<div class="text-2xl font-extrabold text-gray-900 mb-0.5">@Model.ActiveProducts</div>
|
||||
<div class="text-sm text-gray-500">محصولات فعال</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent orders -->
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-6">
|
||||
<div class="flex items-center justify-between mb-5">
|
||||
<h2 class="font-bold text-gray-900">آخرین سفارشها</h2>
|
||||
<a href="/Admin/Orders" class="text-sm text-blue-600 hover:underline">مشاهده همه</a>
|
||||
</div>
|
||||
@if (!Model.RecentOrders.Any())
|
||||
{
|
||||
<p class="text-gray-400 text-sm text-center py-8">هنوز سفارشی ثبت نشده</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b text-gray-500">
|
||||
<th class="pb-3 text-right font-medium">شماره</th>
|
||||
<th class="pb-3 text-right font-medium">مشتری</th>
|
||||
<th class="pb-3 text-right font-medium">مبلغ</th>
|
||||
<th class="pb-3 text-right font-medium">وضعیت</th>
|
||||
<th class="pb-3 text-right font-medium">تاریخ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-50">
|
||||
@foreach (var o in Model.RecentOrders)
|
||||
{
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="py-3 font-mono text-xs text-gray-500">@o.OrderNumber</td>
|
||||
<td class="py-3 font-medium">@o.CustomerName</td>
|
||||
<td class="py-3 text-blue-700 font-bold">@SiteData.FormatPrice(o.Total)</td>
|
||||
<td class="py-3">
|
||||
<span class="text-xs px-2 py-1 rounded-full font-medium @SiteData.OrderStatusBadge(o.Status)">
|
||||
@SiteData.OrderStatusLabel(o.Status)
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-3 text-gray-400 text-xs">@SiteData.ToJalali(o.CreatedAt)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
using AsadiTools.Data;
|
||||
using AsadiTools.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AsadiTools.Pages.Admin;
|
||||
|
||||
[Authorize(AuthenticationSchemes = "AdminCookie")]
|
||||
public class AdminIndexModel(AppDbContext db) : PageModel
|
||||
{
|
||||
public int TotalOrders { get; private set; }
|
||||
public int PendingOrders { get; private set; }
|
||||
public decimal TotalRevenue { get; private set; }
|
||||
public int ActiveProducts { get; private set; }
|
||||
public List<Order> RecentOrders { get; private set; } = [];
|
||||
|
||||
public async Task<IActionResult> OnGetAsync()
|
||||
{
|
||||
TotalOrders = await db.Orders.CountAsync();
|
||||
PendingOrders = await db.Orders.CountAsync(o => o.Status == OrderStatus.Pending);
|
||||
TotalRevenue = await db.Orders.Where(o => o.Status != OrderStatus.Cancelled).SumAsync(o => o.Total);
|
||||
ActiveProducts = await db.Products.CountAsync(p => p.IsActive);
|
||||
RecentOrders = await db.Orders.OrderByDescending(o => o.Id).Take(6).ToListAsync();
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Admin.LoginModel
|
||||
@{ ViewData["Title"] = "ورود به پنل مدیریت"; Layout = null; }
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="fa" dir="rtl">
|
||||
<head>
|
||||
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ورود | پنل مدیریت آساد ابزار</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-Regular.woff2") format("woff2"); font-display:swap; }
|
||||
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-Bold.woff2") format("woff2"); font-weight:700; font-display:swap; }
|
||||
* { font-family:"Vazirmatn",Tahoma,sans-serif !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen bg-gray-50 flex items-center justify-center px-4">
|
||||
<div class="w-full max-w-md">
|
||||
<div class="text-center mb-8">
|
||||
<div class="bg-blue-700 text-white rounded-2xl p-4 inline-flex text-2xl mb-4">🔧</div>
|
||||
<h1 class="text-2xl font-extrabold text-gray-900">پنل مدیریت</h1>
|
||||
<p class="text-gray-500 text-sm mt-1">آساد ابزار کرج</p>
|
||||
</div>
|
||||
<form method="post" class="bg-white rounded-2xl shadow-sm border border-gray-100 p-8 space-y-5">
|
||||
@Html.AntiForgeryToken()
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">نام کاربری</label>
|
||||
<input asp-for="Input.Username" dir="ltr" placeholder="admin"
|
||||
class="w-full border border-gray-200 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">رمز عبور</label>
|
||||
<input asp-for="Input.Password" type="password" dir="ltr" placeholder="••••••••"
|
||||
class="w-full border border-gray-200 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
|
||||
{
|
||||
<div class="bg-red-50 border border-red-200 text-red-700 text-sm px-4 py-3 rounded-xl">@Model.ErrorMessage</div>
|
||||
}
|
||||
<button type="submit" class="w-full bg-blue-700 text-white py-3.5 rounded-xl font-bold hover:bg-blue-800 transition-colors">
|
||||
🔒 ورود
|
||||
</button>
|
||||
<p class="text-xs text-gray-400 text-center">رمز پیشفرض: admin / admin1234</p>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,51 @@
|
||||
using AsadiTools.Data;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace AsadiTools.Pages.Admin;
|
||||
|
||||
public class LoginModel(AppDbContext db) : PageModel
|
||||
{
|
||||
[BindProperty]
|
||||
public InputModel Input { get; set; } = new();
|
||||
public string? ErrorMessage { get; private set; }
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
[Required] public string Username { get; set; } = string.Empty;
|
||||
[Required] public string Password { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
if (User.Identity?.IsAuthenticated == true)
|
||||
return RedirectToPage("/Admin/Index");
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
if (!ModelState.IsValid) return Page();
|
||||
|
||||
var user = db.AdminUsers.FirstOrDefault(u => u.Username == Input.Username);
|
||||
if (user is null || !BCrypt.Net.BCrypt.Verify(Input.Password, user.PasswordHash))
|
||||
{
|
||||
ErrorMessage = "نام کاربری یا رمز اشتباه است";
|
||||
return Page();
|
||||
}
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(ClaimTypes.Name, user.Username),
|
||||
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
};
|
||||
var identity = new ClaimsIdentity(claims, "AdminCookie");
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
await HttpContext.SignInAsync("AdminCookie", principal);
|
||||
return RedirectToPage("/Admin/Index");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Admin.LogoutModel
|
||||
@@ -0,0 +1,14 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages.Admin;
|
||||
|
||||
public class LogoutModel : PageModel
|
||||
{
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
await HttpContext.SignOutAsync("AdminCookie");
|
||||
return RedirectToPage("/Admin/Login");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Admin.Orders.OrdersIndexModel
|
||||
@{ ViewData["Title"] = "سفارشها"; Layout = "_AdminLayout"; }
|
||||
|
||||
<div class="p-6 md:p-8">
|
||||
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">سفارشها</h1>
|
||||
|
||||
@if (!Model.Orders.Any())
|
||||
{
|
||||
<div class="text-center py-20 text-gray-400">
|
||||
<div class="text-5xl mb-4">📦</div>
|
||||
<p>هنوز سفارشی ثبت نشده</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="space-y-4">
|
||||
@foreach (var o in Model.Orders)
|
||||
{
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-6">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4 mb-4">
|
||||
<div>
|
||||
<div class="flex items-center gap-3 mb-1">
|
||||
<span class="font-mono text-sm text-gray-500">@o.OrderNumber</span>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full font-medium @SiteData.OrderStatusBadge(o.Status)">
|
||||
@SiteData.OrderStatusLabel(o.Status)
|
||||
</span>
|
||||
</div>
|
||||
<div class="font-bold text-gray-900">@o.CustomerName</div>
|
||||
<div class="text-sm text-gray-500">@o.CustomerPhone</div>
|
||||
@if (o.CustomerAddress != null) { <div class="text-xs text-gray-400 mt-1">@o.CustomerAddress</div> }
|
||||
</div>
|
||||
<div class="text-left">
|
||||
<div class="text-xl font-extrabold text-blue-700">@SiteData.FormatPrice(o.Total)</div>
|
||||
<div class="text-xs text-gray-400 mt-1">@SiteData.ToJalaliWithTime(o.CreatedAt)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Items -->
|
||||
<div class="bg-gray-50 rounded-xl p-4 mb-4">
|
||||
<p class="text-xs text-gray-500 font-bold mb-2">اقلام سفارش:</p>
|
||||
<ul class="space-y-1">
|
||||
@foreach (var item in o.Items)
|
||||
{
|
||||
<li class="flex justify-between text-sm">
|
||||
<span class="text-gray-700">@item.ProductNameFa × @item.Quantity</span>
|
||||
<span class="text-gray-500">@SiteData.FormatPrice(item.Subtotal)</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@if (o.Notes != null) { <p class="text-sm text-gray-500 mb-4">یادداشت: @o.Notes</p> }
|
||||
|
||||
<!-- Status update -->
|
||||
<form method="post" asp-page-handler="UpdateStatus" class="flex items-center gap-3">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="id" value="@o.Id" />
|
||||
<span class="text-sm text-gray-500 font-medium">وضعیت:</span>
|
||||
<select name="status" class="border border-gray-200 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
@foreach (var s in Enum.GetValues<AsadiTools.Models.OrderStatus>())
|
||||
{
|
||||
<option value="@s" selected="@(o.Status == s)">@SiteData.OrderStatusLabel(s)</option>
|
||||
}
|
||||
</select>
|
||||
<button type="submit" class="bg-blue-700 text-white px-4 py-1.5 rounded-lg text-sm font-medium hover:bg-blue-800 transition-colors">
|
||||
ذخیره
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
using AsadiTools.Data;
|
||||
using AsadiTools.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AsadiTools.Pages.Admin.Orders;
|
||||
|
||||
[Authorize(AuthenticationSchemes = "AdminCookie")]
|
||||
public class OrdersIndexModel(AppDbContext db) : PageModel
|
||||
{
|
||||
public List<Order> Orders { get; private set; } = [];
|
||||
|
||||
public async Task OnGetAsync() =>
|
||||
Orders = await db.Orders
|
||||
.Include(o => o.Items)
|
||||
.OrderByDescending(o => o.Id)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<IActionResult> OnPostUpdateStatusAsync(int id, OrderStatus status)
|
||||
{
|
||||
var order = await db.Orders.FindAsync(id);
|
||||
if (order is not null)
|
||||
{
|
||||
order.Status = status;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
return RedirectToPage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Admin.Products.CreateModel
|
||||
@{ ViewData["Title"] = "افزودن محصول"; Layout = "_AdminLayout"; }
|
||||
|
||||
<div class="p-6 md:p-8 max-w-2xl">
|
||||
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">افزودن محصول جدید</h1>
|
||||
<form method="post" class="bg-white rounded-2xl border border-gray-100 p-6 space-y-5">
|
||||
@Html.AntiForgeryToken()
|
||||
@await Html.PartialAsync("_ProductFormFields", Model.Input)
|
||||
<div class="flex gap-3">
|
||||
<button type="submit" class="flex-1 bg-blue-700 text-white py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">افزودن محصول</button>
|
||||
<a href="/Admin/Products" class="px-5 border border-gray-200 rounded-xl text-sm text-gray-600 hover:bg-gray-50 transition-colors flex items-center">انصراف</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,52 @@
|
||||
using AsadiTools.Data;
|
||||
using AsadiTools.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace AsadiTools.Pages.Admin.Products;
|
||||
|
||||
[Authorize(AuthenticationSchemes = "AdminCookie")]
|
||||
public class CreateModel(AppDbContext db) : PageModel
|
||||
{
|
||||
[BindProperty] public ProductInput Input { get; set; } = new();
|
||||
|
||||
public void OnGet() { }
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
if (!ModelState.IsValid) return Page();
|
||||
db.Products.Add(new Product
|
||||
{
|
||||
NameFa = Input.NameFa,
|
||||
NameEn = Input.NameEn,
|
||||
Description = Input.Description,
|
||||
Price = Input.Price,
|
||||
DiscountPrice = Input.DiscountPrice,
|
||||
Category = Input.Category,
|
||||
Brand = string.IsNullOrEmpty(Input.Brand) ? null : Input.Brand,
|
||||
Sku = Input.Sku,
|
||||
Stock = Input.Stock,
|
||||
IsActive = Input.IsActive,
|
||||
ImageUrl = string.IsNullOrWhiteSpace(Input.ImageUrl) ? null : Input.ImageUrl,
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
return RedirectToPage("/Admin/Products/Index");
|
||||
}
|
||||
}
|
||||
|
||||
public class ProductInput
|
||||
{
|
||||
[Required] public string NameFa { get; set; } = string.Empty;
|
||||
public string? NameEn { get; set; }
|
||||
public string? Description { get; set; }
|
||||
[Required, Range(1, int.MaxValue)] public decimal Price { get; set; }
|
||||
public decimal? DiscountPrice { get; set; }
|
||||
[Required] public string Category { get; set; } = "carbon";
|
||||
public string? Brand { get; set; }
|
||||
public string? Sku { get; set; }
|
||||
public int Stock { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public string? ImageUrl { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Admin.Products.EditModel
|
||||
@{ ViewData["Title"] = "ویرایش محصول"; Layout = "_AdminLayout"; }
|
||||
|
||||
<div class="p-6 md:p-8 max-w-2xl">
|
||||
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">ویرایش محصول</h1>
|
||||
<form method="post" class="bg-white rounded-2xl border border-gray-100 p-6 space-y-5">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="id" value="@Model.ProductId" />
|
||||
@await Html.PartialAsync("_ProductFormFields", Model.Input)
|
||||
<div class="flex gap-3">
|
||||
<button type="submit" class="flex-1 bg-blue-700 text-white py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">ذخیره تغییرات</button>
|
||||
<a href="/Admin/Products" class="px-5 border border-gray-200 rounded-xl text-sm text-gray-600 hover:bg-gray-50 transition-colors flex items-center">انصراف</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,57 @@
|
||||
using AsadiTools.Data;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages.Admin.Products;
|
||||
|
||||
[Authorize(AuthenticationSchemes = "AdminCookie")]
|
||||
public class EditModel(AppDbContext db) : PageModel
|
||||
{
|
||||
[BindProperty] public ProductInput Input { get; set; } = new();
|
||||
public int ProductId { get; private set; }
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(int id)
|
||||
{
|
||||
var p = await db.Products.FindAsync(id);
|
||||
if (p is null) return NotFound();
|
||||
ProductId = id;
|
||||
Input = new ProductInput
|
||||
{
|
||||
NameFa = p.NameFa,
|
||||
NameEn = p.NameEn,
|
||||
Description = p.Description,
|
||||
Price = p.Price,
|
||||
DiscountPrice = p.DiscountPrice,
|
||||
Category = p.Category,
|
||||
Brand = p.Brand,
|
||||
Sku = p.Sku,
|
||||
Stock = p.Stock,
|
||||
IsActive = p.IsActive,
|
||||
ImageUrl = p.ImageUrl,
|
||||
};
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(int id)
|
||||
{
|
||||
if (!ModelState.IsValid) { ProductId = id; return Page(); }
|
||||
var p = await db.Products.FindAsync(id);
|
||||
if (p is null) return NotFound();
|
||||
|
||||
p.NameFa = Input.NameFa;
|
||||
p.NameEn = Input.NameEn;
|
||||
p.Description = Input.Description;
|
||||
p.Price = Input.Price;
|
||||
p.DiscountPrice = Input.DiscountPrice;
|
||||
p.Category = Input.Category;
|
||||
p.Brand = string.IsNullOrEmpty(Input.Brand) ? null : Input.Brand;
|
||||
p.Sku = Input.Sku;
|
||||
p.Stock = Input.Stock;
|
||||
p.IsActive = Input.IsActive;
|
||||
p.ImageUrl = string.IsNullOrWhiteSpace(Input.ImageUrl) ? null : Input.ImageUrl;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return RedirectToPage("/Admin/Products/Index");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Admin.Products.ProductsIndexModel
|
||||
@{ ViewData["Title"] = "محصولات"; Layout = "_AdminLayout"; }
|
||||
|
||||
<div class="p-6 md:p-8">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<h1 class="text-2xl font-extrabold text-gray-900">محصولات</h1>
|
||||
<a href="/Admin/Products/Create" class="flex items-center gap-2 bg-blue-700 text-white px-4 py-2.5 rounded-xl text-sm font-bold hover:bg-blue-800 transition-colors">
|
||||
➕ افزودن محصول
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl border border-gray-100 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 border-b">
|
||||
<tr>
|
||||
<th class="px-5 py-3.5 text-right font-medium text-gray-500">نام</th>
|
||||
<th class="px-5 py-3.5 text-right font-medium text-gray-500">دسته</th>
|
||||
<th class="px-5 py-3.5 text-right font-medium text-gray-500">برند</th>
|
||||
<th class="px-5 py-3.5 text-right font-medium text-gray-500">قیمت</th>
|
||||
<th class="px-5 py-3.5 text-right font-medium text-gray-500">موجودی</th>
|
||||
<th class="px-5 py-3.5 text-right font-medium text-gray-500">وضعیت</th>
|
||||
<th class="px-5 py-3.5 text-right font-medium text-gray-500">عملیات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-50">
|
||||
@foreach (var p in Model.Products)
|
||||
{
|
||||
var cat = SiteData.Categories.FirstOrDefault(c => c.Id == p.Category);
|
||||
var brand = SiteData.Brands.FirstOrDefault(b => b.Id == p.Brand);
|
||||
<tr class="hover:bg-gray-50/50">
|
||||
<td class="px-5 py-4">
|
||||
<div class="font-medium text-gray-900">@p.NameFa</div>
|
||||
@if (p.Sku != null) { <div class="text-xs text-gray-400 font-mono">@p.Sku</div> }
|
||||
</td>
|
||||
<td class="px-5 py-4 text-gray-500">@(cat != null ? cat.Icon + " " + cat.NameFa : p.Category)</td>
|
||||
<td class="px-5 py-4">
|
||||
@if (brand != null)
|
||||
{
|
||||
<span class="text-xs font-bold px-2 py-0.5 rounded-full text-white" style="background-color:@brand.Color">@brand.NameFa</span>
|
||||
}
|
||||
else { <span class="text-gray-400">–</span> }
|
||||
</td>
|
||||
<td class="px-5 py-4 font-bold text-blue-700">@SiteData.FormatPrice(p.Price)</td>
|
||||
<td class="px-5 py-4">
|
||||
<span class="font-bold @(p.Stock == 0 ? "text-red-500" : p.Stock < 5 ? "text-yellow-500" : "text-green-600")">@p.Stock</span>
|
||||
</td>
|
||||
<td class="px-5 py-4">
|
||||
<span class="text-xs px-2 py-1 rounded-full font-medium @(p.IsActive ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-500")">
|
||||
@(p.IsActive ? "فعال" : "غیرفعال")
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-5 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="/Admin/Products/Edit?id=@p.Id" class="text-blue-600 hover:text-blue-800 text-xs font-medium">ویرایش</a>
|
||||
<form method="post" asp-page-handler="Delete" onsubmit="return confirm('حذف شود؟')">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="id" value="@p.Id" />
|
||||
<button type="submit" class="text-red-400 hover:text-red-600 text-xs font-medium">حذف</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
using AsadiTools.Data;
|
||||
using AsadiTools.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AsadiTools.Pages.Admin.Products;
|
||||
|
||||
[Authorize(AuthenticationSchemes = "AdminCookie")]
|
||||
public class ProductsIndexModel(AppDbContext db) : PageModel
|
||||
{
|
||||
public List<Product> Products { get; private set; } = [];
|
||||
|
||||
public async Task OnGetAsync() =>
|
||||
Products = await db.Products.OrderByDescending(p => p.Id).ToListAsync();
|
||||
|
||||
public async Task<IActionResult> OnPostDeleteAsync(int id)
|
||||
{
|
||||
var p = await db.Products.FindAsync(id);
|
||||
if (p is not null) { p.IsActive = false; await db.SaveChangesAsync(); }
|
||||
return RedirectToPage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
@model AsadiTools.Pages.Admin.Products.ProductInput
|
||||
@using AsadiTools.Services
|
||||
|
||||
@{
|
||||
var inputCls = "w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500";
|
||||
}
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="col-span-2">
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">نام فارسی <span class="text-red-500">*</span></label>
|
||||
<input asp-for="NameFa" class="@inputCls" placeholder="مثال: کاربن دیوالت DCD776" />
|
||||
<span asp-validation-for="NameFa" class="text-red-500 text-xs"></span>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">نام انگلیسی</label>
|
||||
<input asp-for="NameEn" class="@inputCls" dir="ltr" placeholder="e.g. DeWalt DCD776 Carbon Brush" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">کد محصول (SKU)</label>
|
||||
<input asp-for="Sku" class="@inputCls" dir="ltr" placeholder="DW-CBR-776" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">دستهبندی <span class="text-red-500">*</span></label>
|
||||
<select asp-for="Category" class="@inputCls">
|
||||
@foreach (var c in SiteData.Categories)
|
||||
{
|
||||
<option value="@c.Id">@c.Icon @c.NameFa</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">برند</label>
|
||||
<select asp-for="Brand" class="@inputCls">
|
||||
<option value="">بدون برند</option>
|
||||
@foreach (var b in SiteData.Brands)
|
||||
{
|
||||
<option value="@b.Id">@b.NameFa</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">موجودی</label>
|
||||
<input asp-for="Stock" type="number" min="0" class="@inputCls" dir="ltr" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">قیمت (تومان) <span class="text-red-500">*</span></label>
|
||||
<input asp-for="Price" type="number" min="0" class="@inputCls" dir="ltr" />
|
||||
<span asp-validation-for="Price" class="text-red-500 text-xs"></span>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">قیمت با تخفیف</label>
|
||||
<input asp-for="DiscountPrice" type="number" min="0" class="@inputCls" dir="ltr" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">وضعیت</label>
|
||||
<select asp-for="IsActive" class="@inputCls">
|
||||
<option value="true">فعال</option>
|
||||
<option value="false">غیرفعال</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">توضیحات</label>
|
||||
<textarea asp-for="Description" rows="3" class="@inputCls resize-none"></textarea>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">آدرس تصویر (URL)</label>
|
||||
<input asp-for="ImageUrl" class="@inputCls" dir="ltr" placeholder="https://..." />
|
||||
<p class="text-xs text-gray-400 mt-1">لینک مستقیم به تصویر محصول. در صورت خالی ماندن آیکون دستهبندی نمایش داده میشود.</p>
|
||||
@if (!string.IsNullOrEmpty(Model?.ImageUrl))
|
||||
{
|
||||
<img src="@Model.ImageUrl" alt="پیشنمایش" class="mt-2 h-24 w-24 object-cover rounded-xl border border-gray-200" onerror="this.style.display='none'" />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,96 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Blog.BlogIndexModel
|
||||
@{ Layout = "_Layout"; }
|
||||
|
||||
<div class="bg-blue-800 text-white py-12 px-4">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<nav class="flex items-center gap-2 text-sm text-blue-300 mb-4">
|
||||
<a href="/" class="hover:text-white">خانه</a><span>/</span>
|
||||
<span class="text-white">بلاگ</span>
|
||||
</nav>
|
||||
<h1 class="text-3xl font-extrabold mb-2">بلاگ آساد ابزار</h1>
|
||||
<p class="text-blue-200">راهنما، نکات فنی و مقالات تخصصی تعمیر ابزار برقی</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-6xl mx-auto px-4 py-10">
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.Tag))
|
||||
{
|
||||
<div class="mb-6 flex items-center gap-3">
|
||||
<span class="bg-blue-100 text-blue-700 px-3 py-1 rounded-full text-sm font-bold">برچسب: @Model.Tag</span>
|
||||
<a href="/blog" class="text-sm text-gray-400 hover:text-gray-600">× حذف فیلتر</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!Model.Posts.Any())
|
||||
{
|
||||
<div class="text-center py-20 text-gray-400">
|
||||
<div class="text-5xl mb-4">📝</div>
|
||||
<p>مقالهای یافت نشد.</p>
|
||||
<a href="/blog" class="text-blue-600 text-sm mt-2 block hover:underline">مشاهده همه مقالات</a>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@foreach (var post in Model.Posts)
|
||||
{
|
||||
<article class="bg-white rounded-2xl overflow-hidden border border-gray-100 hover:shadow-lg transition-shadow flex flex-col">
|
||||
@if (!string.IsNullOrEmpty(post.FeaturedImage))
|
||||
{
|
||||
<a href="/blog/@post.EffectiveSlug" class="block overflow-hidden" style="height:200px">
|
||||
<img src="@post.FeaturedImage" alt="@post.Title" loading="lazy"
|
||||
class="w-full h-full object-cover hover:scale-105 transition-transform duration-500" />
|
||||
</a>
|
||||
}
|
||||
<div class="p-5 flex flex-col flex-1">
|
||||
@if (post.TagList.Any())
|
||||
{
|
||||
<div class="flex flex-wrap gap-1.5 mb-3">
|
||||
@foreach (var tag in post.TagList.Take(3))
|
||||
{
|
||||
<a href="/blog?tag=@Uri.EscapeDataString(tag)"
|
||||
class="text-xs bg-blue-50 text-blue-600 px-2 py-0.5 rounded-full hover:bg-blue-100 transition-colors">@tag</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<h2 class="font-bold text-lg text-gray-900 mb-2 leading-snug hover:text-blue-700 transition-colors">
|
||||
<a href="/blog/@post.EffectiveSlug">@post.Title</a>
|
||||
</h2>
|
||||
@if (!string.IsNullOrEmpty(post.Excerpt))
|
||||
{
|
||||
<p class="text-sm text-gray-500 leading-7 mb-4 line-clamp-3 flex-1">@post.Excerpt</p>
|
||||
}
|
||||
<div class="flex items-center justify-between mt-auto pt-3 border-t border-gray-50">
|
||||
<span class="text-xs text-gray-400">📅 @post.DisplayDate</span>
|
||||
<a href="/blog/@post.EffectiveSlug"
|
||||
class="text-sm text-blue-600 font-medium hover:underline">ادامه مطلب ›</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (Model.TotalPages > 1)
|
||||
{
|
||||
<div class="flex justify-center items-center gap-2 mt-10">
|
||||
@if (Model.CurrentPage > 1)
|
||||
{
|
||||
<a href="/blog?page=@(Model.CurrentPage - 1)@(Model.Tag != null ? "&tag=" + Uri.EscapeDataString(Model.Tag) : "")"
|
||||
class="px-4 py-2 rounded-xl border border-gray-200 text-sm text-gray-600 hover:border-blue-400 hover:text-blue-700 transition-colors">‹ قبلی</a>
|
||||
}
|
||||
@for (var i = Math.Max(1, Model.CurrentPage - 2); i <= Math.Min(Model.TotalPages, Model.CurrentPage + 2); i++)
|
||||
{
|
||||
<a href="/blog?page=@i@(Model.Tag != null ? "&tag=" + Uri.EscapeDataString(Model.Tag) : "")"
|
||||
class="px-4 py-2 rounded-xl border text-sm transition-colors @(i == Model.CurrentPage ? "bg-blue-700 text-white border-blue-700" : "border-gray-200 text-gray-600 hover:border-blue-400")">@i</a>
|
||||
}
|
||||
@if (Model.CurrentPage < Model.TotalPages)
|
||||
{
|
||||
<a href="/blog?page=@(Model.CurrentPage + 1)@(Model.Tag != null ? "&tag=" + Uri.EscapeDataString(Model.Tag) : "")"
|
||||
class="px-4 py-2 rounded-xl border border-gray-200 text-sm text-gray-600 hover:border-blue-400 hover:text-blue-700 transition-colors">بعدی ›</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,36 @@
|
||||
using AsadiTools.Data;
|
||||
using AsadiTools.Models;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AsadiTools.Pages.Blog;
|
||||
|
||||
public class BlogIndexModel(AppDbContext db) : PageModel
|
||||
{
|
||||
public const int PageSize = 6;
|
||||
public List<BlogPost> Posts { get; private set; } = [];
|
||||
public int CurrentPage { get; private set; } = 1;
|
||||
public int TotalPages { get; private set; }
|
||||
public string? Tag { get; private set; }
|
||||
|
||||
public async Task OnGetAsync(string? tag, int page = 1)
|
||||
{
|
||||
Tag = tag;
|
||||
|
||||
var q = db.BlogPosts.Where(p => p.IsPublished);
|
||||
if (!string.IsNullOrEmpty(tag))
|
||||
q = q.Where(p => p.Tags != null && p.Tags.Contains(tag));
|
||||
|
||||
var total = await q.CountAsync();
|
||||
TotalPages = (int)Math.Ceiling(total / (double)PageSize);
|
||||
CurrentPage = Math.Clamp(page, 1, Math.Max(1, TotalPages));
|
||||
|
||||
Posts = await q.OrderByDescending(p => p.PublishedAt)
|
||||
.Skip((CurrentPage - 1) * PageSize)
|
||||
.Take(PageSize)
|
||||
.ToListAsync();
|
||||
|
||||
ViewData["Title"] = "بلاگ آساد ابزار — راهنما و مقالات تعمیر ابزار";
|
||||
ViewData["Description"] = "مقالات تخصصی تعمیر و نگهداری ابزار برقی. راهنمای خرید، نکات فنی و اخبار صنعت ابزار از آساد ابزار کرج.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
@page "/blog/{slug}"
|
||||
@model AsadiTools.Pages.Blog.BlogPostModel
|
||||
@{ Layout = "_Layout"; var p = Model.Post!; var c = SiteData.Company; }
|
||||
|
||||
@section Head {
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "BlogPosting",
|
||||
"headline": "@p.Title.Replace("\"","'")",
|
||||
"description": "@((p.MetaDescription ?? p.Excerpt ?? "").Replace("\"","'"))",
|
||||
"image": "@(p.FeaturedImage ?? "")",
|
||||
"datePublished": "@(p.PublishedAt?.ToString("yyyy-MM-dd") ?? p.CreatedAt.ToString("yyyy-MM-dd"))",
|
||||
"dateModified": "@p.UpdatedAt.ToString("yyyy-MM-dd")",
|
||||
"author": { "@@type": "Organization", "name": "آساد ابزار کرج" },
|
||||
"publisher": { "@@type": "Organization", "name": "آساد ابزار کرج", "logo": { "@@type": "ImageObject", "url": "" } },
|
||||
"mainEntityOfPage": { "@@type": "WebPage", "@@id": "/blog/@p.EffectiveSlug" }
|
||||
}
|
||||
</script>
|
||||
}
|
||||
|
||||
<div class="max-w-6xl mx-auto px-4 py-10">
|
||||
<div class="grid lg:grid-cols-3 gap-10">
|
||||
|
||||
<!-- ── Article ──────────────────────────────────────────────────── -->
|
||||
<article class="lg:col-span-2">
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<nav class="flex items-center gap-2 text-sm text-gray-400 mb-6">
|
||||
<a href="/" class="hover:text-blue-600">خانه</a><span>/</span>
|
||||
<a href="/blog" class="hover:text-blue-600">بلاگ</a><span>/</span>
|
||||
<span class="text-gray-700 line-clamp-1">@p.Title</span>
|
||||
</nav>
|
||||
|
||||
<!-- Tags -->
|
||||
@if (p.TagList.Any())
|
||||
{
|
||||
<div class="flex flex-wrap gap-1.5 mb-4">
|
||||
@foreach (var tag in p.TagList)
|
||||
{
|
||||
<a href="/blog?tag=@Uri.EscapeDataString(tag)"
|
||||
class="text-xs bg-blue-50 text-blue-600 px-2.5 py-1 rounded-full hover:bg-blue-100 transition-colors">@tag</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<h1 class="text-3xl font-extrabold text-gray-900 leading-tight mb-4">@p.Title</h1>
|
||||
|
||||
<div class="flex items-center gap-4 text-sm text-gray-400 mb-8 pb-8 border-b">
|
||||
<span>📅 @p.DisplayDate</span>
|
||||
<span>✍️ آساد ابزار کرج</span>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(p.FeaturedImage))
|
||||
{
|
||||
<div class="rounded-2xl overflow-hidden mb-8" style="max-height:420px">
|
||||
<img src="@p.FeaturedImage" alt="@p.Title" class="w-full h-full object-cover" loading="eager" />
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Content -->
|
||||
<div class="prose prose-lg max-w-none text-gray-700 leading-8
|
||||
[&_h2]:text-2xl [&_h2]:font-extrabold [&_h2]:text-gray-900 [&_h2]:mt-10 [&_h2]:mb-4 [&_h2]:pb-2 [&_h2]:border-b
|
||||
[&_h3]:text-xl [&_h3]:font-bold [&_h3]:text-gray-800 [&_h3]:mt-6 [&_h3]:mb-3
|
||||
[&_p]:mb-4 [&_p]:leading-8
|
||||
[&_ul]:mb-4 [&_ul]:space-y-2 [&_ul]:list-disc [&_ul]:pr-6
|
||||
[&_ol]:mb-4 [&_ol]:space-y-2 [&_ol]:list-decimal [&_ol]:pr-6
|
||||
[&_li]:leading-7
|
||||
[&_blockquote]:border-r-4 [&_blockquote]:border-blue-400 [&_blockquote]:pr-4 [&_blockquote]:italic [&_blockquote]:text-gray-600 [&_blockquote]:my-6
|
||||
[&_strong]:font-bold [&_strong]:text-gray-900
|
||||
[&_table]:w-full [&_table]:border-collapse [&_table]:my-6
|
||||
[&_th]:bg-gray-100 [&_th]:p-3 [&_th]:text-right [&_th]:font-bold [&_th]:border [&_th]:border-gray-200
|
||||
[&_td]:p-3 [&_td]:border [&_td]:border-gray-200 [&_td]:text-right">
|
||||
@Html.Raw(p.Content)
|
||||
</div>
|
||||
|
||||
<!-- Related -->
|
||||
@if (Model.RelatedPosts.Any())
|
||||
{
|
||||
<div class="mt-12 pt-8 border-t">
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-5">مقالات مرتبط</h2>
|
||||
<div class="grid sm:grid-cols-3 gap-4">
|
||||
@foreach (var rp in Model.RelatedPosts)
|
||||
{
|
||||
<a href="/blog/@rp.EffectiveSlug"
|
||||
class="group bg-white rounded-xl border border-gray-100 overflow-hidden hover:shadow-md transition-shadow">
|
||||
@if (!string.IsNullOrEmpty(rp.FeaturedImage))
|
||||
{
|
||||
<div style="height:120px" class="overflow-hidden">
|
||||
<img src="@rp.FeaturedImage" alt="@rp.Title" loading="lazy"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" />
|
||||
</div>
|
||||
}
|
||||
<div class="p-3">
|
||||
<p class="text-sm font-medium text-gray-800 leading-snug line-clamp-2 group-hover:text-blue-700">@rp.Title</p>
|
||||
<p class="text-xs text-gray-400 mt-1">@rp.DisplayDate</p>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</article>
|
||||
|
||||
<!-- ── Sidebar ───────────────────────────────────────────────────── -->
|
||||
<aside class="space-y-5 lg:sticky lg:top-24 lg:self-start">
|
||||
<!-- CTA -->
|
||||
<div class="bg-blue-700 text-white rounded-2xl p-6 text-center">
|
||||
<div class="text-3xl mb-2">🔧</div>
|
||||
<h3 class="font-extrabold mb-2">تعمیر ابزار در کرج</h3>
|
||||
<p class="text-blue-200 text-sm mb-5">تشخیص رایگان • ضمانت ۳ ماهه</p>
|
||||
<a href="tel:@c.TelPhone"
|
||||
class="block bg-white text-blue-700 font-bold py-3 rounded-xl hover:opacity-90 mb-3 transition-opacity">
|
||||
📞 @c.Phone
|
||||
</a>
|
||||
<a href="https://wa.me/@c.Whatsapp" target="_blank"
|
||||
class="block bg-green-500 text-white font-bold py-3 rounded-xl hover:bg-green-600 transition-colors">
|
||||
💬 واتساپ
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Tags cloud -->
|
||||
@if (p.TagList.Any())
|
||||
{
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-5">
|
||||
<h3 class="font-bold text-gray-900 mb-3 text-sm">برچسبها</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@foreach (var tag in p.TagList)
|
||||
{
|
||||
<a href="/blog?tag=@Uri.EscapeDataString(tag)"
|
||||
class="text-xs bg-gray-100 text-gray-600 px-2.5 py-1 rounded-full hover:bg-blue-100 hover:text-blue-700 transition-colors">@tag</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<a href="/blog" class="block bg-gray-50 border border-gray-200 rounded-2xl p-5 hover:shadow-md transition-shadow text-center">
|
||||
<span class="text-xl block mb-1">📖</span>
|
||||
<span class="font-bold text-gray-800 text-sm">مشاهده همه مقالات</span>
|
||||
</a>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,44 @@
|
||||
using AsadiTools.Data;
|
||||
using AsadiTools.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AsadiTools.Pages.Blog;
|
||||
|
||||
public class BlogPostModel(AppDbContext db) : PageModel
|
||||
{
|
||||
public BlogPost? Post { get; private set; }
|
||||
public List<BlogPost> RelatedPosts { get; private set; } = [];
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(string slug)
|
||||
{
|
||||
Post = await db.BlogPosts
|
||||
.Where(p => p.IsPublished && p.Slug == slug)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
// Fallback: numeric slug = post ID
|
||||
if (Post is null && int.TryParse(slug, out var id))
|
||||
Post = await db.BlogPosts.Where(p => p.IsPublished && p.Id == id).FirstOrDefaultAsync();
|
||||
|
||||
if (Post is null) return NotFound();
|
||||
|
||||
// Related: same tag(s)
|
||||
var tags = Post.TagList;
|
||||
if (tags.Length > 0)
|
||||
{
|
||||
RelatedPosts = await db.BlogPosts
|
||||
.Where(p => p.IsPublished && p.Id != Post.Id && p.Tags != null)
|
||||
.ToListAsync();
|
||||
RelatedPosts = RelatedPosts
|
||||
.Where(p => p.TagList.Any(t => tags.Contains(t)))
|
||||
.OrderByDescending(p => p.PublishedAt)
|
||||
.Take(3)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
ViewData["Title"] = Post.Title + " | آساد ابزار";
|
||||
ViewData["Description"] = Post.MetaDescription ?? Post.Excerpt ?? Post.Title;
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
@page "/brands/{brand}"
|
||||
@model AsadiTools.Pages.Brands.BrandDetailModel
|
||||
@{ Layout = "_Layout"; var b = Model.Brand!; var c = SiteData.Company; }
|
||||
|
||||
@section Head {
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@graph": [
|
||||
{
|
||||
"@@type": "LocalBusiness",
|
||||
"name": "آساد ابزار کرج",
|
||||
"telephone": "+98@c.TelPhone.Substring(1)",
|
||||
"address": { "@@type": "PostalAddress", "addressLocality": "کرج", "addressRegion": "البرز", "addressCountry": "IR" },
|
||||
"openingHours": "Mo-Sa 08:00-18:00",
|
||||
"priceRange": "$$"
|
||||
},
|
||||
{
|
||||
"@@type": "Service",
|
||||
"name": "تعمیر ابزار @b.NameFa در کرج",
|
||||
"provider": { "@@type": "LocalBusiness", "name": "آساد ابزار کرج" },
|
||||
"areaServed": { "@@type": "City", "name": "کرج" },
|
||||
"description": "@ViewData["Description"]",
|
||||
"offers": { "@@type": "Offer", "availability": "https://schema.org/InStock" }
|
||||
},
|
||||
{
|
||||
"@@type": "FAQPage",
|
||||
"mainEntity": [
|
||||
@for (int i = 0; i < b.Faqs.Length; i++) {
|
||||
var faq = b.Faqs[i];
|
||||
<text>{ "@@type": "Question", "name": "@faq.Q.Replace("\"","'")", "acceptedAnswer": { "@@type": "Answer", "text": "@faq.A.Replace("\"","'")" } }@(i < b.Faqs.Length - 1 ? "," : "")</text>
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"@@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{ "@@type": "ListItem", "position": 1, "name": "خانه", "item": "/" },
|
||||
{ "@@type": "ListItem", "position": 2, "name": "برندها", "item": "/brands" },
|
||||
{ "@@type": "ListItem", "position": 3, "name": "تعمیر ابزار @b.NameFa", "item": "/brands/@b.Id" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
}
|
||||
|
||||
<!-- ═══════════════════════ HERO ══════════════════════════════════════════ -->
|
||||
<div class="relative text-white py-16 px-4 overflow-hidden" style="min-height:320px">
|
||||
<div class="absolute inset-0">
|
||||
<img src="@b.HeroImage" alt="تعمیر ابزار @b.NameFa در کرج" class="w-full h-full object-cover" />
|
||||
<div class="absolute inset-0" style="background:linear-gradient(135deg, @b.Color+ee 0%, @b.Color+bb 50%, rgba(0,0,0,0.7) 100%)"></div>
|
||||
</div>
|
||||
<div class="relative max-w-6xl mx-auto">
|
||||
<!-- Breadcrumb -->
|
||||
<nav class="flex items-center gap-2 text-sm mb-6 opacity-80" style="color:@(b.TextColor == "#fff" ? "white" : "#374151")">
|
||||
<a href="/" class="hover:opacity-100">خانه</a>
|
||||
<span>/</span>
|
||||
<a href="/brands" class="hover:opacity-100">برندها</a>
|
||||
<span>/</span>
|
||||
<span class="opacity-100 font-medium">@b.NameFa</span>
|
||||
</nav>
|
||||
|
||||
<div class="flex flex-col sm:flex-row items-start sm:items-center gap-6">
|
||||
<div class="w-24 h-24 rounded-2xl flex items-center justify-center text-2xl font-extrabold border-2 border-white/40 bg-white/20 backdrop-blur shrink-0 shadow-xl"
|
||||
style="color:@b.TextColor">
|
||||
@b.Name.Substring(0, Math.Min(3, b.Name.Length))
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex flex-wrap items-center gap-3 mb-3">
|
||||
<h1 class="text-4xl font-extrabold" style="color:@(b.TextColor == "#fff" ? "white" : "#111827")">
|
||||
تعمیر ابزار @b.NameFa در کرج
|
||||
</h1>
|
||||
@if (b.IsOfficial)
|
||||
{
|
||||
<span class="bg-yellow-400 text-gray-900 text-sm font-bold px-3 py-1 rounded-full shadow-lg">🛡️ نمایندگی رسمی</span>
|
||||
}
|
||||
</div>
|
||||
<p class="text-lg max-w-2xl leading-7 opacity-90" style="color:@(b.TextColor == "#fff" ? "#e5e7eb" : "#374151")">
|
||||
@b.Tagline
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2 mt-4 text-sm opacity-80" style="color:@(b.TextColor == "#fff" ? "#d1d5db" : "#6b7280")">
|
||||
<span>📍 تأسیس @b.Founded</span>
|
||||
<span>·</span>
|
||||
<span>🌍 @b.Country</span>
|
||||
@if (b.IsOfficial) { <span>·</span> <span class="text-yellow-300 font-bold">✅ نمایندگی رسمی در کرج</span> }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════ MAIN GRID ═════════════════════════════════════ -->
|
||||
<div class="max-w-6xl mx-auto px-4 py-12 grid lg:grid-cols-3 gap-10">
|
||||
|
||||
<!-- ══ LEFT / MAIN ════════════════════════════════════════════════════ -->
|
||||
<div class="lg:col-span-2 space-y-12">
|
||||
|
||||
@if (b.IsOfficial) {
|
||||
<!-- Official badge -->
|
||||
<div class="bg-yellow-50 border-2 border-yellow-300 rounded-2xl p-6">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-12 h-12 bg-yellow-400 rounded-xl flex items-center justify-center text-2xl shrink-0">🛡️</div>
|
||||
<div>
|
||||
<h2 class="text-xl font-extrabold text-gray-900 mb-2">نمایندگی رسمی مجاز @b.NameFa در کرج</h2>
|
||||
<p class="text-gray-600 leading-7">آساد ابزار تنها نمایندگی رسمی و مجاز برند @b.NameFa در شهر کرج و استان البرز است. تمام تعمیرات توسط تکنیسینهای آموزشدیده رسمی و با قطعات کاملاً اورجینال @b.NameFa انجام میشود. هر تعمیر دارای ضمانتنامه کتبی ۳ ماهه است.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- About brand -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 pb-3 border-b-2 flex items-center gap-3" style="border-color:@b.Color">
|
||||
<span class="w-8 h-8 rounded-lg flex items-center justify-center text-sm font-bold text-white" style="background:@b.Color">@b.Name[0]</span>
|
||||
درباره برند @b.NameFa
|
||||
</h2>
|
||||
<div class="prose prose-lg max-w-none space-y-4 text-gray-600 leading-8">
|
||||
<p>@b.About1</p>
|
||||
<p>@b.About2</p>
|
||||
<p>@b.About3</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Popular models -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 pb-3 border-b-2" style="border-color:@b.Color">
|
||||
مدلهای محبوب @b.NameFa در ایران
|
||||
</h2>
|
||||
<div class="grid sm:grid-cols-2 gap-4">
|
||||
@foreach (var m in b.Models)
|
||||
{
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-5 hover:shadow-md transition-shadow hover:border-gray-200">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="font-mono text-sm font-bold text-gray-800">@m.Model</span>
|
||||
<span class="text-xs font-bold px-2 py-0.5 rounded-full text-white" style="background:@b.Color">@m.Watts</span>
|
||||
</div>
|
||||
<div class="font-semibold text-gray-900 mb-1">@m.NameFa</div>
|
||||
<p class="text-sm text-gray-500 leading-6">@m.Desc</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Repair services -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 pb-3 border-b-2" style="border-color:@b.Color">
|
||||
خدمات تعمیر @b.NameFa در آساد ابزار
|
||||
</h2>
|
||||
<div class="grid sm:grid-cols-2 gap-3">
|
||||
@foreach (var svc in b.RepairServices)
|
||||
{
|
||||
<div class="flex items-start gap-3 bg-white rounded-xl border border-gray-100 p-4 hover:border-gray-200 transition-colors">
|
||||
<span class="w-6 h-6 rounded-full flex items-center justify-center text-white text-xs font-bold shrink-0 mt-0.5" style="background:@b.Color">✓</span>
|
||||
<span class="text-sm text-gray-700 leading-6">@svc</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Common problems -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 pb-3 border-b-2" style="border-color:@b.Color">
|
||||
مشکلات رایج ابزار @b.NameFa و راهحل
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
@foreach (var prob in b.CommonProblems)
|
||||
{
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-6">
|
||||
<h3 class="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
||||
<span class="w-6 h-6 rounded-full bg-red-100 text-red-600 flex items-center justify-center text-xs shrink-0">!</span>
|
||||
@prob.Problem
|
||||
</h3>
|
||||
<div class="grid sm:grid-cols-2 gap-4 text-sm">
|
||||
<div class="bg-red-50 rounded-xl p-3">
|
||||
<div class="text-xs font-bold text-red-700 mb-1">علت احتمالی:</div>
|
||||
<div class="text-gray-600 leading-6">@prob.Cause</div>
|
||||
</div>
|
||||
<div class="bg-green-50 rounded-xl p-3">
|
||||
<div class="text-xs font-bold text-green-700 mb-1">راهحل:</div>
|
||||
<div class="text-gray-600 leading-6">@prob.Solution</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Repair process -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 pb-3 border-b-2" style="border-color:@b.Color">
|
||||
فرآیند تعمیر ابزار @b.NameFa
|
||||
</h2>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
@{ var steps = new[] { ("۱","تحویل ابزار","حضوری یا از طریق پست"), ("۲","بررسی رایگان","تشخیص دقیق عیب"), ("۳","تعمیر تخصصی","قطعه اصل + ضمانت"), ("۴","تحویل با گارانتی","ضمانتنامه کتبی ۳ ماهه") }; }
|
||||
@foreach (var step in steps)
|
||||
{
|
||||
<div class="text-center p-5 bg-gray-50 rounded-2xl border border-gray-100 hover:bg-white hover:shadow-sm transition-all">
|
||||
<div class="w-11 h-11 rounded-full flex items-center justify-center text-lg font-extrabold mx-auto mb-3 shadow"
|
||||
style="background:@b.Color;color:@b.TextColor">@step.Item1</div>
|
||||
<div class="font-bold text-gray-800 text-sm mb-1">@step.Item2</div>
|
||||
<div class="text-xs text-gray-400">@step.Item3</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FAQ -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 pb-3 border-b-2" style="border-color:@b.Color">
|
||||
سوالات متداول درباره تعمیر ابزار @b.NameFa
|
||||
</h2>
|
||||
<div class="space-y-3" id="faq-list">
|
||||
@for (int i = 0; i < b.Faqs.Length; i++)
|
||||
{
|
||||
var faq = b.Faqs[i];
|
||||
<details class="bg-white rounded-2xl border border-gray-100 overflow-hidden group" @(i == 0 ? "open" : "")>
|
||||
<summary class="flex items-center justify-between p-5 cursor-pointer font-bold text-gray-900 hover:bg-gray-50 select-none list-none">
|
||||
<span>@faq.Q</span>
|
||||
<span class="text-gray-400 group-open:rotate-180 transition-transform shrink-0 mr-3">▾</span>
|
||||
</summary>
|
||||
<div class="px-5 pb-5 text-gray-600 leading-8 text-sm border-t border-gray-50 pt-4">@faq.A</div>
|
||||
</details>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- ══ SIDEBAR ══════════════════════════════════════════════════════ -->
|
||||
<div class="space-y-5 lg:sticky lg:top-24 lg:self-start">
|
||||
|
||||
<!-- CTA card -->
|
||||
<div class="rounded-2xl p-6 text-center shadow-lg" style="background:@b.Color">
|
||||
<div class="text-2xl mb-2">🔧</div>
|
||||
<h3 class="font-extrabold text-lg mb-1" style="color:@(b.TextColor == "#fff" ? "white" : "#111827")">
|
||||
درخواست تعمیر @b.NameFa
|
||||
</h3>
|
||||
<p class="text-sm mb-5 opacity-80" style="color:@(b.TextColor == "#fff" ? "#e5e7eb" : "#374151")">
|
||||
مشاوره رایگان – تشخیص رایگان
|
||||
</p>
|
||||
<a href="tel:@c.TelPhone"
|
||||
class="block bg-white font-bold text-center py-3 rounded-xl hover:opacity-90 mb-3 transition-opacity shadow"
|
||||
style="color:@b.Color">
|
||||
📞 @c.Phone
|
||||
</a>
|
||||
<a href="https://wa.me/@c.Whatsapp" target="_blank"
|
||||
class="block bg-green-500 text-white font-bold text-center py-3 rounded-xl hover:bg-green-600 transition-colors">
|
||||
💬 پیام واتساپ
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Guarantees -->
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-6">
|
||||
<h3 class="font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<span class="w-6 h-6 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-xs">⭐</span>
|
||||
مزایای تعمیر در آساد ابزار
|
||||
</h3>
|
||||
<ul class="space-y-3 text-sm text-gray-600">
|
||||
@foreach (var item in new[] {
|
||||
"تشخیص عیب رایگان بدون پیشپرداخت",
|
||||
"قطعات ۱۰۰٪ اورجینال تأییدشده",
|
||||
"ضمانتنامه کتبی ۳ ماهه",
|
||||
"تکنیسین مجاز و متخصص",
|
||||
"قیمت شفاف پیش از شروع",
|
||||
"تحویل سریع زیر ۴۸ ساعت",
|
||||
"ارسال از سراسر کشور"
|
||||
})
|
||||
{
|
||||
<li class="flex items-center gap-2">
|
||||
<span class="text-green-500 shrink-0">✓</span> @item
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Parts shop link -->
|
||||
<a href="/Shop?brand=@b.Id"
|
||||
class="block bg-gray-50 border border-gray-200 rounded-2xl p-5 hover:shadow-md hover:bg-white transition-all">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-2xl">🛒</span>
|
||||
<div>
|
||||
<h3 class="font-bold text-gray-900 text-sm">قطعات یدکی @b.NameFa</h3>
|
||||
<p class="text-xs text-gray-400 mt-0.5">خرید آنلاین از فروشگاه ما</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Other brands -->
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-5">
|
||||
<h3 class="font-bold text-gray-900 mb-3 text-sm">سایر برندها</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@foreach (var ob in BrandSeoData.AllBrands.Where(x => x.Id != b.Id))
|
||||
{
|
||||
<a href="/brands/@ob.Id"
|
||||
class="text-xs px-3 py-1.5 rounded-full border border-gray-200 text-gray-600 hover:text-white transition-all hover:border-transparent"
|
||||
style="--hover-bg:@ob.Color"
|
||||
onmouseover="this.style.background='@ob.Color';this.style.borderColor='@ob.Color'"
|
||||
onmouseout="this.style.background='';this.style.borderColor=''">
|
||||
@ob.NameFa
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════ BOTTOM CTA ════════════════════════════════════ -->
|
||||
<section class="py-14 px-4" style="background:@b.Color">
|
||||
<div class="max-w-3xl mx-auto text-center">
|
||||
<h2 class="text-2xl font-extrabold mb-3" style="color:@(b.TextColor == "#fff" ? "white" : "#111827")">
|
||||
ابزار @b.NameFa شما خراب شده؟
|
||||
</h2>
|
||||
<p class="mb-8 text-lg opacity-85" style="color:@(b.TextColor == "#fff" ? "#e5e7eb" : "#374151")">
|
||||
همین الان تماس بگیرید. تشخیص رایگان، تعمیر با ضمانت کتبی ۳ ماهه.
|
||||
</p>
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
<a href="tel:@c.TelPhone"
|
||||
class="bg-white font-bold px-8 py-3.5 rounded-xl hover:opacity-90 transition-opacity text-lg shadow-lg"
|
||||
style="color:@b.Color">
|
||||
📞 @c.Phone
|
||||
</a>
|
||||
<a href="https://wa.me/@c.Whatsapp" target="_blank"
|
||||
class="bg-green-600 text-white font-bold px-8 py-3.5 rounded-xl hover:bg-green-700 transition-colors text-lg shadow-lg">
|
||||
💬 واتساپ
|
||||
</a>
|
||||
<a href="/Shop?brand=@b.Id"
|
||||
class="bg-white/20 border border-white/50 font-bold px-8 py-3.5 rounded-xl hover:bg-white/30 transition-colors text-lg"
|
||||
style="color:@(b.TextColor == "#fff" ? "white" : "#111827")">
|
||||
🛒 قطعات یدکی
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,22 @@
|
||||
using AsadiTools.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages.Brands;
|
||||
|
||||
public class BrandDetailModel : PageModel
|
||||
{
|
||||
public BrandSeoPage? Brand { get; private set; }
|
||||
|
||||
public IActionResult OnGet(string brand)
|
||||
{
|
||||
Brand = BrandSeoData.AllBrands.FirstOrDefault(b =>
|
||||
b.Id.Equals(brand, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (Brand is null) return NotFound();
|
||||
|
||||
ViewData["Title"] = $"تعمیر ابزار {Brand.NameFa} در کرج | آساد ابزار{(Brand.IsOfficial ? " – نمایندگی رسمی" : "")}";
|
||||
ViewData["Description"] = $"تعمیر تخصصی ابزار {Brand.NameFa} در کرج با ضمانت ۳ ماهه. قطعات اصل، تکنیسین مجاز. {string.Join("، ", Brand.RepairServices.Take(3))}. تماس: ۰۲۶-۳۴۵۶۷۸۹۰";
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Brands.BrandsIndexModel
|
||||
@{ Layout = "_Layout"; }
|
||||
|
||||
<div class="bg-blue-800 text-white py-12 px-4">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<nav class="flex items-center gap-2 text-sm text-blue-300 mb-4">
|
||||
<a href="/" class="hover:text-white">خانه</a><span>/</span>
|
||||
<span class="text-white">برندها</span>
|
||||
</nav>
|
||||
<h1 class="text-3xl font-extrabold mb-2">تعمیر ابزار همه برندها در کرج</h1>
|
||||
<p class="text-blue-200">تخصص در تعمیر برترین برندهای ابزار صنعتی با ضمانت ۳ ماهه</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-6xl mx-auto px-4 py-12">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
@foreach (var b in Model.Brands)
|
||||
{
|
||||
<a href="/brands/@b.Id"
|
||||
class="group relative bg-white rounded-2xl overflow-hidden border-2 hover:shadow-xl transition-all hover:-translate-y-1"
|
||||
style="border-color:@(b.IsOfficial ? "#f59e0b" : "#e5e7eb")">
|
||||
@if (b.IsOfficial)
|
||||
{
|
||||
<div class="absolute top-3 right-3 z-10 bg-yellow-400 text-gray-900 text-xs font-bold px-2.5 py-1 rounded-full shadow">🛡️ رسمی</div>
|
||||
}
|
||||
<div class="relative h-36 overflow-hidden">
|
||||
<img src="@b.HeroImage" alt="تعمیر ابزار @b.NameFa" loading="lazy"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" />
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent"></div>
|
||||
<div class="absolute bottom-3 right-4 flex items-center gap-2">
|
||||
<div class="w-9 h-9 rounded-lg flex items-center justify-center text-xs font-extrabold shadow"
|
||||
style="background-color:@b.Color;color:@b.TextColor">@b.Name.Substring(0,2)</div>
|
||||
<span class="text-white font-bold text-lg">@b.NameFa</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<p class="text-sm text-gray-500 leading-6 mb-3 line-clamp-2">@b.Tagline</p>
|
||||
<div class="flex items-center justify-between text-xs text-gray-400">
|
||||
<span>📍 @b.Country · @b.Founded</span>
|
||||
<span class="text-blue-600 font-medium group-hover:underline">جزئیات ›</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Why trust us -->
|
||||
<div class="mt-16 bg-blue-900 rounded-3xl p-10 text-white text-center">
|
||||
<h2 class="text-2xl font-extrabold mb-3">چرا آساد ابزار؟</h2>
|
||||
<p class="text-blue-200 mb-8 max-w-2xl mx-auto">بیش از ۱۵ سال تجربه تعمیر ابزارهای برقی صنعتی، نمایندگی رسمی دیوالت در کرج، با قطعات اصل و ضمانت کتبی</p>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
@foreach (var s in new[] { ("+۱۵","سال تجربه","🏆"), ("+۵۰۰۰","دستگاه تعمیر شده","🔧"), ("۸","برند پشتیبانی","⭐"), ("۱۰۰٪","ضمانت تعمیر","🛡️") })
|
||||
{
|
||||
<div class="bg-white/10 rounded-2xl p-5">
|
||||
<div class="text-2xl mb-1">@s.Item3</div>
|
||||
<div class="text-2xl font-extrabold text-yellow-300">@s.Item1</div>
|
||||
<div class="text-xs text-blue-200 mt-0.5">@s.Item2</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
using AsadiTools.Services;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages.Brands;
|
||||
|
||||
public class BrandsIndexModel : PageModel
|
||||
{
|
||||
public BrandSeoPage[] Brands { get; } = BrandSeoData.AllBrands;
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
ViewData["Title"] = "تعمیر تخصصی ابزار برقی در کرج | همه برندها | آساد ابزار";
|
||||
ViewData["Description"] = "تعمیر ابزار دیوالت، بوش، هیلتی، متابو، رونیکس، توسن، ریوبی و آ.ا.گ در کرج. نمایندگی رسمی دیوالت. قطعات اصل، ضمانت ۳ ماهه.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Cart.CartIndexModel
|
||||
@{ ViewData["Title"] = "سبد خرید"; Layout = "_Layout"; }
|
||||
|
||||
@if (!Model.Items.Any())
|
||||
{
|
||||
<div class="min-h-[60vh] flex items-center justify-center">
|
||||
<div class="text-center py-20">
|
||||
<div class="text-6xl mb-4">🛒</div>
|
||||
<h2 class="text-xl font-bold text-gray-600 mb-2">سبد خرید شما خالی است</h2>
|
||||
<p class="text-gray-400 mb-6">قطعات مورد نیاز خود را از فروشگاه انتخاب کنید</p>
|
||||
<a href="/Shop" class="bg-blue-700 text-white px-6 py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">مشاهده فروشگاه</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="max-w-4xl mx-auto px-4 py-10">
|
||||
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">سبد خرید</h1>
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<!-- Items -->
|
||||
<div class="md:col-span-2 space-y-3">
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-5 flex items-center gap-4">
|
||||
<div class="bg-blue-50 rounded-xl w-14 h-14 flex items-center justify-center text-2xl shrink-0">🔧</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-bold text-gray-900 text-sm">@item.NameFa</h3>
|
||||
@if (item.Sku != null) { <p class="text-xs text-gray-400">کد: @item.Sku</p> }
|
||||
<p class="text-blue-700 font-bold mt-1">@SiteData.FormatPrice(item.Price)</p>
|
||||
</div>
|
||||
<!-- Qty -->
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<form method="post" asp-page-handler="UpdateQty">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="productId" value="@item.ProductId" />
|
||||
<input type="hidden" name="qty" value="@(item.Qty - 1)" />
|
||||
<button type="submit" class="w-8 h-8 rounded-lg border border-gray-200 flex items-center justify-center hover:bg-gray-50 text-lg">−</button>
|
||||
</form>
|
||||
<span class="w-8 text-center font-bold text-sm">@item.Qty</span>
|
||||
<form method="post" asp-page-handler="UpdateQty">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="productId" value="@item.ProductId" />
|
||||
<input type="hidden" name="qty" value="@(item.Qty + 1)" />
|
||||
<button type="submit" class="w-8 h-8 rounded-lg border border-gray-200 flex items-center justify-center hover:bg-gray-50 text-lg">+</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="text-left shrink-0">
|
||||
<p class="font-bold text-gray-800 text-sm">@SiteData.FormatPrice(item.Subtotal)</p>
|
||||
<form method="post" asp-page-handler="Remove" class="mt-1">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="productId" value="@item.ProductId" />
|
||||
<button type="submit" class="text-red-400 hover:text-red-600 text-xs">حذف</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<form method="post" asp-page-handler="Clear" class="mt-2">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="text-sm text-red-400 hover:text-red-600 transition-colors">🗑️ پاک کردن سبد خرید</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Summary -->
|
||||
<div>
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-6 sticky top-24">
|
||||
<h2 class="font-bold text-gray-900 mb-4 pb-3 border-b">خلاصه سفارش</h2>
|
||||
<div class="space-y-2 text-sm mb-4">
|
||||
<div class="flex justify-between text-gray-600">
|
||||
<span>تعداد اقلام:</span>
|
||||
<span>@Model.Items.Sum(i => i.Qty) عدد</span>
|
||||
</div>
|
||||
<div class="flex justify-between font-bold text-gray-900 text-base pt-2 border-t">
|
||||
<span>جمع کل:</span>
|
||||
<span class="text-blue-700">@SiteData.FormatPrice(Model.Total)</span>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/Checkout" class="block bg-blue-700 text-white text-center py-3.5 rounded-xl font-bold hover:bg-blue-800 transition-colors mb-3">ادامه و ثبت سفارش</a>
|
||||
<a href="/Shop" class="block text-sm text-blue-600 text-center hover:underline">← ادامه خرید</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using AsadiTools.Models;
|
||||
using AsadiTools.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages.Cart;
|
||||
|
||||
public class CartIndexModel(CartService cart) : PageModel
|
||||
{
|
||||
public List<CartItem> Items { get; private set; } = [];
|
||||
public decimal Total { get; private set; }
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
Items = cart.GetItems();
|
||||
Total = cart.Total;
|
||||
}
|
||||
|
||||
public IActionResult OnPostUpdateQty(int productId, int qty)
|
||||
{
|
||||
cart.UpdateQty(productId, qty);
|
||||
return RedirectToPage();
|
||||
}
|
||||
|
||||
public IActionResult OnPostRemove(int productId)
|
||||
{
|
||||
cart.RemoveItem(productId);
|
||||
return RedirectToPage();
|
||||
}
|
||||
|
||||
public IActionResult OnPostClear()
|
||||
{
|
||||
cart.Clear();
|
||||
return RedirectToPage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Checkout.CheckoutIndexModel
|
||||
@{ ViewData["Title"] = "ثبت سفارش"; Layout = "_Layout"; }
|
||||
|
||||
@if (Model.OrderNumber != null)
|
||||
{
|
||||
<div class="min-h-[60vh] flex items-center justify-center">
|
||||
<div class="text-center max-w-md mx-auto px-4 py-20">
|
||||
<div class="text-6xl mb-4">✅</div>
|
||||
<h2 class="text-2xl font-extrabold text-gray-900 mb-3">سفارش ثبت شد!</h2>
|
||||
<p class="text-gray-600 mb-2">شماره سفارش شما:</p>
|
||||
<p class="text-xl font-bold text-blue-700 mb-6 font-mono">@Model.OrderNumber</p>
|
||||
<p class="text-gray-500 text-sm mb-8">به زودی با شما تماس میگیریم تا جزئیات سفارش را هماهنگ کنیم.</p>
|
||||
<a href="/" class="bg-blue-700 text-white px-6 py-3 rounded-xl font-bold hover:bg-blue-800 transition-colors">بازگشت به خانه</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="max-w-4xl mx-auto px-4 py-10">
|
||||
<h1 class="text-2xl font-extrabold text-gray-900 mb-8">ثبت سفارش</h1>
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<form method="post" class="md:col-span-2">
|
||||
@Html.AntiForgeryToken()
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-6 space-y-4">
|
||||
<h2 class="font-bold text-gray-900 mb-2">اطلاعات خریدار</h2>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">نام و نام خانوادگی <span class="text-red-500">*</span></label>
|
||||
<input asp-for="Input.CustomerName" required
|
||||
class="w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="مثال: علی احمدی" />
|
||||
<span asp-validation-for="Input.CustomerName" class="text-red-500 text-xs"></span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">شماره موبایل <span class="text-red-500">*</span></label>
|
||||
<input asp-for="Input.CustomerPhone" required dir="ltr"
|
||||
class="w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="09xxxxxxxxx" />
|
||||
<span asp-validation-for="Input.CustomerPhone" class="text-red-500 text-xs"></span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">آدرس (برای ارسال)</label>
|
||||
<textarea asp-for="Input.CustomerAddress" rows="3"
|
||||
class="w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
||||
placeholder="کرج، ..."></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 mb-1.5 block">توضیحات</label>
|
||||
<textarea asp-for="Input.Notes" rows="2"
|
||||
class="w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
||||
placeholder="توضیحات اضافی..."></textarea>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
|
||||
{
|
||||
<div class="bg-red-50 border border-red-200 text-red-700 text-sm px-4 py-3 rounded-xl">@Model.ErrorMessage</div>
|
||||
}
|
||||
|
||||
<button type="submit" class="w-full bg-blue-700 text-white py-4 rounded-xl font-bold text-lg hover:bg-blue-800 transition-colors">
|
||||
ثبت نهایی سفارش
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Summary -->
|
||||
<div>
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-6 sticky top-24">
|
||||
<h2 class="font-bold text-gray-900 mb-4 pb-3 border-b">اقلام سفارش</h2>
|
||||
<div class="space-y-3 mb-4">
|
||||
@foreach (var item in Model.CartItems)
|
||||
{
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-gray-600 truncate ml-2">@item.NameFa × @item.Qty</span>
|
||||
<span class="font-bold shrink-0">@SiteData.FormatPrice(item.Subtotal)</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="border-t pt-3 flex justify-between font-bold">
|
||||
<span>جمع کل:</span>
|
||||
<span class="text-blue-700">@SiteData.FormatPrice(Model.CartItems.Sum(i => i.Subtotal))</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using AsadiTools.Data;
|
||||
using AsadiTools.Models;
|
||||
using AsadiTools.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace AsadiTools.Pages.Checkout;
|
||||
|
||||
public class CheckoutIndexModel(AppDbContext db, CartService cart) : PageModel
|
||||
{
|
||||
[BindProperty]
|
||||
public InputModel Input { get; set; } = new();
|
||||
|
||||
public List<CartItem> CartItems { get; private set; } = [];
|
||||
public string? OrderNumber { get; private set; }
|
||||
public string? ErrorMessage { get; private set; }
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
[Required(ErrorMessage = "نام الزامی است")]
|
||||
public string CustomerName { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "شماره موبایل الزامی است")]
|
||||
public string CustomerPhone { get; set; } = string.Empty;
|
||||
|
||||
public string? CustomerAddress { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
}
|
||||
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
CartItems = cart.GetItems();
|
||||
if (!CartItems.Any()) return RedirectToPage("/Shop/Index");
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
CartItems = cart.GetItems();
|
||||
if (!CartItems.Any()) return RedirectToPage("/Shop/Index");
|
||||
|
||||
if (!ModelState.IsValid) return Page();
|
||||
|
||||
var orderNumber = $"ORD-{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}";
|
||||
var order = new Order
|
||||
{
|
||||
OrderNumber = orderNumber,
|
||||
CustomerName = Input.CustomerName,
|
||||
CustomerPhone = Input.CustomerPhone,
|
||||
CustomerAddress = Input.CustomerAddress,
|
||||
Notes = Input.Notes,
|
||||
Items = CartItems.Select(i => new OrderItem
|
||||
{
|
||||
ProductId = i.ProductId,
|
||||
ProductNameFa = i.NameFa,
|
||||
Price = i.Price,
|
||||
Quantity = i.Qty,
|
||||
}).ToList(),
|
||||
};
|
||||
order.Subtotal = order.Items.Sum(i => i.Subtotal);
|
||||
order.Total = order.Subtotal;
|
||||
|
||||
db.Orders.Add(order);
|
||||
await db.SaveChangesAsync();
|
||||
cart.Clear();
|
||||
|
||||
OrderNumber = orderNumber;
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Contact.ContactIndexModel
|
||||
@{
|
||||
ViewData["Title"] = "تماس با آساد ابزار – نمایندگی دیوالت کرج";
|
||||
ViewData["Description"] = "آدرس، تلفن و ساعات کاری آساد ابزار کرج. نمایندگی رسمی دیوالت در کرج.";
|
||||
Layout = "_Layout";
|
||||
var c = SiteData.Company;
|
||||
var mapBbox = $"{c.MapLng - 0.02},{c.MapLat - 0.02},{c.MapLng + 0.02},{c.MapLat + 0.02}";
|
||||
var mapUrl = $"https://www.openstreetmap.org/export/embed.html?bbox={mapBbox}&layer=mapnik&marker={c.MapLat},{c.MapLng}";
|
||||
}
|
||||
|
||||
<div class="bg-blue-800 text-white py-12 px-4">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<h1 class="text-3xl font-extrabold mb-2">تماس با ما</h1>
|
||||
<p class="text-blue-200">آساد ابزار – نمایندگی رسمی دیوالت در کرج</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-6xl mx-auto px-4 py-12">
|
||||
<div class="grid md:grid-cols-2 gap-10">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-6">اطلاعات تماس</h2>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start gap-4 p-5 bg-white rounded-2xl border border-gray-100">
|
||||
<span class="text-2xl mt-0.5">📞</span>
|
||||
<div>
|
||||
<div class="font-bold text-gray-800 mb-2">تلفن</div>
|
||||
<a href="tel:@c.TelPhone" class="block text-blue-600 hover:underline text-lg font-bold">@c.Phone</a>
|
||||
<a href="tel:@c.TelMobile" class="block text-blue-600 hover:underline">@c.Mobile</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-4 p-5 bg-white rounded-2xl border border-gray-100">
|
||||
<span class="text-2xl mt-0.5">📍</span>
|
||||
<div>
|
||||
<div class="font-bold text-gray-800 mb-1">آدرس</div>
|
||||
<p class="text-gray-600">@c.Address</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-4 p-5 bg-white rounded-2xl border border-gray-100">
|
||||
<span class="text-2xl mt-0.5">🕐</span>
|
||||
<div>
|
||||
<div class="font-bold text-gray-800 mb-1">ساعات کاری</div>
|
||||
<p class="text-gray-600">@c.WorkingHours</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<a href="https://wa.me/@c.Whatsapp" target="_blank"
|
||||
class="flex-1 flex items-center justify-center gap-2 bg-green-500 text-white py-3.5 rounded-xl font-bold hover:bg-green-600 transition-colors">
|
||||
💬 واتساپ
|
||||
</a>
|
||||
<a href="https://instagram.com/@c.Instagram" target="_blank"
|
||||
class="flex-1 flex items-center justify-center gap-2 bg-gradient-to-r from-purple-500 to-pink-500 text-white py-3.5 rounded-xl font-bold hover:opacity-90 transition-opacity">
|
||||
📷 اینستاگرام
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-4">برندهای رسمی</h2>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
@foreach (var b in SiteData.Brands)
|
||||
{
|
||||
<div class="flex items-center gap-3 p-3 bg-white rounded-xl border border-gray-100">
|
||||
<div class="w-9 h-9 rounded-lg flex items-center justify-center text-sm font-bold shrink-0"
|
||||
style="background-color:@b.Color;color:@b.TextColor">@b.Name[0]</div>
|
||||
<div>
|
||||
<div class="font-bold text-gray-800 text-sm">@b.NameFa</div>
|
||||
@if (b.IsOfficial) { <div class="text-xs text-yellow-600 font-medium">نمایندگی رسمی</div> }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-6">موقعیت روی نقشه</h2>
|
||||
<div class="rounded-2xl overflow-hidden border border-gray-200 mb-6" style="height:320px">
|
||||
<iframe src="@mapUrl"
|
||||
width="100%" height="320" style="border:0" loading="lazy"
|
||||
title="موقعیت آساد ابزار کرج در نقشه"></iframe>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mb-4">
|
||||
نقشه از <a href="https://www.openstreetmap.org" target="_blank" class="underline">OpenStreetMap</a> —
|
||||
پس از تعیین آدرس دقیق، مختصات را در <code>SiteData.Company.MapLat/MapLng</code> بهروز کنید.
|
||||
</p>
|
||||
<div class="bg-yellow-50 border border-yellow-200 rounded-2xl p-5">
|
||||
<h3 class="font-bold text-gray-900 mb-2">مراجعه حضوری</h3>
|
||||
<p class="text-sm text-gray-600 leading-7">
|
||||
برای تعمیر ابزار میتوانید حضوراً مراجعه کنید یا با تماس قبلی ابزار خود را برای ما ارسال نمایید.
|
||||
پس از تشخیص عیب، هزینه تعمیر اعلام میشود.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages.Contact;
|
||||
|
||||
public class ContactIndexModel : PageModel
|
||||
{
|
||||
public void OnGet() { }
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.DeWalt.DeWaltIndexModel
|
||||
@{ Layout = "_Layout"; }
|
||||
|
||||
@section Head {
|
||||
<script type="application/ld+json">
|
||||
{"@@context":"https://schema.org","@@type":"Service","name":"تعمیر ابزار دیوالت در کرج","provider":{"@@type":"LocalBusiness","name":"آساد ابزار کرج","telephone":"+98-261-XXXXXXX","address":{"@@type":"PostalAddress","addressLocality":"کرج","addressCountry":"IR"}},"serviceType":"تعمیر ابزار برقی","brand":{"@@type":"Brand","name":"DeWalt"},"areaServed":"کرج"}
|
||||
</script>
|
||||
}
|
||||
|
||||
<!-- ═══════════ HERO ═══════════ -->
|
||||
<section class="relative text-white overflow-hidden" style="min-height:420px;background:#1a1200">
|
||||
<div class="absolute inset-0 bg-cover bg-center opacity-30"
|
||||
style="background-image:url('https://images.unsplash.com/photo-1504148455328-c376907d081c?w=1400&q=80&auto=format&fit=crop')"></div>
|
||||
<div class="absolute inset-0 bg-gradient-to-l from-yellow-900/40 to-transparent"></div>
|
||||
<div class="relative max-w-6xl mx-auto px-4 py-16 flex flex-col md:flex-row items-center gap-10">
|
||||
<!-- DeWalt logo area -->
|
||||
<div class="shrink-0">
|
||||
<div class="w-40 h-40 rounded-3xl flex items-center justify-center shadow-2xl border-4 border-yellow-400"
|
||||
style="background:#FFCD00">
|
||||
<span class="font-extrabold text-gray-900 text-3xl tracking-tighter">DeWALT</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inline-flex items-center gap-2 bg-yellow-400 text-gray-900 text-sm font-bold px-4 py-1.5 rounded-full mb-4">
|
||||
🛡️ نمایندگی رسمی DeWalt در کرج
|
||||
</div>
|
||||
<h1 class="text-4xl md:text-5xl font-extrabold leading-tight mb-4">
|
||||
تعمیر تخصصی<br>
|
||||
<span class="text-yellow-400">ابزار دیوالت</span>
|
||||
</h1>
|
||||
<p class="text-gray-300 text-lg leading-8 max-w-2xl mb-6">
|
||||
آساد ابزار مجاز تنها مرکز تعمیر رسمی ابزار DeWalt در شهر کرج است.
|
||||
بیش از ۱۵ مدل از پرفروشترین ابزار دیوالت را با قطعات اصل تعمیر میکنیم.
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<a href="tel:02634567890" class="bg-yellow-400 text-gray-900 font-bold px-6 py-3 rounded-xl hover:bg-yellow-300 transition-colors">📞 تماس برای تعمیر</a>
|
||||
<a href="#tools" class="border-2 border-yellow-400/50 text-yellow-300 font-bold px-6 py-3 rounded-xl hover:border-yellow-400 transition-colors">مشاهده ابزارها ↓</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ TRUST STRIP ═══════════ -->
|
||||
<div class="bg-yellow-400 py-3 px-4">
|
||||
<div class="max-w-6xl mx-auto flex flex-wrap justify-center gap-6 text-gray-900 text-sm font-semibold">
|
||||
@foreach (var t in new[] { "🛡️ ضمانت ۳ ماهه کتبی", "🔩 قطعات اصل DeWalt", "⚡ تحویل زیر ۴۸ ساعت", "📞 مشاوره رایگان", "🎓 تکنیسین آموزشدیده DeWalt" })
|
||||
{
|
||||
<span>@t</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ CATEGORY TABS ═══════════ -->
|
||||
<nav id="tools" class="bg-white border-b sticky top-[65px] z-40">
|
||||
<div class="max-w-6xl mx-auto px-4 flex gap-1 overflow-x-auto py-2">
|
||||
@{
|
||||
var cats = new[] {
|
||||
("all", "همه ابزار"),
|
||||
("drill", "🔩 دریل"),
|
||||
("driver", "🔧 پیچگوشتی"),
|
||||
("grinder", "⚙️ فرز آنگولر"),
|
||||
("rotary-hammer", "🏗️ بتنکن"),
|
||||
("saw", "🪚 ابزار برش"),
|
||||
("chop-saw", "💿 گردبر"),
|
||||
("miter-saw", "📐 فارسیبر"),
|
||||
("woodworking", "🪵 نجاری"),
|
||||
("multi", "🔧 چندکاره"),
|
||||
("sander", "💨 سنباده"),
|
||||
("hedge-trimmer", "🌿 شمشادزن"),
|
||||
("laser-level", "🔴 تراز لیزری"),
|
||||
("laser-measure", "📏 متر لیزری"),
|
||||
};
|
||||
}
|
||||
@foreach (var (catId, catName) in cats)
|
||||
{
|
||||
<button onclick="filterTools('@catId')"
|
||||
id="tab-@catId"
|
||||
class="tab-btn shrink-0 px-4 py-2 rounded-lg text-sm font-medium transition-colors text-gray-600 hover:bg-yellow-50 hover:text-yellow-700 whitespace-nowrap">
|
||||
@catName
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- ═══════════ TOOLS GRID ═══════════ -->
|
||||
<section class="py-12 px-4 bg-gray-50">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div id="tools-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@{
|
||||
var catImages = new Dictionary<string, string>
|
||||
{
|
||||
["drill"] = "https://images.unsplash.com/photo-1572981779307-38b8cabb2407?w=500&q=75&auto=format&fit=crop",
|
||||
["driver"] = "https://images.unsplash.com/photo-1530124566582-a618bc2615dc?w=500&q=75&auto=format&fit=crop",
|
||||
["grinder"] = "https://images.unsplash.com/photo-1487452066049-a710f7296400?w=500&q=75&auto=format&fit=crop",
|
||||
["rotary-hammer"] = "https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=500&q=75&auto=format&fit=crop",
|
||||
["saw"] = "https://images.unsplash.com/photo-1504148455328-c376907d081c?w=500&q=75&auto=format&fit=crop",
|
||||
["chop-saw"] = "https://images.unsplash.com/photo-1503387762-592deb58ef4e?w=500&q=75&auto=format&fit=crop",
|
||||
["miter-saw"] = "https://images.unsplash.com/photo-1558618047-3c8c1d8b9df1?w=500&q=75&auto=format&fit=crop",
|
||||
["woodworking"] = "https://images.unsplash.com/photo-1588854337115-1c67d9247e4d?w=500&q=75&auto=format&fit=crop",
|
||||
["multi"] = "https://images.unsplash.com/photo-1518770660439-4636190af475?w=500&q=75&auto=format&fit=crop",
|
||||
["sander"] = "https://images.unsplash.com/photo-1581579438747-1dc8d17bbce4?w=500&q=75&auto=format&fit=crop",
|
||||
["hedge-trimmer"] = "https://images.unsplash.com/photo-1416879595882-3373a0480b5b?w=500&q=75&auto=format&fit=crop",
|
||||
["laser-level"] = "https://images.unsplash.com/photo-1609220136736-443140cfeaa8?w=500&q=75&auto=format&fit=crop",
|
||||
["laser-measure"] = "https://images.unsplash.com/photo-1518770660439-4636190af475?w=500&q=75&auto=format&fit=crop",
|
||||
};
|
||||
}
|
||||
@foreach (var tool in Model.Tools)
|
||||
{
|
||||
var img = catImages.TryGetValue(tool.CategoryId, out var u) ? u : catImages["drill"];
|
||||
<div class="tool-card bg-white rounded-2xl overflow-hidden shadow-sm hover:shadow-xl transition-all hover:-translate-y-1 border border-gray-100"
|
||||
data-cat="@tool.CategoryId">
|
||||
<!-- Image -->
|
||||
<div class="relative h-44 overflow-hidden">
|
||||
<img src="@img" alt="تعمیر @tool.NameFa" loading="lazy"
|
||||
class="w-full h-full object-cover" />
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent"></div>
|
||||
<!-- DeWalt badge -->
|
||||
<div class="absolute top-3 left-3">
|
||||
<span class="bg-yellow-400 text-gray-900 font-extrabold text-xs px-2 py-1 rounded-lg">DeWALT</span>
|
||||
</div>
|
||||
<!-- Power badge -->
|
||||
<div class="absolute top-3 right-3">
|
||||
<span class="bg-black/60 text-white text-xs px-2 py-1 rounded-lg font-medium">@tool.Power</span>
|
||||
</div>
|
||||
<!-- Icon overlay -->
|
||||
<div class="absolute bottom-3 right-4 text-3xl">@tool.Icon</div>
|
||||
</div>
|
||||
|
||||
<div class="p-5">
|
||||
<!-- Title & models -->
|
||||
<h3 class="font-bold text-gray-900 text-lg mb-1 leading-snug">@tool.NameFa</h3>
|
||||
<p class="text-xs text-gray-400 mb-3 font-mono">@string.Join(" · ", tool.Models)</p>
|
||||
<p class="text-gray-600 text-sm leading-7 mb-4">@tool.Description</p>
|
||||
|
||||
<!-- Repair services -->
|
||||
<div class="border-t pt-4">
|
||||
<p class="text-xs font-bold text-gray-500 uppercase tracking-wider mb-2">خدمات تعمیر</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
@foreach (var r in tool.RepairItems)
|
||||
{
|
||||
<span class="text-xs bg-yellow-50 text-yellow-800 border border-yellow-200 px-2 py-0.5 rounded-full">@r</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CTA -->
|
||||
<a href="tel:02634567890"
|
||||
class="mt-4 block w-full bg-yellow-400 text-gray-900 font-bold text-center py-2.5 rounded-xl hover:bg-yellow-300 transition-colors text-sm">
|
||||
📞 درخواست تعمیر
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<p id="no-results" class="hidden text-center text-gray-400 py-10">ابزاری در این دستهبندی یافت نشد.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ REPAIR PROCESS ═══════════ -->
|
||||
<section class="py-16 px-4 bg-white">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-2">فرآیند تعمیر ابزار دیوالت</h2>
|
||||
<p class="text-gray-500">ساده، شفاف و با ضمانت</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-6">
|
||||
@{
|
||||
var steps = new[] {
|
||||
("۱", "تحویل دستگاه", "ابزار را بهصورت حضوری تحویل دهید یا از طریق پست ارسال کنید.", "📦"),
|
||||
("۲", "بررسی رایگان", "در کمتر از ۲ ساعت عیبیابی کامل و برآورد قیمت ارائه میشود.", "🔍"),
|
||||
("۳", "تعمیر با قطعه اصل", "تعمیر توسط تکنیسین مجاز DeWalt با قطعات اورجینال.", "🔧"),
|
||||
("۴", "تحویل با ضمانت", "تحویل دستگاه با ضمانتنامه کتبی ۳ ماهه.", "🛡️"),
|
||||
};
|
||||
}
|
||||
@foreach (var (num, title, desc, icon) in steps)
|
||||
{
|
||||
<div class="text-center relative">
|
||||
<div class="w-16 h-16 rounded-2xl bg-yellow-400 flex items-center justify-center text-2xl mx-auto mb-4 shadow-md">
|
||||
@icon
|
||||
</div>
|
||||
<div class="absolute top-0 right-1/2 translate-x-1/2 -translate-y-1 bg-gray-900 text-white text-xs font-bold w-5 h-5 rounded-full flex items-center justify-center">@num</div>
|
||||
<h3 class="font-bold text-gray-900 mb-2">@title</h3>
|
||||
<p class="text-sm text-gray-500 leading-6">@desc</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ GENUINE PARTS SECTION ═══════════ -->
|
||||
<section class="py-16 px-4" style="background:linear-gradient(135deg,#fef9c3,#fef08a)">
|
||||
<div class="max-w-6xl mx-auto grid md:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">قطعات اصل DeWalt</h2>
|
||||
<p class="text-gray-700 leading-8 mb-6">
|
||||
آساد ابزار تنها از قطعات یدکی ۱۰۰٪ اورجینال DeWalt استفاده میکند.
|
||||
قطعات مستقیم از نمایندگی رسمی ایران تأمین میشود و دارای شناسه تأیید اصالت هستند.
|
||||
هیچ قطعه چینی یا غیراصل در تعمیرات ما استفاده نمیشود.
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
@foreach (var part in new[] { "کاربن (ذغال) اصل", "بیرینگ اورجینال", "کلید و رئوستا", "آرمیچر و استاتور", "گیربکس کامل", "چاک ۱۳mm", "پیستون بتنکن", "تیغه شمشادزن" })
|
||||
{
|
||||
<div class="flex items-center gap-2 bg-white/60 rounded-xl px-3 py-2">
|
||||
<span class="text-yellow-600 font-bold">⚡</span>
|
||||
<span class="text-sm text-gray-800">@part</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative rounded-3xl overflow-hidden shadow-xl" style="height:320px">
|
||||
<img src="https://images.unsplash.com/photo-1518770660439-4636190af475?w=700&q=85&auto=format&fit=crop"
|
||||
alt="قطعات یدکی اصل دیوالت" loading="lazy"
|
||||
class="w-full h-full object-cover" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ FAQ ═══════════ -->
|
||||
<section class="py-16 px-4 bg-white">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-8 text-center">سؤالات متداول درباره تعمیر دیوالت</h2>
|
||||
<div class="space-y-4">
|
||||
@{
|
||||
var faqs = new[] {
|
||||
("چطور بفهمم ابزار دیوالتم نیاز به تعمیر دارد؟",
|
||||
"علائم رایج: جرقه داخل ابزار، کاهش قدرت، صدای غیرعادی، داغ شدن بیش از حد، لرزش شدید. در صورت مشاهده هر کدام، دستگاه را خاموش کنید و با ما تماس بگیرید."),
|
||||
("هزینه تعمیر ابزار دیوالت چقدر است؟",
|
||||
"هزینه بستگی به نوع عیب و مدل دارد. بررسی اولیه و برآورد قیمت کاملاً رایگان است. قبل از شروع تعمیر، قیمت دقیق به شما اعلام میشود."),
|
||||
("آیا ضمانت تعمیر دارید؟",
|
||||
"بله، تمام تعمیرات آساد ابزار دارای ضمانتنامه کتبی ۳ ماهه هستند. در صورت بروز مشکل مجدد در همان قطعه، رایگان تعمیر میشود."),
|
||||
("چه مدت طول میکشد ابزار تعمیر شود؟",
|
||||
"اکثر تعمیرات معمولی در ۲۴ تا ۴۸ ساعت انجام میشود. تعمیرات سنگینتر که نیاز به سفارش قطعه دارند ممکن است ۳ تا ۵ روز طول بکشد."),
|
||||
("آیا تعمیرات از شهرهای دیگر قبول میکنید؟",
|
||||
"بله، ابزار را میتوانید از طریق پست یا تیپاکس ارسال کنید. ما پس از تعمیر، ابزار را به آدرس شما ارسال میکنیم."),
|
||||
};
|
||||
}
|
||||
@foreach (var (q, a) in faqs)
|
||||
{
|
||||
<details class="group border border-gray-200 rounded-2xl overflow-hidden">
|
||||
<summary class="flex items-center justify-between p-5 cursor-pointer font-semibold text-gray-800 hover:bg-gray-50">
|
||||
@q
|
||||
<span class="text-yellow-600 group-open:rotate-45 transition-transform shrink-0 mr-3 text-xl">+</span>
|
||||
</summary>
|
||||
<div class="px-5 pb-5 text-gray-600 text-sm leading-7 border-t border-gray-100 pt-4">
|
||||
@a
|
||||
</div>
|
||||
</details>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ CONTACT CTA ═══════════ -->
|
||||
<section class="py-14 px-4 bg-gray-900 text-white">
|
||||
<div class="max-w-3xl mx-auto text-center">
|
||||
<h2 class="text-3xl font-extrabold mb-3">ابزار دیوالت خود را تعمیر کنید</h2>
|
||||
<p class="text-gray-400 mb-8">تنها نمایندگی مجاز تعمیر DeWalt در کرج</p>
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
<a href="tel:02634567890" class="bg-yellow-400 text-gray-900 font-bold px-8 py-3.5 rounded-xl hover:bg-yellow-300 transition-colors text-lg">📞 ۰۲۶-۳۴۵۶۷۸۹۰</a>
|
||||
<a href="https://wa.me/989123456789" class="bg-green-600 text-white font-bold px-8 py-3.5 rounded-xl hover:bg-green-700 transition-colors text-lg">💬 واتساپ</a>
|
||||
<a href="/Contact" class="border-2 border-white/30 text-white font-bold px-8 py-3.5 rounded-xl hover:border-white transition-colors text-lg">📍 آدرس ما</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function filterTools(cat) {
|
||||
const cards = document.querySelectorAll('.tool-card');
|
||||
const tabs = document.querySelectorAll('.tab-btn');
|
||||
let visible = 0;
|
||||
cards.forEach(c => {
|
||||
const show = cat === 'all' || c.dataset.cat === cat;
|
||||
c.style.display = show ? '' : 'none';
|
||||
if (show) visible++;
|
||||
});
|
||||
document.getElementById('no-results').classList.toggle('hidden', visible > 0);
|
||||
tabs.forEach(t => {
|
||||
const active = t.id === 'tab-' + cat;
|
||||
t.classList.toggle('bg-yellow-400', active);
|
||||
t.classList.toggle('text-gray-900', active);
|
||||
t.classList.toggle('font-bold', active);
|
||||
t.classList.toggle('text-gray-600', !active);
|
||||
});
|
||||
}
|
||||
// Activate "all" tab on load
|
||||
filterTools('all');
|
||||
</script>
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using AsadiTools.Services;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages.DeWalt;
|
||||
|
||||
public class DeWaltIndexModel : PageModel
|
||||
{
|
||||
public DeWaltTool[] Tools { get; } = SiteData.DeWaltTools;
|
||||
public BrandInfo Brand { get; } = SiteData.Brands.First(b => b.Id == "dewalt");
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
ViewData["Title"] = "تعمیر ابزار دیوالت در کرج | نمایندگی رسمی DeWalt";
|
||||
ViewData["Description"] = "نمایندگی رسمی تعمیر ابزار دیوالت (DeWalt) در کرج. تعمیر دریل، فرز، بتنکن، جیگساو و تمام ابزار DeWalt با قطعات اصل و ضمانت ۳ ماهه.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
@page
|
||||
@model ErrorModel
|
||||
@{
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages;
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
[IgnoreAntiforgeryToken]
|
||||
public class ErrorModel : PageModel
|
||||
{
|
||||
public string? RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.IndexModel
|
||||
@{
|
||||
ViewData["Title"] = "آساد ابزار - نمایندگی رسمی دیوالت در کرج";
|
||||
ViewData["Description"] = "نمایندگی رسمی دیوالت، ماکیتا، رونیکس، توسن و بلک اند دکر در کرج. تعمیر دریل، فرز، مینی فرز، شمشاد زن و بتن کن با ضمانت.";
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
||||
@section Head {
|
||||
<script type="application/ld+json">
|
||||
{"@@context":"https://schema.org","@@type":"LocalBusiness","name":"آساد ابزار کرج","image":"https://images.unsplash.com/photo-1504148455328-c376907d081c?w=800","telephone":"+98@SiteData.Company.TelPhone.Substring(1)","address":{"@@type":"PostalAddress","streetAddress":"@SiteData.Company.Address","addressLocality":"کرج","addressRegion":"البرز","postalCode":"31489","addressCountry":"IR"},"openingHours":"Mo-Sa 08:00-18:00","priceRange":"$$","sameAs":["https://instagram.com/@SiteData.Company.Instagram"]}
|
||||
</script>
|
||||
}
|
||||
|
||||
<!-- ═══════════════════════════════ HERO ═══════════════════════════════════ -->
|
||||
<section class="relative text-white overflow-hidden" style="min-height:580px">
|
||||
<!-- Background image with overlay -->
|
||||
<div class="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||
style="background-image:url('https://images.unsplash.com/photo-1504148455328-c376907d081c?w=1400&q=80&auto=format&fit=crop')"></div>
|
||||
<div class="absolute inset-0 bg-gradient-to-l from-blue-900/95 via-blue-900/85 to-blue-800/70"></div>
|
||||
|
||||
<div class="relative max-w-6xl mx-auto px-4 py-20 grid md:grid-cols-2 gap-12 items-center">
|
||||
<!-- Text -->
|
||||
<div>
|
||||
<div class="inline-flex items-center gap-2 bg-yellow-400 text-gray-900 text-sm font-bold px-4 py-1.5 rounded-full mb-5 shadow-lg">
|
||||
🛡️ نمایندگی رسمی دیوالت در کرج
|
||||
</div>
|
||||
<h1 class="text-4xl md:text-5xl font-extrabold leading-tight mb-5">
|
||||
تعمیر تخصصی<br>
|
||||
<span class="text-yellow-300">ابزار صنعتی</span><br>
|
||||
در کرج
|
||||
</h1>
|
||||
<p class="text-blue-100 text-lg leading-8 mb-8">
|
||||
با بیش از ۱۵ سال تجربه، تعمیر دریل، فرز، مینی فرز، بتنکن و شمشادزن
|
||||
برندهای دیوالت، ماکیتا، رونیکس، توسن و بلک اند دکر.
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-3 mb-10">
|
||||
<a href="/Contact" class="bg-yellow-400 text-gray-900 font-bold px-6 py-3 rounded-xl hover:bg-yellow-300 transition-colors shadow-lg">📞 تماس برای تعمیر</a>
|
||||
<a href="/Shop" class="border-2 border-white/80 text-white font-bold px-6 py-3 rounded-xl hover:bg-white hover:text-blue-900 transition-colors">🛒 فروشگاه قطعات</a>
|
||||
<a href="/DeWalt" class="bg-white/10 border border-white/30 text-white font-bold px-6 py-3 rounded-xl hover:bg-white/20 transition-colors">🔧 ابزار دیوالت</a>
|
||||
</div>
|
||||
<!-- Quick trust badges -->
|
||||
<div class="flex flex-wrap gap-3">
|
||||
@foreach (var badge in new[] { "✅ ضمانت ۳ ماهه", "✅ قطعات اصل", "✅ تشخیص رایگان", "✅ تحویل سریع" })
|
||||
{
|
||||
<span class="text-sm text-blue-100 bg-white/10 px-3 py-1 rounded-full">@badge</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats cards -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
@foreach (var s in new[] {
|
||||
("+۱۵", "سال تجربه", "🏆"),
|
||||
("+۵۰۰۰", "دستگاه تعمیر شده", "🔧"),
|
||||
("۵", "برند پشتیبانی", "⭐"),
|
||||
("۱۰۰٪", "ضمانت تعمیر", "🛡️")
|
||||
})
|
||||
{
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 text-center border border-white/20 hover:bg-white/15 transition-colors">
|
||||
<div class="text-3xl mb-2">@s.Item3</div>
|
||||
<div class="text-3xl font-extrabold text-yellow-300 mb-1">@s.Item1</div>
|
||||
<div class="text-sm text-blue-100">@s.Item2</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════ DEWALT FEATURED ════════════════════════════════ -->
|
||||
<section class="py-16 px-4" style="background:linear-gradient(135deg,#1a1a1a 0%,#2d2d00 50%,#1a1a00 100%)">
|
||||
<div class="max-w-6xl mx-auto grid md:grid-cols-2 gap-12 items-center">
|
||||
<!-- Image -->
|
||||
<div class="relative rounded-3xl overflow-hidden shadow-2xl" style="height:380px">
|
||||
<img src="https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=700&q=85&auto=format&fit=crop"
|
||||
alt="ابزار دیوالت" class="w-full h-full object-cover" loading="lazy" />
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
|
||||
<div class="absolute bottom-4 right-4">
|
||||
<span class="bg-yellow-400 text-gray-900 font-extrabold px-4 py-2 rounded-xl text-sm shadow-lg">DEWALT</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="text-white">
|
||||
<div class="inline-flex items-center gap-2 bg-yellow-400 text-gray-900 text-xs font-bold px-3 py-1 rounded-full mb-4">
|
||||
🛡️ مجاز از DeWalt ایران
|
||||
</div>
|
||||
<h2 class="text-3xl font-extrabold mb-4 leading-tight">
|
||||
نمایندگی رسمی<br>
|
||||
<span class="text-yellow-400">DeWalt</span> در کرج
|
||||
</h2>
|
||||
<p class="text-gray-300 leading-8 mb-6">
|
||||
آساد ابزار افتخار دارد تنها نمایندگی مجاز تعمیر ابزار دیوالت در کرج باشد.
|
||||
تمامی تعمیرات توسط تکنیسینهای آموزشدیده و با قطعات کاملاً اورجینال انجام میشود.
|
||||
خدمات پشتیبانی شامل بیش از ۱۵ مدل از محبوبترین ابزار دیوالت است.
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-3 mb-6">
|
||||
@foreach (var f in new[] { ("۱۵+", "مدل ابزار"), ("قطعه اصل", "دیوالت"), ("آموزش رسمی", "تکنیسین"), ("ضمانت کتبی", "تعمیر") })
|
||||
{
|
||||
<div class="bg-white/10 rounded-xl p-3 text-center border border-yellow-400/30">
|
||||
<div class="font-bold text-yellow-400 text-lg">@f.Item1</div>
|
||||
<div class="text-xs text-gray-300">@f.Item2</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<a href="/DeWalt" class="bg-yellow-400 text-gray-900 font-bold px-6 py-3 rounded-xl hover:bg-yellow-300 transition-colors">مشاهده همه ابزار دیوالت</a>
|
||||
<a href="/Services/Brand?id=dewalt" class="border border-yellow-400/50 text-yellow-300 font-bold px-6 py-3 rounded-xl hover:border-yellow-400 transition-colors">خدمات تعمیر ›</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════ BRANDS ════════════════════════════════════════ -->
|
||||
<section class="py-16 px-4 bg-white">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-2">برندهایی که تعمیر میکنیم</h2>
|
||||
<p class="text-gray-500">تخصص ما در نمایندگی و تعمیر برترین برندهای ابزار صنعتی جهان</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||||
@foreach (var b in SiteData.Brands)
|
||||
{
|
||||
<a href="/Services/Brand?id=@b.Id"
|
||||
class="group relative border-2 rounded-2xl p-5 text-center hover:shadow-xl transition-all hover:-translate-y-1"
|
||||
style="border-color:@(b.IsOfficial ? "#f59e0b" : "#e5e7eb")">
|
||||
@if (b.IsOfficial)
|
||||
{
|
||||
<div class="absolute -top-3 right-1/2 translate-x-1/2 bg-yellow-400 text-gray-900 text-xs font-bold px-2 py-0.5 rounded-full whitespace-nowrap">🛡️ رسمی</div>
|
||||
}
|
||||
<div class="w-16 h-16 rounded-2xl flex items-center justify-center text-2xl font-extrabold mx-auto mb-3 shadow-md"
|
||||
style="background-color:@b.Color;color:@b.TextColor">
|
||||
@b.Name.Substring(0, 2)
|
||||
</div>
|
||||
<div class="font-bold text-gray-800">@b.NameFa</div>
|
||||
<div class="text-xs text-gray-400 mt-0.5">@b.Name</div>
|
||||
<div class="text-xs text-blue-600 mt-2 group-hover:underline">خدمات تعمیر ›</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════ TOOLS WE REPAIR ═══════════════════════════════ -->
|
||||
<section class="py-16 px-4 bg-gray-50">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-2">ابزارهایی که تعمیر میکنیم</h2>
|
||||
<p class="text-gray-500">تخصص کامل در تعمیر انواع ابزار برقی صنعتی</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@{
|
||||
var toolImages = new Dictionary<string, string>
|
||||
{
|
||||
["drill"] = "https://images.unsplash.com/photo-1572981779307-38b8cabb2407?w=500&q=75&auto=format&fit=crop",
|
||||
["grinder"] = "https://images.unsplash.com/photo-1487452066049-a710f7296400?w=500&q=75&auto=format&fit=crop",
|
||||
["mini-grinder"] = "https://images.unsplash.com/photo-1504148455328-c376907d081c?w=500&q=75&auto=format&fit=crop",
|
||||
["hedge-trimmer"] = "https://images.unsplash.com/photo-1416879595882-3373a0480b5b?w=500&q=75&auto=format&fit=crop",
|
||||
["rotary-hammer"] = "https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=500&q=75&auto=format&fit=crop",
|
||||
["laser-level"] = "https://images.unsplash.com/photo-1609220136736-443140cfeaa8?w=500&q=75&auto=format&fit=crop",
|
||||
["gerd-bar"] = "https://images.unsplash.com/photo-1503387762-592deb58ef4e?w=500&q=75&auto=format&fit=crop",
|
||||
["miter-saw"] = "https://images.unsplash.com/photo-1558618047-3c8c1d8b9df1?w=500&q=75&auto=format&fit=crop",
|
||||
["woodworking"] = "https://images.unsplash.com/photo-1588854337115-1c67d9247e4d?w=500&q=75&auto=format&fit=crop",
|
||||
["laser-measure"] = "https://images.unsplash.com/photo-1518770660439-4636190af475?w=500&q=75&auto=format&fit=crop",
|
||||
};
|
||||
}
|
||||
@foreach (var t in SiteData.ToolTypes)
|
||||
{
|
||||
var img = toolImages.TryGetValue(t.Id, out var u) ? u : "";
|
||||
<a href="/Services" class="bg-white rounded-2xl overflow-hidden border border-gray-100 hover:shadow-lg transition-all hover:-translate-y-0.5 block group">
|
||||
@if (!string.IsNullOrEmpty(img))
|
||||
{
|
||||
<div class="h-44 overflow-hidden relative">
|
||||
<img src="@img" alt="تعمیر @t.NameFa" loading="lazy"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" />
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent"></div>
|
||||
<div class="absolute bottom-3 right-4 text-3xl">@t.Icon</div>
|
||||
</div>
|
||||
}
|
||||
<div class="p-6">
|
||||
<h3 class="font-bold text-xl text-gray-900 mb-2">تعمیر @t.NameFa</h3>
|
||||
<p class="text-gray-500 text-sm leading-7 mb-4">@t.Description</p>
|
||||
<ul class="space-y-1.5">
|
||||
@foreach (var issue in t.CommonIssues.Take(3))
|
||||
{
|
||||
<li class="flex items-start gap-2 text-sm text-gray-600">
|
||||
<span class="text-green-500 shrink-0 mt-0.5">✓</span> @issue
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<div class="mt-4 text-blue-600 text-sm font-medium group-hover:underline">مشاهده جزئیات ›</div>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════ WORKSHOP GALLERY ═══════════════════════════════ -->
|
||||
<section class="py-16 px-4 bg-white">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-2">کارگاه تعمیر آساد ابزار</h2>
|
||||
<p class="text-gray-500">نگاهی به محیط کاری و تجهیزات تخصصی ما</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
@{
|
||||
var gallery = new[]
|
||||
{
|
||||
("https://images.unsplash.com/photo-1581579438747-1dc8d17bbce4?w=600&q=80&auto=format&fit=crop", "کارگاه تعمیر ابزار"),
|
||||
("https://images.unsplash.com/photo-1530124566582-a618bc2615dc?w=600&q=80&auto=format&fit=crop", "جعبه ابزار تخصصی"),
|
||||
("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=600&q=80&auto=format&fit=crop", "ابزار دیوالت"),
|
||||
("https://images.unsplash.com/photo-1607400201515-c2c41c07d307?w=600&q=80&auto=format&fit=crop", "تکنیسین متخصص"),
|
||||
("https://images.unsplash.com/photo-1518770660439-4636190af475?w=600&q=80&auto=format&fit=crop", "قطعات یدکی"),
|
||||
("https://images.unsplash.com/photo-1504148455328-c376907d081c?w=600&q=80&auto=format&fit=crop", "ابزار صنعتی"),
|
||||
};
|
||||
}
|
||||
@foreach (var (src, alt) in gallery)
|
||||
{
|
||||
<div class="relative rounded-2xl overflow-hidden group" style="height:200px">
|
||||
<img src="@src" alt="@alt" loading="lazy"
|
||||
class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500" />
|
||||
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors"></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════ WHY US ════════════════════════════════════════ -->
|
||||
<section class="py-16 px-4 bg-gray-50">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-2">چرا آساد ابزار؟</h2>
|
||||
<p class="text-gray-500">دلایل اعتماد هزاران مشتری به ما</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
@foreach (var item in new[] {
|
||||
("🛡️", "ضمانت تعمیر", "ضمانت کتبی ۳ ماهه روی کلیه تعمیرات انجامشده", "bg-blue-50 border-blue-100"),
|
||||
("🔩", "قطعات ۱۰۰٪ اصل", "فقط قطعات یدکی اورجینال از نمایندگی رسمی", "bg-yellow-50 border-yellow-100"),
|
||||
("⚡", "تعمیر سریع", "اکثر تعمیرات در کمتر از ۴۸ ساعت تحویل داده میشود", "bg-green-50 border-green-100"),
|
||||
("⭐", "تکنیسین مجرب", "بیش از ۱۵ سال سابقه تعمیر تخصصی ابزار صنعتی", "bg-purple-50 border-purple-100"),
|
||||
("💰", "قیمت شفاف", "برآورد رایگان قبل از شروع تعمیر بدون هزینه پنهان", "bg-red-50 border-red-100"),
|
||||
("📦", "ارسال سراسری", "تعمیر ابزار از سراسر کشور از طریق پست و تیپاکس", "bg-orange-50 border-orange-100"),
|
||||
("📞", "مشاوره رایگان", "مشاوره تلفنی رایگان برای تشخیص عیب دستگاه", "bg-teal-50 border-teal-100"),
|
||||
("🔄", "خدمات پیشگیرانه", "سرویس دورهای و تنظیم ابزار برای جلوگیری از خرابی", "bg-indigo-50 border-indigo-100"),
|
||||
})
|
||||
{
|
||||
<div class="text-center p-6 rounded-2xl border @item.Item4">
|
||||
<div class="text-3xl mb-3">@item.Item1</div>
|
||||
<h3 class="font-bold text-gray-900 mb-2">@item.Item2</h3>
|
||||
<p class="text-sm text-gray-500 leading-6">@item.Item3</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════ TECHNICIAN SECTION ════════════════════════════ -->
|
||||
<section class="py-16 px-4 bg-blue-900 text-white">
|
||||
<div class="max-w-6xl mx-auto grid md:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<h2 class="text-3xl font-extrabold mb-4">تکنیسینهای ما، متخصصترین در البرز</h2>
|
||||
<p class="text-blue-100 leading-8 mb-6">
|
||||
تیم تکنیسینهای آساد ابزار با گذراندن دورههای آموزشی رسمی دیوالت و ماکیتا،
|
||||
تجربه تعمیر بیش از ۵۰۰۰ دستگاه را دارند. از سادهترین مشکل مانند تعویض کاربن،
|
||||
تا پیچیدهترین عیوب الکترونیکی و مکانیکی.
|
||||
</p>
|
||||
<div class="space-y-3">
|
||||
@foreach (var f in new[] {
|
||||
"آموزش رسمی از DeWalt و Makita",
|
||||
"تجربه بیش از ۱۵ سال تعمیر تخصصی",
|
||||
"استفاده از دستگاههای اندازهگیری حرفهای",
|
||||
"بهروز بودن با آخرین مدلهای ابزار",
|
||||
"ارائه رسید و فاکتور رسمی برای تمام تعمیرات",
|
||||
})
|
||||
{
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-5 h-5 rounded-full bg-yellow-400 flex items-center justify-center text-gray-900 text-xs font-bold shrink-0">✓</span>
|
||||
<span class="text-blue-100 text-sm">@f</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative rounded-3xl overflow-hidden shadow-2xl" style="height:380px">
|
||||
<img src="https://images.unsplash.com/photo-1581579438747-1dc8d17bbce4?w=700&q=85&auto=format&fit=crop"
|
||||
alt="تکنیسین تعمیر ابزار آساد ابزار کرج" loading="lazy"
|
||||
class="w-full h-full object-cover" />
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-blue-900/80 to-transparent"></div>
|
||||
<div class="absolute bottom-5 right-5 left-5">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-xl p-4 border border-white/20">
|
||||
<div class="font-bold text-sm mb-1">آساد ابزار کرج</div>
|
||||
<div class="text-blue-200 text-xs">نمایندگی رسمی دیوالت • ۱۵ سال تجربه</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════ CTA ═══════════════════════════════════════════ -->
|
||||
<section class="py-16 px-4 bg-yellow-400">
|
||||
<div class="max-w-3xl mx-auto text-center">
|
||||
<h2 class="text-3xl font-extrabold text-gray-900 mb-4">ابزار شما خراب شده؟</h2>
|
||||
<p class="text-gray-700 text-lg mb-8">همین الان با ما تماس بگیرید. مشاوره رایگان و تعمیر با ضمانت کتبی.</p>
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
<a href="tel:@SiteData.Company.TelPhone" class="bg-gray-900 text-white font-bold px-8 py-3.5 rounded-xl hover:bg-gray-800 transition-colors text-lg shadow-lg">
|
||||
📞 @SiteData.Company.Phone
|
||||
</a>
|
||||
<a href="https://wa.me/@SiteData.Company.Whatsapp" target="_blank"
|
||||
class="bg-green-600 text-white font-bold px-8 py-3.5 rounded-xl hover:bg-green-700 transition-colors text-lg shadow-lg">
|
||||
💬 واتساپ
|
||||
</a>
|
||||
<a href="/DeWalt" class="bg-white text-gray-900 font-bold px-8 py-3.5 rounded-xl hover:bg-gray-50 transition-colors text-lg shadow-lg">
|
||||
🔧 ابزار دیوالت
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages;
|
||||
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
@page
|
||||
@model PrivacyModel
|
||||
@{
|
||||
ViewData["Title"] = "Privacy Policy";
|
||||
}
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
|
||||
<p>Use this page to detail your site's privacy policy.</p>
|
||||
@@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages;
|
||||
|
||||
public class PrivacyModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Services.BrandModel
|
||||
@{ ViewData["Title"] = $"تعمیر ابزار {Model.Brand?.NameFa} در کرج"; Layout = "_Layout"; }
|
||||
|
||||
@if (Model.Brand is null)
|
||||
{
|
||||
<div class="max-w-xl mx-auto text-center py-20"><p class="text-gray-500">برند یافت نشد.</p></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
var b = Model.Brand;
|
||||
var isDewalt = b.Id == "dewalt";
|
||||
|
||||
<!-- ═══════════ HERO ═══════════ -->
|
||||
<div class="relative text-white py-14 px-4 overflow-hidden" style="background:linear-gradient(135deg,@b.Color+dd,@b.Color+99)">
|
||||
@if (isDewalt)
|
||||
{
|
||||
<div class="absolute inset-0 bg-cover bg-center opacity-20"
|
||||
style="background-image:url('https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200&q=80&auto=format&fit=crop')"></div>
|
||||
}
|
||||
<div class="relative max-w-6xl mx-auto flex flex-col sm:flex-row items-center gap-6">
|
||||
<div class="w-20 h-20 rounded-2xl flex items-center justify-center text-2xl font-extrabold border-2 border-white/30 bg-white/20 shrink-0 shadow-lg"
|
||||
style="color:@b.TextColor">
|
||||
@b.Name.Substring(0, 2)
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex flex-wrap items-center gap-3 mb-2">
|
||||
<h1 class="text-3xl font-extrabold" style="color:@(b.TextColor == "#fff" ? "white" : "#1f2937")">
|
||||
تعمیر ابزار @b.NameFa در کرج
|
||||
</h1>
|
||||
@if (b.IsOfficial)
|
||||
{
|
||||
<span class="bg-white text-gray-900 text-sm font-bold px-3 py-1 rounded-full shadow">🛡️ نمایندگی رسمی</span>
|
||||
}
|
||||
</div>
|
||||
<p class="opacity-90 max-w-xl" style="color:@(b.TextColor == "#fff" ? "#e0e0e0" : "#374151")">@b.Description</p>
|
||||
@if (isDewalt)
|
||||
{
|
||||
<a href="/DeWalt" class="inline-block mt-3 bg-yellow-400 text-gray-900 font-bold text-sm px-4 py-2 rounded-lg hover:bg-yellow-300 transition-colors">
|
||||
مشاهده همه ابزار دیوالت ›
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-6xl mx-auto px-4 py-12 grid lg:grid-cols-3 gap-10">
|
||||
<!-- ═══════════ MAIN CONTENT ═══════════ -->
|
||||
<div class="lg:col-span-2 space-y-8">
|
||||
@if (b.IsOfficial)
|
||||
{
|
||||
<div class="bg-yellow-50 border border-yellow-200 rounded-2xl p-6">
|
||||
<div class="flex items-start gap-3">
|
||||
<span class="text-yellow-600 text-2xl shrink-0">🛡️</span>
|
||||
<div>
|
||||
<h2 class="font-bold text-gray-900 mb-2 text-lg">نمایندگی رسمی @b.NameFa در کرج</h2>
|
||||
<p class="text-gray-600 text-sm leading-7">
|
||||
آساد ابزار افتخار دارد نمایندگی رسمی برند @b.NameFa را در شهر کرج داشته باشد.
|
||||
تمام تعمیرات توسط تکنیسینهای آموزشدیده و با قطعات کاملاً اورجینال انجام میشود.
|
||||
هر تعمیر دارای ضمانتنامه کتبی ۳ ماهه است.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- DeWalt: full tools grid -->
|
||||
@if (isDewalt)
|
||||
{
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-5 pb-2 border-b flex items-center gap-2">
|
||||
<span class="bg-yellow-400 text-gray-900 text-xs font-bold px-2 py-0.5 rounded">۱۵+ مدل</span>
|
||||
ابزار دیوالت که تعمیر میکنیم
|
||||
</h2>
|
||||
<div class="grid sm:grid-cols-2 gap-4">
|
||||
@{
|
||||
var catImgs = new Dictionary<string, string>
|
||||
{
|
||||
["drill"] = "https://images.unsplash.com/photo-1572981779307-38b8cabb2407?w=400&q=70&auto=format&fit=crop",
|
||||
["driver"] = "https://images.unsplash.com/photo-1530124566582-a618bc2615dc?w=400&q=70&auto=format&fit=crop",
|
||||
["grinder"] = "https://images.unsplash.com/photo-1487452066049-a710f7296400?w=400&q=70&auto=format&fit=crop",
|
||||
["rotary-hammer"] = "https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=400&q=70&auto=format&fit=crop",
|
||||
["saw"] = "https://images.unsplash.com/photo-1504148455328-c376907d081c?w=400&q=70&auto=format&fit=crop",
|
||||
["chop-saw"] = "https://images.unsplash.com/photo-1503387762-592deb58ef4e?w=400&q=70&auto=format&fit=crop",
|
||||
["miter-saw"] = "https://images.unsplash.com/photo-1558618047-3c8c1d8b9df1?w=400&q=70&auto=format&fit=crop",
|
||||
["woodworking"] = "https://images.unsplash.com/photo-1588854337115-1c67d9247e4d?w=400&q=70&auto=format&fit=crop",
|
||||
["multi"] = "https://images.unsplash.com/photo-1518770660439-4636190af475?w=400&q=70&auto=format&fit=crop",
|
||||
["sander"] = "https://images.unsplash.com/photo-1581579438747-1dc8d17bbce4?w=400&q=70&auto=format&fit=crop",
|
||||
["hedge-trimmer"] = "https://images.unsplash.com/photo-1416879595882-3373a0480b5b?w=400&q=70&auto=format&fit=crop",
|
||||
["laser-level"] = "https://images.unsplash.com/photo-1609220136736-443140cfeaa8?w=400&q=70&auto=format&fit=crop",
|
||||
["laser-measure"] = "https://images.unsplash.com/photo-1518770660439-4636190af475?w=400&q=70&auto=format&fit=crop",
|
||||
};
|
||||
}
|
||||
@foreach (var tool in SiteData.DeWaltTools)
|
||||
{
|
||||
var img = catImgs.TryGetValue(tool.CategoryId, out var tu) ? tu : catImgs["drill"];
|
||||
<div class="bg-white rounded-2xl border border-gray-100 overflow-hidden hover:shadow-md transition-shadow">
|
||||
<div class="relative h-36 overflow-hidden">
|
||||
<img src="@img" alt="تعمیر @tool.NameFa" loading="lazy"
|
||||
class="w-full h-full object-cover" />
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
|
||||
<div class="absolute top-2 left-2">
|
||||
<span class="bg-yellow-400 text-gray-900 text-xs font-bold px-1.5 py-0.5 rounded">@tool.Power</span>
|
||||
</div>
|
||||
<div class="absolute bottom-2 right-3 text-2xl">@tool.Icon</div>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<h3 class="font-bold text-gray-900 text-sm mb-0.5">@tool.NameFa</h3>
|
||||
<p class="text-xs text-gray-400 font-mono mb-2">@string.Join(" · ", tool.Models.Take(3))</p>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@foreach (var r in tool.RepairItems.Take(3))
|
||||
{
|
||||
<span class="text-xs bg-yellow-50 text-yellow-800 border border-yellow-100 px-1.5 py-0.5 rounded">@r</span>
|
||||
}
|
||||
@if (tool.RepairItems.Length > 3)
|
||||
{
|
||||
<span class="text-xs text-gray-400">+@(tool.RepairItems.Length - 3) مورد دیگر</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mt-6 text-center">
|
||||
<a href="/DeWalt" class="inline-block bg-yellow-400 text-gray-900 font-bold px-6 py-3 rounded-xl hover:bg-yellow-300 transition-colors">
|
||||
مشاهده صفحه کامل ابزار دیوالت ›
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Non-DeWalt brands: existing tool service cards -->
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-5 pb-2 border-b">خدمات تعمیر @b.NameFa</h2>
|
||||
<div class="space-y-4">
|
||||
@foreach (var tool in SiteData.ToolTypes.Where(t => b.Services.Contains(t.NameFa)))
|
||||
{
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-6 hover:shadow-sm transition-shadow">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<span class="text-2xl">@tool.Icon</span>
|
||||
<h3 class="font-bold text-gray-900">تعمیر @tool.NameFa @b.NameFa</h3>
|
||||
</div>
|
||||
<p class="text-gray-500 text-sm leading-7 mb-4">@tool.Description</p>
|
||||
<ul class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
@foreach (var issue in tool.CommonIssues)
|
||||
{
|
||||
<li class="flex items-start gap-2 text-sm text-gray-600">
|
||||
<span class="text-green-500 shrink-0">✓</span> @issue
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
<!-- Repair process -->
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-5 pb-2 border-b">فرآیند تعمیر</h2>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
@foreach (var step in new[] { ("۱","تحویل ابزار","حضوری یا پست"), ("۲","بررسی رایگان","تشخیص سریع"), ("۳","تعمیر تخصصی","قطعه اصل"), ("۴","تحویل با ضمانت","۳ ماه گارانتی") })
|
||||
{
|
||||
<div class="text-center p-4 bg-gray-50 rounded-2xl">
|
||||
<div class="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold mx-auto mb-3 shadow"
|
||||
style="background-color:@b.Color;color:@b.TextColor">@step.Item1</div>
|
||||
<div class="font-bold text-gray-800 text-sm">@step.Item2</div>
|
||||
<div class="text-xs text-gray-400 mt-1">@step.Item3</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ SIDEBAR ═══════════ -->
|
||||
<div class="space-y-5">
|
||||
<div class="rounded-2xl p-6 text-white" style="background-color:@b.Color">
|
||||
<h3 class="font-bold text-lg mb-1" style="color:@(b.TextColor == "#fff" ? "white" : "#1f2937")">درخواست تعمیر</h3>
|
||||
<p class="text-sm mb-5 opacity-80" style="color:@(b.TextColor == "#fff" ? "#e0e0e0" : "#374151")">مشاوره رایگان – تماس بگیرید</p>
|
||||
<a href="tel:@SiteData.Company.TelPhone" class="block bg-white font-bold text-center py-3 rounded-xl hover:opacity-90 mb-3 transition-opacity"
|
||||
style="color:@b.Color">📞 @SiteData.Company.Phone</a>
|
||||
<a href="https://wa.me/@SiteData.Company.Whatsapp" target="_blank"
|
||||
class="block bg-green-500 text-white font-bold text-center py-3 rounded-xl hover:bg-green-600 transition-colors">💬 واتساپ</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl border p-6">
|
||||
<h3 class="font-bold text-gray-900 mb-4">⭐ مزایای خدمات ما</h3>
|
||||
<ul class="space-y-2.5 text-sm text-gray-600">
|
||||
@foreach (var item in new[] { "بررسی و تشخیص عیب رایگان", "قطعات ۱۰۰٪ اورجینال", "ضمانتنامه کتبی ۳ ماهه", "تکنیسین مجاز و متخصص", "قیمت شفاف قبل از شروع کار", "تحویل سریع زیر ۴۸ ساعت", "ارسال و دریافت از سراسر کشور" })
|
||||
{
|
||||
<li class="flex items-center gap-2">
|
||||
<span class="text-green-500 shrink-0">✓</span> @item
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@if (isDewalt)
|
||||
{
|
||||
<div class="bg-yellow-50 border border-yellow-200 rounded-2xl p-5">
|
||||
<h3 class="font-bold text-gray-900 mb-3">🔩 قطعات موجود DeWalt</h3>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
@foreach (var p in new[] { "کاربن", "بیرینگ", "آرمیچر", "گیربکس", "چاک", "کلید", "استاتور", "پیستون" })
|
||||
{
|
||||
<span class="text-xs bg-yellow-100 text-yellow-800 border border-yellow-200 px-2 py-0.5 rounded-full">@p</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<a href="/Shop" class="block bg-gray-50 border border-gray-200 rounded-2xl p-5 hover:shadow-md transition-shadow">
|
||||
<h3 class="font-bold text-gray-900 mb-1">🛒 فروشگاه قطعات</h3>
|
||||
<p class="text-sm text-gray-500">قطعات یدکی @b.NameFa را مستقیم سفارش دهید.</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using AsadiTools.Services;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages.Services;
|
||||
|
||||
public class BrandModel : PageModel
|
||||
{
|
||||
public BrandInfo? Brand { get; private set; }
|
||||
|
||||
public void OnGet(string id)
|
||||
{
|
||||
Brand = SiteData.Brands.FirstOrDefault(b => b.Id == id);
|
||||
if (Brand is not null)
|
||||
ViewData["Description"] = $"{Brand.Description} تعمیر {string.Join("، ", Brand.Services)} {Brand.NameFa} در کرج.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Services.ServicesIndexModel
|
||||
@{ ViewData["Title"] = "خدمات تعمیر ابزار برقی در کرج"; Layout = "_Layout"; }
|
||||
|
||||
<div class="bg-blue-800 text-white py-12 px-4">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<h1 class="text-3xl font-extrabold mb-2">خدمات تعمیر ابزار</h1>
|
||||
<p class="text-blue-200 text-lg">تعمیر تخصصی انواع ابزار برقی صنعتی توسط تکنیسینهای مجرب در کرج</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-6xl mx-auto px-4 py-12">
|
||||
<section class="mb-14">
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-6 pb-2 border-b">خدمات بر اساس برند</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
@foreach (var b in SiteData.Brands)
|
||||
{
|
||||
<a href="/Services/Brand?id=@b.Id" class="group bg-white rounded-2xl border border-gray-100 p-6 hover:shadow-lg transition-all hover:-translate-y-0.5">
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<div class="w-12 h-12 rounded-xl flex items-center justify-center text-xl font-extrabold shrink-0"
|
||||
style="background-color:@b.Color;color:@b.TextColor">@b.Name[0]</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-bold text-gray-900">@b.NameFa</span>
|
||||
@if (b.IsOfficial) { <span class="text-xs bg-yellow-400 text-gray-900 px-1.5 py-0.5 rounded font-bold">رسمی</span> }
|
||||
</div>
|
||||
<div class="text-xs text-gray-400">@b.Name</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mb-4 leading-7">@b.Description</p>
|
||||
<div class="flex flex-wrap gap-1.5 mb-3">
|
||||
@foreach (var svc in b.Services) { <span class="text-xs bg-gray-100 text-gray-600 px-2 py-0.5 rounded-full">@svc</span> }
|
||||
</div>
|
||||
<div class="text-sm text-blue-600 font-medium">مشاهده خدمات کامل ›</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-6 pb-2 border-b">خدمات بر اساس نوع ابزار</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
@foreach (var t in SiteData.ToolTypes)
|
||||
{
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<span class="text-3xl">@t.Icon</span>
|
||||
<h3 class="font-bold text-xl text-gray-900">@t.NameFa</h3>
|
||||
</div>
|
||||
<p class="text-gray-500 text-sm leading-7 mb-4">@t.Description</p>
|
||||
<p class="text-sm font-bold text-gray-700 mb-3">مشکلات رایج:</p>
|
||||
<ul class="space-y-2">
|
||||
@foreach (var issue in t.CommonIssues)
|
||||
{
|
||||
<li class="flex items-start gap-2 text-sm text-gray-600"><span class="text-green-500 shrink-0">✓</span> @issue</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages.Services;
|
||||
|
||||
public class ServicesIndexModel : PageModel
|
||||
{
|
||||
public void OnGet() { }
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fa" dir="rtl">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] | پنل مدیریت آساد ابزار</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-Regular.woff2") format("woff2"); font-weight:400; font-display:swap; }
|
||||
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-Bold.woff2") format("woff2"); font-weight:700; font-display:swap; }
|
||||
*, body { font-family: "Vazirmatn", Tahoma, Arial, sans-serif !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-800">
|
||||
<div class="flex min-h-screen">
|
||||
<!-- Sidebar -->
|
||||
<aside class="fixed right-0 top-0 h-full w-56 bg-gray-900 text-white flex flex-col z-40">
|
||||
<div class="p-5 border-b border-gray-800">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="bg-blue-600 rounded-lg p-1.5 text-sm">🔧</div>
|
||||
<div>
|
||||
<div class="font-bold text-sm">آساد ابزار</div>
|
||||
<div class="text-xs text-gray-400">پنل مدیریت</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="flex-1 p-4 space-y-1">
|
||||
<a href="/Admin" class="flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
|
||||
📊 داشبورد
|
||||
</a>
|
||||
<a href="/Admin/Products" class="flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
|
||||
📦 محصولات
|
||||
</a>
|
||||
<a href="/Admin/Orders" class="flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
|
||||
🛍️ سفارشها
|
||||
</a>
|
||||
<a href="/Admin/Blog" class="flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
|
||||
📝 بلاگ
|
||||
</a>
|
||||
<a href="/Admin/ChangePassword" class="flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
|
||||
🔑 تغییر رمز عبور
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-gray-800">
|
||||
<form method="post" action="/Admin/Logout">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="text-sm text-red-400 hover:text-red-300 transition-colors">🚪 خروج</button>
|
||||
</form>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main -->
|
||||
<main class="flex-1 mr-56 overflow-auto">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,111 @@
|
||||
@inject AsadiTools.Services.CartService Cart
|
||||
<!DOCTYPE html>
|
||||
<html lang="fa" dir="rtl">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@(ViewData["Title"] != null ? ViewData["Title"] + " | آساد ابزار کرج" : "آساد ابزار - نمایندگی رسمی دیوالت در کرج")</title>
|
||||
<meta name="description" content="@(ViewData["Description"] ?? "نمایندگی رسمی دیوالت، ماکیتا، رونیکس، توسن و بلک اند دکر در کرج. تعمیر دریل، فرز، مینی فرز، شمشاد زن و بتن کن.")" />
|
||||
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-Regular.woff2") format("woff2"); font-weight:400; font-display:swap; }
|
||||
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-Bold.woff2") format("woff2"); font-weight:700; font-display:swap; }
|
||||
@@font-face { font-family:"Vazirmatn"; src:url("https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@@v33.003/fonts/webfonts/Vazirmatn-ExtraBold.woff2") format("woff2"); font-weight:800; font-display:swap; }
|
||||
*, body { font-family: "Vazirmatn", Tahoma, Arial, sans-serif !important; }
|
||||
::-webkit-scrollbar { width:5px } ::-webkit-scrollbar-thumb { background:#94a3b8; border-radius:3px }
|
||||
</style>
|
||||
@await RenderSectionAsync("Head", required: false)
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-800 flex flex-col min-h-screen">
|
||||
|
||||
<!-- Top bar -->
|
||||
<div class="bg-blue-800 text-white text-sm py-1.5 px-4">
|
||||
<div class="max-w-6xl mx-auto flex justify-between items-center">
|
||||
<a href="tel:@SiteData.Company.TelPhone" class="hover:text-yellow-300 transition-colors">📞 @SiteData.Company.Phone</a>
|
||||
<span class="text-yellow-300 font-bold text-xs">نمایندگی رسمی دیوالت در کرج</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<header class="bg-white shadow-sm sticky top-0 z-50">
|
||||
<div class="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between gap-4">
|
||||
<a href="/" class="flex items-center gap-2 shrink-0">
|
||||
<div class="bg-blue-700 text-white rounded-lg p-2 text-lg">🔧</div>
|
||||
<div>
|
||||
<div class="font-bold text-lg text-gray-900 leading-tight">آساد ابزار</div>
|
||||
<div class="text-xs text-gray-400">کرج • تعمیر ابزار صنعتی</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<nav class="hidden md:flex items-center gap-5">
|
||||
<a href="/" class="text-gray-600 hover:text-blue-700 font-medium transition-colors text-sm">خانه</a>
|
||||
<a href="/Services" class="text-gray-600 hover:text-blue-700 font-medium transition-colors text-sm">خدمات</a>
|
||||
<a href="/brands" class="text-gray-600 hover:text-blue-700 font-medium transition-colors text-sm">برندها</a>
|
||||
<a href="/blog" class="text-gray-600 hover:text-blue-700 font-medium transition-colors text-sm">بلاگ</a>
|
||||
<a href="/DeWalt" class="inline-flex items-center gap-1 bg-yellow-400 text-gray-900 font-bold px-3 py-1 rounded-lg text-sm hover:bg-yellow-300 transition-colors">🛡️ دیوالت</a>
|
||||
<a href="/Shop" class="text-gray-600 hover:text-blue-700 font-medium transition-colors text-sm">فروشگاه قطعات</a>
|
||||
<a href="/Contact" class="text-gray-600 hover:text-blue-700 font-medium transition-colors text-sm">تماس با ما</a>
|
||||
</nav>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="/Cart" class="relative flex items-center gap-1.5 bg-blue-700 text-white px-3 py-2 rounded-lg text-sm font-medium hover:bg-blue-800 transition-colors">
|
||||
🛒 <span class="hidden sm:inline">سبد خرید</span>
|
||||
@if (Cart.Count > 0)
|
||||
{
|
||||
<span class="absolute -top-1.5 -left-1.5 bg-yellow-400 text-gray-900 text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center">@Cart.Count</span>
|
||||
}
|
||||
</a>
|
||||
<button onclick="document.getElementById('mobile-menu').classList.toggle('hidden')" class="md:hidden p-2 rounded-lg hover:bg-gray-100">☰</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mobile-menu" class="hidden md:hidden border-t bg-white px-4 py-3 space-y-1">
|
||||
<a href="/" class="block py-2.5 px-3 rounded-lg hover:bg-blue-50 hover:text-blue-700 font-medium">خانه</a>
|
||||
<a href="/Services" class="block py-2.5 px-3 rounded-lg hover:bg-blue-50 hover:text-blue-700 font-medium">خدمات</a>
|
||||
<a href="/brands" class="block py-2.5 px-3 rounded-lg hover:bg-blue-50 hover:text-blue-700 font-medium">برندها</a>
|
||||
<a href="/blog" class="block py-2.5 px-3 rounded-lg hover:bg-blue-50 hover:text-blue-700 font-medium">بلاگ</a>
|
||||
<a href="/DeWalt" class="block py-2.5 px-3 rounded-lg bg-yellow-50 text-gray-900 font-bold">🛡️ ابزار دیوالت (رسمی)</a>
|
||||
<a href="/Shop" class="block py-2.5 px-3 rounded-lg hover:bg-blue-50 hover:text-blue-700 font-medium">فروشگاه قطعات</a>
|
||||
<a href="/Contact" class="block py-2.5 px-3 rounded-lg hover:bg-blue-50 hover:text-blue-700 font-medium">تماس با ما</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex-1">
|
||||
@RenderBody()
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-gray-900 text-gray-300 mt-auto">
|
||||
<div class="max-w-6xl mx-auto px-4 py-12 grid grid-cols-1 md:grid-cols-3 gap-10">
|
||||
<div>
|
||||
<h3 class="text-white font-bold text-lg mb-4">آساد ابزار</h3>
|
||||
<p class="text-sm leading-7 text-gray-400">با بیش از ۱۵ سال تجربه، نمایندگی رسمی دیوالت در کرج. تعمیر تخصصی ابزار صنعتی با قطعات اصل.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-white font-bold text-lg mb-4">تماس</h3>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li>📞 <a href="tel:@SiteData.Company.TelPhone" class="hover:text-white">@SiteData.Company.Phone</a></li>
|
||||
<li>📱 <a href="tel:@SiteData.Company.TelMobile" class="hover:text-white">@SiteData.Company.Mobile</a></li>
|
||||
<li>📍 @SiteData.Company.Address</li>
|
||||
<li>🕐 @SiteData.Company.WorkingHours</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-white font-bold text-lg mb-4">خدمات</h3>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li><a href="/Services/Brand?id=dewalt" class="hover:text-white">تعمیر ابزار دیوالت <span class="text-yellow-400 text-xs">(رسمی)</span></a></li>
|
||||
<li><a href="/Services/Brand?id=makita" class="hover:text-white">تعمیر ابزار ماکیتا</a></li>
|
||||
<li><a href="/Services/Brand?id=ronix" class="hover:text-white">تعمیر ابزار رونیکس</a></li>
|
||||
<li><a href="/Services/Brand?id=tosan" class="hover:text-white">تعمیر ابزار توسن</a></li>
|
||||
<li><a href="/Shop" class="hover:text-white">فروشگاه قطعات یدکی</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-800 py-4 text-center text-xs text-gray-500">
|
||||
© @DateTime.Now.Year آساد ابزار کرج – تمام حقوق محفوظ است
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,48 @@
|
||||
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||
for details on configuring this project to bundle and minify static web assets. */
|
||||
|
||||
a.navbar-brand {
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0077cc;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.border-top {
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.box-shadow {
|
||||
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
button.accept-policy {
|
||||
font-size: 1rem;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
line-height: 60px;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>
|
||||
@@ -0,0 +1,74 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Shop.DetailModel
|
||||
@{ ViewData["Title"] = Model.Product?.NameFa ?? "قطعه"; Layout = "_Layout"; }
|
||||
|
||||
@if (Model.Product is null)
|
||||
{
|
||||
<div class="max-w-xl mx-auto text-center py-20"><p class="text-gray-500">محصول یافت نشد.</p><a href="/Shop" class="text-blue-600 hover:underline mt-2 block">بازگشت به فروشگاه</a></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
var p = Model.Product;
|
||||
var cat = SiteData.Categories.FirstOrDefault(c => c.Id == p.Category);
|
||||
var brand = SiteData.Brands.FirstOrDefault(b => b.Id == p.Brand);
|
||||
|
||||
<div class="max-w-5xl mx-auto px-4 py-8">
|
||||
<nav class="flex items-center gap-2 text-sm text-gray-500 mb-8">
|
||||
<a href="/" class="hover:text-blue-600">خانه</a><span>/</span>
|
||||
<a href="/Shop" class="hover:text-blue-600">فروشگاه</a><span>/</span>
|
||||
<span class="text-gray-800">@p.NameFa</span>
|
||||
</nav>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-10">
|
||||
<div class="rounded-3xl overflow-hidden aspect-square @(string.IsNullOrEmpty(p.ImageUrl) ? "bg-gradient-to-br from-blue-50 to-blue-100 flex items-center justify-center text-8xl" : "")">
|
||||
@if (!string.IsNullOrEmpty(p.ImageUrl))
|
||||
{
|
||||
<img src="@p.ImageUrl" alt="@p.NameFa" class="w-full h-full object-cover" />
|
||||
}
|
||||
else
|
||||
{
|
||||
@(cat?.Icon ?? "🔧")
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex flex-wrap gap-2 mb-4">
|
||||
@if (brand != null) { <span class="text-sm font-bold px-3 py-1 rounded-full text-white" style="background-color:@brand.Color">@brand.NameFa</span> }
|
||||
@if (cat != null) { <span class="text-sm px-3 py-1 rounded-full bg-gray-100 text-gray-600">@cat.Icon @cat.NameFa</span> }
|
||||
</div>
|
||||
<h1 class="text-2xl font-extrabold text-gray-900 mb-2">@p.NameFa</h1>
|
||||
@if (p.NameEn != null) { <p class="text-gray-400 text-sm mb-4 font-mono">@p.NameEn</p> }
|
||||
@if (p.Sku != null) { <p class="text-xs text-gray-400 mb-6 bg-gray-100 inline-block px-3 py-1 rounded-full">کد محصول: @p.Sku</p> }
|
||||
@if (p.Description != null) { <p class="text-gray-600 leading-7 mb-6 text-sm">@p.Description</p> }
|
||||
|
||||
<div class="mb-6">
|
||||
@if (p.HasDiscount) { <p class="text-gray-400 line-through text-lg">@SiteData.FormatPrice(p.Price)</p> }
|
||||
<p class="text-3xl font-extrabold text-blue-700">@SiteData.FormatPrice(p.FinalPrice)</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
@if (p.Stock > 0)
|
||||
{
|
||||
<span class="text-green-600 text-sm font-medium">🟢 موجود در انبار (@p.Stock عدد)</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-red-500 text-sm font-medium">🔴 ناموجود</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<form method="post" class="flex gap-3">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="productId" value="@p.Id" />
|
||||
<input type="hidden" name="nameFa" value="@p.NameFa" />
|
||||
<input type="hidden" name="price" value="@p.FinalPrice" />
|
||||
<input type="hidden" name="sku" value="@p.Sku" />
|
||||
<button type="submit" @(p.Stock == 0 ? "disabled" : "")
|
||||
class="flex-1 flex items-center justify-center gap-2 bg-blue-700 text-white py-3.5 rounded-xl font-bold hover:bg-blue-800 transition-colors disabled:bg-gray-200 disabled:text-gray-400">
|
||||
🛒 @(p.Stock == 0 ? "ناموجود" : "افزودن به سبد خرید")
|
||||
</button>
|
||||
<a href="/Cart" class="flex items-center gap-2 border-2 border-blue-700 text-blue-700 py-3.5 px-4 rounded-xl font-bold hover:bg-blue-50">سبد</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using AsadiTools.Data;
|
||||
using AsadiTools.Models;
|
||||
using AsadiTools.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace AsadiTools.Pages.Shop;
|
||||
|
||||
public class DetailModel(AppDbContext db, CartService cart) : PageModel
|
||||
{
|
||||
public Product? Product { get; private set; }
|
||||
|
||||
public async Task OnGetAsync(int id)
|
||||
{
|
||||
Product = await db.Products.FindAsync(id);
|
||||
}
|
||||
|
||||
public IActionResult OnPost(int productId, string nameFa, decimal price, string? sku)
|
||||
{
|
||||
cart.AddItem(new CartItem { ProductId = productId, NameFa = nameFa, Price = price, Sku = sku, Qty = 1 });
|
||||
return RedirectToPage("/Cart/Index");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
@page
|
||||
@model AsadiTools.Pages.Shop.ShopIndexModel
|
||||
@{ ViewData["Title"] = "فروشگاه قطعات یدکی ابزار برقی"; Layout = "_Layout"; }
|
||||
|
||||
<div class="bg-blue-800 text-white py-10 px-4">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<h1 class="text-3xl font-extrabold mb-2">فروشگاه قطعات یدکی</h1>
|
||||
<p class="text-blue-200">قطعات اصل ابزار برقی صنعتی با گارانتی</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-6xl mx-auto px-4 py-8">
|
||||
<!-- Filters -->
|
||||
<div class="bg-white rounded-2xl border border-gray-100 p-5 mb-8">
|
||||
<form method="get" class="mb-5">
|
||||
<input type="text" name="search" value="@Model.Search" placeholder="جستجو در قطعات..."
|
||||
class="w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
</form>
|
||||
|
||||
<div class="mb-4">
|
||||
<p class="text-xs text-gray-500 font-bold mb-2">دستهبندی</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="/Shop" class="text-sm px-3 py-1.5 rounded-full border transition-colors @(Model.Category == null ? "bg-blue-700 text-white border-blue-700" : "border-gray-200 text-gray-600 hover:border-blue-400")">همه</a>
|
||||
@foreach (var cat in SiteData.Categories)
|
||||
{
|
||||
<a href="/Shop?category=@cat.Id@(Model.Brand != null ? "&brand=" + Model.Brand : "")"
|
||||
class="text-sm px-3 py-1.5 rounded-full border transition-colors @(Model.Category == cat.Id ? "bg-blue-700 text-white border-blue-700" : "border-gray-200 text-gray-600 hover:border-blue-400")">
|
||||
@cat.Icon @cat.NameFa
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 font-bold mb-2">برند</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="/Shop@(Model.Category != null ? "?category=" + Model.Category : "")"
|
||||
class="text-sm px-3 py-1.5 rounded-full border transition-colors @(Model.Brand == null ? "bg-blue-700 text-white border-blue-700" : "border-gray-200 text-gray-600 hover:border-blue-400")">همه برندها</a>
|
||||
@foreach (var brand in SiteData.Brands)
|
||||
{
|
||||
<a href="/Shop?brand=@brand.Id@(Model.Category != null ? "&category=" + Model.Category : "")"
|
||||
class="text-sm px-3 py-1.5 rounded-full border transition-colors @(Model.Brand == brand.Id ? "bg-blue-700 text-white border-blue-700" : "border-gray-200 text-gray-600 hover:border-blue-400")">
|
||||
@brand.NameFa
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-500 mb-5">@Model.TotalCount محصول یافت شد</p>
|
||||
|
||||
@if (!Model.Products.Any())
|
||||
{
|
||||
<div class="text-center py-20 text-gray-400">
|
||||
<div class="text-5xl mb-4">🔍</div>
|
||||
<p>محصولی یافت نشد</p>
|
||||
<a href="/Shop" class="text-blue-600 text-sm mt-2 hover:underline block">مشاهده همه محصولات</a>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-5">
|
||||
@foreach (var p in Model.Products)
|
||||
{
|
||||
var cat = SiteData.Categories.FirstOrDefault(c => c.Id == p.Category);
|
||||
var brand = SiteData.Brands.FirstOrDefault(b => b.Id == p.Brand);
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 hover:shadow-md transition-shadow flex flex-col">
|
||||
@if (!string.IsNullOrEmpty(p.ImageUrl))
|
||||
{
|
||||
<div class="rounded-t-xl overflow-hidden" style="height:160px">
|
||||
<img src="@p.ImageUrl" alt="@p.NameFa" loading="lazy"
|
||||
class="w-full h-full object-cover"
|
||||
onerror="this.parentElement.innerHTML='<div class=\'bg-gradient-to-br from-blue-50 to-blue-100 h-full flex items-center justify-center text-5xl\'>@(cat?.Icon ?? "🔧")</div>'" />
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="bg-gradient-to-br from-blue-50 to-blue-100 rounded-t-xl p-8 flex items-center justify-center text-5xl">
|
||||
@(cat?.Icon ?? "🔧")
|
||||
</div>
|
||||
}
|
||||
<div class="p-4 flex flex-col flex-1 gap-3">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
@if (brand != null) { <span class="text-xs font-bold px-2 py-0.5 rounded-full text-white" style="background-color:@brand.Color">@brand.NameFa</span> }
|
||||
@if (cat != null) { <span class="text-xs px-2 py-0.5 rounded-full bg-gray-100 text-gray-600">@cat.NameFa</span> }
|
||||
@if (p.Stock == 0) { <span class="text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-600">ناموجود</span> }
|
||||
</div>
|
||||
<div>
|
||||
<a href="/Shop/Detail?id=@p.Id" class="font-bold text-gray-900 hover:text-blue-700 transition-colors leading-snug block">@p.NameFa</a>
|
||||
@if (p.Sku != null) { <p class="text-xs text-gray-400 mt-0.5">کد: @p.Sku</p> }
|
||||
</div>
|
||||
<div class="mt-auto">
|
||||
@if (p.HasDiscount) { <p class="text-sm text-gray-400 line-through">@SiteData.FormatPrice(p.Price)</p> }
|
||||
<p class="text-lg font-bold text-blue-700">@SiteData.FormatPrice(p.FinalPrice)</p>
|
||||
</div>
|
||||
<form method="post" asp-page-handler="AddToCart">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="productId" value="@p.Id" />
|
||||
<input type="hidden" name="nameFa" value="@p.NameFa" />
|
||||
<input type="hidden" name="price" value="@p.FinalPrice" />
|
||||
<input type="hidden" name="sku" value="@p.Sku" />
|
||||
<button type="submit" @(p.Stock == 0 ? "disabled" : "")
|
||||
class="w-full flex items-center justify-center gap-2 bg-blue-700 text-white py-2.5 rounded-lg text-sm font-medium hover:bg-blue-800 transition-colors disabled:bg-gray-200 disabled:text-gray-400 disabled:cursor-not-allowed">
|
||||
🛒 @(p.Stock == 0 ? "ناموجود" : "افزودن به سبد")
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.TotalPages > 1)
|
||||
{
|
||||
<div class="flex justify-center items-center gap-2 mt-10">
|
||||
@if (Model.CurrentPage > 1)
|
||||
{
|
||||
<a href="/Shop?page=@(Model.CurrentPage - 1)@(Model.Category != null ? "&category=" + Model.Category : "")@(Model.Brand != null ? "&brand=" + Model.Brand : "")@(Model.Search != null ? "&search=" + Model.Search : "")"
|
||||
class="px-4 py-2 rounded-xl border border-gray-200 text-sm text-gray-600 hover:border-blue-400 hover:text-blue-700 transition-colors">
|
||||
‹ قبلی
|
||||
</a>
|
||||
}
|
||||
@for (var i = Math.Max(1, Model.CurrentPage - 2); i <= Math.Min(Model.TotalPages, Model.CurrentPage + 2); i++)
|
||||
{
|
||||
<a href="/Shop?page=@i@(Model.Category != null ? "&category=" + Model.Category : "")@(Model.Brand != null ? "&brand=" + Model.Brand : "")@(Model.Search != null ? "&search=" + Model.Search : "")"
|
||||
class="px-4 py-2 rounded-xl border text-sm transition-colors @(i == Model.CurrentPage ? "bg-blue-700 text-white border-blue-700" : "border-gray-200 text-gray-600 hover:border-blue-400 hover:text-blue-700")">
|
||||
@i
|
||||
</a>
|
||||
}
|
||||
@if (Model.CurrentPage < Model.TotalPages)
|
||||
{
|
||||
<a href="/Shop?page=@(Model.CurrentPage + 1)@(Model.Category != null ? "&category=" + Model.Category : "")@(Model.Brand != null ? "&brand=" + Model.Brand : "")@(Model.Search != null ? "&search=" + Model.Search : "")"
|
||||
class="px-4 py-2 rounded-xl border border-gray-200 text-sm text-gray-600 hover:border-blue-400 hover:text-blue-700 transition-colors">
|
||||
بعدی ›
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,48 @@
|
||||
using AsadiTools.Data;
|
||||
using AsadiTools.Models;
|
||||
using AsadiTools.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AsadiTools.Pages.Shop;
|
||||
|
||||
public class ShopIndexModel(AppDbContext db, CartService cart) : PageModel
|
||||
{
|
||||
public const int PageSize = 12;
|
||||
|
||||
public List<Product> Products { get; private set; } = [];
|
||||
public string? Category { get; private set; }
|
||||
public string? Brand { get; private set; }
|
||||
public string? Search { get; private set; }
|
||||
public int CurrentPage { get; private set; } = 1;
|
||||
public int TotalPages { get; private set; }
|
||||
public int TotalCount { get; private set; }
|
||||
|
||||
public async Task OnGetAsync(string? category, string? brand, string? search, int page = 1)
|
||||
{
|
||||
Category = category;
|
||||
Brand = brand;
|
||||
Search = search;
|
||||
|
||||
var q = db.Products.Where(p => p.IsActive);
|
||||
if (category is not null) q = q.Where(p => p.Category == category);
|
||||
if (brand is not null) q = q.Where(p => p.Brand == brand);
|
||||
if (search is not null) q = q.Where(p => p.NameFa.Contains(search) || (p.NameEn != null && p.NameEn.Contains(search)) || (p.Sku != null && p.Sku.Contains(search)));
|
||||
|
||||
TotalCount = await q.CountAsync();
|
||||
TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize);
|
||||
CurrentPage = Math.Clamp(page, 1, Math.Max(1, TotalPages));
|
||||
|
||||
Products = await q.OrderByDescending(p => p.Id)
|
||||
.Skip((CurrentPage - 1) * PageSize)
|
||||
.Take(PageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public IActionResult OnPostAddToCart(int productId, string nameFa, decimal price, string? sku)
|
||||
{
|
||||
cart.AddItem(new CartItem { ProductId = productId, NameFa = nameFa, Price = price, Sku = sku, Qty = 1 });
|
||||
return RedirectToPage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@using AsadiTools
|
||||
@using AsadiTools.Models
|
||||
@using AsadiTools.Services
|
||||
@namespace AsadiTools.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
Reference in New Issue
Block a user