first commit
This commit is contained in:
442
views/admin/layout.html
Normal file
442
views/admin/layout.html
Normal 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
66
views/admin/login.html
Normal 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>
|
||||
75
views/admin/pages/category_form.html
Normal file
75
views/admin/pages/category_form.html
Normal 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">Açı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>
|
||||
113
views/admin/pages/hero_form.html
Normal file
113
views/admin/pages/hero_form.html
Normal 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">Açı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 }}
|
||||
175
views/admin/pages/post_form.html
Normal file
175
views/admin/pages/post_form.html
Normal 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>
|
||||
87
views/admin/pages/product_category_form.html
Normal file
87
views/admin/pages/product_category_form.html
Normal 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">Açı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>
|
||||
191
views/admin/pages/product_form.html
Normal file
191
views/admin/pages/product_form.html
Normal 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>
|
||||
22
views/admin/pages/product_tag_form.html
Normal file
22
views/admin/pages/product_tag_form.html
Normal 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>
|
||||
21
views/admin/pages/tag_form.html
Normal file
21
views/admin/pages/tag_form.html
Normal 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>
|
||||
79
views/admin/pages/user_form.html
Normal file
79
views/admin/pages/user_form.html
Normal 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>
|
||||
64
views/admin/partials/carts.html
Normal file
64
views/admin/partials/carts.html
Normal 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>
|
||||
82
views/admin/partials/categories.html
Normal file
82
views/admin/partials/categories.html
Normal 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 }}">« Ö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 »</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
71
views/admin/partials/category_views.html
Normal file
71
views/admin/partials/category_views.html
Normal 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 }}">« Ö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 »</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
74
views/admin/partials/comments.html
Normal file
74
views/admin/partials/comments.html
Normal 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 }}">« Ö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 »</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
75
views/admin/partials/dashboard.html
Normal file
75
views/admin/partials/dashboard.html
Normal 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>
|
||||
103
views/admin/partials/posts.html
Normal file
103
views/admin/partials/posts.html
Normal 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 }}">« Ö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 »</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
73
views/admin/partials/product_categories.html
Normal file
73
views/admin/partials/product_categories.html
Normal 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>
|
||||
46
views/admin/partials/product_category_views.html
Normal file
46
views/admin/partials/product_category_views.html
Normal 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>
|
||||
65
views/admin/partials/product_comments.html
Normal file
65
views/admin/partials/product_comments.html
Normal 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>
|
||||
63
views/admin/partials/product_tags.html
Normal file
63
views/admin/partials/product_tags.html
Normal 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>
|
||||
102
views/admin/partials/products.html
Normal file
102
views/admin/partials/products.html
Normal 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 }}">« Ö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 »</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
538
views/admin/partials/settings.html
Normal file
538
views/admin/partials/settings.html
Normal 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>
|
||||
71
views/admin/partials/tags.html
Normal file
71
views/admin/partials/tags.html
Normal 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 }}">« Ö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 »</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
118
views/admin/partials/users.html
Normal file
118
views/admin/partials/users.html
Normal 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
172
views/coming_soon.html
Normal 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>
|
||||
Reference in New Issue
Block a user