Initial commit — AsadiTools v1.0
CI/CD / CI — dotnet build (push) Successful in 44s
CI/CD / Deploy — docker compose (push) Failing after 1s

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:
Soroush Asadi
2026-06-01 22:08:43 +03:30
commit f97f891d67
146 changed files with 88128 additions and 0 deletions
+90
View File
@@ -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>
}
+71
View File
@@ -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();
}
}