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,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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user