refactor: move docs and examples to src folder, update README with accurate benchmarks
This commit is contained in:
@@ -1,42 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
// Get dependencies
|
||||
const pugz_dep = b.dependency("pugz", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
const httpz_dep = b.dependency("httpz", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
// Main executable
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "demo",
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.imports = &.{
|
||||
.{ .name = "pugz", .module = pugz_dep.module("pugz") },
|
||||
.{ .name = "httpz", .module = httpz_dep.module("httpz") },
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
const run_step = b.step("run", "Run the demo server");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
.{
|
||||
.name = .demo,
|
||||
.version = "0.0.1",
|
||||
.fingerprint = 0xd642dfa01393173d,
|
||||
.minimum_zig_version = "0.15.2",
|
||||
.dependencies = .{
|
||||
.pugz = .{
|
||||
.path = "../..",
|
||||
},
|
||||
.httpz = .{
|
||||
.url = "git+https://github.com/karlseguin/http.zig?ref=master#9ef2ffe8d611ff2e1081e5cf39cb4632c145c5b9",
|
||||
.hash = "httpz-0.0.0-PNVzrIowBwAFr_kqBN1W4KBMC2Ofutasj2ZfNAIcfTzF",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
"views",
|
||||
},
|
||||
}
|
||||
@@ -1,752 +0,0 @@
|
||||
/* Pugz Store - Clean Modern CSS */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary: #3b82f6;
|
||||
--primary-dark: #2563eb;
|
||||
--text: #1f2937;
|
||||
--text-muted: #6b7280;
|
||||
--bg: #ffffff;
|
||||
--bg-alt: #f9fafb;
|
||||
--border: #e5e7eb;
|
||||
--success: #10b981;
|
||||
--radius: 8px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.container {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: var(--bg);
|
||||
border-bottom: 1px solid var(--border);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cart-link {
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background: var(--text);
|
||||
color: white;
|
||||
padding: 40px 0;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-content p {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-radius: var(--radius);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
|
||||
color: white;
|
||||
padding: 80px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hero .btn-outline {
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hero .btn-outline:hover {
|
||||
border-color: white;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Sections */
|
||||
.section {
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
.section-alt {
|
||||
background: var(--bg-alt);
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: var(--bg-alt);
|
||||
padding: 40px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Feature Grid */
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.feature-card ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.feature-card li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* Category Grid */
|
||||
.category-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.category-card {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 32px 24px;
|
||||
text-align: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.category-card:hover {
|
||||
border-color: var(--primary);
|
||||
text-decoration: none;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.category-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin: 0 auto 16px;
|
||||
background: var(--bg-alt);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.category-card h3 {
|
||||
font-size: 1rem;
|
||||
color: var(--text);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.category-card span {
|
||||
font-size: 14px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Product Grid */
|
||||
.product-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 24px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.product-image {
|
||||
position: relative;
|
||||
height: 180px;
|
||||
background: var(--bg-alt);
|
||||
}
|
||||
|
||||
.product-badge {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.product-category {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 1rem;
|
||||
margin: 6px 0 12px;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/* Products Toolbar */
|
||||
.products-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.results-count {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.sort-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sort-options label {
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sort-options select {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Pagination */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.page-link {
|
||||
padding: 8px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.page-link:hover {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.page-link.active {
|
||||
background: var(--primary);
|
||||
border-color: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Cart */
|
||||
.cart-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 340px;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.cart-items {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.cart-item {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto auto;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.cart-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.cart-item-info h3 {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.cart-item-price {
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cart-item-qty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.qty-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg);
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.qty-input {
|
||||
width: 48px;
|
||||
height: 32px;
|
||||
border: 1px solid var(--border);
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cart-item-total {
|
||||
font-weight: 600;
|
||||
min-width: 80px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cart-item-remove {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.cart-item-remove:hover {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.cart-actions {
|
||||
padding: 20px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.cart-summary {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 24px;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.cart-summary h3 {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.summary-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.summary-total {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* About Page */
|
||||
.about-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.about-main h2 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.about-main h3 {
|
||||
margin: 24px 0 12px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.about-main p {
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.feature-list li {
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.about-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
background: var(--bg-alt);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.info-card h3 {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.info-card ul {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
font-size: 14px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.info-card li {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* Product Detail */
|
||||
.product-detail {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 48px;
|
||||
}
|
||||
|
||||
.product-detail-image {
|
||||
background: var(--bg-alt);
|
||||
border-radius: var(--radius);
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.product-image-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.product-detail-info h1 {
|
||||
font-size: 2rem;
|
||||
margin: 8px 0 16px;
|
||||
}
|
||||
|
||||
.product-price-large {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.product-description {
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 24px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.product-actions {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.quantity-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.quantity-selector label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.product-meta {
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.product-meta p {
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
font-size: 14px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.breadcrumb a {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.breadcrumb a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.breadcrumb span {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
/* Error Page */
|
||||
.error-page {
|
||||
padding: 100px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-code {
|
||||
font-size: 8rem;
|
||||
font-weight: 700;
|
||||
color: var(--border);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.error-content h2 {
|
||||
margin: 16px 0 8px;
|
||||
}
|
||||
|
||||
.error-content p {
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.error-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Utility Classes */
|
||||
.text-success {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.header-content {
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.nav {
|
||||
order: 3;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cart-layout,
|
||||
.about-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.cart-item {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
@@ -1,419 +0,0 @@
|
||||
//! Pugz Store Demo - A comprehensive e-commerce demo showcasing Pugz capabilities
|
||||
//!
|
||||
//! Features demonstrated:
|
||||
//! - Template inheritance (extends/block)
|
||||
//! - Partial includes (header, footer)
|
||||
//! - Mixins with parameters (product-card, rating, forms)
|
||||
//! - Conditionals and loops
|
||||
//! - Data binding
|
||||
//! - Pretty printing
|
||||
//! - LRU cache with TTL
|
||||
|
||||
const std = @import("std");
|
||||
const httpz = @import("httpz");
|
||||
const pugz = @import("pugz");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
// ============================================================================
|
||||
// Data Types
|
||||
// ============================================================================
|
||||
|
||||
const Product = struct {
|
||||
id: []const u8,
|
||||
name: []const u8,
|
||||
price: []const u8,
|
||||
image: []const u8,
|
||||
rating: u8,
|
||||
category: []const u8,
|
||||
categorySlug: []const u8,
|
||||
sale: bool = false,
|
||||
description: []const u8 = "",
|
||||
reviewCount: []const u8 = "0",
|
||||
};
|
||||
|
||||
const Category = struct {
|
||||
name: []const u8,
|
||||
slug: []const u8,
|
||||
icon: []const u8,
|
||||
count: []const u8,
|
||||
active: bool = false,
|
||||
};
|
||||
|
||||
const CartItem = struct {
|
||||
id: []const u8,
|
||||
name: []const u8,
|
||||
price: []const u8,
|
||||
image: []const u8,
|
||||
variant: []const u8,
|
||||
quantity: []const u8,
|
||||
total: []const u8,
|
||||
};
|
||||
|
||||
const Cart = struct {
|
||||
items: []const CartItem,
|
||||
subtotal: []const u8,
|
||||
shipping: []const u8,
|
||||
discount: ?[]const u8 = null,
|
||||
discountCode: ?[]const u8 = null,
|
||||
tax: []const u8,
|
||||
total: []const u8,
|
||||
};
|
||||
|
||||
const ShippingMethod = struct {
|
||||
id: []const u8,
|
||||
name: []const u8,
|
||||
time: []const u8,
|
||||
price: []const u8,
|
||||
};
|
||||
|
||||
const State = struct {
|
||||
code: []const u8,
|
||||
name: []const u8,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Sample Data
|
||||
// ============================================================================
|
||||
|
||||
const sample_products = [_]Product{
|
||||
.{
|
||||
.id = "1",
|
||||
.name = "Wireless Headphones",
|
||||
.price = "79.99",
|
||||
.image = "/images/headphones.jpg",
|
||||
.rating = 4,
|
||||
.category = "Electronics",
|
||||
.categorySlug = "electronics",
|
||||
.sale = true,
|
||||
.description = "Premium wireless headphones with noise cancellation",
|
||||
.reviewCount = "128",
|
||||
},
|
||||
.{
|
||||
.id = "2",
|
||||
.name = "Smart Watch Pro",
|
||||
.price = "199.99",
|
||||
.image = "/images/watch.jpg",
|
||||
.rating = 5,
|
||||
.category = "Electronics",
|
||||
.categorySlug = "electronics",
|
||||
.description = "Advanced fitness tracking and notifications",
|
||||
.reviewCount = "256",
|
||||
},
|
||||
.{
|
||||
.id = "3",
|
||||
.name = "Laptop Stand",
|
||||
.price = "49.99",
|
||||
.image = "/images/stand.jpg",
|
||||
.rating = 4,
|
||||
.category = "Accessories",
|
||||
.categorySlug = "accessories",
|
||||
.description = "Ergonomic aluminum laptop stand",
|
||||
.reviewCount = "89",
|
||||
},
|
||||
.{
|
||||
.id = "4",
|
||||
.name = "USB-C Hub",
|
||||
.price = "39.99",
|
||||
.image = "/images/hub.jpg",
|
||||
.rating = 4,
|
||||
.category = "Accessories",
|
||||
.categorySlug = "accessories",
|
||||
.sale = true,
|
||||
.description = "7-in-1 USB-C hub with HDMI and card reader",
|
||||
.reviewCount = "312",
|
||||
},
|
||||
.{
|
||||
.id = "5",
|
||||
.name = "Mechanical Keyboard",
|
||||
.price = "129.99",
|
||||
.image = "/images/keyboard.jpg",
|
||||
.rating = 5,
|
||||
.category = "Electronics",
|
||||
.categorySlug = "electronics",
|
||||
.description = "RGB mechanical keyboard with Cherry MX switches",
|
||||
.reviewCount = "445",
|
||||
},
|
||||
.{
|
||||
.id = "6",
|
||||
.name = "Desk Lamp",
|
||||
.price = "34.99",
|
||||
.image = "/images/lamp.jpg",
|
||||
.rating = 4,
|
||||
.category = "Home Office",
|
||||
.categorySlug = "home-office",
|
||||
.description = "LED desk lamp with adjustable brightness",
|
||||
.reviewCount = "67",
|
||||
},
|
||||
};
|
||||
|
||||
const sample_categories = [_]Category{
|
||||
.{ .name = "Electronics", .slug = "electronics", .icon = "E", .count = "24" },
|
||||
.{ .name = "Accessories", .slug = "accessories", .icon = "A", .count = "18" },
|
||||
.{ .name = "Home Office", .slug = "home-office", .icon = "H", .count = "12" },
|
||||
.{ .name = "Clothing", .slug = "clothing", .icon = "C", .count = "36" },
|
||||
};
|
||||
|
||||
const sample_cart_items = [_]CartItem{
|
||||
.{
|
||||
.id = "1",
|
||||
.name = "Wireless Headphones",
|
||||
.price = "79.99",
|
||||
.image = "/images/headphones.jpg",
|
||||
.variant = "Black",
|
||||
.quantity = "1",
|
||||
.total = "79.99",
|
||||
},
|
||||
.{
|
||||
.id = "5",
|
||||
.name = "Mechanical Keyboard",
|
||||
.price = "129.99",
|
||||
.image = "/images/keyboard.jpg",
|
||||
.variant = "RGB",
|
||||
.quantity = "1",
|
||||
.total = "129.99",
|
||||
},
|
||||
};
|
||||
|
||||
const sample_cart = Cart{
|
||||
.items = &sample_cart_items,
|
||||
.subtotal = "209.98",
|
||||
.shipping = "0",
|
||||
.tax = "18.90",
|
||||
.total = "228.88",
|
||||
};
|
||||
|
||||
const shipping_methods = [_]ShippingMethod{
|
||||
.{ .id = "standard", .name = "Standard Shipping", .time = "5-7 business days", .price = "0" },
|
||||
.{ .id = "express", .name = "Express Shipping", .time = "2-3 business days", .price = "9.99" },
|
||||
.{ .id = "overnight", .name = "Overnight Shipping", .time = "Next business day", .price = "19.99" },
|
||||
};
|
||||
|
||||
const us_states = [_]State{
|
||||
.{ .code = "CA", .name = "California" },
|
||||
.{ .code = "NY", .name = "New York" },
|
||||
.{ .code = "TX", .name = "Texas" },
|
||||
.{ .code = "FL", .name = "Florida" },
|
||||
.{ .code = "WA", .name = "Washington" },
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Application
|
||||
// ============================================================================
|
||||
|
||||
const App = struct {
|
||||
allocator: Allocator,
|
||||
view: pugz.ViewEngine,
|
||||
|
||||
pub fn init(allocator: Allocator) !App {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.view = try pugz.ViewEngine.init(allocator, .{
|
||||
.views_dir = "views",
|
||||
.pretty = true,
|
||||
.max_cached_templates = 50,
|
||||
.cache_ttl_seconds = 10, // 10s TTL for development
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *App) void {
|
||||
self.view.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Request Handlers
|
||||
// ============================================================================
|
||||
|
||||
fn home(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
const html = app.view.render(res.arena, "pages/home", .{
|
||||
.title = "Home",
|
||||
.cartCount = "2",
|
||||
.authenticated = true,
|
||||
.items = &[_][]const u8{ "Wireless Headphones", "Smart Watch", "Laptop Stand", "USB-C Hub" },
|
||||
}) catch |err| {
|
||||
return renderError(res, err);
|
||||
};
|
||||
|
||||
res.content_type = .HTML;
|
||||
res.body = html;
|
||||
}
|
||||
|
||||
fn products(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
const html = app.view.render(res.arena, "pages/products", .{
|
||||
.title = "All Products",
|
||||
.cartCount = "2",
|
||||
.productCount = "6",
|
||||
}) catch |err| {
|
||||
return renderError(res, err);
|
||||
};
|
||||
|
||||
res.content_type = .HTML;
|
||||
res.body = html;
|
||||
}
|
||||
|
||||
fn productDetail(app: *App, req: *httpz.Request, res: *httpz.Response) !void {
|
||||
const id = req.param("id") orelse "1";
|
||||
_ = id;
|
||||
|
||||
const html = app.view.render(res.arena, "pages/product-detail", .{
|
||||
.cartCount = "2",
|
||||
.productName = "Wireless Headphones",
|
||||
.category = "Electronics",
|
||||
.price = "79.99",
|
||||
.description = "Premium wireless headphones with active noise cancellation. Experience crystal-clear audio whether you're working, traveling, or relaxing at home.",
|
||||
.sku = "WH-001-BLK",
|
||||
}) catch |err| {
|
||||
return renderError(res, err);
|
||||
};
|
||||
|
||||
res.content_type = .HTML;
|
||||
res.body = html;
|
||||
}
|
||||
|
||||
fn cart(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
const html = app.view.render(res.arena, "pages/cart", .{
|
||||
.title = "Shopping Cart",
|
||||
.cartCount = "2",
|
||||
.cartItems = &sample_cart_items,
|
||||
.subtotal = sample_cart.subtotal,
|
||||
.shipping = sample_cart.shipping,
|
||||
.tax = sample_cart.tax,
|
||||
.total = sample_cart.total,
|
||||
}) catch |err| {
|
||||
return renderError(res, err);
|
||||
};
|
||||
|
||||
res.content_type = .HTML;
|
||||
res.body = html;
|
||||
}
|
||||
|
||||
fn about(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
const html = app.view.render(res.arena, "pages/about", .{
|
||||
.title = "About",
|
||||
.cartCount = "2",
|
||||
}) catch |err| {
|
||||
return renderError(res, err);
|
||||
};
|
||||
|
||||
res.content_type = .HTML;
|
||||
res.body = html;
|
||||
}
|
||||
|
||||
fn notFound(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
res.status = 404;
|
||||
|
||||
const html = app.view.render(res.arena, "pages/404", .{
|
||||
.title = "Page Not Found",
|
||||
.cartCount = "2",
|
||||
}) catch |err| {
|
||||
return renderError(res, err);
|
||||
};
|
||||
|
||||
res.content_type = .HTML;
|
||||
res.body = html;
|
||||
}
|
||||
|
||||
fn renderError(res: *httpz.Response, err: anyerror) void {
|
||||
res.status = 500;
|
||||
res.content_type = .HTML;
|
||||
res.body = std.fmt.allocPrint(res.arena,
|
||||
\\<!DOCTYPE html>
|
||||
\\<html>
|
||||
\\<head><title>Error</title></head>
|
||||
\\<body>
|
||||
\\<h1>500 - Server Error</h1>
|
||||
\\<p>Error: {s}</p>
|
||||
\\</body>
|
||||
\\</html>
|
||||
, .{@errorName(err)}) catch "Internal Server Error";
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Static Files
|
||||
// ============================================================================
|
||||
|
||||
fn serveStatic(_: *App, req: *httpz.Request, res: *httpz.Response) !void {
|
||||
const path = req.url.path;
|
||||
|
||||
// Strip leading slash and prepend public folder
|
||||
const rel_path = if (path.len > 0 and path[0] == '/') path[1..] else path;
|
||||
const full_path = std.fmt.allocPrint(res.arena, "public/{s}", .{rel_path}) catch {
|
||||
res.status = 500;
|
||||
res.body = "Internal Server Error";
|
||||
return;
|
||||
};
|
||||
|
||||
// Read file from disk
|
||||
const content = std.fs.cwd().readFileAlloc(res.arena, full_path, 10 * 1024 * 1024) catch {
|
||||
res.status = 404;
|
||||
res.body = "Not Found";
|
||||
return;
|
||||
};
|
||||
|
||||
// Set content type based on extension
|
||||
if (std.mem.endsWith(u8, path, ".css")) {
|
||||
res.content_type = .CSS;
|
||||
} else if (std.mem.endsWith(u8, path, ".js")) {
|
||||
res.content_type = .JS;
|
||||
} else if (std.mem.endsWith(u8, path, ".html")) {
|
||||
res.content_type = .HTML;
|
||||
}
|
||||
|
||||
res.body = content;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main
|
||||
// ============================================================================
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer if (gpa.deinit() == .leak) @panic("leak");
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app = try App.init(allocator);
|
||||
defer app.deinit();
|
||||
|
||||
const port = 8081;
|
||||
var server = try httpz.Server(*App).init(allocator, .{ .port = port }, &app);
|
||||
defer server.deinit();
|
||||
|
||||
var router = try server.router(.{});
|
||||
|
||||
// Pages
|
||||
router.get("/", home, .{});
|
||||
router.get("/products", products, .{});
|
||||
router.get("/products/:id", productDetail, .{});
|
||||
router.get("/cart", cart, .{});
|
||||
router.get("/about", about, .{});
|
||||
|
||||
// Static files
|
||||
router.get("/css/*", serveStatic, .{});
|
||||
|
||||
std.debug.print(
|
||||
\\
|
||||
\\ ____ ____ _
|
||||
\\ | _ \ _ _ __ _ ____ / ___|| |_ ___ _ __ ___
|
||||
\\ | |_) | | | |/ _` |_ / \___ \| __/ _ \| '__/ _ \
|
||||
\\ | __/| |_| | (_| |/ / ___) | || (_) | | | __/
|
||||
\\ |_| \__,_|\__, /___| |____/ \__\___/|_| \___|
|
||||
\\ |___/
|
||||
\\
|
||||
\\ Server running at http://localhost:{d}
|
||||
\\
|
||||
\\ Routes:
|
||||
\\ GET / - Home page
|
||||
\\ GET /products - Products page
|
||||
\\ GET /products/:id - Product detail
|
||||
\\ GET /cart - Shopping cart
|
||||
\\ GET /about - About page
|
||||
\\
|
||||
\\ Press Ctrl+C to stop.
|
||||
\\
|
||||
, .{port});
|
||||
|
||||
try server.listen();
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
doctype html
|
||||
html(lang="en")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
link(rel="stylesheet" href="/css/style.css")
|
||||
block title
|
||||
title Pugz Store
|
||||
body
|
||||
header.header
|
||||
.container
|
||||
.header-content
|
||||
a.logo(href="/") Pugz Store
|
||||
nav.nav
|
||||
a.nav-link(href="/") Home
|
||||
a.nav-link(href="/products") Products
|
||||
a.nav-link(href="/about") About
|
||||
.header-actions
|
||||
a.cart-link(href="/cart")
|
||||
| Cart (#{cartCount})
|
||||
|
||||
main
|
||||
block content
|
||||
|
||||
footer.footer
|
||||
.container
|
||||
.footer-content
|
||||
p Built with Pugz - A Pug template engine for Zig
|
||||
@@ -1,12 +0,0 @@
|
||||
//- Alert/notification mixins
|
||||
|
||||
mixin alert(message, type)
|
||||
- var alertClass = type ? "alert alert-" + type : "alert alert-info"
|
||||
.alert(class=alertClass)
|
||||
p= message
|
||||
|
||||
mixin alert-dismissible(message, type)
|
||||
- var alertClass = type ? "alert alert-" + type : "alert alert-info"
|
||||
.alert.alert-dismissible(class=alertClass)
|
||||
p= message
|
||||
button.alert-close(type="button" aria-label="Close") x
|
||||
@@ -1,15 +0,0 @@
|
||||
//- Button mixins with various styles
|
||||
|
||||
mixin btn(text, type)
|
||||
- var btnClass = type ? "btn btn-" + type : "btn btn-primary"
|
||||
button(class=btnClass)= text
|
||||
|
||||
mixin btn-link(href, text, type)
|
||||
- var btnClass = type ? "btn btn-" + type : "btn btn-primary"
|
||||
a(href=href class=btnClass)= text
|
||||
|
||||
mixin btn-icon(icon, text, type)
|
||||
- var btnClass = type ? "btn btn-" + type : "btn btn-primary"
|
||||
button(class=btnClass)
|
||||
span.icon= icon
|
||||
span= text
|
||||
@@ -1,17 +0,0 @@
|
||||
//- Cart item display
|
||||
|
||||
mixin cart-item(item)
|
||||
.cart-item
|
||||
.cart-item-image
|
||||
img(src=item.image alt=item.name)
|
||||
.cart-item-details
|
||||
h4.cart-item-name #{item.name}
|
||||
p.cart-item-variant #{item.variant}
|
||||
span.cart-item-price $#{item.price}
|
||||
.cart-item-quantity
|
||||
button.qty-btn.qty-minus -
|
||||
input.qty-input(type="number" value=item.quantity min="1")
|
||||
button.qty-btn.qty-plus +
|
||||
.cart-item-total
|
||||
span $#{item.total}
|
||||
button.cart-item-remove(aria-label="Remove item") x
|
||||
@@ -1,25 +0,0 @@
|
||||
//- Form input mixins
|
||||
|
||||
mixin input(name, label, type, placeholder)
|
||||
.form-group
|
||||
label(for=name)= label
|
||||
input.form-control(type=type id=name name=name placeholder=placeholder)
|
||||
|
||||
mixin input-required(name, label, type, placeholder)
|
||||
.form-group
|
||||
label(for=name)
|
||||
= label
|
||||
span.required *
|
||||
input.form-control(type=type id=name name=name placeholder=placeholder required)
|
||||
|
||||
mixin select(name, label, options)
|
||||
.form-group
|
||||
label(for=name)= label
|
||||
select.form-control(id=name name=name)
|
||||
each opt in options
|
||||
option(value=opt.value)= opt.label
|
||||
|
||||
mixin textarea(name, label, placeholder, rows)
|
||||
.form-group
|
||||
label(for=name)= label
|
||||
textarea.form-control(id=name name=name placeholder=placeholder rows=rows)
|
||||
@@ -1,38 +0,0 @@
|
||||
//- Product card mixin - displays a product in grid/list view
|
||||
//- Parameters:
|
||||
//- product: { id, name, price, image, rating, category }
|
||||
|
||||
mixin product-card(product)
|
||||
article.product-card
|
||||
a.product-image(href="/products/" + product.id)
|
||||
img(src=product.image alt=product.name)
|
||||
if product.sale
|
||||
span.badge.badge-sale Sale
|
||||
.product-info
|
||||
span.product-category #{product.category}
|
||||
h3.product-name
|
||||
a(href="/products/" + product.id) #{product.name}
|
||||
.product-rating
|
||||
+rating(product.rating)
|
||||
.product-footer
|
||||
span.product-price $#{product.price}
|
||||
button.btn.btn-primary.btn-sm(data-product=product.id) Add to Cart
|
||||
|
||||
//- Featured product card with larger display
|
||||
mixin product-featured(product)
|
||||
article.product-card.product-featured
|
||||
.product-image-large
|
||||
img(src=product.image alt=product.name)
|
||||
if product.sale
|
||||
span.badge.badge-sale Sale
|
||||
.product-details
|
||||
span.product-category #{product.category}
|
||||
h2.product-name #{product.name}
|
||||
p.product-description #{product.description}
|
||||
.product-rating
|
||||
+rating(product.rating)
|
||||
span.review-count (#{product.reviewCount} reviews)
|
||||
.product-price-large $#{product.price}
|
||||
.product-actions
|
||||
button.btn.btn-primary.btn-lg Add to Cart
|
||||
button.btn.btn-outline Wishlist
|
||||
@@ -1,13 +0,0 @@
|
||||
//- Star rating display
|
||||
//- Parameters:
|
||||
//- stars: number of stars (1-5)
|
||||
|
||||
mixin rating(stars)
|
||||
.stars
|
||||
- var i = 1
|
||||
while i <= 5
|
||||
if i <= stars
|
||||
span.star.star-filled
|
||||
else
|
||||
span.star.star-empty
|
||||
- i = i + 1
|
||||
@@ -1,15 +0,0 @@
|
||||
extends layouts/base.pug
|
||||
|
||||
block title
|
||||
title #{title} | Pugz Store
|
||||
|
||||
block content
|
||||
section.error-page
|
||||
.container
|
||||
.error-content
|
||||
h1.error-code 404
|
||||
h2 Page Not Found
|
||||
p The page you are looking for does not exist or has been moved.
|
||||
.error-actions
|
||||
a.btn.btn-primary(href="/") Go Home
|
||||
a.btn.btn-outline(href="/products") View Products
|
||||
@@ -1,51 +0,0 @@
|
||||
extends layouts/base.pug
|
||||
|
||||
block title
|
||||
title #{title} | Pugz Store
|
||||
|
||||
block content
|
||||
section.page-header
|
||||
.container
|
||||
h1 About Pugz
|
||||
p A Pug template engine written in Zig
|
||||
|
||||
section.section
|
||||
.container
|
||||
.about-grid
|
||||
.about-main
|
||||
h2 What is Pugz?
|
||||
p Pugz is a high-performance Pug template engine implemented in Zig. It provides both runtime interpretation and build-time compilation for maximum flexibility.
|
||||
|
||||
h3 Key Features
|
||||
ul.feature-list
|
||||
li Template inheritance with extends and blocks
|
||||
li Partial includes for modular templates
|
||||
li Mixins for reusable components
|
||||
li Conditionals (if/else/unless)
|
||||
li Iteration with each loops
|
||||
li Variable interpolation
|
||||
li Pretty-printed output
|
||||
li LRU caching with TTL
|
||||
|
||||
h3 Performance
|
||||
p Compiled templates run approximately 3x faster than Pug.js, with zero runtime parsing overhead.
|
||||
|
||||
.about-sidebar
|
||||
.info-card
|
||||
h3 This Demo Shows
|
||||
ul
|
||||
li Template inheritance (extends)
|
||||
li Named blocks
|
||||
li Conditional rendering
|
||||
li Variable interpolation
|
||||
li Simple iteration
|
||||
|
||||
.info-card
|
||||
h3 Links
|
||||
ul
|
||||
li
|
||||
a(href="https://github.com/ankitpatial/pugz") GitHub Repository
|
||||
li
|
||||
a(href="/products") View Products
|
||||
li
|
||||
a(href="/") Back to Home
|
||||
@@ -1,47 +0,0 @@
|
||||
extends layouts/base.pug
|
||||
|
||||
block title
|
||||
title #{title} | Pugz Store
|
||||
|
||||
block content
|
||||
section.page-header
|
||||
.container
|
||||
h1 Shopping Cart
|
||||
p Review your items before checkout
|
||||
|
||||
section.section
|
||||
.container
|
||||
.cart-layout
|
||||
.cart-main
|
||||
.cart-items
|
||||
each item in cartItems
|
||||
.cart-item
|
||||
.cart-item-info
|
||||
h3 #{name}
|
||||
p.text-muted #{variant}
|
||||
span.cart-item-price $#{price}
|
||||
.cart-item-qty
|
||||
button.qty-btn -
|
||||
input.qty-input(type="text" value=quantity)
|
||||
button.qty-btn +
|
||||
.cart-item-total $#{total}
|
||||
button.cart-item-remove x
|
||||
|
||||
.cart-actions
|
||||
a.btn.btn-outline(href="/products") Continue Shopping
|
||||
|
||||
.cart-summary
|
||||
h3 Order Summary
|
||||
.summary-row
|
||||
span Subtotal
|
||||
span $#{subtotal}
|
||||
.summary-row
|
||||
span Shipping
|
||||
span.text-success Free
|
||||
.summary-row
|
||||
span Tax
|
||||
span $#{tax}
|
||||
.summary-row.summary-total
|
||||
span Total
|
||||
span $#{total}
|
||||
a.btn.btn-primary.btn-block(href="/checkout") Proceed to Checkout
|
||||
@@ -1,144 +0,0 @@
|
||||
extends layouts/base.pug
|
||||
include mixins/forms.pug
|
||||
include mixins/alerts.pug
|
||||
include mixins/buttons.pug
|
||||
|
||||
block content
|
||||
h1 Checkout
|
||||
|
||||
if errors
|
||||
+alert("Please correct the errors below", "error")
|
||||
|
||||
.checkout-layout
|
||||
form.checkout-form(action="/checkout" method="POST")
|
||||
//- Shipping Information
|
||||
section.checkout-section
|
||||
h2 Shipping Information
|
||||
|
||||
.form-row
|
||||
+input-required("firstName", "First Name", "text", "John")
|
||||
+input-required("lastName", "Last Name", "text", "Doe")
|
||||
|
||||
+input-required("email", "Email Address", "email", "john@example.com")
|
||||
+input-required("phone", "Phone Number", "tel", "+1 (555) 123-4567")
|
||||
|
||||
+input-required("address", "Street Address", "text", "123 Main St")
|
||||
+input("address2", "Apartment, suite, etc.", "text", "Apt 4B")
|
||||
|
||||
.form-row
|
||||
+input-required("city", "City", "text", "New York")
|
||||
.form-group
|
||||
label(for="state")
|
||||
| State
|
||||
span.required *
|
||||
select.form-control#state(name="state" required)
|
||||
option(value="") Select State
|
||||
each state in states
|
||||
option(value=state.code)= state.name
|
||||
+input-required("zip", "ZIP Code", "text", "10001")
|
||||
|
||||
.form-group
|
||||
label(for="country")
|
||||
| Country
|
||||
span.required *
|
||||
select.form-control#country(name="country" required)
|
||||
option(value="US" selected) United States
|
||||
option(value="CA") Canada
|
||||
|
||||
//- Shipping Method
|
||||
section.checkout-section
|
||||
h2 Shipping Method
|
||||
|
||||
.shipping-options
|
||||
each method in shippingMethods
|
||||
label.shipping-option
|
||||
input(type="radio" name="shipping" value=method.id checked=method.id == "standard")
|
||||
.shipping-info
|
||||
span.shipping-name #{method.name}
|
||||
span.shipping-time #{method.time}
|
||||
span.shipping-price
|
||||
if method.price > 0
|
||||
| $#{method.price}
|
||||
else
|
||||
| Free
|
||||
|
||||
//- Payment Information
|
||||
section.checkout-section
|
||||
h2 Payment Information
|
||||
|
||||
.payment-methods-select
|
||||
label.payment-method
|
||||
input(type="radio" name="paymentMethod" value="card" checked)
|
||||
span Credit/Debit Card
|
||||
label.payment-method
|
||||
input(type="radio" name="paymentMethod" value="paypal")
|
||||
span PayPal
|
||||
|
||||
.card-details(id="card-details")
|
||||
+input-required("cardNumber", "Card Number", "text", "1234 5678 9012 3456")
|
||||
|
||||
.form-row
|
||||
+input-required("expiry", "Expiration Date", "text", "MM/YY")
|
||||
+input-required("cvv", "CVV", "text", "123")
|
||||
|
||||
+input-required("cardName", "Name on Card", "text", "John Doe")
|
||||
|
||||
.form-group
|
||||
label.checkbox-label
|
||||
input(type="checkbox" name="saveCard")
|
||||
span Save card for future purchases
|
||||
|
||||
//- Billing Address
|
||||
section.checkout-section
|
||||
.form-group
|
||||
label.checkbox-label
|
||||
input(type="checkbox" name="sameAsShipping" checked)
|
||||
span Billing address same as shipping
|
||||
|
||||
.billing-address(id="billing-address" style="display: none")
|
||||
+input-required("billingAddress", "Street Address", "text", "")
|
||||
.form-row
|
||||
+input-required("billingCity", "City", "text", "")
|
||||
+input-required("billingState", "State", "text", "")
|
||||
+input-required("billingZip", "ZIP Code", "text", "")
|
||||
|
||||
button.btn.btn-primary.btn-lg(type="submit") Place Order
|
||||
|
||||
//- Order Summary Sidebar
|
||||
aside.order-summary
|
||||
h3 Order Summary
|
||||
|
||||
.summary-items
|
||||
each item in cart.items
|
||||
.summary-item
|
||||
img(src=item.image alt=item.name)
|
||||
.item-info
|
||||
span.item-name #{item.name}
|
||||
span.item-qty x#{item.quantity}
|
||||
span.item-price $#{item.total}
|
||||
|
||||
.summary-details
|
||||
.summary-row
|
||||
span Subtotal
|
||||
span $#{cart.subtotal}
|
||||
|
||||
if cart.discount
|
||||
.summary-row.discount
|
||||
span Discount
|
||||
span -$#{cart.discount}
|
||||
|
||||
.summary-row
|
||||
span Shipping
|
||||
span#shipping-cost $#{selectedShipping.price}
|
||||
|
||||
.summary-row
|
||||
span Tax
|
||||
span $#{cart.tax}
|
||||
|
||||
.summary-row.total
|
||||
span Total
|
||||
span $#{cart.total}
|
||||
|
||||
.secure-checkout
|
||||
span Secure Checkout
|
||||
p Your information is protected with 256-bit SSL encryption
|
||||
@@ -1,56 +0,0 @@
|
||||
extends layouts/base.pug
|
||||
|
||||
block title
|
||||
title #{title} | Pugz Store
|
||||
|
||||
block content
|
||||
section.hero
|
||||
.container
|
||||
h1 Welcome to Pugz Store
|
||||
p Discover amazing products powered by Zig
|
||||
.hero-actions
|
||||
a.btn.btn-primary(href="/products") Shop Now
|
||||
a.btn.btn-outline(href="/about") Learn More
|
||||
|
||||
section.section
|
||||
.container
|
||||
h2 Template Features
|
||||
.feature-grid
|
||||
.feature-card
|
||||
h3 Conditionals
|
||||
if authenticated
|
||||
p.text-success You are logged in!
|
||||
else
|
||||
p.text-muted Please log in to continue.
|
||||
|
||||
.feature-card
|
||||
h3 Variables
|
||||
p Title: #{title}
|
||||
p Cart Items: #{cartCount}
|
||||
|
||||
.feature-card
|
||||
h3 Iteration
|
||||
ul
|
||||
each item in items
|
||||
li= item
|
||||
|
||||
.feature-card
|
||||
h3 Clean Syntax
|
||||
p Pug templates compile to HTML with minimal overhead.
|
||||
|
||||
section.section.section-alt
|
||||
.container
|
||||
h2 Shop by Category
|
||||
.category-grid
|
||||
a.category-card(href="/products?cat=electronics")
|
||||
.category-icon E
|
||||
h3 Electronics
|
||||
span 24 products
|
||||
a.category-card(href="/products?cat=accessories")
|
||||
.category-icon A
|
||||
h3 Accessories
|
||||
span 18 products
|
||||
a.category-card(href="/products?cat=home")
|
||||
.category-icon H
|
||||
h3 Home Office
|
||||
span 12 products
|
||||
@@ -1,65 +0,0 @@
|
||||
extends layouts/base.pug
|
||||
|
||||
block title
|
||||
title #{productName} | Pugz Store
|
||||
|
||||
block content
|
||||
section.page-header
|
||||
.container
|
||||
.breadcrumb
|
||||
a(href="/") Home
|
||||
span /
|
||||
a(href="/products") Products
|
||||
span /
|
||||
span #{productName}
|
||||
|
||||
section.section
|
||||
.container
|
||||
.product-detail
|
||||
.product-detail-image
|
||||
.product-image-placeholder
|
||||
.product-detail-info
|
||||
span.product-category #{category}
|
||||
h1 #{productName}
|
||||
.product-price-large $#{price}
|
||||
p.product-description #{description}
|
||||
|
||||
.product-actions
|
||||
.quantity-selector
|
||||
label Quantity:
|
||||
button.qty-btn -
|
||||
input.qty-input(type="text" value="1")
|
||||
button.qty-btn +
|
||||
a.btn.btn-primary.btn-lg(href="/cart") Add to Cart
|
||||
|
||||
.product-meta
|
||||
p SKU: #{sku}
|
||||
p Category: #{category}
|
||||
|
||||
section.section.section-alt
|
||||
.container
|
||||
h2 You May Also Like
|
||||
.product-grid
|
||||
.product-card
|
||||
.product-image
|
||||
.product-info
|
||||
span.product-category Electronics
|
||||
h3.product-name Smart Watch Pro
|
||||
.product-price $199.99
|
||||
a.btn.btn-sm(href="/products/2") View Details
|
||||
|
||||
.product-card
|
||||
.product-image
|
||||
.product-info
|
||||
span.product-category Accessories
|
||||
h3.product-name Laptop Stand
|
||||
.product-price $49.99
|
||||
a.btn.btn-sm(href="/products/3") View Details
|
||||
|
||||
.product-card
|
||||
.product-image
|
||||
.product-info
|
||||
span.product-category Accessories
|
||||
h3.product-name USB-C Hub
|
||||
.product-price $39.99
|
||||
a.btn.btn-sm(href="/products/4") View Details
|
||||
@@ -1,79 +0,0 @@
|
||||
extends layouts/base.pug
|
||||
|
||||
block title
|
||||
title #{title} | Pugz Store
|
||||
|
||||
block content
|
||||
section.page-header
|
||||
.container
|
||||
h1 All Products
|
||||
p Browse our selection of quality products
|
||||
|
||||
section.section
|
||||
.container
|
||||
.products-toolbar
|
||||
span.results-count #{productCount} products
|
||||
.sort-options
|
||||
label Sort by:
|
||||
select
|
||||
option(value="featured") Featured
|
||||
option(value="price-low") Price: Low to High
|
||||
option(value="price-high") Price: High to Low
|
||||
|
||||
.product-grid
|
||||
.product-card
|
||||
.product-image
|
||||
.product-badge Sale
|
||||
.product-info
|
||||
span.product-category Electronics
|
||||
h3.product-name Wireless Headphones
|
||||
.product-price $79.99
|
||||
a.btn.btn-sm(href="/products/1") View Details
|
||||
|
||||
.product-card
|
||||
.product-image
|
||||
.product-info
|
||||
span.product-category Electronics
|
||||
h3.product-name Smart Watch Pro
|
||||
.product-price $199.99
|
||||
a.btn.btn-sm(href="/products/2") View Details
|
||||
|
||||
.product-card
|
||||
.product-image
|
||||
.product-info
|
||||
span.product-category Accessories
|
||||
h3.product-name Laptop Stand
|
||||
.product-price $49.99
|
||||
a.btn.btn-sm(href="/products/3") View Details
|
||||
|
||||
.product-card
|
||||
.product-image
|
||||
.product-badge Sale
|
||||
.product-info
|
||||
span.product-category Accessories
|
||||
h3.product-name USB-C Hub
|
||||
.product-price $39.99
|
||||
a.btn.btn-sm(href="/products/4") View Details
|
||||
|
||||
.product-card
|
||||
.product-image
|
||||
.product-info
|
||||
span.product-category Electronics
|
||||
h3.product-name Mechanical Keyboard
|
||||
.product-price $129.99
|
||||
a.btn.btn-sm(href="/products/5") View Details
|
||||
|
||||
.product-card
|
||||
.product-image
|
||||
.product-info
|
||||
span.product-category Home Office
|
||||
h3.product-name Desk Lamp
|
||||
.product-price $34.99
|
||||
a.btn.btn-sm(href="/products/6") View Details
|
||||
|
||||
.pagination
|
||||
a.page-link(href="#") Prev
|
||||
a.page-link.active(href="#") 1
|
||||
a.page-link(href="#") 2
|
||||
a.page-link(href="#") 3
|
||||
a.page-link(href="#") Next
|
||||
@@ -1,4 +0,0 @@
|
||||
footer.footer
|
||||
.container
|
||||
.footer-content
|
||||
p Built with Pugz - A Pug template engine for Zig
|
||||
@@ -1,3 +0,0 @@
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
link(rel="stylesheet" href="/css/style.css")
|
||||
@@ -1,11 +0,0 @@
|
||||
header.header
|
||||
.container
|
||||
.header-content
|
||||
a.logo(href="/") Pugz Store
|
||||
nav.nav
|
||||
a.nav-link(href="/") Home
|
||||
a.nav-link(href="/products") Products
|
||||
a.nav-link(href="/about") About
|
||||
.header-actions
|
||||
a.cart-link(href="/cart")
|
||||
| Cart (#{cartCount})
|
||||
Reference in New Issue
Block a user