first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:33:39 +03:00
commit 4362c3b83f
1991 changed files with 285411 additions and 0 deletions

442
views/admin/layout.html Normal file
View File

@@ -0,0 +1,442 @@
<!DOCTYPE html>
<html lang="tr" data-bs-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Paneli</title>
<!-- Bootstrap CSS -->
<link href="/assets/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<!-- Admin Theme CSS -->
<link href="/admin/css/theme.css" rel="stylesheet">
<!-- HTMX -->
<script src="/assets/htmx/htmx.js"></script>
<!-- jQuery -->
<script src="/assets/jquery/dist/jquery.min.js"></script>
<!-- Alpine.js -->
<script defer src="/assets/alpinejs/dist/cdn.min.js"></script>
<!-- SweetAlert2 -->
<link rel="stylesheet" href="/assets/sweetalert2/dist/sweetalert2.min.css">
<script src="/assets/sweetalert2/dist/sweetalert2.all.min.js"></script>
<!-- Main JS -->
<script src="/admin/js/main.js"></script>
</head>
<body x-data>
<!-- Sidebar -->
<nav id="sidebar" :class="{ 'show': $store.sidebar.open }">
<div class="sidebar-header">
<h5 class="mb-0">AdminPanel</h5>
</div>
<div class="sidebar-nav list-group list-group-flush mt-3">
<a href="#" class="nav-link active" hx-get="/admin/content/dashboard" hx-target="#main-content"
hx-push-url="true">
<i class="bi bi-speedometer2 me-2"></i> Dashboard
</a>
<a href="#" class="nav-link" hx-get="/admin/content/users" hx-target="#main-content" hx-push-url="true">
<i class="bi bi-people me-2"></i> Kullanıcılar
</a>
<div class="nav-item">
<a class="nav-link collapsed" data-bs-toggle="collapse" href="#blogMenu" role="button"
aria-expanded="false" aria-controls="blogMenu">
<i class="bi bi-journal-text me-2"></i> Blog
</a>
<div class="collapse" id="blogMenu">
<div class="list-group list-group-flush ms-3">
<a href="#" class="list-group-item list-group-item-action" hx-get="/admin/content/posts"
hx-target="#main-content" hx-push-url="true">Yazılar</a>
<a href="#" class="list-group-item list-group-item-action" hx-get="/admin/content/categories"
hx-target="#main-content" hx-push-url="true">Kategoriler</a>
<a href="#" class="list-group-item list-group-item-action" hx-get="/admin/content/tags"
hx-target="#main-content" hx-push-url="true">Taglar</a>
<a href="#" class="list-group-item list-group-item-action"
hx-get="/admin/content/category-views" hx-target="#main-content" hx-push-url="true">Kategori
Görünümleri</a>
<a href="#" class="list-group-item list-group-item-action" hx-get="/admin/content/comments"
hx-target="#main-content" hx-push-url="true">Yorumlar</a>
</div>
</div>
</div>
<div class="nav-item">
<a class="nav-link collapsed" data-bs-toggle="collapse" href="#productMenu" role="button"
aria-expanded="false" aria-controls="productMenu">
<i class="bi bi-box me-2"></i> Ürünler
</a>
<div class="collapse" id="productMenu">
<div class="list-group list-group-flush ms-3">
<a href="#" class="list-group-item list-group-item-action" hx-get="/admin/content/products"
hx-target="#main-content" hx-push-url="true">Ürünler</a>
<a href="#" class="list-group-item list-group-item-action"
hx-get="/admin/content/product-categories" hx-target="#main-content"
hx-push-url="true">Kategoriler</a>
<a href="#" class="list-group-item list-group-item-action" hx-get="/admin/content/product-tags"
hx-target="#main-content" hx-push-url="true">Etiketler</a>
<!-- Sepetler (Carts) -->
<a href="#" class="list-group-item list-group-item-action border-top mt-2"
hx-get="/admin/content/carts" hx-target="#main-content" hx-push-url="true">
<i class="bi bi-cart3 me-1"></i> Sepetler
</a>
<!-- Ürün İçgörüleri -->
<a href="#" class="list-group-item list-group-item-action"
hx-get="/admin/content/product-comments" hx-target="#main-content" hx-push-url="true">
<i class="bi bi-chat-left-text me-1"></i> Yorumlar
</a>
<a href="#" class="list-group-item list-group-item-action"
hx-get="/admin/content/product-category-views" hx-target="#main-content" hx-push-url="true">
<i class="bi bi-eye me-1"></i> Görüntülenmeler
</a>
</div>
</div>
</div>
<a href="#" class="nav-link" hx-get="/admin/content/settings" hx-target="#main-content" hx-push-url="true">
<i class="bi bi-gear me-2"></i> Ayarlar
</a>
<a href="/logout" class="nav-link text-danger mt-5">
<i class="bi bi-box-arrow-right me-2"></i> Çıkış
</a>
</div>
</nav>
<!-- Header -->
<header id="main-header">
<button class="btn btn-link link-body-emphasis d-lg-none me-3" @click="$store.sidebar.toggle()">
<i class="bi bi-list fs-4"></i>
</button>
<h5 class="mb-0 d-none d-lg-block">Dashboard</h5>
<div class="ms-auto d-flex align-items-center gap-3">
<!-- Theme Toggle -->
<button class="btn btn-link nav-link" @click="$store.theme.toggle()">
<i class="bi" :class="$store.theme.mode === 'light' ? 'bi-moon-stars' : 'bi-sun'"></i>
<span class="d-none d-md-inline ms-1"
x-text="$store.theme.mode === 'light' ? 'Koyu Mod' : 'Aydınlık Mod'"></span>
</button>
<!-- User Dropdown (Optional) -->
<div class="dropdown">
<a href="#" class="d-flex align-items-center link-body-emphasis text-decoration-none dropdown-toggle"
data-bs-toggle="dropdown" aria-expanded="false">
<img id="adminAvatar" src="https://github.com/mdo.png" alt="" width="32" height="32"
class="rounded-circle me-2">
<strong id="adminName">Admin</strong>
</a>
<ul class="dropdown-menu dropdown-menu-end text-small shadow">
<li><a class="dropdown-item" href="#">Profil</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item" href="/logout">Çıkış Yap</a></li>
</ul>
</div>
</div>
</header>
<!-- Main Content -->
<div id="main-content-wrapper">
<main id="main-content" class="p-4 fade-in">
{{embed}}
</main>
</div>
<!-- Global Modal Container for HTMX -->
<div class="modal fade" id="userModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content" id="modal-area">
<!-- Modal Content will be loaded here via HTMX -->
</div>
</div>
</div>
<!-- Bootstrap Bundle with Popper -->
<script src="/assets/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('htmx:afterSwap', function (event) {
// Only handle modal area swaps
if (event.detail.target.id === 'modal-area') {
// If the response is not empty (i.e., form content or error), show the modal
if (event.detail.target.innerHTML.trim() !== "") {
const el = document.getElementById('userModal');
let modal = bootstrap.Modal.getInstance(el);
if (!modal) {
modal = new bootstrap.Modal(el);
}
modal.show();
}
}
});
// Initialize Quill if injected via HTMX into any target
document.addEventListener('htmx:afterSwap', function (event) {
try {
var target = event.detail.target;
if (!target || !target.querySelector) return;
var editorEl = target.querySelector('#quillEditor');
if (!editorEl) return;
function doInit() {
if (editorEl.__quillInitialized) return;
var textarea = target.querySelector('#contentInput');
var quill = new Quill(editorEl, {
theme: 'snow',
modules: {
toolbar: {
container: [['bold', 'italic', 'underline'], [{ 'list': 'ordered' }, { 'list': 'bullet' }], ['link', 'image'], ['code-block'], ['clean']],
handlers: {
image: function () {
try { window.showImageModal(quill); } catch (e) { console.error('open image modal failed', e); }
}
}
}
}
});
editorEl.__quillInitialized = true;
if (textarea && textarea.value && textarea.value.trim() !== '') {
quill.clipboard.dangerouslyPasteHTML(textarea.value);
}
if (textarea && textarea.form) {
textarea.form.addEventListener('submit', function () { textarea.value = quill.root.innerHTML; });
}
}
if (window.Quill) {
doInit();
} else {
// load CSS if missing
if (!document.querySelector('link[href="/assets/quill/dist/quill.snow.css"]')) {
var l = document.createElement('link'); l.rel = 'stylesheet'; l.href = '/assets/quill/dist/quill.snow.css'; document.head.appendChild(l);
}
if (!document.querySelector('script[src="/assets/quill/dist/quill.js"]')) {
var s = document.createElement('script'); s.src = '/assets/quill/dist/quill.js'; s.onload = doInit; document.body.appendChild(s);
} else {
// script already present, try init shortly
setTimeout(doInit, 50);
}
}
} catch (e) {
console.error('Quill HTMX init error', e);
}
});
// Global SweetAlert2 Helpers
const Toast = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
didOpen: (toast) => {
toast.addEventListener('mouseenter', Swal.stopTimer)
toast.addEventListener('mouseleave', Swal.resumeTimer)
}
});
function showToast(icon, title) {
Toast.fire({
icon: icon,
title: title
});
}
function confirmDelete(event, form) {
event.preventDefault();
Swal.fire({
title: 'Emin misiniz?',
text: "Bu işlem geri alınamaz!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Evet, Sil!',
cancelButtonText: 'İptal'
}).then((result) => {
if (result.isConfirmed) {
form.submit();
}
});
return false;
}
function confirmRestore(event, form) {
event.preventDefault();
Swal.fire({
title: 'Geri Yükle?',
text: "Bu kullanıcı tekrar aktif edilecek.",
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#28a745',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Evet, Geri Yükle!',
cancelButtonText: 'İptal'
}).then((result) => {
if (result.isConfirmed) {
form.submit();
}
});
return false;
}
// Check for success/error parameters in URL
document.addEventListener('DOMContentLoaded', () => {
const urlParams = new URLSearchParams(window.location.search);
const successMsg = urlParams.get('success');
const errorMsg = urlParams.get('error');
if (successMsg) {
showToast('success', successMsg);
// Clean URL
window.history.replaceState({}, document.title, window.location.pathname);
}
if (errorMsg) {
showToast('error', errorMsg);
window.history.replaceState({}, document.title, window.location.pathname);
}
});
// Listen for the closeModal event triggered by HX-Trigger
document.addEventListener('closeModal', function () {
const el = document.getElementById('userModal');
const modal = bootstrap.Modal.getInstance(el);
if (modal) {
modal.hide();
// Clear content after hiding
setTimeout(() => {
document.getElementById('modal-area').innerHTML = "";
}, 500);
}
});
</script>
<script>
// Populate admin dropdown with real name/email/avatar
document.addEventListener('DOMContentLoaded', function () {
fetch('/admin/me', { credentials: 'same-origin' })
.then(function (res) { if (!res.ok) throw new Error('no'); return res.json(); })
.then(function (data) {
if (!data) return;
try {
var nameEl = document.getElementById('adminName');
var avatarEl = document.getElementById('adminAvatar');
if (nameEl) nameEl.textContent = data.name || data.email || 'Admin';
if (avatarEl && data.avatar) avatarEl.src = data.avatar;
} catch (e) { }
}).catch(function () { /* ignore */ });
});
</script>
<style>
/* Ensure editor images behave */
.ql-figure img {
display: block;
margin: auto;
max-width: 100%;
height: auto;
}
</style>
<!-- Image Modal (global for admin) -->
<div class="modal fade" id="imageModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Resim Ekle</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Kapat"></button>
</div>
<div class="modal-body">
<div class="mb-2">
<label class="form-label">Resim URL</label>
<input id="imageUrlInput" class="form-control" placeholder="https://...">
</div>
<div class="mb-2 row g-2">
<div class="col-6"><input id="imageWidthInput" class="form-control" placeholder="Genişlik (px)">
</div>
<div class="col-6"><input id="imageHeightInput" class="form-control"
placeholder="Yükseklik (px)"></div>
</div>
<div class="mb-2">
<img id="imagePreviewModal" src="" alt="preview"
style="display:none;max-width:100%;border:1px solid #ddd;padding:4px;">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">İptal</button>
<button id="imageInsertBtn" type="button" class="btn btn-primary">Ekle</button>
</div>
</div>
</div>
</div>
<script>
// Register a block embed blot to hold images so code-block doesn't swallow them
(function registerFigureBlot() {
if (!window.Quill) return;
try {
var BlockEmbed = Quill.import('blots/block/embed');
class FigureBlot extends BlockEmbed {
static blotName = 'figure';
static tagName = 'figure';
static className = 'ql-figure';
static create(value) {
let node = super.create();
let img = document.createElement('img');
img.setAttribute('src', value.src);
if (value.style) img.style.cssText = value.style;
node.appendChild(img);
return node;
}
static value(node) {
let img = node.querySelector('img');
return { src: img.getAttribute('src'), style: img.style.cssText };
}
}
Quill.register(FigureBlot);
} catch (e) {
// ignore if already registered or Quill not ready
console.debug('FigureBlot register failed', e);
}
})();
// Show image modal and insert into provided quill instance
window.showImageModal = function (quill) {
if (!quill) return;
const modalEl = document.getElementById('imageModal');
const urlInput = document.getElementById('imageUrlInput');
const wInput = document.getElementById('imageWidthInput');
const hInput = document.getElementById('imageHeightInput');
const preview = document.getElementById('imagePreviewModal');
urlInput.value = '';
wInput.value = '';
hInput.value = '';
preview.style.display = 'none';
urlInput.addEventListener('input', function onChange() {
const v = urlInput.value.trim();
if (v) { preview.src = v; preview.style.display = 'block'; } else { preview.style.display = 'none'; }
});
var bsModal = new bootstrap.Modal(modalEl);
bsModal.show();
const insertBtn = document.getElementById('imageInsertBtn');
const handler = function () {
let src = urlInput.value.trim();
if (!src) return alert('URL girin');
if (src.charAt(0) === '/') src = window.location.origin + src;
let w = parseInt(wInput.value) || 0;
let h = parseInt(hInput.value) || 0;
let style = 'max-width:100%;height:auto;';
if (w) style = 'width:' + w + 'px;' + (h ? 'height:' + h + 'px;' : 'height:auto;');
if (h && !w) style = 'height:' + h + 'px;max-width:100%;';
var range = quill.getSelection(true) || { index: quill.getLength() };
quill.insertEmbed(range.index, 'figure', { src: src, style: style });
bsModal.hide();
insertBtn.removeEventListener('click', handler);
};
insertBtn.addEventListener('click', handler);
};
</script>
</body>
</html>

66
views/admin/login.html Normal file
View File

@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="tr" data-bs-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Giriş</title>
<link href="/assets/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="/admin/css/theme.css" rel="stylesheet">
<script src="/assets/htmx/htmx.js"></script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<style>
body,
html {
height: 100%;
}
.login-container {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background-color: var(--bg-body);
}
.login-card {
width: 100%;
max-width: 400px;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
background-color: var(--bg-sidebar);
border: 1px solid var(--border-color);
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-card">
<h3 class="text-center mb-4">Admin Giriş</h3>
<form action="/login" method="POST" hx-post="/login" hx-target="#login-feedback" hx-swap="innerHTML">
<div class="mb-3">
<label for="email" class="form-label">E-posta</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Şifre</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<!-- Cloudflare Turnstile -->
<div class="cf-turnstile" data-sitekey="0x4AAAAAACHzHKvlEwMamxCM"></div>
<div id="login-feedback" class="mb-3 text-danger"></div>
<button type="submit" class="btn btn-primary w-100">Giriş Yap</button>
</form>
<div class="mt-3 text-center">
<a href="/" class="text-decoration-none text-muted">Siteye Dön</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,75 @@
<div class="container-fluid">
<h2>{{ if .IsEdit }}Kategori Düzenle{{ else }}Yeni Kategori{{ end }}</h2>
<div class="card border-0 shadow-sm">
<div class="card-body">
{{ if .IsEdit }}
<form action="/admin/categories/{{ .Category.ID }}/update" method="POST">
{{ else }}
<form action="/admin/categories/create" method="POST">
{{ end }}
<div class="mb-3">
<label class="form-label">Başlık</label>
<input id="category-title" type="text" name="title" class="form-control" value="{{ if .IsEdit }}{{ .Category.Title }}{{ end }}" required>
</div>
<div class="mb-3">
<label class="form-label">Slug</label>
<div class="input-group">
<input id="category-slug" type="text" name="slug" class="form-control" value="{{ if .IsEdit }}{{ .Category.Slug }}{{ end }}" required>
<button id="reset-slug" type="button" class="btn btn-outline-secondary">Oluştur</button>
</div>
<div class="form-text">Slug otomatik oluşturulur; düzenleyebilirsiniz.</div>
</div>
<div class="mb-3">
<label class="form-label">ıklama</label>
<textarea name="description" class="form-control">{{ if .IsEdit }}{{ .Category.Description }}{{ end }}</textarea>
</div>
<div class="mb-3">
<label class="form-label">Parent</label>
<select name="parent_id" class="form-select">
<option value="">-- Yok --</option>
{{ range .Parents }}
<option value="{{ .ID }}" {{ if $.IsEdit }}{{ if eq $.ParentID .ID }}selected{{ end }}{{ end }}>{{ .Title }}</option>
{{ end }}
</select>
</div>
<div class="d-flex gap-2">
<a href="/admin/content/categories" class="btn btn-secondary">Geri</a>
<button class="btn btn-primary" type="submit">Kaydet</button>
</div>
</form>
</div>
</div>
</div>
<script>
(function(){
function slugify(s){
if(!s) return '';
s = s.trim();
// Turkish char replacements
s = s.replace(/ç/g,'c').replace(/Ç/g,'c')
.replace(/ğ/g,'g').replace(/Ğ/g,'g')
.replace(/ı/g,'i').replace(/İ/g,'i')
.replace(/ö/g,'o').replace(/Ö/g,'o')
.replace(/ş/g,'s').replace(/Ş/g,'s')
.replace(/ü/g,'u').replace(/Ü/g,'u');
// remove diacritics
s = s.normalize('NFKD').replace(/\p{M}/gu, '');
s = s.toLowerCase();
s = s.replace(/[^a-z0-9]+/g,'-');
s = s.replace(/^-+|-+$/g,'');
return s;
}
const titleEl = document.getElementById('category-title');
const slugEl = document.getElementById('category-slug');
const resetBtn = document.getElementById('reset-slug');
if(!titleEl || !slugEl) return;
let manual = false;
slugEl.addEventListener('input', ()=> { manual = true; });
titleEl.addEventListener('input', ()=> {
if(!manual){ slugEl.value = slugify(titleEl.value); }
});
resetBtn.addEventListener('click', ()=> { slugEl.value = slugify(titleEl.value); manual = false; });
})();
</script>

View File

@@ -0,0 +1,113 @@
{{ define "admin/pages/hero_form" }}
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>{{ if .IsEdit }}Banner Düzenle{{ else }}Yeni Banner Ekle{{ end }}</h2>
<a href="/admin/content/settings" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Geri Dön
</a>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body">
<form action="{{ if .IsEdit }}/admin/heroes/{{ .Hero.ID }}/update{{ else }}/admin/heroes/create{{ end }}"
method="POST" enctype="multipart/form-data">
<div class="row">
<div class="col-md-6 mb-3">
<label for="title" class="form-label">Başlık</label>
<input type="text" class="form-control" id="title" name="title" value="{{ .Hero.Title }}">
</div>
<div class="col-md-6 mb-3">
<label for="color" class="form-label">Renk Kodu (örn. #ffffff)</label>
<div class="input-group">
<input type="color" class="form-control form-control-color" id="colorPicker"
value="{{ .Hero.Color }}" title="Renk seç">
<input type="text" class="form-control" id="color" name="color" value="{{ .Hero.Color }}"
placeholder="#000000" required>
</div>
<script>
document.getElementById('colorPicker').addEventListener('input', function () {
document.getElementById('color').value = this.value;
});
document.getElementById('color').addEventListener('input', function () {
document.getElementById('colorPicker').value = this.value;
});
</script>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="text1" class="form-label">Alt Başlık (Text1)</label>
<input type="text" class="form-control" id="text1" name="text1" value="{{ .Hero.Text1 }}">
</div>
<div class="col-md-6 mb-3">
<label for="text2" class="form-label">ıklama (Text2)</label>
<input type="text" class="form-control" id="text2" name="text2" value="{{ .Hero.Text2 }}">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="text4" class="form-label">Buton Metni (Text4)</label>
<input type="text" class="form-control" id="text4" name="text4" value="{{ .Hero.Text4 }}">
</div>
<div class="col-md-6 mb-3">
<label for="text5" class="form-label">Buton Linki (Text5)</label>
<input type="text" class="form-control" id="text5" name="text5" value="{{ .Hero.Text5 }}">
</div>
</div>
<div class="row">
<div class="col-md-3 mb-3">
<label for="width" class="form-label">Genişlik (px)</label>
<input type="number" class="form-control" id="width" name="width" value="{{ .Hero.Width }}">
</div>
<div class="col-md-3 mb-3">
<label for="height" class="form-label">Yükseklik (px)</label>
<input type="number" class="form-control" id="height" name="height" value="{{ .Hero.Height }}">
</div>
<div class="col-md-3 mb-3">
<label for="quality" class="form-label">Kalite (1-100)</label>
<input type="number" class="form-control" id="quality" name="quality"
value="{{ .Hero.Quality }}" placeholder="80">
</div>
<div class="col-md-3 mb-3">
<label for="format" class="form-label">Format</label>
<select class="form-select" id="format" name="format">
<option value="avif" {{ if eq .Hero.Format "avif" }}selected{{ end }}>AVIF (Önerilen)
</option>
<option value="webp" {{ if eq .Hero.Format "webp" }}selected{{ end }}>WebP</option>
<option value="jpg" {{ if eq .Hero.Format "jpg" }}selected{{ end }}>JPG</option>
<option value="png" {{ if eq .Hero.Format "png" }}selected{{ end }}>PNG</option>
</select>
</div>
</div>
<div class="mb-3">
<label for="image" class="form-label">Resim Yükle</label>
<input type="file" class="form-control" id="image" name="image" accept="image/*">
{{ if .Hero.Image }}
<div class="mt-2">
<p>Mevcut Resim: <a href="{{ .Hero.Image }}" target="_blank">{{ .Hero.Image }}</a></p>
<img src="{{ .Hero.Image }}" alt="Current Hero Image"
style="max-height: 100px; border-radius: 5px;">
</div>
{{ end }}
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="is_active" name="is_active" {{ if or (not
.IsEdit) .Hero.IsActive }}checked{{ end }}>
<label class="form-check-label" for="is_active">Aktif</label>
</div>
<div class="d-flex justify-content-end gap-2">
<a href="/admin/content/settings" class="btn btn-light">İptal</a>
<button type="submit" class="btn btn-primary">Kaydet</button>
</div>
</form>
</div>
</div>
</div>
{{ end }}

View File

@@ -0,0 +1,175 @@
<div class="container">
<div class="row">
<div class="col-md-8">
<div class="card shadow-sm">
<div class="card-body">
<h4>{{ if .IsEdit }}Yazıyı Düzenle{{ else }}Yeni Yazı{{ end }}</h4>
<form action="{{ if .IsEdit }}/admin/posts/{{ .Post.ID }}/update{{ else }}/admin/posts/create{{ end }}" method="POST" enctype="multipart/form-data">
<div class="mb-3">
<label class="form-label">Başlık</label>
<input type="text" name="title" class="form-control" value="{{ if .IsEdit }}{{ .Post.Title }}{{ end }}">
</div>
<div class="mb-3">
<label class="form-label">Slug</label>
<input type="text" name="slug" class="form-control" value="{{ if .IsEdit }}{{ .Post.Slug }}{{ end }}">
</div>
<div class="mb-3">
<label class="form-label">İçerik</label>
<textarea id="contentInput" name="content" rows="8" class="form-control d-none">{{ if .IsEdit }}{{ .Post.Content }}{{ end }}</textarea>
<div id="quillEditor" style="min-height:300px;border:1px solid #ddd;border-radius:4px;">
<!-- initial content will be loaded from the hidden textarea by JS -->
</div>
</div>
<div class="mb-3">
<label class="form-label">Kategoriler (Ctrl/Tık birden çok seçim)</label>
<select name="category_ids" class="form-select" multiple>
{{ range .Categories }}
<option value="{{ .ID }}" {{ if $.IsEdit }}{{ range $.Post.Categories }}{{ if eq .ID $.ID }}selected{{ end }}{{ end }}{{ end }}>{{ .Title }}</option>
{{ end }}
</select>
</div>
<div class="mb-3">
<label class="form-label">Taglar (Ctrl/Tık birden çok seçim)</label>
<select name="tag_ids" class="form-select" multiple>
{{ range .Tags }}
<option value="{{ .ID }}" {{ if $.IsEdit }}{{ range $.Post.Tags }}{{ if eq .ID $.ID }}selected{{ end }}{{ end }}{{ end }}>{{ .Name }}</option>
{{ end }}
</select>
</div>
<div class="mb-3">
<label class="form-label">Görsel (Tek dosya)</label>
<input type="file" name="image" class="form-control" accept="image/*">
<div class="mt-2">
<img id="imagePreview" src="" alt="preview" class="rounded" style="display:none;width:150px;height:150px;object-fit:cover;border:1px solid #ddd;">
</div>
<div class="row g-2">
<div class="col-3">
<label class="form-label">Genişlik</label>
<input type="number" name="width" class="form-control" value="{{ if .IsEdit }}{{ .Post.Width }}{{ else }}1920{{ end }}">
</div>
<div class="col-3">
<label class="form-label">Yükseklik</label>
<input type="number" name="height" class="form-control" value="{{ if .IsEdit }}{{ .Post.Height }}{{ else }}1080{{ end }}">
</div>
<div class="col-3">
<label class="form-label">Kalite</label>
<input type="number" name="quality" class="form-control" value="{{ if .IsEdit }}{{ .Post.Quality }}{{ else }}80{{ end }}">
</div>
<div class="col-3">
<label class="form-label">Format</label>
<select name="format" class="form-select">
<option value="avif" {{ if .IsEdit }}{{ if eq .Post.Format "avif" }}selected{{ end }}{{ end }}>AVIF</option>
<option value="webp" {{ if .IsEdit }}{{ if eq .Post.Format "webp" }}selected{{ end }}{{ end }}>WebP</option>
<option value="jpg" {{ if .IsEdit }}{{ if eq .Post.Format "jpg" }}selected{{ end }}{{ end }}>JPG</option>
<option value="png" {{ if .IsEdit }}{{ if eq .Post.Format "png" }}selected{{ end }}{{ end }}>PNG</option>
</select>
</div>
</div>
<div class="mt-4 d-flex gap-2">
<button class="btn btn-primary">{{ if .IsEdit }}Güncelle{{ else }}Oluştur{{ end }}</button>
<a href="/admin/content/posts" class="btn btn-outline-secondary" hx-get="/admin/content/posts" hx-target="#main-content" hx-push-url="true">İptal</a>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm">
<div class="card-body">
<h6>Mevcut Görsel</h6>
{{ if .IsEdit }}
{{ if .Post.Images }}
<div class="small text-muted">Görsel yolları: {{ .Post.Images }}</div>
{{ else }}
<div class="text-muted small">Henüz görsel yüklenmedi.</div>
{{ end }}
{{ else }}
<div class="text-muted small">Henüz görsel yüklenmedi.</div>
{{ end }}
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function(){
const fileInput = document.querySelector('input[name="image"]');
const preview = document.getElementById('imagePreview');
const formEl = document.querySelector('form');
// show existing image if provided by controller
const firstImage = '{{ .FirstImage }}';
if (firstImage && firstImage !== '') {
preview.src = firstImage;
preview.style.display = 'block';
}
if (fileInput) {
fileInput.addEventListener('change', function(e){
const f = this.files && this.files[0];
if (!f) return;
const reader = new FileReader();
reader.onload = function(ev){
preview.src = ev.target.result;
preview.style.display = 'block';
}
reader.readAsDataURL(f);
});
}
if (formEl) {
formEl.addEventListener('submit', function() {
const selected = fileInput && fileInput.files && fileInput.files[0] ? fileInput.files[0] : null;
console.log('[post_form submit]', {
action: formEl.action,
method: formEl.method,
enctype: formEl.enctype,
imageName: selected ? selected.name : null,
imageSize: selected ? selected.size : 0
});
});
}
});
</script>
<!-- Quill: load local CSS and JS; initialize editor and sync to textarea on submit -->
<link href="/assets/quill/dist/quill.snow.css" rel="stylesheet">
<script src="/assets/quill/dist/quill.js"></script>
<script>
(function initQuill(){
try {
var textarea = document.getElementById('contentInput');
var editorContainer = document.getElementById('quillEditor');
if (!editorContainer) return;
// Avoid double-init
if (editorContainer.__quillInitialized) return;
// Initialize Quill
var quill = new Quill(editorContainer, {
theme: 'snow',
modules: {
toolbar: {
container: [['bold', 'italic', 'underline'], [{ 'list': 'ordered'}, { 'list': 'bullet' }], ['link', 'image'], ['code-block'], ['clean']],
handlers: {
image: function() {
try { window.showImageModal(quill); } catch(e) { console.error('open image modal failed', e); }
}
}
}
}
});
editorContainer.__quillInitialized = true;
// Load initial content from textarea (if any)
if (textarea && textarea.value && textarea.value.trim() !== '') {
quill.clipboard.dangerouslyPasteHTML(textarea.value);
}
// On form submit, copy html to hidden textarea
var form = textarea && textarea.form;
if (form) {
form.addEventListener('submit', function(e){
textarea.value = quill.root.innerHTML;
});
}
} catch (err) {
console.error('Quill init error:', err);
}
})();
</script>

View File

@@ -0,0 +1,87 @@
<div class="container-fluid">
<h2>{{ if .IsEdit }}Ürün Kategorisi Düzenle{{ else }}Yeni Ürün Kategorisi{{ end }}</h2>
<div class="card border-0 shadow-sm">
<div class="card-body">
{{ if .IsEdit }}
<form action="/admin/product-categories/{{ .Category.ID }}/update" method="POST">
{{ else }}
<form action="/admin/product-categories/create" method="POST">
{{ end }}
<div class="mb-3">
<label class="form-label">Başlık</label>
<input id="category-title" type="text" name="title" class="form-control"
value="{{ if .IsEdit }}{{ .Category.Title }}{{ end }}" required>
</div>
<div class="mb-3">
<label class="form-label">Slug</label>
<div class="input-group">
<input id="category-slug" type="text" name="slug" class="form-control"
value="{{ if .IsEdit }}{{ .Category.Slug }}{{ end }}">
<button id="reset-slug" type="button" class="btn btn-outline-secondary">Oluştur</button>
</div>
<div class="form-text">Boş bırakılırsa başlıktan otomatik oluşturulur; düzenleyebilirsiniz.
</div>
</div>
<div class="mb-3">
<label class="form-label">ıklama</label>
<textarea name="description"
class="form-control">{{ if .IsEdit }}{{ .Category.Description }}{{ end }}</textarea>
</div>
<div class="mb-3">
<label class="form-label">Anahtar Kelimeler (Keywords)</label>
<input type="text" name="keywords" class="form-control"
value="{{ if .IsEdit }}{{ .Category.Keywords }}{{ end }}">
</div>
<div class="mb-3">
<label class="form-label">Üst Kategori</label>
<select name="parent_id" class="form-select">
<option value="">-- Yok --</option>
{{ range .Parents }}
{{ $isSelected := false }}
{{ if $.IsEdit }}{{ if $.Category.ParentID }}{{ if eq (print $.Category.ParentID) (print
.ID) }}{{ $isSelected = true }}{{ end }}{{ end }}{{ end }}
<option value="{{ .ID }}" {{ if $isSelected }}selected{{ end }}>{{ .Title }}</option>
{{ end }}
</select>
</div>
<div class="d-flex gap-2">
<a href="/admin/content/product-categories" class="btn btn-secondary">Geri</a>
<button class="btn btn-primary" type="submit">Kaydet</button>
</div>
</form>
</div>
</div>
</div>
<script>
(function () {
function slugify(s) {
if (!s) return '';
s = s.trim();
// Turkish char replacements
s = s.replace(/ç/g, 'c').replace(/Ç/g, 'c')
.replace(/ğ/g, 'g').replace(/Ğ/g, 'g')
.replace(/ı/g, 'i').replace(/İ/g, 'i')
.replace(/ö/g, 'o').replace(/Ö/g, 'o')
.replace(/ş/g, 's').replace(/Ş/g, 's')
.replace(/ü/g, 'u').replace(/Ü/g, 'u');
// remove diacritics
s = s.normalize('NFKD').replace(/\p{M}/gu, '');
s = s.toLowerCase();
s = s.replace(/[^a-z0-9]+/g, '-');
s = s.replace(/^-+|-+$/g, '');
return s;
}
const titleEl = document.getElementById('category-title');
const slugEl = document.getElementById('category-slug');
const resetBtn = document.getElementById('reset-slug');
if (!titleEl || !slugEl) return;
let manual = false;
slugEl.addEventListener('input', () => { manual = true; });
titleEl.addEventListener('input', () => {
if (!manual) { slugEl.value = slugify(titleEl.value); }
});
resetBtn.addEventListener('click', () => { slugEl.value = slugify(titleEl.value); manual = false; });
})();
</script>

View File

@@ -0,0 +1,191 @@
<div class="container">
<div class="row">
<div class="col-md-8">
<div class="card shadow-sm">
<div class="card-body">
<h4>{{ if .IsEdit }}Ürünü Düzenle{{ else }}Yeni Ürün{{ end }}</h4>
<form
action="{{ if .IsEdit }}/admin/products/{{ .Product.ID }}/update{{ else }}/admin/products/create{{ end }}"
method="POST" enctype="multipart/form-data">
<div class="mb-3">
<label class="form-label">Başlık</label>
<input type="text" name="title" class="form-control"
value="{{ if .IsEdit }}{{ .Product.Title }}{{ end }}" required>
</div>
<div class="mb-3">
<label class="form-label">Slug</label>
<input type="text" name="slug" class="form-control"
value="{{ if .IsEdit }}{{ .Product.Slug }}{{ end }}"
placeholder="Boş bırakılırsa başlıktan otomatik oluşturulur">
</div>
<div class="mb-3">
<label class="form-label">İçerik</label>
<textarea id="contentInput" name="content" rows="8"
class="form-control d-none">{{ if .IsEdit }}{{ .Product.Content }}{{ end }}</textarea>
<div id="quillEditor" style="min-height:300px;border:1px solid #ddd;border-radius:4px;">
<!-- initial content will be loaded from the hidden textarea by JS -->
</div>
</div>
<div class="mb-3">
<label class="form-label">Kategoriler (Ctrl/Tık birden çok seçim)</label>
<select name="categories" class="form-select" multiple>
{{ range .Categories }}
<option value="{{ .ID }}" {{ if $.IsEdit }}{{ range $.Product.Categories }}{{ if eq .ID
$.ID }}selected{{ end }}{{ end }}{{ end }}>{{ .Title }}</option>
{{ end }}
</select>
</div>
<div class="mb-3">
<label class="form-label">Etiketler (Ctrl/Tık birden çok seçim)</label>
<select name="tags" class="form-select" multiple>
{{ range .Tags }}
<option value="{{ .ID }}" {{ if $.IsEdit }}{{ range $.Product.Tags }}{{ if eq .ID $.ID
}}selected{{ end }}{{ end }}{{ end }}>{{ .Name }}</option>
{{ end }}
</select>
</div>
<div class="mb-3">
<label class="form-label">Ana Görsel (Tek dosya)</label>
<input type="file" name="image" class="form-control" accept="image/*">
<div class="mt-2">
<img id="imagePreview" src="" alt="preview" class="rounded"
style="display:none;width:150px;height:150px;object-fit:cover;border:1px solid #ddd;">
</div>
</div>
<div class="mb-3">
<label class="form-label">Fiyat</label>
<input type="number" step="0.01" name="price" class="form-control"
value="{{ if .IsEdit }}{{ .Product.Price }}{{ else }}0.00{{ end }}" required>
</div>
<div class="row g-2 mb-3">
<div class="col-3">
<label class="form-label">Genişlik</label>
<input type="number" name="width" class="form-control"
value="{{ if .IsEdit }}{{ .Product.Width }}{{ end }}" placeholder="Örn: 800">
</div>
<div class="col-3">
<label class="form-label">Yükseklik</label>
<input type="number" name="height" class="form-control"
value="{{ if .IsEdit }}{{ .Product.Height }}{{ end }}" placeholder="Örn: 600">
</div>
<div class="col-3">
<label class="form-label">Kalite</label>
<input type="number" name="quality" class="form-control"
value="{{ if .IsEdit }}{{ .Product.Quality }}{{ else }}80{{ end }}">
</div>
<div class="col-3">
<label class="form-label">Format</label>
<select name="format" class="form-select">
<option value="avif" {{ if .IsEdit }}{{ if eq .Product.Format "avif" }}selected{{
end }}{{ end }}>AVIF</option>
<option value="webp" {{ if .IsEdit }}{{ if eq .Product.Format "webp" }}selected{{
end }}{{ end }}>WebP</option>
<option value="jpg" {{ if .IsEdit }}{{ if eq .Product.Format "jpg" }}selected{{ end
}}{{ end }}>JPG</option>
<option value="png" {{ if .IsEdit }}{{ if eq .Product.Format "png" }}selected{{ end
}}{{ end }}>PNG</option>
</select>
</div>
</div>
<div class="mt-4 d-flex gap-2">
<button type="submit" class="btn btn-primary">{{ if .IsEdit }}Güncelle{{ else }}Oluştur{{
end }}</button>
<a href="/admin/content/products" class="btn btn-outline-secondary"
hx-get="/admin/content/products" hx-target="#main-content" hx-push-url="true">İptal</a>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm mb-3">
<div class="card-body">
<h6>Mevcut Ana Görsel</h6>
{{ if .IsEdit }}
{{ if .FirstImage }}
<div class="mb-2">
<img src="{{ .FirstImage }}" class="img-fluid rounded" alt="Mevcut Görsel">
</div>
{{ else }}
<div class="text-muted small">Henüz görsel yüklenmedi.</div>
{{ end }}
{{ else }}
<div class="text-muted small">Yeni ürün için henüz görsel yok.</div>
{{ end }}
</div>
</div>
<div class="card shadow-sm">
<div class="card-body">
<h6>Bilgi</h6>
<p class="small text-muted mb-0">Ürün eklerken Kategori ve Etiket seçimi yapabilirsiniz.
Seçeneklerin dolu olması için ilgili menülerden kategori ve etiket verisi girmeyi unutmayın.</p>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const fileInput = document.querySelector('input[name="image"]');
const preview = document.getElementById('imagePreview');
// show existing image if provided by controller but not rendered inline above
const firstImage = '{{ .FirstImage }}';
if (firstImage && firstImage !== '') {
preview.src = firstImage;
preview.style.display = 'block';
}
if (fileInput) {
fileInput.addEventListener('change', function (e) {
const f = this.files && this.files[0];
if (!f) return;
const reader = new FileReader();
reader.onload = function (ev) {
preview.src = ev.target.result;
preview.style.display = 'block';
}
reader.readAsDataURL(f);
});
}
});
</script>
<!-- Quill: load local CSS and JS; initialize editor and sync to textarea on submit -->
<link href="/assets/quill/dist/quill.snow.css" rel="stylesheet">
<script src="/assets/quill/dist/quill.js"></script>
<script>
(function initQuill() {
try {
var textarea = document.getElementById('contentInput');
var editorContainer = document.getElementById('quillEditor');
if (!editorContainer) return;
if (editorContainer.__quillInitialized) return;
var quill = new Quill(editorContainer, {
theme: 'snow',
modules: {
toolbar: {
container: [['bold', 'italic', 'underline'], [{ 'list': 'ordered' }, { 'list': 'bullet' }], ['link', 'image'], ['code-block'], ['clean']],
handlers: {
image: function () {
try { window.showImageModal(quill); } catch (e) { console.error('open image modal failed', e); }
}
}
}
}
});
editorContainer.__quillInitialized = true;
if (textarea && textarea.value && textarea.value.trim() !== '') {
quill.clipboard.dangerouslyPasteHTML(textarea.value);
}
var form = textarea && textarea.form;
if (form) {
form.addEventListener('submit', function (e) {
textarea.value = quill.root.innerHTML;
});
}
} catch (err) {
console.error('Quill init error:', err);
}
})();
</script>

View File

@@ -0,0 +1,22 @@
<div class="container-fluid">
<h2>{{ if .IsEdit }}Ürün Etiketi Düzenle{{ else }}Yeni Ürün Etiketi{{ end }}</h2>
<div class="card border-0 shadow-sm">
<div class="card-body">
{{ if .IsEdit }}
<form action="/admin/product-tags/{{ .Tag.ID }}/update" method="POST">
{{ else }}
<form action="/admin/product-tags/create" method="POST">
{{ end }}
<div class="mb-3">
<label class="form-label">İsim</label>
<input type="text" name="name" class="form-control"
value="{{ if .IsEdit }}{{ .Tag.Name }}{{ end }}" required>
</div>
<div class="d-flex gap-2">
<a href="/admin/content/product-tags" class="btn btn-secondary">Geri</a>
<button class="btn btn-primary" type="submit">Kaydet</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<div class="container-fluid">
<h2>{{ if .IsEdit }}Tag Düzenle{{ else }}Yeni Tag{{ end }}</h2>
<div class="card border-0 shadow-sm">
<div class="card-body">
{{ if .IsEdit }}
<form action="/admin/tags/{{ .Tag.ID }}/update" method="POST">
{{ else }}
<form action="/admin/tags/create" method="POST">
{{ end }}
<div class="mb-3">
<label class="form-label">İsim</label>
<input type="text" name="name" class="form-control" value="{{ if .IsEdit }}{{ .Tag.Name }}{{ end }}" required>
</div>
<div class="d-flex gap-2">
<a href="/admin/content/tags" class="btn btn-secondary">Geri</a>
<button class="btn btn-primary" type="submit">Kaydet</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,79 @@
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white py-3">
<h5 class="mb-0">{{ if .IsEdit }}Kullanıcı Düzenle{{ else }}Yeni Kullanıcı Oluştur{{ end }}</h5>
</div>
<div class="card-body p-4">
<form
action="{{ if .IsEdit }}/admin/users/{{ .User.ID }}/update{{ else }}/admin/users/create{{ end }}"
method="POST" enctype="multipart/form-data">
<div class="mb-3">
<label for="username" class="form-label">Kullanıcı Adı</label>
<input type="text" class="form-control" id="username" name="username"
value="{{ if .IsEdit }}{{ .User.UserName }}{{ end }}" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">E-posta</label>
<input type="email" class="form-control" id="email" name="email"
value="{{ if .IsEdit }}{{ .User.Email }}{{ end }}" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Şifre {{ if .IsEdit }}<small
class="text-muted">(Boş bırakılırsa değişmez)</small>{{ end }}</label>
<input type="password" class="form-control" id="password" name="password" {{ if not .IsEdit
}}required{{ end }}>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="is_admin" name="is_admin" {{ if .IsEdit
}}{{ if isTrue .User.IsAdmin }}checked{{ end }}{{ end }}>
<label class="form-check-label" for="is_admin">
Yönetici (Admin)
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="email_verified" name="email_verified" {{
if .IsEdit }}{{ if isTrue .User.EmailVerified }}checked{{ end }}{{ end }}>
<label class="form-check-label" for="email_verified">
E-posta Doğrulanmış
</label>
</div>
<!-- Profile fields -->
<div class="mb-3">
<label for="first_name" class="form-label">İsim</label>
<input type="text" class="form-control" id="first_name" name="first_name"
value="{{ if .IsEdit }}{{ if .User.Profile }}{{ range .User.Profile }}{{ .FirstName }}{{ end }}{{ end }}{{ end }}">
</div>
<div class="mb-3">
<label for="last_name" class="form-label">Soyisim</label>
<input type="text" class="form-control" id="last_name" name="last_name"
value="{{ if .IsEdit }}{{ if .User.Profile }}{{ range .User.Profile }}{{ .LastName }}{{ end }}{{ end }}{{ end }}">
</div>
<div class="mb-3">
<label class="form-label">Avatar</label>
<input type="file" class="form-control" name="avatar" accept="image/*">
{{ if .IsEdit }}
{{ if .User.Profile }}
{{ range .User.Profile }}
{{ if .AvatarURL }}
<div class="mt-2">
<img src="{{ .AvatarURL }}" width="64" height="64" class="rounded-circle">
</div>
{{ end }}
{{ end }}
{{ end }}
{{ end }}
</div>
<div class="d-flex justify-content-end gap-2">
<a href="/admin/content/users" class="btn btn-secondary">İptal</a>
<button type="submit" class="btn btn-primary">Kaydet</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,64 @@
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="mb-0">Sepet Yönetimi</h2>
<div class="d-flex gap-2 align-items-center">
<form class="d-flex" action="/admin/content/carts" method="GET">
<input type="search" name="search" class="form-control form-control-sm me-2"
placeholder="User ID ile Ara..." value="{{ .Search }}">
<button class="btn btn-sm btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</form>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-3 border-0 text-muted small text-uppercase">ID</th>
<th class="border-0 text-muted small text-uppercase">Kullanıcı (User ID)</th>
<th class="border-0 text-muted small text-uppercase">Ürün Sayısı (Çeşit)</th>
<th class="border-0 text-muted small text-uppercase">Oluşturulma Tarihi</th>
<th class="border-0 text-muted small text-uppercase text-end pe-3">İşlemler</th>
</tr>
</thead>
<tbody>
{{ range .Carts }}
<tr>
<td class="ps-3"><span class="text-secondary fw-medium">#{{ .ID }}</span></td>
<td>
<div class="d-flex align-items-center">
<div class="ms-2">
<div class="fw-semibold text-dark">Kullanıcı ID: {{ .UserID }}</div>
</div>
</div>
</td>
<td>
<span class="badge bg-info text-dark rounded-pill">{{ len .Items }} çeşit ürün
eklendi</span>
</td>
<td class="text-secondary small">
{{ .CreatedAt.Format "02.01.2006 15:04" }}
</td>
<td class="text-end pe-3">
<form action="/admin/carts/{{ .ID }}/delete" method="POST" class="d-inline"
onsubmit="return confirm('Bu sepeti silmek istediğinize emin misiniz?');">
<button type="submit" class="btn btn-sm btn-outline-danger" title="Sil">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
{{ else }}
<tr>
<td colspan="5" class="text-center py-5 text-muted">
<i class="bi bi-cart-x fs-1 d-block mb-2"></i>
Henüz kullanıcılar sepet oluşturmamış.
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -0,0 +1,82 @@
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="mb-0">Kategoriler</h2>
<div class="d-flex gap-2 align-items-center">
<form class="d-flex" action="/admin/content/categories" method="GET">
<input type="search" name="search" class="form-control form-control-sm me-2" placeholder="Ara..." value="{{ .Search }}">
<button class="btn btn-sm btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</form>
<a href="/admin/categories/new" class="btn btn-sm btn-primary">Yeni Kategori</a>
<div class="btn-group">
<a href="/admin/content/categories" class="btn btn-sm btn-outline-primary {{ if not .ShowDeleted }}active{{ end }}">Aktif</a>
<a href="/admin/content/categories?deleted=true" class="btn btn-sm btn-outline-danger {{ if .ShowDeleted }}active{{ end }}">Silinenler</a>
</div>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body p-3">
<div class="table-responsive">
<table class="table table-hover table-sm align-middle">
<thead class="bg-transparent text-muted small">
<tr>
<th>Başlık</th>
<th class="text-muted">Slug</th>
<th class="text-muted">Parent</th>
<th class="text-muted">Oluşturma</th>
<th class="text-end text-muted">İşlemler</th>
</tr>
</thead>
<tbody>
{{ if .Categories }}
{{ range .Categories }}
<tr>
<td class="fw-semibold">{{ .Title }}</td>
<td class="text-secondary small text-nowrap">{{ .Slug }}</td>
<td>
{{ if .Parent }}
<span class="badge bg-secondary">{{ .Parent.Title }}</span>
{{ else }}
<span class="text-muted">-</span>
{{ end }}
</td>
<td class="text-muted small">{{ .CreatedAt.Format "2006-01-02" }}</td>
<td class="text-end">
<div class="btn-group" role="group">
{{ if $.ShowDeleted }}
<form action="/admin/categories/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success" title="Geri Yükle"><i class="bi bi-arrow-counterclockwise"></i></button>
</form>
{{ else }}
<a href="/admin/categories/{{ .ID }}/edit" class="btn btn-sm btn-outline-secondary" title="Düzenle"><i class="bi bi-pencil"></i></a>
<form action="/admin/categories/{{ .ID }}/delete" method="POST" onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger" title="Sil"><i class="bi bi-trash"></i></button>
</form>
{{ end }}
</div>
</td>
</tr>
{{ end }}
{{ else }}
<tr>
<td colspan="5" class="text-center text-muted py-4">Kayıt yok</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
<!-- Pagination -->
{{ if gt .TotalPages 1 }}
<div class="d-flex justify-content-end mt-3">
<nav>
<ul class="pagination pagination-sm mb-0">
<li class="page-item {{ if eq .Page 1 }}disabled{{ end }}"><a class="page-link" href="/admin/content/categories?page={{ .PrevPage }}">&laquo; Önceki</a></li>
<li class="page-item disabled"><span class="page-link">Sayfa {{ .Page }} / {{ .TotalPages }}</span></li>
<li class="page-item {{ if eq .Page .TotalPages }}disabled{{ end }}"><a class="page-link" href="/admin/content/categories?page={{ .NextPage }}">Sonraki &raquo;</a></li>
</ul>
</nav>
</div>
{{ end }}
</div>
</div>
</div>

View File

@@ -0,0 +1,71 @@
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="mb-0">Category Views</h2>
<div class="d-flex gap-2 align-items-center">
<form class="d-flex" action="/admin/content/category-views" method="GET">
<input type="search" name="search" class="form-control form-control-sm me-2" placeholder="IP ara..." value="{{ .Search }}">
<button class="btn btn-sm btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</form>
<div class="btn-group">
<a href="/admin/content/category-views" class="btn btn-sm btn-outline-primary {{ if not .ShowDeleted }}active{{ end }}">Aktif</a>
<a href="/admin/content/category-views?deleted=true" class="btn btn-sm btn-outline-danger {{ if .ShowDeleted }}active{{ end }}">Silinenler</a>
</div>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body p-3">
<div class="table-responsive">
<table class="table table-hover table-sm align-middle">
<thead class="bg-transparent text-muted small">
<tr>
<th>Category</th>
<th class="text-muted">IP Address</th>
<th class="text-muted">Oluşturma</th>
<th class="text-end text-muted">İşlemler</th>
</tr>
</thead>
<tbody>
{{ if .Views }}
{{ range .Views }}
<tr>
<td>{{ index $.CatMap .CategoryID }}</td>
<td class="text-secondary small">{{ .IPAddress }}</td>
<td class="text-muted small">{{ .CreatedAt.Format "2006-01-02" }}</td>
<td class="text-end">
<div class="btn-group">
{{ if $.ShowDeleted }}
<form action="/admin/category-views/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success" title="Geri Yükle"><i class="bi bi-arrow-counterclockwise"></i></button>
</form>
{{ else }}
<form action="/admin/category-views/{{ .ID }}/delete" method="POST" onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger" title="Sil"><i class="bi bi-trash"></i></button>
</form>
{{ end }}
</div>
</td>
</tr>
{{ end }}
{{ else }}
<tr>
<td colspan="4" class="text-center text-muted py-4">Kayıt yok</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
{{ if gt .TotalPages 1 }}
<div class="d-flex justify-content-end mt-3">
<nav>
<ul class="pagination pagination-sm mb-0">
<li class="page-item {{ if eq .Page 1 }}disabled{{ end }}"><a class="page-link" href="/admin/content/category-views?page={{ .PrevPage }}">&laquo; Önceki</a></li>
<li class="page-item disabled"><span class="page-link">Sayfa {{ .Page }} / {{ .TotalPages }}</span></li>
<li class="page-item {{ if eq .Page .TotalPages }}disabled{{ end }}"><a class="page-link" href="/admin/content/category-views?page={{ .NextPage }}">Sonraki &raquo;</a></li>
</ul>
</nav>
</div>
{{ end }}
</div>
</div>
</div>

View File

@@ -0,0 +1,74 @@
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="mb-0">Yorumlar</h2>
<div class="d-flex gap-2 align-items-center">
<form class="d-flex" action="/admin/content/comments" method="GET">
<input type="search" name="search" class="form-control form-control-sm me-2" placeholder="Ara..." value="{{ .Search }}">
<button class="btn btn-sm btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</form>
<div class="btn-group">
<a href="/admin/content/comments" class="btn btn-sm btn-outline-primary {{ if not .ShowDeleted }}active{{ end }}">Aktif</a>
<a href="/admin/content/comments?deleted=true" class="btn btn-sm btn-outline-danger {{ if .ShowDeleted }}active{{ end }}">Silinenler</a>
</div>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body p-3">
<div class="table-responsive">
<table class="table table-hover table-sm align-middle">
<thead class="bg-transparent text-muted small">
<tr>
<th>Kullanıcı</th>
<th>Post ID</th>
<th class="text-muted">Yorum</th>
<th class="text-muted">Oluşturma</th>
<th class="text-end text-muted">İşlemler</th>
</tr>
</thead>
<tbody>
{{ if .Comments }}
{{ range .Comments }}
<tr>
<td>{{ .UserID }}</td>
<td>{{ .PostID }}</td>
<td class="text-truncate" style="max-width:400px;">{{ .Body }}</td>
<td class="text-muted small">{{ .CreatedAt.Format "2006-01-02" }}</td>
<td class="text-end">
<div class="btn-group" role="group">
{{ if $.ShowDeleted }}
<form action="/admin/comments/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success" title="Geri Yükle"><i class="bi bi-arrow-counterclockwise"></i></button>
</form>
{{ else }}
<a href="/admin/comments/{{ .ID }}/edit" class="btn btn-sm btn-outline-secondary" title="Düzenle"><i class="bi bi-pencil"></i></a>
<form action="/admin/comments/{{ .ID }}/delete" method="POST" onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger" title="Sil"><i class="bi bi-trash"></i></button>
</form>
{{ end }}
</div>
</td>
</tr>
{{ end }}
{{ else }}
<tr>
<td colspan="5" class="text-center text-muted py-4">Kayıt yok</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
{{ if gt .TotalPages 1 }}
<div class="d-flex justify-content-end mt-3">
<nav>
<ul class="pagination pagination-sm mb-0">
<li class="page-item {{ if eq .Page 1 }}disabled{{ end }}"><a class="page-link" href="/admin/content/comments?page={{ .PrevPage }}">&laquo; Önceki</a></li>
<li class="page-item disabled"><span class="page-link">Sayfa {{ .Page }} / {{ .TotalPages }}</span></li>
<li class="page-item {{ if eq .Page .TotalPages }}disabled{{ end }}"><a class="page-link" href="/admin/content/comments?page={{ .NextPage }}">Sonraki &raquo;</a></li>
</ul>
</nav>
</div>
{{ end }}
</div>
</div>
</div>

View File

@@ -0,0 +1,75 @@
<div class="container-fluid">
<h2 class="mb-4">Dashboard</h2>
<!-- Stats Cards -->
<div class="row g-4 mb-4">
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<h6 class="card-subtitle mb-2 text-muted">Toplam Kullanıcı</h6>
<h3 class="card-title">1,250</h3>
<p class="card-text text-success"><i class="bi bi-arrow-up"></i> %5 artış</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<h6 class="card-subtitle mb-2 text-muted">Aktif Oturumlar</h6>
<h3 class="card-title">45</h3>
<p class="card-text text-primary">Anlık</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<h6 class="card-subtitle mb-2 text-muted">Sistem Yükü</h6>
<h3 class="card-title">%12</h3>
<p class="card-text text-success">Normal</p>
</div>
</div>
</div>
</div>
<!-- Recent Activity Table -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-transparent border-0">
<h5 class="mb-0">Son Aktiviteler</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Kullanıcı</th>
<th>İşlem</th>
<th>Tarih</th>
<th>Durum</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ahmet Yılmaz</td>
<td>Giriş Yaptı</td>
<td>10 dk önce</td>
<td><span class="badge bg-success">Başarılı</span></td>
</tr>
<tr>
<td>Ayşe Demir</td>
<td>Profil Güncelledi</td>
<td>1 saat önce</td>
<td><span class="badge bg-primary">Kaydedildi</span></td>
</tr>
<tr>
<td>Mehmet Kara</td>
<td>Hatalı Giriş</td>
<td>2 saat önce</td>
<td><span class="badge bg-danger">Hata</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,103 @@
<div class="container-fluid">
{{ if .Error }}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{{ .Error }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{{ end }}
{{ if .Success }}
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ .Success }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{{ end }}
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="mb-0">Yazılar</h2>
<div class="d-flex gap-2 align-items-center">
<form class="d-flex" action="/admin/content/posts" method="GET">
<input type="search" name="search" class="form-control form-control-sm me-2" placeholder="Ara..." value="{{ .Search }}">
<button class="btn btn-sm btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</form>
<a href="/admin/posts/new" class="btn btn-sm btn-primary">Yeni Yazı</a>
<div class="btn-group">
<a href="/admin/content/posts" class="btn btn-sm btn-outline-primary {{ if not .ShowDeleted }}active{{ end }}">Aktif</a>
<a href="/admin/content/posts?deleted=true" class="btn btn-sm btn-outline-danger {{ if .ShowDeleted }}active{{ end }}">Silinenler</a>
</div>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body p-3">
<div class="table-responsive">
<table class="table table-hover table-sm align-middle">
<thead class="bg-transparent text-muted small">
<tr>
<th>Başlık</th>
<th>Kategoriler</th>
<th>Taglar</th>
<th class="text-muted">Oluşturma</th>
<th class="text-end text-muted">İşlemler</th>
</tr>
</thead>
<tbody>
{{ if .Posts }}
{{ range .Posts }}
<tr>
<td style="width:56px;">
{{ with index $.ImageMap .ID }}
<img src="{{ . }}" alt="thumb" class="rounded" style="width:48px;height:48px;object-fit:cover;">
{{ else }}
<div class="bg-secondary rounded" style="width:48px;height:48px;"></div>
{{ end }}
</td>
<td>{{ .Title }}</td>
<td>
{{ range .Categories }}
<span class="badge bg-secondary me-1">{{ .Title }}</span>
{{ end }}
</td>
<td>
{{ range .Tags }}
<span class="badge bg-info text-dark me-1">{{ .Name }}</span>
{{ end }}
</td>
<td class="text-muted small">{{ .CreatedAt.Format "2006-01-02" }}</td>
<td class="text-end">
<div class="btn-group" role="group">
{{ if $.ShowDeleted }}
<form action="/admin/posts/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success" title="Geri Yükle"><i class="bi bi-arrow-counterclockwise"></i></button>
</form>
{{ else }}
<a href="/admin/posts/{{ .ID }}/edit" class="btn btn-sm btn-outline-secondary" title="Düzenle"><i class="bi bi-pencil"></i></a>
<form action="/admin/posts/{{ .ID }}/delete" method="POST" onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger" title="Sil"><i class="bi bi-trash"></i></button>
</form>
{{ end }}
</div>
</td>
</tr>
{{ end }}
{{ else }}
<tr>
<td colspan="5" class="text-center text-muted py-4">Kayıt yok</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
{{ if gt .TotalPages 1 }}
<div class="d-flex justify-content-end mt-3">
<nav>
<ul class="pagination pagination-sm mb-0">
<li class="page-item {{ if eq .Page 1 }}disabled{{ end }}"><a class="page-link" href="/admin/content/posts?page={{ .PrevPage }}">&laquo; Önceki</a></li>
<li class="page-item disabled"><span class="page-link">Sayfa {{ .Page }} / {{ .TotalPages }}</span></li>
<li class="page-item {{ if eq .Page .TotalPages }}disabled{{ end }}"><a class="page-link" href="/admin/content/posts?page={{ .NextPage }}">Sonraki &raquo;</a></li>
</ul>
</nav>
</div>
{{ end }}
</div>
</div>
</div>

View File

@@ -0,0 +1,73 @@
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="mb-0">Ürün Kategorileri</h2>
<div class="d-flex gap-2 align-items-center">
<a href="/admin/product-categories/new" class="btn btn-sm btn-primary">Yeni Kategori</a>
<div class="btn-group">
<a href="/admin/content/product-categories"
class="btn btn-sm btn-outline-primary {{ if not .ShowDeleted }}active{{ end }}">Aktif</a>
<a href="/admin/content/product-categories?deleted=true"
class="btn btn-sm btn-outline-danger {{ if .ShowDeleted }}active{{ end }}">Silinenler</a>
</div>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body p-3">
<div class="table-responsive">
<table class="table table-hover table-sm align-middle">
<thead class="bg-transparent text-muted small">
<tr>
<th>Başlık</th>
<th class="text-muted">Slug</th>
<th class="text-muted">Üst Kategori</th>
<th class="text-muted">Oluşturma</th>
<th class="text-end text-muted">İşlemler</th>
</tr>
</thead>
<tbody>
{{ if .Categories }}
{{ range .Categories }}
<tr>
<td class="fw-semibold">{{ .Title }}</td>
<td class="text-secondary small text-nowrap">{{ .Slug }}</td>
<td>
{{ if .Parent }}
<span class="badge bg-secondary">{{ .Parent.Title }}</span>
{{ else }}
<span class="text-muted">-</span>
{{ end }}
</td>
<td class="text-muted small">{{ .CreatedAt.Format "2006-01-02" }}</td>
<td class="text-end">
<div class="btn-group" role="group">
{{ if $.ShowDeleted }}
<form action="/admin/product-categories/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success" title="Geri Yükle"><i
class="bi bi-arrow-counterclockwise"></i></button>
</form>
{{ else }}
<a href="/admin/product-categories/{{ .ID }}/edit"
class="btn btn-sm btn-outline-secondary" title="Düzenle"><i
class="bi bi-pencil"></i></a>
<form action="/admin/product-categories/{{ .ID }}/delete" method="POST"
onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger" title="Sil"><i
class="bi bi-trash"></i></button>
</form>
{{ end }}
</div>
</td>
</tr>
{{ end }}
{{ else }}
<tr>
<td colspan="5" class="text-center text-muted py-4">Kayıt yok</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,46 @@
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="mb-0">Kategori Görüntülenmeleri</h2>
<div class="d-flex gap-2 align-items-center">
<form class="d-flex" action="/admin/content/product-category-views" method="GET">
<input type="search" name="category_id" class="form-control form-control-sm me-2"
placeholder="Category ID..." value="{{ .CategoryID }}">
<button class="btn btn-sm btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</form>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-3 border-0 text-muted small text-uppercase">ID</th>
<th class="border-0 text-muted small text-uppercase">Kategori ID</th>
<th class="border-0 text-muted small text-uppercase">IP Adresi</th>
<th class="border-0 text-muted small text-uppercase">Ziyaret Tarihi</th>
</tr>
</thead>
<tbody>
{{ range .Views }}
<tr>
<td class="ps-3"><span class="text-secondary fw-medium">#{{ .ID }}</span></td>
<td><span class="badge bg-primary">Kategori #{{ .CategoryID }}</span></td>
<td><span class="text-dark font-monospace">{{ .IPAddress }}</span></td>
<td class="text-secondary small">
{{ .CreatedAt.Format "02.01.2006 15:04" }}
</td>
</tr>
{{ else }}
<tr>
<td colspan="4" class="text-center py-5 text-muted">
<i class="bi bi-eye fs-1 d-block mb-2"></i>
Kategorileriniz henüz görüntülenmemiş veya veri yok.
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -0,0 +1,65 @@
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="mb-0">Ürün Yorumları</h2>
<div class="d-flex gap-2 align-items-center">
<form class="d-flex" action="/admin/content/product-comments" method="GET">
<input type="search" name="product_id" class="form-control form-control-sm me-2"
placeholder="Ürün ID ile Ara..." value="{{ .ProductID }}">
<button class="btn btn-sm btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</form>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-3 border-0 text-muted small text-uppercase">ID</th>
<th class="border-0 text-muted small text-uppercase">Kullanıcı (User ID)</th>
<th class="border-0 text-muted small text-uppercase">Yorum (Body)</th>
<th class="border-0 text-muted small text-uppercase">Ürün ID</th>
<th class="border-0 text-muted small text-uppercase">Tarih</th>
<th class="border-0 text-muted small text-uppercase text-end pe-3">İşlemler</th>
</tr>
</thead>
<tbody>
{{ range .ProductComments }}
<tr>
<td class="ps-3"><span class="text-secondary fw-medium">#{{ .ID }}</span></td>
<td>
<div class="d-flex align-items-center">
<span class="badge bg-secondary">User #{{ .UserID }}</span>
</div>
</td>
<td>
<div class="text-dark">{{ .Body }}</div>
</td>
<td>
<span class="badge bg-primary">Product #{{ .ProductID }}</span>
</td>
<td class="text-secondary small">
{{ .CreatedAt.Format "02.01.2006 15:04" }}
</td>
<td class="text-end pe-3">
<form action="/admin/product-comments/{{ .ID }}/delete" method="POST" class="d-inline"
onsubmit="return confirm('Bu yorumu silmek istediğinize emin misiniz?');">
<button type="submit" class="btn btn-sm btn-outline-danger" title="Sil">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
{{ else }}
<tr>
<td colspan="6" class="text-center py-5 text-muted">
<i class="bi bi-chat-dots fs-1 d-block mb-2"></i>
Henüz ürünlere yorum yapılmamış.
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -0,0 +1,63 @@
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="mb-0">Ürün Etiketleri</h2>
<div class="d-flex gap-2 align-items-center">
<a href="/admin/product-tags/new" class="btn btn-sm btn-primary">Yeni Etiket</a>
<div class="btn-group">
<a href="/admin/content/product-tags"
class="btn btn-sm btn-outline-primary {{ if not .ShowDeleted }}active{{ end }}">Aktif</a>
<a href="/admin/content/product-tags?deleted=true"
class="btn btn-sm btn-outline-danger {{ if .ShowDeleted }}active{{ end }}">Silinenler</a>
</div>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body p-3">
<div class="table-responsive">
<table class="table table-hover table-sm align-middle">
<thead class="bg-transparent text-muted small">
<tr>
<th>İsim</th>
<th class="text-muted">Oluşturma</th>
<th class="text-end text-muted">İşlemler</th>
</tr>
</thead>
<tbody>
{{ if .Tags }}
{{ range .Tags }}
<tr>
<td class="fw-semibold">{{ .Name }}</td>
<td class="text-muted small">{{ .CreatedAt.Format "2006-01-02" }}</td>
<td class="text-end">
<div class="btn-group" role="group">
{{ if $.ShowDeleted }}
<form action="/admin/product-tags/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success" title="Geri Yükle"><i
class="bi bi-arrow-counterclockwise"></i></button>
</form>
{{ else }}
<a href="/admin/product-tags/{{ .ID }}/edit"
class="btn btn-sm btn-outline-secondary" title="Düzenle"><i
class="bi bi-pencil"></i></a>
<form action="/admin/product-tags/{{ .ID }}/delete" method="POST"
onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger" title="Sil"><i
class="bi bi-trash"></i></button>
</form>
{{ end }}
</div>
</td>
</tr>
{{ end }}
{{ else }}
<tr>
<td colspan="3" class="text-center text-muted py-4">Kayıt yok</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,102 @@
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="mb-0">Ürünler</h2>
<div class="d-flex gap-2 align-items-center">
<form class="d-flex" action="/admin/content/products" method="GET">
<input type="search" name="search" class="form-control form-control-sm me-2" placeholder="Ara..."
value="{{ .Search }}">
<button class="btn btn-sm btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</form>
<a href="/admin/products/new" class="btn btn-sm btn-primary">Yeni Ürün</a>
<div class="btn-group">
<a href="/admin/content/products"
class="btn btn-sm btn-outline-primary {{ if not .ShowDeleted }}active{{ end }}">Aktif</a>
<a href="/admin/content/products?deleted=true"
class="btn btn-sm btn-outline-danger {{ if .ShowDeleted }}active{{ end }}">Silinenler</a>
</div>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body p-3">
<div class="table-responsive">
<table class="table table-hover table-sm align-middle">
<thead class="bg-transparent text-muted small">
<tr>
<th>Ürün</th>
<th>Başlık</th>
<th>Kategoriler</th>
<th>Etiketler</th>
<th class="text-muted">Oluşturma</th>
<th class="text-end text-muted">İşlemler</th>
</tr>
</thead>
<tbody>
{{ if .Products }}
{{ range .Products }}
<tr>
<td style="width:56px;">
{{ with index $.ImageMap .ID }}
<img src="{{ . }}" alt="thumb" class="rounded"
style="width:48px;height:48px;object-fit:cover;">
{{ else }}
<div class="bg-secondary rounded" style="width:48px;height:48px;"></div>
{{ end }}
</td>
<td>{{ .Title }}</td>
<td>
{{ range .Categories }}
<span class="badge bg-secondary me-1">{{ .Title }}</span>
{{ end }}
</td>
<td>
{{ range .Tags }}
<span class="badge bg-info text-dark me-1">{{ .Name }}</span>
{{ end }}
</td>
<td class="text-muted small">{{ .CreatedAt.Format "2006-01-02" }}</td>
<td class="text-end">
<div class="btn-group" role="group">
{{ if $.ShowDeleted }}
<form action="/admin/products/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success" title="Geri Yükle"><i
class="bi bi-arrow-counterclockwise"></i></button>
</form>
{{ else }}
<a href="/admin/products/{{ .ID }}/edit" class="btn btn-sm btn-outline-secondary"
title="Düzenle"><i class="bi bi-pencil"></i></a>
<form action="/admin/products/{{ .ID }}/delete" method="POST"
onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger" title="Sil"><i
class="bi bi-trash"></i></button>
</form>
{{ end }}
</div>
</td>
</tr>
{{ end }}
{{ else }}
<tr>
<td colspan="6" class="text-center text-muted py-4">Kayıt yok</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
{{ if gt .TotalPages 1 }}
<div class="d-flex justify-content-end mt-3">
<nav>
<ul class="pagination pagination-sm mb-0">
<li class="page-item {{ if eq .Page 1 }}disabled{{ end }}"><a class="page-link"
href="/admin/content/products?page={{ .PrevPage }}">&laquo; Önceki</a></li>
<li class="page-item disabled"><span class="page-link">Sayfa {{ .Page }} / {{ .TotalPages
}}</span></li>
<li class="page-item {{ if eq .Page .TotalPages }}disabled{{ end }}"><a class="page-link"
href="/admin/content/products?page={{ .NextPage }}">Sonraki &raquo;</a></li>
</ul>
</nav>
</div>
{{ end }}
</div>
</div>
</div>

View File

@@ -0,0 +1,538 @@
<div class="container-fluid" hx-trigger="heroListChanged from:body" hx-get="/admin/content/settings" hx-target="this"
hx-swap="outerHTML">
<h2 class="mb-4">Ayarlar & Banner Yönetimi</h2>
<ul class="nav nav-tabs mb-4" id="settingsTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="general-tab" data-bs-toggle="tab" data-bs-target="#general"
type="button" role="tab">Genel Ayarlar</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="security-tab" data-bs-toggle="tab" data-bs-target="#security" type="button" role="tab">Güvenlik</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="hero-tab" data-bs-toggle="tab" data-bs-target="#hero" type="button"
role="tab">Banner (Hero) Yönetimi</button>
</li>
</ul>
<div class="tab-content" id="settingsTabContent">
<!-- General Settings Tab -->
<div class="tab-pane fade show active" id="general" role="tabpanel">
<div class="card border-0 shadow-sm">
<div class="card-body">
<form action="/admin/settings" method="POST" enctype="multipart/form-data">
<div class="row">
<div class="col-md-6 mb-3">
<label for="title" class="form-label">Site Başlığı</label>
<input type="text" class="form-control" id="title" name="title"
value="{{ .Setting.Title }}">
</div>
<div class="col-md-6 mb-3">
<label for="meta_title" class="form-label">Meta Başlık</label>
<input type="text" class="form-control" id="meta_title" name="meta_title"
value="{{ .Setting.MetaTitle }}">
</div>
</div>
<div class="mb-3">
<label for="meta_description" class="form-label">Meta Açıklama</label>
<textarea class="form-control" id="meta_description" name="meta_description"
rows="2">{{ .Setting.MetaDescription }}</textarea>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="email" class="form-label">E-posta</label>
<input type="email" class="form-control" id="email" name="email"
value="{{ .Setting.Email }}">
</div>
<div class="col-md-6 mb-3">
<label for="phone" class="form-label">Telefon</label>
<input type="text" class="form-control" id="phone" name="phone"
value="{{ .Setting.Phone }}">
</div>
</div>
<div class="mb-3">
<label for="address" class="form-label">Adres</label>
<textarea class="form-control" id="address" name="address"
rows="2">{{ .Setting.Address }}</textarea>
</div>
<div class="mb-3">
<label for="url" class="form-label">Site URL</label>
<input type="text" class="form-control" id="url" name="url" value="{{ .Setting.URL }}">
</div>
<h5 class="mt-4">Logo Ayarları</h5>
<div class="row">
<!-- White Logo (WLogo) -->
<div class="col-md-6">
<div class="card border p-3 mb-3">
<h6 class="mb-3">Beyaz Logo (Genellikle Koyu Zemin İçin)</h6>
<div class="mb-3">
<label class="form-label">Logo Yükle</label>
<input type="file" class="form-control" name="w_logo" accept="image/*">
{{ if .Setting.WLogo }}
<div class="mt-2 text-center p-2 logo-preview logo-preview-light border rounded">
<img src="{{ .Setting.WLogo }}" alt="WLogo" style="max-height: 50px;">
</div>
{{ end }}
</div>
<div class="row g-2">
<div class="col-6">
<label class="form-label small text-muted mb-0">Genişlik</label>
<input type="number" class="form-control form-control-sm" name="w_width"
placeholder="Otomatik" value="{{ .Setting.WWidth }}">
</div>
<div class="col-6">
<label class="form-label small text-muted mb-0">Yükseklik</label>
<input type="number" class="form-control form-control-sm" name="w_height"
placeholder="Otomatik" value="{{ .Setting.WHeight }}">
</div>
<div class="col-6">
<label class="form-label small text-muted mb-0">Kalite (1-100)</label>
<input type="number" class="form-control form-control-sm" name="w_quality"
placeholder="80" value="{{ .Setting.WQuality }}">
</div>
<div class="col-6">
<label class="form-label small text-muted mb-0">Format</label>
<select class="form-select form-select-sm" name="w_format">
<option value="avif" {{ if eq .Setting.WFormat "avif" }}selected{{ end
}}>AVIF (Önerilen)</option>
<option value="webp" {{ if eq .Setting.WFormat "webp" }}selected{{ end
}}>WebP</option>
<option value="png" {{ if eq .Setting.WFormat "png" }}selected{{ end }}>
PNG</option>
<option value="jpg" {{ if eq .Setting.WFormat "jpg" }}selected{{ end }}>
JPG</option>
</select>
</div>
</div>
</div>
</div>
<!-- Black Logo (BLogo) -->
<div class="col-md-6">
<div class="card border p-3 mb-3">
<h6 class="mb-3">Siyah Logo (Genellikle Beyaz Zemin İçin)</h6>
<div class="mb-3">
<label class="form-label">Logo Yükle</label>
<input type="file" class="form-control" name="b_logo" accept="image/*">
{{ if .Setting.BLogo }}
<div class="mt-2 text-center p-2 logo-preview logo-preview-light border rounded">
<img src="{{ .Setting.BLogo }}" alt="BLogo" style="max-height: 50px;">
</div>
{{ end }}
</div>
<div class="row g-2">
<div class="col-6">
<label class="form-label small text-muted mb-0">Genişlik</label>
<input type="number" class="form-control form-control-sm" name="b_width"
placeholder="Otomatik" value="{{ .Setting.BWidth }}">
</div>
<div class="col-6">
<label class="form-label small text-muted mb-0">Yükseklik</label>
<input type="number" class="form-control form-control-sm" name="b_height"
placeholder="Otomatik" value="{{ .Setting.BHeight }}">
</div>
<div class="col-6">
<label class="form-label small text-muted mb-0">Kalite (1-100)</label>
<input type="number" class="form-control form-control-sm" name="b_quality"
placeholder="80" value="{{ .Setting.BQuality }}">
</div>
<div class="col-6">
<label class="form-label small text-muted mb-0">Format</label>
<select class="form-select form-select-sm" name="b_format">
<option value="avif" {{ if eq .Setting.BFormat "avif" }}selected{{ end
}}>AVIF (Önerilen)</option>
<option value="webp" {{ if eq .Setting.BFormat "webp" }}selected{{ end
}}>WebP</option>
<option value="png" {{ if eq .Setting.BFormat "png" }}selected{{ end }}>
PNG</option>
<option value="jpg" {{ if eq .Setting.BFormat "jpg" }}selected{{ end }}>
JPG</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- CORS and Rate Limit moved to Güvenlik tab -->
<h5 class="mt-4">Sosyal Medya</h5>
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Instagram</label>
<input type="text" class="form-control" name="instagram"
value="{{ .Setting.Instagram }}">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Twitter (X)</label>
<input type="text" class="form-control" name="x" value="{{ .Setting.X }}">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Facebook</label>
<input type="text" class="form-control" name="facebook" value="{{ .Setting.Facebook }}">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Whatsapp</label>
<input type="text" class="form-control" name="whatsapp" value="{{ .Setting.Whatsapp }}">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Linkedin</label>
<input type="text" class="form-control" name="linkedin" value="{{ .Setting.Linkedin }}">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Pinterest</label>
<input type="text" class="form-control" name="pinterest"
value="{{ .Setting.Pinterest }}">
</div>
</div>
<div class="mb-3 form-check form-switch">
<input class="form-check-input" type="checkbox" id="is_active" name="is_active" {{ if
.Setting.IsActive }}checked{{ end }}>
<label class="form-check-label" for="is_active">Site Aktif</label>
</div>
<button type="submit" class="btn btn-primary">Kaydet</button>
</form>
</div>
</div>
</div>
<!-- Security Tab (CORS / Rate Limits) -->
<div class="tab-pane fade" id="security" role="tabpanel">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">CORS Yönetimi</h5>
<div class="btn-group">
<a href="/admin/content/settings" class="btn btn-outline-primary {{ if not .ShowDeleted }}active{{ end }}">Aktif</a>
<a href="/admin/content/settings?deleted=true" class="btn btn-outline-danger {{ if .ShowDeleted }}active{{ end }}">Silinenler</a>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="card border p-3 mb-3">
<h6 class="mb-3">Whitelist (İzinli Origin'ler)</h6>
{{ if .EditWhitelist.ID }}
<form action="/admin/settings/cors/whitelist/{{ .EditWhitelist.ID }}/update" method="POST" class="mb-3">
<div class="input-group mb-2">
<input type="text" name="origin" class="form-control" placeholder="https://example.com" required value="{{ .EditWhitelist.Origin }}">
</div>
<div class="mb-2">
<input type="text" name="description" class="form-control" placeholder="Açıklama (opsiyonel)" value="{{ .EditWhitelist.Description }}">
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="wl_active" name="is_active" {{ if .EditWhitelist.IsActive }}checked{{ end }}>
<label class="form-check-label" for="wl_active">Aktif</label>
</div>
<div class="d-flex gap-2 justify-content-end">
<a href="/admin/content/settings" class="btn btn-sm btn-secondary">İptal</a>
<button class="btn btn-sm btn-primary" type="submit">Güncelle</button>
</div>
</form>
{{ else }}
<form action="/admin/settings/cors/whitelist/create" method="POST" class="mb-3">
<div class="input-group mb-2">
<input type="text" name="origin" class="form-control" placeholder="https://example.com" required>
</div>
<div class="mb-2">
<input type="text" name="description" class="form-control" placeholder="Açıklama (opsiyonel)">
</div>
<div class="d-flex gap-2 justify-content-end">
<button class="btn btn-sm btn-primary" type="submit">Ekle</button>
</div>
</form>
{{ end }}
<div>
<ul class="list-group list-group-flush">
{{ if .CorsWhitelist }}
{{ range .CorsWhitelist }}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>{{ .Origin }}</strong>
<div class="small text-muted">{{ .Description }}</div>
</div>
<div class="btn-group">
{{ if $.ShowDeleted }}
<form action="/admin/settings/cors/whitelist/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success">Geri Yükle</button>
</form>
{{ else }}
<a href="/admin/content/settings?edit_whitelist={{ .ID }}" class="btn btn-sm btn-outline-secondary">Düzenle</a>
<form action="/admin/settings/cors/whitelist/{{ .ID }}/delete" method="POST" onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger">Sil</button>
</form>
{{ end }}
</div>
</li>
{{ end }}
{{ else }}
<li class="list-group-item">Kayıt yok</li>
{{ end }}
</ul>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card border p-3 mb-3">
<h6 class="mb-3">Blacklist (Yasaklı Origin'ler)</h6>
{{ if .EditBlacklist.ID }}
<form action="/admin/settings/cors/blacklist/{{ .EditBlacklist.ID }}/update" method="POST" class="mb-3">
<div class="input-group mb-2">
<input type="text" name="origin" class="form-control" placeholder="https://bad.com" required value="{{ .EditBlacklist.Origin }}">
</div>
<div class="mb-2">
<input type="text" name="reason" class="form-control" placeholder="Sebep (opsiyonel)" value="{{ .EditBlacklist.Reason }}">
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="bl_active" name="is_active" {{ if .EditBlacklist.IsActive }}checked{{ end }}>
<label class="form-check-label" for="bl_active">Aktif</label>
</div>
<div class="d-flex gap-2 justify-content-end">
<a href="/admin/content/settings" class="btn btn-sm btn-secondary">İptal</a>
<button class="btn btn-sm btn-primary" type="submit">Güncelle</button>
</div>
</form>
{{ else }}
<form action="/admin/settings/cors/blacklist/create" method="POST" class="mb-3">
<div class="input-group mb-2">
<input type="text" name="origin" class="form-control" placeholder="https://bad.com" required>
</div>
<div class="mb-2">
<input type="text" name="reason" class="form-control" placeholder="Sebep (opsiyonel)">
</div>
<div class="d-flex gap-2 justify-content-end">
<button class="btn btn-sm btn-primary" type="submit">Ekle</button>
</div>
</form>
{{ end }}
<div>
<ul class="list-group list-group-flush">
{{ if .CorsBlacklist }}
{{ range .CorsBlacklist }}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>{{ .Origin }}</strong>
<div class="small text-muted">{{ .Reason }}</div>
</div>
<div class="btn-group">
{{ if $.ShowDeleted }}
<form action="/admin/settings/cors/blacklist/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success">Geri Yükle</button>
</form>
{{ else }}
<a href="/admin/content/settings?edit_blacklist={{ .ID }}" class="btn btn-sm btn-outline-secondary">Düzenle</a>
<form action="/admin/settings/cors/blacklist/{{ .ID }}/delete" method="POST" onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger">Sil</button>
</form>
{{ end }}
</div>
</li>
{{ end }}
{{ else }}
<li class="list-group-item">Kayıt yok</li>
{{ end }}
</ul>
</div>
</div>
</div>
</div>
<h5 class="mb-3">Rate Limit Ayarları</h5>
<div class="card border p-3 mb-3">
{{ if .EditRateLimit.ID }}
<form action="/admin/settings/rate-limit/{{ .EditRateLimit.ID }}/update" method="POST" class="row g-2 mb-3">
<div class="col-md-3">
<input type="text" name="name" class="form-control" placeholder="İsim (ör. login)" required value="{{ .EditRateLimit.Name }}">
</div>
<div class="col-md-3">
<input type="number" name="max_requests" class="form-control" placeholder="Max istek" required value="{{ .EditRateLimit.MaxRequests }}">
</div>
<div class="col-md-3">
<input type="number" name="window_seconds" class="form-control" placeholder="Pencere (s)" required value="{{ .EditRateLimit.WindowSeconds }}">
</div>
<div class="col-md-3 d-flex gap-2">
<a href="/admin/content/settings" class="btn btn-secondary">İptal</a>
<button class="btn btn-primary" type="submit">Güncelle</button>
</div>
</form>
{{ else }}
<form action="/admin/settings/rate-limit/create" method="POST" class="row g-2 mb-3">
<div class="col-md-3">
<input type="text" name="name" class="form-control" placeholder="İsim (ör. login)" required>
</div>
<div class="col-md-3">
<input type="number" name="max_requests" class="form-control" placeholder="Max istek" required>
</div>
<div class="col-md-3">
<input type="number" name="window_seconds" class="form-control" placeholder="Pencere (s)" required>
</div>
<div class="col-md-3 d-flex gap-2">
<button class="btn btn-primary" type="submit">Ekle</button>
</div>
</form>
{{ end }}
<div>
<ul class="list-group list-group-flush">
{{ if .RateLimits }}
{{ range .RateLimits }}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>{{ .Name }}</strong>
<div class="small text-muted">{{ .MaxRequests }} req / {{ .WindowSeconds }}s</div>
</div>
<div class="btn-group">
{{ if $.ShowDeleted }}
<form action="/admin/settings/rate-limit/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success">Geri Yükle</button>
</form>
{{ else }}
<a href="/admin/content/settings?edit_ratelimit={{ .ID }}" class="btn btn-sm btn-outline-secondary">Düzenle</a>
<form action="/admin/settings/rate-limit/{{ .ID }}/delete" method="POST" onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger">Sil</button>
</form>
{{ end }}
</div>
</li>
{{ end }}
{{ else }}
<li class="list-group-item">Kayıt yok</li>
{{ end }}
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Hero Management Tab -->
<div class="tab-pane fade" id="hero" role="tabpanel">
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="btn-group" role="group">
<a href="/admin/content/settings"
class="btn btn-outline-primary {{ if not .ShowDeleted }}active{{ end }}">Aktif</a>
<a href="/admin/content/settings?deleted=true"
class="btn btn-outline-danger {{ if .ShowDeleted }}active{{ end }}">Silinenler</a>
</div>
<a href="/admin/heroes/new" class="btn btn-primary">
<i class="bi bi-plus-lg"></i> Yeni Banner Ekle
</a>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Görsel/Renk</th>
<th>Başlık</th>
<th>Alt Başlık</th>
<th>Durum</th>
<th>İşlemler</th>
</tr>
</thead>
<tbody>
{{ range .Heroes }}
<tr>
<td>
<div style="width: 50px; height: 30px; background-color: {{ .Color }}; border-radius: 4px; border: 1px solid #ddd;"
title="{{ .Color }}"></div>
</td>
<td>{{ .Title }}</td>
<td>{{ .Text1 }}</td>
<td>
{{ if .IsActive }}
<span class="badge bg-success">Aktif</span>
{{ else }}
<span class="badge bg-secondary">Pasif</span>
{{ end }}
</td>
<td>
<div class="d-flex gap-2">
{{ if $.ShowDeleted }}
<form action="/admin/heroes/{{ .ID }}/restore" method="POST"
onsubmit="return confirmRestore(event, this);">
<button type="submit" class="btn btn-sm btn-success" title="Geri Yükle">
<i class="bi bi-arrow-counterclockwise"></i>
</button>
</form>
{{ else }}
<a href="/admin/heroes/{{ .ID }}/edit"
class="btn btn-sm btn-outline-secondary" title="Düzenle">
<i class="bi bi-pencil"></i>
</a>
<form action="/admin/heroes/{{ .ID }}/delete" method="POST"
onsubmit="return confirmDelete(event, this);">
<button type="submit" class="btn btn-sm btn-outline-danger" title="Sil">
<i class="bi bi-trash"></i>
</button>
</form>
{{ end }}
</div>
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Simple tab state preservation using URL hash or local storage is complex with HTMX
// For now, let's just make sure tabs work via Bootstrap JS
// The tabs might reset to first tab on HTMX swap/update if we are not careful.
// Since we reload the whole #settings-container (this file) on update, tabs will reset.
// We can add a small script to check URL param or sessionStorage to activate correct tab.
(function(){
const STORAGE_KEY = 'admin.settings.activeTab';
// Save active tab id when clicked
document.querySelectorAll('#settingsTab button[data-bs-toggle="tab"]').forEach(btn => {
btn.addEventListener('shown.bs.tab', (e) => {
try { sessionStorage.setItem(STORAGE_KEY, e.target.id); } catch(e){}
});
});
// Restore on load
function restoreTab(){
try{
const id = sessionStorage.getItem(STORAGE_KEY);
if(id){
const btn = document.getElementById(id);
if(btn){
const tab = new bootstrap.Tab(btn);
tab.show();
}
}
}catch(e){}
}
document.addEventListener('DOMContentLoaded', restoreTab);
// HTMX: after swapping settings container, restore the tab
document.addEventListener('htmx:afterSwap', function(evt){
if(evt.detail.target && evt.detail.target.id === undefined){ return; }
const tgt = evt.detail.target;
if(tgt && (tgt.id === 'users-container' || tgt.id === 'main-content' || tgt.id === 'modal-area')){
// not our container
return;
}
// if settings container was swapped, restore
if(tgt && tgt.closest && tgt.closest('.container-fluid') && tgt.closest('.container-fluid').querySelector('#settingsTab')){
restoreTab();
}
});
})();
</script>

View File

@@ -0,0 +1,71 @@
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="mb-0">Taglar</h2>
<div class="d-flex gap-2 align-items-center">
<form class="d-flex" action="/admin/content/tags" method="GET">
<input type="search" name="search" class="form-control form-control-sm me-2" placeholder="Ara..." value="{{ .Search }}">
<button class="btn btn-sm btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</form>
<a href="/admin/tags/new" class="btn btn-sm btn-primary">Yeni Tag</a>
<div class="btn-group">
<a href="/admin/content/tags" class="btn btn-sm btn-outline-primary {{ if not .ShowDeleted }}active{{ end }}">Aktif</a>
<a href="/admin/content/tags?deleted=true" class="btn btn-sm btn-outline-danger {{ if .ShowDeleted }}active{{ end }}">Silinenler</a>
</div>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body p-3">
<div class="table-responsive">
<table class="table table-hover table-sm align-middle">
<thead class="bg-transparent text-muted small">
<tr>
<th>İsim</th>
<th class="text-muted">Oluşturma</th>
<th class="text-end text-muted">İşlemler</th>
</tr>
</thead>
<tbody>
{{ if .Tags }}
{{ range .Tags }}
<tr>
<td class="fw-semibold">{{ .Name }}</td>
<td class="text-muted small">{{ .CreatedAt.Format "2006-01-02" }}</td>
<td class="text-end">
<div class="btn-group" role="group">
{{ if $.ShowDeleted }}
<form action="/admin/tags/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success" title="Geri Yükle"><i class="bi bi-arrow-counterclockwise"></i></button>
</form>
{{ else }}
<a href="/admin/tags/{{ .ID }}/edit" class="btn btn-sm btn-outline-secondary" title="Düzenle"><i class="bi bi-pencil"></i></a>
<form action="/admin/tags/{{ .ID }}/delete" method="POST" onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger" title="Sil"><i class="bi bi-trash"></i></button>
</form>
{{ end }}
</div>
</td>
</tr>
{{ end }}
{{ else }}
<tr>
<td colspan="3" class="text-center text-muted py-4">Kayıt yok</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
{{ if gt .TotalPages 1 }}
<div class="d-flex justify-content-end mt-3">
<nav>
<ul class="pagination pagination-sm mb-0">
<li class="page-item {{ if eq .Page 1 }}disabled{{ end }}"><a class="page-link" href="/admin/content/tags?page={{ .PrevPage }}">&laquo; Önceki</a></li>
<li class="page-item disabled"><span class="page-link">Sayfa {{ .Page }} / {{ .TotalPages }}</span></li>
<li class="page-item {{ if eq .Page .TotalPages }}disabled{{ end }}"><a class="page-link" href="/admin/content/tags?page={{ .NextPage }}">Sonraki &raquo;</a></li>
</ul>
</nav>
</div>
{{ end }}
</div>
</div>
</div>

View File

@@ -0,0 +1,118 @@
<div class="container-fluid" hx-trigger="userListChanged from:body"
hx-get="/admin/content/users?page={{ .Page }}&search={{ .Search }}" hx-target="this" hx-swap="outerHTML">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Kullanıcılar</h2>
<div>
<input type="text" class="form-control d-inline-block w-auto me-2" placeholder="Ara..." name="search"
value="{{ .Search }}" hx-get="/admin/content/users" hx-trigger="keyup changed delay:500ms"
hx-target="#users-container" hx-push-url="true">
<div class="btn-group me-2" role="group">
<a href="/admin/content/users"
class="btn btn-outline-primary {{ if not .ShowDeleted }}active{{ end }}">Aktif</a>
<a href="/admin/content/users?deleted=true"
class="btn btn-outline-danger {{ if .ShowDeleted }}active{{ end }}">Silinenler</a>
</div>
<a href="/admin/users/new" class="btn btn-primary">
<i class="bi bi-plus-lg"></i> Yeni Kullanıcı
</a>
</div>
</div>
<div class="card border-0 shadow-sm" id="users-container">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>ID</th>
<th>Ad Soyad</th>
<th>Email</th>
<th>Rol</th>
<th>Durum</th>
<th>İşlemler</th>
</tr>
</thead>
<tbody>
{{ range .Users }}
<tr>
<td>{{ .ID }}</td>
<td>
<div class="d-flex align-items-center">
{{ if .Profile }}
{{ range .Profile }}
{{ if .AvatarURL }}
<img src="{{ .AvatarURL }}" class="rounded-circle me-2" width="32" height="32"
alt="">
{{ end }}
{{ end }}
{{ end }}
{{ .UserName }}
</div>
</td>
<td>{{ .Email }}</td>
<td>
{{ if isTrue .IsAdmin }}
<span class="badge bg-danger">Admin</span>
{{ else }}
<span class="badge bg-info">User</span>
{{ end }}
</td>
<td>
{{ if isTrue .EmailVerified }}
<span class="text-success"><i class="bi bi-check-circle-fill"></i> Onaylı</span>
{{ else }}
<span class="text-warning"><i class="bi bi-exclamation-circle-fill"></i> Bekliyor</span>
{{ end }}
</td>
<td>
<div class="d-flex gap-2">
{{ if $.ShowDeleted }}
<form action="/admin/users/{{ .ID }}/restore" method="POST"
onsubmit="return confirmRestore(event, this);">
<button type="submit" class="btn btn-sm btn-success" title="Geri Yükle">
<i class="bi bi-arrow-counterclockwise"></i> Geri Yükle
</button>
</form>
{{ else }}
<a href="/admin/users/{{ .ID }}/edit" class="btn btn-sm btn-outline-secondary"
title="Düzenle">
<i class="bi bi-pencil"></i>
</a>
<form action="/admin/users/{{ .ID }}/delete" method="POST"
onsubmit="return confirmDelete(event, this);">
<button type="submit" class="btn btn-sm btn-outline-danger" title="Sil">
<i class="bi bi-trash"></i>
</button>
</form>
{{ end }}
</div>
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
<!-- Pagination -->
{{ if gt .TotalPages 1 }}
<nav class="mt-3">
<ul class="pagination justify-content-center">
<li class="page-item {{ if le .Page 1 }}disabled{{ end }}">
<a class="page-link" href="#"
hx-get="/admin/content/users?page={{ .PrevPage }}&search={{ .Search }}"
hx-target="#users-container">Önceki</a>
</li>
<li class="page-item disabled"><span class="page-link">{{ .Page }} / {{ .TotalPages }}</span></li>
<li class="page-item {{ if ge .Page .TotalPages }}disabled{{ end }}">
<a class="page-link" href="#"
hx-get="/admin/content/users?page={{ .NextPage }}&search={{ .Search }}"
hx-target="#users-container">Sonraki</a>
</li>
</ul>
</nav>
{{ end }}
</div>
</div>
</div>

172
views/coming_soon.html Normal file
View File

@@ -0,0 +1,172 @@
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Çok Yakında - AreS Fiber API</title>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color: #0f172a;
--text-color: #f8fafc;
--accent-color: #3b82f6;
--secondary-color: #64748b;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Outfit', sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
position: relative;
}
/* Background Gradient Animation */
body::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(59, 130, 246, 0.1) 0%, transparent 60%);
animation: rotate 20s linear infinite;
z-index: -1;
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.container {
text-align: center;
padding: 2rem;
max-width: 600px;
z-index: 1;
opacity: 0;
animation: fadeIn 1s ease-out 0.5s forwards;
}
h1 {
font-size: 4rem;
font-weight: 700;
margin-bottom: 1rem;
background: linear-gradient(to right, #60a5fa, #3b82f6);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: -2px;
}
p {
font-size: 1.25rem;
color: var(--secondary-color);
margin-bottom: 2rem;
line-height: 1.6;
}
.divider {
width: 80px;
height: 4px;
background: var(--accent-color);
margin: 0 auto 2rem;
border-radius: 2px;
}
.social-links {
display: flex;
justify-content: center;
gap: 1.5rem;
margin-top: 3rem;
}
.social-link {
color: var(--secondary-color);
text-decoration: none;
font-size: 1.5rem;
transition: color 0.3s ease, transform 0.3s ease;
}
.social-link:hover {
color: var(--accent-color);
transform: translateY(-3px);
}
/* Countdown or Loading Animation (Optional visual element) */
.loader {
display: inline-block;
width: 50px;
height: 50px;
border: 3px solid rgba(255, 255, 255, 0.1);
border-radius: 50%;
border-top-color: var(--accent-color);
animation: spin 1s ease-in-out infinite;
margin-bottom: 2rem;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 768px) {
h1 {
font-size: 3rem;
}
p {
font-size: 1rem;
}
}
</style>
</head>
<body>
<div class="container">
<div class="loader"></div>
<h1>Çok Yakında</h1>
<div class="divider"></div>
<p>Harika bir şey hazırlıyoruz. Burası yakında harika özelliklerle dolu yeni anasayfamız olacak.</p>
<p>Bizi izlemeye devam edin!</p>
<div class="social-links">
<a href="#" class="social-link" title="Twitter">Twitter</a>
<a href="#" class="social-link" title="GitHub">GitHub</a>
<a href="#" class="social-link" title="Instagram">Instagram</a>
</div>
</div>
<script defer src="https://umami.beyhan.gen.tr/script.js" data-website-id="f92fa05e-bbcc-460b-8246-8e11085d832f"></script>
</body>
</html>