Compare commits
29 Commits
production
...
main
Author | SHA1 | Date | |
---|---|---|---|
78357efab7 | |||
0d87a843c2 | |||
|
982aee2a99 | ||
f09d6b8c9c | |||
fb8236daf3 | |||
07aac28e8a | |||
8a7d2eab98 | |||
44dfdec0b0 | |||
|
bfbc8db1fc | ||
|
2b945a5243 | ||
|
68479e8844 | ||
7cba5c3ebd | |||
0252dc8326 | |||
5c59fffc5c | |||
ead36a80de | |||
3b0879934e | |||
f5bab5de8a | |||
657daa7466 | |||
aeb1d56e85 | |||
31a39e9642 | |||
e6565fc33a | |||
bb5315fad5 | |||
43168c4273 | |||
eae9f9fd28 | |||
21d61178ac | |||
c5486575a3 | |||
acc32b08ca | |||
f38f8a286f | |||
6588ee8c46 |
41
backend/app/Http/Controllers/AdminAuthController.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class AdminAuthController extends Controller
|
||||
{
|
||||
public function showLogin()
|
||||
{
|
||||
return view('admin.auth.login');
|
||||
}
|
||||
|
||||
public function login(Request $request)
|
||||
{
|
||||
$credentials = $request->validate([
|
||||
'email' => ['required','email'],
|
||||
'password' => ['required'],
|
||||
]);
|
||||
|
||||
$remember = $request->boolean('remember');
|
||||
|
||||
if (Auth::guard('admin')->attempt($credentials, $remember)) {
|
||||
$request->session()->regenerate();
|
||||
return redirect()->intended(route('admin.dashboard'));
|
||||
}
|
||||
|
||||
return back()->withErrors([
|
||||
'email' => 'Email atau password salah.',
|
||||
])->onlyInput('email');
|
||||
}
|
||||
|
||||
public function logout(Request $request)
|
||||
{
|
||||
Auth::guard('admin')->logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
return redirect()->route('admin.login');
|
||||
}
|
||||
}
|
21
backend/app/Http/Controllers/Api/KategoriApiController.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Kategori;
|
||||
|
||||
class KategoriApiController extends Controller
|
||||
{
|
||||
// Ambil semua kategori
|
||||
public function index()
|
||||
{
|
||||
return response()->json(Kategori::all());
|
||||
}
|
||||
|
||||
// Ambil detail satu kategori
|
||||
public function show(Kategori $kategori)
|
||||
{
|
||||
return response()->json($kategori);
|
||||
}
|
||||
}
|
48
backend/app/Http/Controllers/Api/KhitanApiController.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Pelanggan;
|
||||
use App\Models\PelangganDetail;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class KhitanApiController extends Controller
|
||||
{
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nama_pemesan' => 'required|string|max:255',
|
||||
'no_hp' => 'required|string|max:20',
|
||||
'email' => 'required|email',
|
||||
'nama_anak' => 'required|string|max:255',
|
||||
'nama_orangtua' => 'required|string|max:255',
|
||||
'alamat' => 'required|string',
|
||||
'tanggal_acara' => 'required|date',
|
||||
'link_musik' => 'nullable|string',
|
||||
'kata_pengucapan' => 'nullable|string',
|
||||
'galeri' => 'nullable|string',
|
||||
'template_id' => 'required|exists:templates,id',
|
||||
]);
|
||||
|
||||
$pelanggan = Pelanggan::create([
|
||||
'nama_pemesan' => $data['nama_pemesan'],
|
||||
'nama_template' => 'Template Khitan',
|
||||
'kategori' => 'khitan',
|
||||
'email' => $data['email'],
|
||||
'no_tlpn' => $data['no_hp'],
|
||||
'harga' => 0,
|
||||
]);
|
||||
|
||||
PelangganDetail::create([
|
||||
'pelanggan_id' => $pelanggan->id,
|
||||
'detail_form' => $data,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Form khitan berhasil dikirim',
|
||||
'data' => $pelanggan->load('details')
|
||||
], 201);
|
||||
}
|
||||
}
|
49
backend/app/Http/Controllers/Api/PernikahanApiController.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Pelanggan;
|
||||
use App\Models\PelangganDetail;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PernikahanApiController extends Controller
|
||||
{
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nama_pemesan' => 'required|string|max:255',
|
||||
'no_hp' => 'required|string|max:20',
|
||||
'email' => 'required|email',
|
||||
'nama_pria' => 'required|string|max:255',
|
||||
'nama_wanita' => 'required|string|max:255',
|
||||
'alamat' => 'required|string',
|
||||
'tanggal_acara' => 'required|date',
|
||||
'link_undangan' => 'nullable|string',
|
||||
'kata_pengucapan' => 'nullable|string',
|
||||
'galeri' => 'nullable|string',
|
||||
'link_musik' => 'nullable|string',
|
||||
'template_id' => 'required|exists:templates,id',
|
||||
]);
|
||||
|
||||
$pelanggan = Pelanggan::create([
|
||||
'nama_pemesan' => $data['nama_pemesan'],
|
||||
'nama_template' => 'Template Pernikahan',
|
||||
'kategori' => 'pernikahan',
|
||||
'email' => $data['email'],
|
||||
'no_tlpn' => $data['no_hp'],
|
||||
'harga' => 0,
|
||||
]);
|
||||
|
||||
PelangganDetail::create([
|
||||
'pelanggan_id' => $pelanggan->id,
|
||||
'detail_form' => $data,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Form pernikahan berhasil dikirim',
|
||||
'data' => $pelanggan->load('details')
|
||||
], 201);
|
||||
}
|
||||
}
|
55
backend/app/Http/Controllers/Api/ReviewController.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Review;
|
||||
|
||||
class ReviewController extends Controller
|
||||
{
|
||||
// Ambil semua ulasan
|
||||
public function index()
|
||||
{
|
||||
$reviews = Review::all();
|
||||
return view('admin.reviews.index', compact('reviews'));
|
||||
}
|
||||
|
||||
|
||||
// Simpan ulasan baru
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'rating' => 'required|integer|min:1|max:5',
|
||||
'message' => 'required|string',
|
||||
'name' => 'required|string|max:100',
|
||||
'city' => 'required|string|max:100',
|
||||
]);
|
||||
|
||||
$review = Review::create($validated);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Ulasan berhasil disimpan',
|
||||
'data' => $review
|
||||
], 201);
|
||||
}
|
||||
|
||||
// Tampilkan ulasan tertentu
|
||||
public function show($id)
|
||||
{
|
||||
$review = Review::findOrFail($id);
|
||||
return response()->json($review, 200);
|
||||
}
|
||||
|
||||
|
||||
// Hapus ulasan
|
||||
public function destroy($id)
|
||||
{
|
||||
$review = Review::findOrFail($id);
|
||||
$review->delete();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Ulasan berhasil dihapus'
|
||||
], 200);
|
||||
}
|
||||
}
|
21
backend/app/Http/Controllers/Api/TemplateApiController.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Template;
|
||||
|
||||
class TemplateApiController extends Controller
|
||||
{
|
||||
// User hanya bisa lihat semua template
|
||||
public function index()
|
||||
{
|
||||
return response()->json(Template::with(['kategori','fitur'])->get());
|
||||
}
|
||||
|
||||
// User bisa lihat detail 1 template
|
||||
public function show(Template $template)
|
||||
{
|
||||
return response()->json($template->load(['kategori','fitur']));
|
||||
}
|
||||
}
|
51
backend/app/Http/Controllers/Api/UlangTahunApiController.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Pelanggan;
|
||||
use App\Models\PelangganDetail;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UlangTahunApiController extends Controller
|
||||
{
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nama_pemesan' => 'required|string|max:255',
|
||||
'no_hp' => 'required|string|max:20',
|
||||
'email' => 'required|email',
|
||||
'nama_panjang' => 'required|string|max:255',
|
||||
'nama_panggilan' => 'required|string|max:255',
|
||||
'ulang_tahun_ke' => 'required|integer',
|
||||
'anak_ke' => 'required|integer',
|
||||
'nama_orangtua' => 'required|string|max:255',
|
||||
'alamat' => 'required|string',
|
||||
'tanggal_acara' => 'required|date',
|
||||
'link_musik' => 'nullable|string',
|
||||
'kata_pengucapan' => 'nullable|string',
|
||||
'galeri' => 'nullable|string',
|
||||
'template_id' => 'required|exists:templates,id',
|
||||
]);
|
||||
|
||||
$pelanggan = Pelanggan::create([
|
||||
'nama_pemesan' => $data['nama_pemesan'],
|
||||
'nama_template' => 'Template Ulang Tahun',
|
||||
'kategori' => 'ulang_tahun',
|
||||
'email' => $data['email'],
|
||||
'no_tlpn' => $data['no_hp'],
|
||||
'harga' => 0,
|
||||
]);
|
||||
|
||||
PelangganDetail::create([
|
||||
'pelanggan_id' => $pelanggan->id,
|
||||
'detail_form' => $data,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Form ulang tahun berhasil dikirim',
|
||||
'data' => $pelanggan->load('details')
|
||||
], 201);
|
||||
}
|
||||
}
|
56
backend/app/Http/Controllers/FiturController.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Fitur;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FiturController extends Controller
|
||||
{
|
||||
// Tampilkan semua fitur (halaman admin)
|
||||
public function index()
|
||||
{
|
||||
$fitur = Fitur::all();
|
||||
return view('admin.fitur.index', compact('fitur'));
|
||||
}
|
||||
|
||||
// Form tambah fitur
|
||||
public function create()
|
||||
{
|
||||
return view('admin.fitur.create');
|
||||
}
|
||||
|
||||
// Simpan fitur baru
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'deskripsi' => 'required|string',
|
||||
]);
|
||||
|
||||
Fitur::create($data);
|
||||
|
||||
return redirect()->route('admin.fitur.index')->with('success', 'Fitur berhasil ditambahkan!');
|
||||
}
|
||||
|
||||
public function update(Request $request, Fitur $fitur)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'deskripsi' => 'required|string',
|
||||
]);
|
||||
|
||||
$fitur->update($data);
|
||||
|
||||
return redirect()->route('admin.fitur.index')->with('success', 'Fitur berhasil diperbarui!');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Hapus fitur
|
||||
public function destroy(Fitur $fitur)
|
||||
{
|
||||
$fitur->delete();
|
||||
|
||||
return redirect()->route('admin.fitur.index')->with('success', 'Fitur berhasil dihapus!');
|
||||
}
|
||||
}
|
64
backend/app/Http/Controllers/KategoriController.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Kategori;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class KategoriController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$kategori = Kategori::all();
|
||||
return view('admin.kategori.index', compact('kategori'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('admin.kategori.create');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nama' => 'required|string|max:255',
|
||||
'deskripsi' => 'nullable|string',
|
||||
'foto' => 'nullable|image|mimes:jpg,jpeg,png,gif|max:5120',
|
||||
]);
|
||||
|
||||
if ($request->hasFile('foto')) {
|
||||
$data['foto'] = $request->file('foto')->store('kategori', 'public');
|
||||
}
|
||||
|
||||
Kategori::create($data);
|
||||
return redirect()->route('admin.kategori.index')->with('success', 'Kategori berhasil ditambahkan!');
|
||||
}
|
||||
|
||||
public function edit(Kategori $kategori)
|
||||
{
|
||||
return view('admin.kategori.edit', compact('kategori'));
|
||||
}
|
||||
|
||||
public function update(Request $request, Kategori $kategori)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nama' => 'required|string|max:255',
|
||||
'deskripsi' => 'nullable|string',
|
||||
'foto' => 'nullable|image|mimes:jpg,jpeg,png,gif|max:5120',
|
||||
]);
|
||||
|
||||
if ($request->hasFile('foto')) {
|
||||
$data['foto'] = $request->file('foto')->store('kategori', 'public');
|
||||
}
|
||||
|
||||
$kategori->update($data);
|
||||
return redirect()->route('admin.kategori.index')->with('success', 'Kategori berhasil diperbarui!');
|
||||
}
|
||||
|
||||
public function destroy(Kategori $kategori)
|
||||
{
|
||||
$kategori->delete();
|
||||
return redirect()->route('admin.kategori.index')->with('success', 'Kategori berhasil dihapus!');
|
||||
}
|
||||
}
|
29
backend/app/Http/Controllers/PelangganController.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Pelanggan;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PelangganController extends Controller
|
||||
{
|
||||
// Tampilkan semua pelanggan (admin)
|
||||
public function index()
|
||||
{
|
||||
$pelanggans = Pelanggan::all();
|
||||
return view('admin.pelanggan.index', compact('pelanggans'));
|
||||
}
|
||||
|
||||
// Detail pelanggan
|
||||
public function show(Pelanggan $pelanggan)
|
||||
{
|
||||
return view('admin.pelanggan.show', compact('pelanggan'));
|
||||
}
|
||||
|
||||
// Hapus pelanggan
|
||||
public function destroy(Pelanggan $pelanggan)
|
||||
{
|
||||
$pelanggan->delete();
|
||||
return redirect()->route('admin.pelanggan.index')->with('success', 'Pelanggan berhasil dihapus!');
|
||||
}
|
||||
}
|
82
backend/app/Http/Controllers/TemplateController.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Template;
|
||||
use App\Models\Kategori;
|
||||
use App\Models\Fitur;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class TemplateController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$templates = Template::with(['kategori','fitur'])->get();
|
||||
$kategoris = Kategori::all();
|
||||
$fiturs = Fitur::all();
|
||||
|
||||
return view('admin.templates.index', compact('templates', 'kategoris', 'fiturs'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nama_template' => 'required|string|max:255',
|
||||
'kategori_id' => 'required|exists:kategoris,id',
|
||||
'fitur_id' => 'required|exists:fiturs,id',
|
||||
'foto' => 'nullable|image|mimes:jpg,jpeg,png,gif|max:5120',
|
||||
'harga' => 'required|numeric|min:0'
|
||||
]);
|
||||
|
||||
if ($request->hasFile('foto')) {
|
||||
$data['foto'] = $request->file('foto')->store('templates', 'public');
|
||||
}
|
||||
|
||||
Template::create($data);
|
||||
return redirect()->route('templates.index')->with('success', 'Template berhasil ditambahkan!');
|
||||
}
|
||||
|
||||
public function update(Request $request, Template $template)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nama_template' => 'required|string|max:255',
|
||||
'kategori_id' => 'required|exists:kategoris,id',
|
||||
'fitur_id' => 'required|exists:fiturs,id',
|
||||
'foto' => 'nullable|image|mimes:jpg,jpeg,png,gif|max:5120',
|
||||
'harga' => 'required|numeric|min:0'
|
||||
]);
|
||||
|
||||
if ($request->hasFile('foto')) {
|
||||
if ($template->foto && Storage::disk('public')->exists($template->foto)) {
|
||||
Storage::disk('public')->delete($template->foto);
|
||||
}
|
||||
$data['foto'] = $request->file('foto')->store('templates', 'public');
|
||||
}
|
||||
|
||||
$template->update($data);
|
||||
return redirect()->route('templates.index')->with('success', 'Template berhasil diperbarui!');
|
||||
}
|
||||
|
||||
public function destroy(Template $template)
|
||||
{
|
||||
if ($template->foto && Storage::disk('public')->exists($template->foto)) {
|
||||
Storage::disk('public')->delete($template->foto);
|
||||
}
|
||||
|
||||
$template->delete();
|
||||
return redirect()->route('templates.index')->with('success', 'Template berhasil dihapus!');
|
||||
}
|
||||
|
||||
public function byKategori($id)
|
||||
{
|
||||
$kategori = Kategori::findOrFail($id);
|
||||
$templates = Template::with(['kategori','fitur'])
|
||||
->where('kategori_id', $id)
|
||||
->get();
|
||||
$kategoris = Kategori::all();
|
||||
$fiturs = Fitur::all();
|
||||
|
||||
return view('admin.templates.index', compact('templates', 'kategoris', 'fiturs', 'kategori'));
|
||||
}
|
||||
}
|
15
backend/app/Models/Admin.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class Admin extends Authenticatable
|
||||
{
|
||||
use Notifiable;
|
||||
|
||||
protected $fillable = ['name','email','password'];
|
||||
|
||||
protected $hidden = ['password','remember_token'];
|
||||
}
|
15
backend/app/Models/Fitur.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
// app/Models/Fitur.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Fitur extends Model
|
||||
{
|
||||
protected $fillable = ['deskripsi'];
|
||||
|
||||
public function templates()
|
||||
{
|
||||
return $this->hasMany(Template::class);
|
||||
}
|
||||
}
|
15
backend/app/Models/Kategori.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
// app/Models/Kategori.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Kategori extends Model
|
||||
{
|
||||
protected $fillable = ['nama', 'deskripsi', 'foto'];
|
||||
|
||||
public function templates()
|
||||
{
|
||||
return $this->hasMany(Template::class);
|
||||
}
|
||||
}
|
19
backend/app/Models/Khitan.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
// app/Models/Khitan.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Khitan extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'template_id', 'nama_pemesan', 'no_hp', 'email',
|
||||
'nama_panjang', 'nama_pendek', 'nama_orangtua',
|
||||
'alamat', 'tanggal_acara', 'link_musik', 'galeri',
|
||||
'kata_pengucapan'
|
||||
];
|
||||
|
||||
public function template() {
|
||||
return $this->belongsTo(Template::class);
|
||||
}
|
||||
}
|
25
backend/app/Models/Pelanggan.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Pelanggan extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'nama_pemesan',
|
||||
'nama_template',
|
||||
'kategori',
|
||||
'email',
|
||||
'no_tlpn',
|
||||
'harga',
|
||||
];
|
||||
|
||||
public function details()
|
||||
{
|
||||
return $this->hasOne(PelangganDetail::class);
|
||||
}
|
||||
}
|
22
backend/app/Models/PelangganDetail.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PelangganDetail extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['pelanggan_id', 'detail_form'];
|
||||
|
||||
protected $casts = [
|
||||
'detail_form' => 'array',
|
||||
];
|
||||
|
||||
public function pelanggan()
|
||||
{
|
||||
return $this->belongsTo(Pelanggan::class);
|
||||
}
|
||||
}
|
18
backend/app/Models/Pernikahan.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
// app/Models/Pernikahan.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Pernikahan extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'template_id', 'nama_pemesan', 'no_hp', 'email',
|
||||
'nama_pria', 'nama_wanita', 'alamat', 'tanggal_acara',
|
||||
'link_undangan', 'kata_pengucapan', 'galeri', 'link_musik'
|
||||
];
|
||||
|
||||
public function template() {
|
||||
return $this->belongsTo(Template::class);
|
||||
}
|
||||
}
|
18
backend/app/Models/Review.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Review extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'rating',
|
||||
'message',
|
||||
'name',
|
||||
'city',
|
||||
];
|
||||
}
|
30
backend/app/Models/Template.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
// app/Models/Template.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Template extends Model
|
||||
{
|
||||
protected $fillable = ['nama_template', 'kategori_id', 'fitur_id', 'foto', 'harga'];
|
||||
|
||||
public function kategori() {
|
||||
return $this->belongsTo(Kategori::class);
|
||||
}
|
||||
|
||||
public function fitur() {
|
||||
return $this->belongsTo(Fitur::class);
|
||||
}
|
||||
|
||||
public function pernikahan() {
|
||||
return $this->hasOne(Pernikahan::class);
|
||||
}
|
||||
|
||||
public function ulangTahun() {
|
||||
return $this->hasOne(UlangTahun::class);
|
||||
}
|
||||
|
||||
public function khitan() {
|
||||
return $this->hasOne(Khitan::class);
|
||||
}
|
||||
}
|
19
backend/app/Models/UlangTahun.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
// app/Models/UlangTahun.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UlangTahun extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'template_id', 'nama_pemesan', 'no_hp', 'email',
|
||||
'nama_panjang', 'nama_panggilan', 'ulang_tahun_ke', 'anak_ke',
|
||||
'nama_orangtua', 'alamat', 'tanggal_acara', 'link_musik',
|
||||
'kata_pengucapan', 'galeri'
|
||||
];
|
||||
|
||||
public function template() {
|
||||
return $this->belongsTo(Template::class);
|
||||
}
|
||||
}
|
@ -2,94 +2,39 @@
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Defaults
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default authentication "guard" and password
|
||||
| reset options for your application. You may change these defaults
|
||||
| as required, but they're a perfect start for most applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'guard' => 'web',
|
||||
'passwords' => 'users',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, you may define every authentication guard for your application.
|
||||
| Of course, a great default configuration has been defined for you
|
||||
| here which uses session storage and the Eloquent user provider.
|
||||
|
|
||||
| All authentication drivers have a user provider. This defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| mechanisms used by this application to persist your user's data.
|
||||
|
|
||||
| Supported: "session"
|
||||
|
|
||||
*/
|
||||
|
||||
'guards' => [
|
||||
// Guard untuk user biasa
|
||||
'web' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
|
||||
// Guard untuk admin
|
||||
'admin' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'admins',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All authentication drivers have a user provider. This defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| mechanisms used by this application to persist your user's data.
|
||||
|
|
||||
| If you have multiple user tables or models you may configure multiple
|
||||
| sources which represent each model / table. These sources may then
|
||||
| be assigned to any extra authentication guards you have defined.
|
||||
|
|
||||
| Supported: "database", "eloquent"
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => [
|
||||
// Provider untuk user biasa
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\Models\User::class,
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
// Provider untuk admin
|
||||
'admins' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\Models\Admin::class,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Resetting Passwords
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may specify multiple password reset configurations if you have more
|
||||
| than one user table or model in the application and you want to have
|
||||
| separate password reset settings based on the specific user types.
|
||||
|
|
||||
| The expire time is the number of minutes that each reset token will be
|
||||
| considered valid. This security feature keeps tokens short-lived so
|
||||
| they have less time to be guessed. You may change this as needed.
|
||||
|
|
||||
| The throttle setting is the number of seconds a user must wait before
|
||||
| generating more password reset tokens. This prevents the user from
|
||||
| quickly generating a very large amount of password reset tokens.
|
||||
|
|
||||
*/
|
||||
|
||||
'passwords' => [
|
||||
'users' => [
|
||||
'provider' => 'users',
|
||||
@ -97,19 +42,14 @@ return [
|
||||
'expire' => 60,
|
||||
'throttle' => 60,
|
||||
],
|
||||
'admins' => [
|
||||
'provider' => 'admins',
|
||||
'table' => 'password_reset_tokens',
|
||||
'expire' => 60,
|
||||
'throttle' => 60,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Confirmation Timeout
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the amount of seconds before a password confirmation
|
||||
| times out and the user is prompted to re-enter their password via the
|
||||
| confirmation screen. By default, the timeout lasts for three hours.
|
||||
|
|
||||
*/
|
||||
|
||||
'password_timeout' => 10800,
|
||||
|
||||
];
|
||||
|
23
backend/database/factories/TemplateFactory.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Template>
|
||||
*/
|
||||
class TemplateFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('admins', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('admins');
|
||||
}
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
// database/migrations/2025_09_08_000001_create_kategoris_table.php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void {
|
||||
Schema::create('kategoris', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama');
|
||||
$table->text('deskripsi')->nullable();
|
||||
$table->string('foto')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
Schema::dropIfExists('kategoris');
|
||||
}
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('reviews', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->tinyInteger('rating'); // nilai bintang 1–5
|
||||
$table->text('message');
|
||||
$table->string('name');
|
||||
$table->string('city');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('reviews');
|
||||
}
|
||||
};
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
// database/migrations/2025_09_08_000002_create_fiturs_table.php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void {
|
||||
Schema::create('fiturs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->text('deskripsi');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
Schema::dropIfExists('fiturs');
|
||||
}
|
||||
};
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
// database/migrations/2025_09_08_000003_create_templates_table.php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void {
|
||||
Schema::create('templates', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama_template');
|
||||
$table->foreignId('kategori_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('fitur_id')->constrained()->cascadeOnDelete();
|
||||
$table->decimal('harga', 10, 2)->default(0);
|
||||
$table->string('foto')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
Schema::dropIfExists('templates');
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('pelanggans', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama_pemesan');
|
||||
$table->string('nama_template');
|
||||
$table->string('kategori');
|
||||
$table->string('email');
|
||||
$table->string('no_tlpn');
|
||||
$table->decimal('harga', 15, 2)->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('pelanggans');
|
||||
}
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('pelanggan_details', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('pelanggan_id');
|
||||
$table->json('detail_form'); // data sesuai kategori
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('pelanggan_id')->references('id')->on('pelanggans')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('pelanggan_details');
|
||||
}
|
||||
};
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
// database/migrations/2025_09_08_000004_create_pernikahans_table.php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void {
|
||||
Schema::create('pernikahans', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('template_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('nama_pemesan');
|
||||
$table->string('no_hp');
|
||||
$table->string('email');
|
||||
$table->string('nama_pria');
|
||||
$table->string('nama_wanita');
|
||||
$table->string('alamat');
|
||||
$table->date('tanggal_acara');
|
||||
$table->string('link_undangan')->nullable();
|
||||
$table->text('kata_pengucapan')->nullable();
|
||||
$table->string('galeri')->nullable();
|
||||
$table->string('link_musik')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
Schema::dropIfExists('pernikahans');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
// database/migrations/2025_09_08_000005_create_ulang_tahuns_table.php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void {
|
||||
Schema::create('ulang_tahuns', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('template_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('nama_pemesan');
|
||||
$table->string('no_hp');
|
||||
$table->string('email');
|
||||
$table->string('nama_panjang');
|
||||
$table->string('nama_panggilan');
|
||||
$table->integer('ulang_tahun_ke');
|
||||
$table->integer('anak_ke');
|
||||
$table->string('nama_orangtua');
|
||||
$table->string('alamat');
|
||||
$table->date('tanggal_acara');
|
||||
$table->string('link_musik')->nullable();
|
||||
$table->text('kata_pengucapan')->nullable();
|
||||
$table->string('galeri')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
Schema::dropIfExists('ulang_tahuns');
|
||||
}
|
||||
};
|
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
// database/migrations/2025_09_08_000006_create_khitans_table.php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void {
|
||||
Schema::create('khitans', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('template_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('nama_pemesan');
|
||||
$table->string('no_hp');
|
||||
$table->string('email');
|
||||
$table->string('nama_panjang');
|
||||
$table->string('nama_pendek');
|
||||
$table->string('nama_orangtua');
|
||||
$table->string('alamat');
|
||||
$table->date('tanggal_acara');
|
||||
$table->string('link_musik')->nullable();
|
||||
$table->string('galeri')->nullable();
|
||||
$table->text('kata_pengucapan')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
Schema::dropIfExists('khitans');
|
||||
}
|
||||
};
|
||||
|
@ -2,16 +2,20 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use App\Models\Admin;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class AdminSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
//
|
||||
Admin::updateOrCreate(
|
||||
['email' => 'admin@example.com'],
|
||||
[
|
||||
'name' => 'Super Admin',
|
||||
'password' => Hash::make('password123'), // ganti setelah login
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -7,16 +7,10 @@ use Illuminate\Database\Seeder;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Seed the application's database.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// \App\Models\User::factory(10)->create();
|
||||
|
||||
// \App\Models\User::factory()->create([
|
||||
// 'name' => 'Test User',
|
||||
// 'email' => 'test@example.com',
|
||||
// ]);
|
||||
}
|
||||
{
|
||||
$this->call([
|
||||
AdminSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
BIN
backend/public/images/logo.png
Normal file
After Width: | Height: | Size: 20 KiB |
109
backend/resources/views/admin/auth/login.blade.php
Normal file
@ -0,0 +1,109 @@
|
||||
<!doctype html>
|
||||
<html lang="id">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Login Admin</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background: #ffffff;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: #EFEFEF;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0 25px rgb(0, 123, 255);
|
||||
width: 100%;
|
||||
max-width: 400px; /* batas lebar maksimum */
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
display: block;
|
||||
margin: 0 auto 15px;
|
||||
max-width: 130px;
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
background-color: #3bb9ff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.btn-login:hover {
|
||||
background-color: #1a8edb;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Responsive padding */
|
||||
@media (max-width: 576px) {
|
||||
.login-card {
|
||||
padding: 20px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="login-card">
|
||||
<div class="text-center mb-3">
|
||||
<!-- Logo -->
|
||||
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="login-logo">
|
||||
</div>
|
||||
|
||||
<h4 class="text-center mb-2 fw-bold">SELAMAT DATANG</h4>
|
||||
<p class="text-center text-muted mb-4">Silakan masukkan email dan password anda.</p>
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
{{ $errors->first() }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('admin.login.post') }}" method="POST">
|
||||
@csrf
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nama</label>
|
||||
<input type="email" name="email" value="{{ old('email') }}" class="form-control"
|
||||
placeholder="Masukkan nama Anda" required autofocus>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Kata Sandi</label>
|
||||
<input type="password" name="password" class="form-control"
|
||||
placeholder="Masukkan kata sandi Anda" required>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="remember" id="remember">
|
||||
<label class="form-check-label" for="remember">Ingat saya</label>
|
||||
</div>
|
||||
<a href="#" class="text-decoration-none mt-2 mt-sm-0">Lupa kata sandi</a>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-login text-white w-100">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
54
backend/resources/views/admin/dashboard.blade.php
Normal file
@ -0,0 +1,54 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Halaman Dasbor')
|
||||
|
||||
@section('content')
|
||||
<div class="container mx-auto py-4">
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold">Halaman Dasbor</h3>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="bg-blue-100 text-blue-600 px-3 py-2 rounded-lg flex items-center gap-2 text-sm">
|
||||
<i class="bi bi-clock-history"></i>
|
||||
{{ \Carbon\Carbon::now()->translatedFormat('l, d F Y') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="bg-white rounded-lg shadow-md p-4 flex justify-between items-center hover:shadow-lg transition-transform duration-300 transform hover:-translate-y-2">
|
||||
<div>
|
||||
<h5 class="text-gray-500 text-sm">Kategori</h5>
|
||||
<h3 class="font-bold text-xl">10</h3>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-blue-100 text-blue-600 rounded-lg flex items-center justify-center text-xl">
|
||||
<i class="bi bi-diagram-3"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-4 flex justify-between items-center hover:shadow-lg transition-transform duration-300 transform hover:-translate-y-2">
|
||||
<div>
|
||||
<h5 class="text-gray-500 text-sm">Templat</h5>
|
||||
<h3 class="font-bold text-xl">20</h3>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-blue-100 text-blue-600 rounded-lg flex items-center justify-center text-xl">
|
||||
<i class="bi bi-card-list"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-4 flex justify-between items-center hover:shadow-lg transition-transform duration-300 transform hover:-translate-y-2">
|
||||
<div>
|
||||
<h5 class="text-gray-500 text-sm">Pelanggan</h5>
|
||||
<h3 class="font-bold text-xl">24</h3>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-blue-100 text-blue-600 rounded-lg flex items-center justify-center text-xl">
|
||||
<i class="bi bi-person"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pesan login -->
|
||||
<div class="bg-green-100 text-green-700 p-4 rounded-lg mt-4">
|
||||
Berhasil login sebagai <strong>{{ auth('admin')->user()->name }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
164
backend/resources/views/admin/fitur/index.blade.php
Normal file
@ -0,0 +1,164 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Manajemen Fitur')
|
||||
|
||||
@section('content')
|
||||
<div class="container mx-auto py-4">
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold">Manajemen Fitur</h3>
|
||||
<button id="openTambahModal" class="bg-blue-600 text-white px-3 py-2.5 rounded flex items-center">
|
||||
<i class="bi bi-plus-lg mr-1"></i> Tambah Fitur
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tabel Fitur -->
|
||||
<div class="bg-white rounded-lg shadow-sm">
|
||||
<div class="p-4 overflow-x-auto">
|
||||
<table class="w-full table-fixed border border-gray-300 text-left">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="w-[10%] p-2 border border-gray-300 text-center">Nomor</th>
|
||||
<th class="w-[70%] p-2 border border-gray-300 text-center">Fitur</th>
|
||||
<th class="w-[20%] p-2 border border-gray-300 text-center">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($fitur as $key => $item)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="p-2 border border-gray-300">{{ $key + 1 }}</td>
|
||||
<td class="p-2 border border-gray-300 truncate whitespace-nowrap">{{ $item->deskripsi }}
|
||||
</td>
|
||||
<td class="p-2 border border-gray-300 text-center">
|
||||
<div class="flex justify-center space-x-2">
|
||||
<button class="text-blue-600 flex items-center pr-4 openEditModalBtn"
|
||||
data-id="{{ $item->id }}">
|
||||
<i class="bi bi-pencil mr-1"></i> Ubah
|
||||
</button>
|
||||
<form action="{{ route('admin.fitur.destroy', $item->id) }}" method="POST"
|
||||
class="inline" onsubmit="return confirm('Yakin mau hapus fitur ini?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button class="text-red-600 flex items-center">
|
||||
<i class="bi bi-trash mr-1"></i> Hapus
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="3" class="p-2 text-center text-gray-500">Belum ada fitur</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Tambah -->
|
||||
<div id="modalTambah" class="fixed inset-0 hidden items-center justify-center z-50">
|
||||
<div class="absolute inset-0 bg-black opacity-50" id="closeTambahModal"></div>
|
||||
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
|
||||
<form action="{{ route('admin.fitur.store') }}" method="POST">
|
||||
@csrf
|
||||
<div class="p-4 border-b">
|
||||
<h5 class="text-lg font-medium">Tambah Fitur</h5>
|
||||
</div>
|
||||
<div class="p-4 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Nama Fitur</label>
|
||||
<input type="text" name="deskripsi" class="w-full p-2 border rounded" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 border-t flex justify-end space-x-2">
|
||||
<button type="button" id="closeTambahBtn"
|
||||
class="bg-gray-300 text-black px-3 py-1 rounded">Batal</button>
|
||||
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Edit -->
|
||||
@foreach ($fitur as $item)
|
||||
<div id="modalEdit{{ $item->id }}" class="fixed inset-0 hidden items-center justify-center z-50">
|
||||
<div class="absolute inset-0 bg-black opacity-50 closeEditOverlay" data-id="{{ $item->id }}"></div>
|
||||
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
|
||||
<form action="{{ route('admin.fitur.update', $item->id) }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<div class="p-4 border-b">
|
||||
<h5 class="text-lg font-medium">Edit Fitur</h5>
|
||||
</div>
|
||||
<div class="p-4 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Nama Fitur</label>
|
||||
<input type="text" name="deskripsi" value="{{ $item->deskripsi }}"
|
||||
class="w-full p-2 border rounded" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 border-t flex justify-end space-x-2">
|
||||
<button type="button" class="bg-gray-300 text-black px-3 py-1 rounded closeEditBtn"
|
||||
data-id="{{ $item->id }}">Batal</button>
|
||||
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan Perubahan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<script>
|
||||
// Modal Tambah
|
||||
const openTambahModal = document.getElementById('openTambahModal');
|
||||
const modalTambah = document.getElementById('modalTambah');
|
||||
const closeTambahBtn = document.getElementById('closeTambahBtn');
|
||||
const closeTambahOverlay = document.getElementById('closeTambahModal');
|
||||
|
||||
openTambahModal.addEventListener('click', () => {
|
||||
modalTambah.classList.remove('hidden');
|
||||
modalTambah.classList.add('flex');
|
||||
});
|
||||
closeTambahBtn.addEventListener('click', () => {
|
||||
modalTambah.classList.add('hidden');
|
||||
modalTambah.classList.remove('flex');
|
||||
});
|
||||
closeTambahOverlay.addEventListener('click', () => {
|
||||
modalTambah.classList.add('hidden');
|
||||
modalTambah.classList.remove('flex');
|
||||
});
|
||||
|
||||
// Modal Edit
|
||||
const openEditBtns = document.querySelectorAll('.openEditModalBtn');
|
||||
const closeEditBtns = document.querySelectorAll('.closeEditBtn');
|
||||
const closeEditOverlays = document.querySelectorAll('.closeEditOverlay');
|
||||
|
||||
openEditBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.dataset.id;
|
||||
const modal = document.getElementById('modalEdit' + id);
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
});
|
||||
});
|
||||
|
||||
closeEditBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.dataset.id;
|
||||
const modal = document.getElementById('modalEdit' + id);
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
});
|
||||
});
|
||||
|
||||
closeEditOverlays.forEach(overlay => {
|
||||
overlay.addEventListener('click', () => {
|
||||
const id = overlay.dataset.id;
|
||||
const modal = document.getElementById('modalEdit' + id);
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
231
backend/resources/views/admin/kategori/index.blade.php
Normal file
@ -0,0 +1,231 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Manajemen Kategori')
|
||||
|
||||
@section('content')
|
||||
<div class="container mx-auto py-4">
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold">Manajemen Kategori</h3>
|
||||
<button id="openTambahModal" class="bg-blue-600 text-white px-3 py-2.5 rounded flex items-center">
|
||||
<i class="bi bi-plus-lg mr-1"></i> Tambah Kategori
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Flash Message -->
|
||||
@if (session('success'))
|
||||
<div id="toast-success" class="mb-4 p-3 rounded bg-green-100 text-green-800 border border-green-300 shadow">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
<script>
|
||||
setTimeout(() => {
|
||||
document.getElementById('toast-success')?.remove();
|
||||
}, 3000);
|
||||
</script>
|
||||
@endif
|
||||
|
||||
@if ($errors->any())
|
||||
<div id="toast-error" class="mb-4 p-3 rounded bg-red-100 text-red-800 border border-red-300 shadow">
|
||||
<ul class="list-disc ml-5">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
<script>
|
||||
setTimeout(() => {
|
||||
document.getElementById('toast-error')?.remove();
|
||||
}, 5000);
|
||||
</script>
|
||||
@endif
|
||||
|
||||
<!-- Tabel Kategori -->
|
||||
<div class="bg-white rounded-lg shadow-sm">
|
||||
<div class="p-4 overflow-x-auto">
|
||||
<table class="w-full table-fixed text-left border border-gray-300 border-collapse">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="p-2 border border-gray-300 w-[50px] text-center">Nomor</th>
|
||||
<th class="p-2 border border-gray-300 w-[150px] text-center">Nama Kategori</th>
|
||||
<th class="p-2 border border-gray-300 w-[300px] text-center">Keterangan</th>
|
||||
<th class="p-2 border border-gray-300 w-[90px] text-center">Foto</th>
|
||||
<th class="p-2 border border-gray-300 w-[110px] text-center">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($kategori as $key => $item)
|
||||
<tr>
|
||||
<td class="p-2 border border-gray-300 text-center truncate">{{ $key + 1 }}</td>
|
||||
<td class="p-2 border border-gray-300 truncate">{{ $item->nama }}</td>
|
||||
<td class="p-2 border border-gray-300 truncate">{{ $item->deskripsi ?? '-' }}</td>
|
||||
<td class="p-2 border border-gray-300 text-center">
|
||||
<div
|
||||
class="w-12 h-12 overflow-hidden rounded bg-gray-100 flex items-center justify-center mx-auto">
|
||||
<img src="{{ $item->foto ? asset('storage/' . $item->foto) : asset('default-image.png') }}"
|
||||
alt="foto" class="max-w-full max-h-full object-contain">
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-2 border border-gray-300 text-center">
|
||||
<div class="flex justify-center space-x-2">
|
||||
<button
|
||||
class="text-blue-600 hover:underline flex items-center pr-4 openEditModalBtn"
|
||||
data-id="{{ $item->id }}">
|
||||
<i class="bi bi-pencil mr-1"></i> Ubah
|
||||
</button>
|
||||
<form action="{{ route('admin.kategori.destroy', $item->id) }}" method="POST"
|
||||
class="inline" onsubmit="return confirm('Yakin mau hapus kategori ini?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button class="text-red-600 hover:underline flex items-center">
|
||||
<i class="bi bi-trash mr-1"></i> Hapus
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="p-2 text-center text-gray-500 border border-gray-300">Belum ada
|
||||
kategori</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Tambah -->
|
||||
<div id="modalTambah" class="fixed inset-0 hidden items-center justify-center z-50">
|
||||
<div class="absolute inset-0 bg-black opacity-50" id="closeTambahModal"></div>
|
||||
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
|
||||
<form action="{{ route('admin.kategori.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="p-4 border-b">
|
||||
<h5 class="text-lg font-medium">Tambah Kategori</h5>
|
||||
</div>
|
||||
<div class="p-4 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Nama</label>
|
||||
<input type="text" name="nama" class="w-full p-2 border rounded" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Deskripsi</label>
|
||||
<textarea name="deskripsi" class="w-full p-2 border rounded" rows="3"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Foto</label>
|
||||
<input type="file" name="foto" class="w-full p-2 border rounded" accept="image/*">
|
||||
<small class="text-gray-500">Format yang didukung: JPG, PNG, GIF. Maksimal 5MB.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 border-t flex justify-end space-x-2">
|
||||
<button type="button" id="closeTambahBtn"
|
||||
class="bg-gray-300 text-black px-3 py-1 rounded">Batal</button>
|
||||
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Edit -->
|
||||
@foreach ($kategori as $item)
|
||||
<div id="modalEdit{{ $item->id }}" class="fixed inset-0 hidden items-center justify-center z-50">
|
||||
<div class="absolute inset-0 bg-black opacity-50 closeEditOverlay" data-id="{{ $item->id }}"></div>
|
||||
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
|
||||
<form action="{{ route('admin.kategori.update', $item->id) }}" method="POST"
|
||||
enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<div class="p-4 border-b">
|
||||
<h5 class="text-lg font-medium">Edit Kategori</h5>
|
||||
</div>
|
||||
<div class="p-4 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Nama</label>
|
||||
<input type="text" name="nama" value="{{ $item->nama }}"
|
||||
class="w-full p-2 border rounded" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Deskripsi</label>
|
||||
<textarea name="deskripsi" class="w-full p-2 border rounded" rows="3">{{ $item->deskripsi }}</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Foto</label>
|
||||
<input type="file" name="foto" class="w-full p-2 border rounded" accept="image/*">
|
||||
<small class="text-gray-500">Format yang didukung: JPG, PNG, GIF. Maksimal 5MB.</small>
|
||||
@if ($item->foto)
|
||||
<div class="mt-2">
|
||||
<small class="text-gray-500">Foto saat ini:</small><br>
|
||||
<div
|
||||
class="w-20 h-20 mt-1 overflow-hidden rounded bg-gray-100 flex items-center justify-center border">
|
||||
<img src="{{ asset('storage/' . $item->foto) }}" alt="foto"
|
||||
class="max-w-full max-h-full object-contain">
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 border-t flex justify-end space-x-2">
|
||||
<button type="button" class="bg-gray-300 text-black px-3 py-1 rounded closeEditBtn"
|
||||
data-id="{{ $item->id }}">Batal</button>
|
||||
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan Perubahan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<script>
|
||||
// Modal Tambah
|
||||
const openTambahModal = document.getElementById('openTambahModal');
|
||||
const modalTambah = document.getElementById('modalTambah');
|
||||
const closeTambahBtn = document.getElementById('closeTambahBtn');
|
||||
const closeTambahOverlay = document.getElementById('closeTambahModal');
|
||||
|
||||
openTambahModal.addEventListener('click', () => {
|
||||
modalTambah.classList.remove('hidden');
|
||||
modalTambah.classList.add('flex');
|
||||
});
|
||||
closeTambahBtn.addEventListener('click', () => {
|
||||
modalTambah.classList.add('hidden');
|
||||
modalTambah.classList.remove('flex');
|
||||
});
|
||||
closeTambahOverlay.addEventListener('click', () => {
|
||||
modalTambah.classList.add('hidden');
|
||||
modalTambah.classList.remove('flex');
|
||||
});
|
||||
|
||||
// Modal Edit
|
||||
const openEditBtns = document.querySelectorAll('.openEditModalBtn');
|
||||
const closeEditBtns = document.querySelectorAll('.closeEditBtn');
|
||||
const closeEditOverlays = document.querySelectorAll('.closeEditOverlay');
|
||||
|
||||
openEditBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.dataset.id;
|
||||
const modal = document.getElementById('modalEdit' + id);
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
});
|
||||
});
|
||||
|
||||
closeEditBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.dataset.id;
|
||||
const modal = document.getElementById('modalEdit' + id);
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
});
|
||||
});
|
||||
|
||||
closeEditOverlays.forEach(overlay => {
|
||||
overlay.addEventListener('click', () => {
|
||||
const id = overlay.dataset.id;
|
||||
const modal = document.getElementById('modalEdit' + id);
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
133
backend/resources/views/admin/pelanggan/index.blade.php
Normal file
@ -0,0 +1,133 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Manajemen Pelanggan')
|
||||
|
||||
@section('content')
|
||||
<div class="container mx-auto py-4">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold">Manajemen Pelanggan</h3>
|
||||
</div>
|
||||
|
||||
<!-- Flash Message -->
|
||||
@if (session('success'))
|
||||
<div id="toast-success" class="mb-4 p-3 rounded bg-green-100 text-green-800 border border-green-300 shadow">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
<script>
|
||||
setTimeout(() => document.getElementById('toast-success')?.remove(), 3000);
|
||||
</script>
|
||||
@endif
|
||||
|
||||
<!-- Tabel Pelanggan -->
|
||||
<div class="bg-white rounded-lg shadow-sm">
|
||||
<div class="p-4 overflow-x-auto">
|
||||
<table class="w-full text-left border-collapse border border-gray-300">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="p-2 border border-gray-300 text-center w-16">Nomor</th>
|
||||
<th class="p-2 border border-gray-300 text-center">Nama</th>
|
||||
<th class="p-2 border border-gray-300 text-center">Email</th>
|
||||
<th class="p-2 border border-gray-300 text-center">No. Telepon</th>
|
||||
<th class="p-2 border border-gray-300 text-center">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($pelanggans as $key => $pelanggan)
|
||||
<tr>
|
||||
<td class="p-2 border border-gray-300 text-center">{{ $key + 1 }}</td>
|
||||
<td class="p-2 border border-gray-300 truncate">{{ $pelanggan->nama_pemesan }}</td>
|
||||
<td class="p-2 border border-gray-300 truncate">{{ $pelanggan->email }}</td>
|
||||
<td class="p-2 border border-gray-300 truncate">{{ $pelanggan->no_tlpn ?? '-' }}</td>
|
||||
<td class="p-2 border border-gray-300 text-center">
|
||||
<div class="flex justify-center space-x-2">
|
||||
<a href="{{ route('admin.pelanggan.show', $pelanggan->id) }}"
|
||||
class="text-blue-600 hover:underline flex items-center">
|
||||
<i class="bi bi-eye mr-1"></i> Detail
|
||||
</a>
|
||||
<button class="text-red-600 hover:underline flex items-center openDeleteModalBtn"
|
||||
data-id="{{ $pelanggan->id }}">
|
||||
<i class="bi bi-trash mr-1"></i> Hapus
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="p-2 text-center text-gray-500 border border-gray-300">
|
||||
Belum ada pelanggan
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Hapus Pelanggan -->
|
||||
@foreach ($pelanggans as $pelanggan)
|
||||
<div id="modalDelete{{ $pelanggan->id }}" class="fixed inset-0 hidden items-center justify-center z-50">
|
||||
<div class="absolute inset-0 bg-black opacity-50 closeDeleteOverlay" data-id="{{ $pelanggan->id }}"></div>
|
||||
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
|
||||
<div class="p-4 border-b">
|
||||
<h5 class="text-lg font-medium">Hapus Pelanggan</h5>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<p>Apakah Anda yakin ingin menghapus pelanggan <strong>{{ $pelanggan->nama }}</strong>?</p>
|
||||
</div>
|
||||
<div class="p-4 border-t flex justify-end space-x-2">
|
||||
<button type="button" class="bg-gray-300 text-black px-3 py-1 rounded closeDeleteBtn"
|
||||
data-id="{{ $pelanggan->id }}">Batal</button>
|
||||
<form action="{{ route('admin.pelanggan.destroy', $pelanggan->id) }}" method="POST" class="inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button class="bg-red-600 text-white px-3 py-1 rounded">Hapus</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const openDeleteBtns = document.querySelectorAll('.openDeleteModalBtn');
|
||||
const closeDeleteBtns = document.querySelectorAll('.closeDeleteBtn');
|
||||
const closeDeleteOverlays = document.querySelectorAll('.closeDeleteOverlay');
|
||||
|
||||
openDeleteBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.dataset.id;
|
||||
const modal = document.getElementById('modalDelete' + id);
|
||||
if (modal) {
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
closeDeleteBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.dataset.id;
|
||||
const modal = document.getElementById('modalDelete' + id);
|
||||
if (modal) {
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
closeDeleteOverlays.forEach(overlay => {
|
||||
overlay.addEventListener('click', () => {
|
||||
const id = overlay.dataset.id;
|
||||
const modal = document.getElementById('modalDelete' + id);
|
||||
if (modal) {
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
119
backend/resources/views/admin/reviews/index.blade.php
Normal file
@ -0,0 +1,119 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Manajemen Ulasan')
|
||||
|
||||
@section('content')
|
||||
<div class="container mx-auto py-4">
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold">Manajemen Ulasan</h3>
|
||||
</div>
|
||||
|
||||
<!-- Alert sukses -->
|
||||
@if (session('success'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4" role="alert">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Tabel Ulasan -->
|
||||
<div class="bg-white rounded-lg shadow-sm">
|
||||
<div class="p-4 overflow-x-auto">
|
||||
<table class="w-full table-fixed text-left border border-gray-300 border-collapse">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="p-2 border border-gray-300 w-[50px] text-center">Nomor</th>
|
||||
<th class="p-2 border border-gray-300 w-[150px] text-center">Nama</th>
|
||||
<th class="p-2 border border-gray-300 w-[120px] text-center">Kota</th>
|
||||
<th class="p-2 border border-gray-300 w-[120px] text-center">Penilaian</th>
|
||||
<th class="p-2 border border-gray-300 w-[350px] text-center">Pesan</th>
|
||||
<th class="p-2 border border-gray-300 w-[120px] text-center">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($reviews as $key => $review)
|
||||
<tr>
|
||||
<td class="p-2 border border-gray-300 text-center truncate">{{ $key + 1 }}</td>
|
||||
<td class="p-2 border border-gray-300 truncate">{{ $review->name }}</td>
|
||||
<td class="p-2 border border-gray-300 truncate">{{ $review->city }}</td>
|
||||
<td class="p-2 border border-gray-300 text-center">
|
||||
@for ($s = 1; $s <= 5; $s++)
|
||||
<i
|
||||
class="bi {{ $s <= $review->rating ? 'bi-star-fill text-yellow-500' : 'bi-star text-gray-400' }}"></i>
|
||||
@endfor
|
||||
</td>
|
||||
<td class="p-2 border border-gray-300 truncate" title="{{ $review->message }}">
|
||||
{{ $review->message }}
|
||||
</td>
|
||||
<td class="p-2 border border-gray-300 text-center">
|
||||
<div class="flex justify-center space-x-2">
|
||||
<button class="text-red-600 hover:underline flex items-center btn-delete"
|
||||
data-action="{{ route('admin.reviews.destroy', $review->id) }}"
|
||||
data-name="{{ $review->name }}">
|
||||
<i class="bi bi-trash mr-1"></i> Hapus
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="p-2 text-center text-gray-500 border border-gray-300">
|
||||
Belum ada ulasan
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Konfirmasi Hapus Tailwind -->
|
||||
<div id="modalDelete" class="fixed inset-0 hidden items-center justify-center z-50">
|
||||
<div class="absolute inset-0 bg-black opacity-50" id="modalDeleteOverlay"></div>
|
||||
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
|
||||
<form id="deleteForm" method="POST">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<div class="p-4 border-b">
|
||||
<h5 class="text-lg font-medium">Hapus Ulasan</h5>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
Apakah Anda yakin ingin menghapus ulasan dari <strong id="deleteName">—</strong>?
|
||||
</div>
|
||||
<div class="p-4 border-t flex justify-end space-x-2">
|
||||
<button type="button" id="closeDeleteModal"
|
||||
class="bg-gray-300 text-black px-3 py-1 rounded">Batal</button>
|
||||
<button type="submit" class="bg-red-600 text-white px-3 py-1 rounded">Ya, Hapus</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const modal = document.getElementById('modalDelete');
|
||||
const overlay = document.getElementById('modalDeleteOverlay');
|
||||
const closeBtn = document.getElementById('closeDeleteModal');
|
||||
const deleteForm = document.getElementById('deleteForm');
|
||||
const deleteName = document.getElementById('deleteName');
|
||||
|
||||
document.querySelectorAll('.btn-delete').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
deleteForm.action = btn.dataset.action;
|
||||
deleteName.textContent = btn.dataset.name || 'pengguna ini';
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
});
|
||||
});
|
||||
|
||||
const closeModal = () => {
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
}
|
||||
|
||||
closeBtn.addEventListener('click', closeModal);
|
||||
overlay.addEventListener('click', closeModal);
|
||||
});
|
||||
</script>
|
||||
@endsection
|
279
backend/resources/views/admin/templates/index.blade.php
Normal file
@ -0,0 +1,279 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Manajemen Template')
|
||||
|
||||
@section('content')
|
||||
<div class="container mx-auto py-4">
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold">
|
||||
@isset($kategori)
|
||||
Template Kategori: {{ $kategori->nama }}
|
||||
@else
|
||||
Semua Template
|
||||
@endisset
|
||||
</h3>
|
||||
|
||||
@if (!isset($kategori))
|
||||
<button id="openTambahModal" class="bg-blue-600 text-white px-3 py-2.5 rounded flex items-center">
|
||||
<i class="bi bi-plus-lg mr-1"></i> Tambah Template
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Flash Message -->
|
||||
@if (session('success'))
|
||||
<div id="toast-success" class="mb-4 p-3 rounded bg-green-100 text-green-800 border border-green-300 shadow">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
<script>setTimeout(() => document.getElementById('toast-success')?.remove(), 3000);</script>
|
||||
@endif
|
||||
|
||||
@if ($errors->any())
|
||||
<div id="toast-error" class="mb-4 p-3 rounded bg-red-100 text-red-800 border border-red-300 shadow">
|
||||
<ul class="list-disc ml-5">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
<script>setTimeout(() => document.getElementById('toast-error')?.remove(), 5000);</script>
|
||||
@endif
|
||||
|
||||
<!-- Tabel Template -->
|
||||
<div class="bg-white rounded-lg shadow-sm">
|
||||
<div class="p-4 overflow-x-auto">
|
||||
<table class="w-full table-fixed text-left border border-gray-300 border-collapse">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="p-2 border border-gray-300 w-[50px] text-center">Nomor</th>
|
||||
<th class="p-2 border border-gray-300 w-[200px] text-center">Nama Template</th>
|
||||
<th class="p-2 border border-gray-300 w-[150px] text-center">Kategori</th>
|
||||
<th class="p-2 border border-gray-300 w-[150px] text-center">Fitur</th>
|
||||
<th class="p-2 border border-gray-300 w-[90px] text-center">Foto</th>
|
||||
<th class="p-2 border border-gray-300 w-[100px] text-center">Harga</th>
|
||||
<th class="p-2 border border-gray-300 w-[130px] text-center">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($templates as $key => $template)
|
||||
<tr>
|
||||
<td class="p-2 border border-gray-300 text-center">{{ $key + 1 }}</td>
|
||||
<td class="p-2 border border-gray-300 truncate">{{ $template->nama_template }}</td>
|
||||
<td class="p-2 border border-gray-300 truncate">{{ $template->kategori->nama ?? '-' }}</td>
|
||||
<td class="p-2 border border-gray-300 truncate">{{ $template->fitur->deskripsi ?? '-' }}</td>
|
||||
<td class="p-2 border border-gray-300 text-center">
|
||||
<div class="w-12 h-12 overflow-hidden rounded bg-gray-100 flex items-center justify-center mx-auto">
|
||||
<img src="{{ $template->foto ? asset('storage/' . $template->foto) : asset('default-image.png') }}"
|
||||
alt="foto" class="max-w-full max-h-full object-contain">
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-2 border border-gray-300 text-right font-semibold">
|
||||
Rp {{ number_format($template->harga, 0, ',', '.') }}
|
||||
</td>
|
||||
<td class="p-2 border border-gray-300 text-center">
|
||||
<div class="flex justify-center space-x-2">
|
||||
<button class="text-blue-600 hover:underline flex items-center openEditModalBtn"
|
||||
data-id="{{ $template->id }}">
|
||||
<i class="bi bi-pencil mr-1"></i> Edit
|
||||
</button>
|
||||
<form action="{{ route('templates.destroy', $template->id) }}" method="POST"
|
||||
class="inline" onsubmit="return confirm('Hapus template ini?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button class="text-red-600 hover:underline flex items-center">
|
||||
<i class="bi bi-trash mr-1"></i> Hapus
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="p-2 text-center text-gray-500 border border-gray-300">Belum ada template</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Tambah Template -->
|
||||
@if (!isset($kategori))
|
||||
<div id="modalTambah" class="fixed inset-0 hidden items-center justify-center z-50">
|
||||
<div class="absolute inset-0 bg-black opacity-50" id="closeTambahModal"></div>
|
||||
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
|
||||
<form action="{{ route('templates.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="p-4 border-b">
|
||||
<h5 class="text-lg font-medium">Tambah Template</h5>
|
||||
</div>
|
||||
<div class="p-4 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Nama Template</label>
|
||||
<input type="text" name="nama_template" class="w-full p-2 border rounded" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Kategori</label>
|
||||
<select name="kategori_id" class="w-full p-2 border rounded" required>
|
||||
@foreach ($kategoris as $kategori)
|
||||
<option value="{{ $kategori->id }}">{{ $kategori->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Fitur</label>
|
||||
<select name="fitur_id" class="w-full p-2 border rounded" required>
|
||||
@foreach ($fiturs as $fitur)
|
||||
<option value="{{ $fitur->id }}">{{ $fitur->deskripsi }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Harga</label>
|
||||
<input type="number" name="harga" class="w-full p-2 border rounded" required min="0" step="1000">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Foto (opsional)</label>
|
||||
<input type="file" name="foto" class="w-full p-2 border rounded" accept="image/*">
|
||||
<small class="text-gray-500">Format yang didukung: JPG, PNG, GIF. Maksimal 5MB.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 border-t flex justify-end space-x-2">
|
||||
<button type="button" id="closeTambahBtn" class="bg-gray-300 text-black px-3 py-1 rounded">Batal</button>
|
||||
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Modal Edit Template -->
|
||||
@foreach ($templates as $template)
|
||||
<div id="modalEdit{{ $template->id }}" class="fixed inset-0 hidden items-center justify-center z-50">
|
||||
<div class="absolute inset-0 bg-black opacity-50 closeEditOverlay" data-id="{{ $template->id }}"></div>
|
||||
<div class="bg-white rounded-lg shadow-lg w-full max-w-md z-50 overflow-hidden">
|
||||
<form action="{{ route('templates.update', $template->id) }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf @method('PUT')
|
||||
<div class="p-4 border-b">
|
||||
<h5 class="text-lg font-medium">Edit Template</h5>
|
||||
</div>
|
||||
<div class="p-4 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Nama Template</label>
|
||||
<input type="text" name="nama_template" value="{{ $template->nama_template }}"
|
||||
class="w-full p-2 border rounded" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Kategori</label>
|
||||
<select name="kategori_id" class="w-full p-2 border rounded" required>
|
||||
@foreach ($kategoris as $kategori)
|
||||
<option value="{{ $kategori->id }}" @selected($kategori->id == $template->kategori_id)>{{ $kategori->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Fitur</label>
|
||||
<select name="fitur_id" class="w-full p-2 border rounded" required>
|
||||
@foreach ($fiturs as $fitur)
|
||||
<option value="{{ $fitur->id }}" @selected($fitur->id == $template->fitur_id)>{{ $fitur->deskripsi }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Harga</label>
|
||||
<input type="number" name="harga" value="{{ $template->harga }}"
|
||||
class="w-full p-2 border rounded" required min="0" step="1000">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium">Foto (opsional)</label>
|
||||
@if ($template->foto)
|
||||
<div class="mb-2">
|
||||
<small class="text-gray-500">Foto saat ini:</small>
|
||||
<div class="w-20 h-20 mt-1 overflow-hidden rounded bg-gray-100 flex items-center justify-center border">
|
||||
<img src="{{ asset('storage/' . $template->foto) }}" alt="foto"
|
||||
class="max-w-full max-h-full object-contain">
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<input type="file" name="foto" class="w-full p-2 border rounded" accept="image/*">
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 border-t flex justify-end space-x-2">
|
||||
<button type="button" class="bg-gray-300 text-black px-3 py-1 rounded closeEditBtn"
|
||||
data-id="{{ $template->id }}">Batal</button>
|
||||
<button class="bg-blue-600 text-white px-3 py-1 rounded">Simpan Perubahan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Modal Tambah
|
||||
const openTambahModal = document.getElementById('openTambahModal');
|
||||
const modalTambah = document.getElementById('modalTambah');
|
||||
const closeTambahBtn = document.getElementById('closeTambahBtn');
|
||||
const closeTambahOverlay = document.getElementById('closeTambahModal');
|
||||
|
||||
if (openTambahModal) {
|
||||
openTambahModal.addEventListener('click', () => {
|
||||
modalTambah.classList.remove('hidden');
|
||||
modalTambah.classList.add('flex');
|
||||
});
|
||||
}
|
||||
if (closeTambahBtn) {
|
||||
closeTambahBtn.addEventListener('click', () => {
|
||||
modalTambah.classList.add('hidden');
|
||||
modalTambah.classList.remove('flex');
|
||||
});
|
||||
}
|
||||
if (closeTambahOverlay) {
|
||||
closeTambahOverlay.addEventListener('click', () => {
|
||||
modalTambah.classList.add('hidden');
|
||||
modalTambah.classList.remove('flex');
|
||||
});
|
||||
}
|
||||
|
||||
// Modal Edit
|
||||
const openEditBtns = document.querySelectorAll('.openEditModalBtn');
|
||||
const closeEditBtns = document.querySelectorAll('.closeEditBtn');
|
||||
const closeEditOverlays = document.querySelectorAll('.closeEditOverlay');
|
||||
|
||||
openEditBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.dataset.id;
|
||||
const modal = document.getElementById('modalEdit' + id);
|
||||
if (modal) {
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
closeEditBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.dataset.id;
|
||||
const modal = document.getElementById('modalEdit' + id);
|
||||
if (modal) {
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
closeEditOverlays.forEach(overlay => {
|
||||
overlay.addEventListener('click', () => {
|
||||
const id = overlay.dataset.id;
|
||||
const modal = document.getElementById('modalEdit' + id);
|
||||
if (modal) {
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
@endsection
|
149
backend/resources/views/layouts/app.blade.php
Normal file
@ -0,0 +1,149 @@
|
||||
<!doctype html>
|
||||
<html lang="id">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>@yield('title', 'Admin Panel')</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f1f5f9;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
background: #fff;
|
||||
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.submenu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.submenu.show {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="flex">
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar flex flex-col">
|
||||
<!-- LOGO -->
|
||||
<div class="text-center py-4 border-b">
|
||||
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="mx-auto mb-2" style="max-height: 80px;">
|
||||
</div>
|
||||
|
||||
<!-- MENU -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<p class="text-gray-500 uppercase text-xs font-semibold px-3 mt-4 mb-2">Menu Utama</p>
|
||||
<ul class="px-2 space-y-1">
|
||||
<li>
|
||||
<a href="{{ route('admin.dashboard') }}"
|
||||
class="flex items-center py-2 px-3 rounded hover:bg-blue-50 {{ request()->is('admin/dashboard') ? 'bg-blue-100 text-blue-600' : 'text-gray-700' }}">
|
||||
<i class="bi bi-house-door me-2"></i> Dasbor
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ route('admin.kategori.index') }}"
|
||||
class="flex items-center py-2 px-3 rounded hover:bg-blue-50 {{ request()->is('admin/kategori*') ? 'bg-blue-100 text-blue-600' : 'text-gray-700' }}">
|
||||
<i class="bi bi-diagram-3 me-2"></i> Kategori
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ route('admin.fitur.index') }}"
|
||||
class="flex items-center py-2 px-3 rounded hover:bg-blue-50 {{ request()->is('admin/fitur*') ? 'bg-blue-100 text-blue-600' : 'text-gray-700' }}">
|
||||
<i class="bi bi-grid me-2"></i> Fitur
|
||||
</a>
|
||||
</li>
|
||||
<!-- Dropdown Templat -->
|
||||
<li>
|
||||
@php
|
||||
$isTemplatePage = request()->is('templates*'); // cek apakah sedang di halaman template
|
||||
@endphp
|
||||
<button
|
||||
class="w-full flex items-center justify-between py-2 px-3 text-gray-700 rounded hover:bg-blue-50 {{ $isTemplatePage ? 'bg-blue-100 text-blue-600' : '' }}"
|
||||
id="templatBtn">
|
||||
<span><i class="bi bi-card-list me-2"></i> Templat</span>
|
||||
<i class="bi bi-chevron-down transition-transform {{ $isTemplatePage ? 'rotate-180' : '' }}"
|
||||
id="templatIcon"></i>
|
||||
</button>
|
||||
<ul class="submenu pl-6 space-y-1 {{ $isTemplatePage ? 'show' : '' }}" id="templatSubmenu">
|
||||
<li>
|
||||
<a href="{{ route('templates.index') }}"
|
||||
class="block py-2 px-2 rounded {{ request()->is('templates') ? 'bg-blue-100 text-blue-600' : 'text-gray-500 hover:text-blue-600 hover:bg-blue-50' }}">
|
||||
Semua Template
|
||||
</a>
|
||||
</li>
|
||||
@foreach (\App\Models\Kategori::all() as $kategori)
|
||||
<li>
|
||||
<a href="{{ route('templates.byKategori', $kategori->id) }}"
|
||||
class="block py-2 px-2 rounded {{ request()->is('templates/kategori/' . $kategori->id) ? 'bg-blue-100 text-blue-600' : 'text-gray-500 hover:text-blue-600 hover:bg-blue-50' }}">
|
||||
{{ $kategori->nama }}
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ route('admin.pelanggan.index') }}"
|
||||
class="flex items-center py-2 px-3 rounded hover:bg-blue-50 text-gray-700">
|
||||
<i class="bi bi-people me-2"></i> Pelanggan
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="{{ route('admin.reviews.index') }}"
|
||||
class="flex items-center py-2 px-3 rounded hover:bg-blue-50 {{ request()->is('admin/ulasan') ? 'bg-blue-100 text-blue-600' : 'text-gray-700' }}">
|
||||
<i class="bi bi-chat-dots me-2"></i> Ulasan
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p class="text-gray-500 uppercase text-xs font-semibold px-3 mt-4 mb-2">Akun</p>
|
||||
<ul class="px-2 space-y-1">
|
||||
<li>
|
||||
<a href="{{ route('admin.logout') }}"
|
||||
onclick="event.preventDefault(); document.getElementById('logout-form').submit();"
|
||||
class="flex items-center py-2 px-3 text-red-600 font-semibold hover:bg-red-50 rounded">
|
||||
<i class="bi bi-box-arrow-right me-2"></i> Keluar
|
||||
</a>
|
||||
<form id="logout-form" action="{{ route('admin.logout') }}" method="POST" class="hidden">
|
||||
@csrf
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<div class="flex-1 ml-[250px] p-6">
|
||||
@yield('content')
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Dropdown Templat
|
||||
const templatBtn = document.getElementById('templatBtn');
|
||||
const templatSubmenu = document.getElementById('templatSubmenu');
|
||||
const templatIcon = document.getElementById('templatIcon');
|
||||
|
||||
templatBtn.addEventListener('click', () => {
|
||||
templatSubmenu.classList.toggle('show');
|
||||
templatIcon.classList.toggle('rotate-180');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -2,18 +2,26 @@
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\Api\ReviewController;
|
||||
use App\Http\Controllers\Api\KategoriApiController;
|
||||
use App\Http\Controllers\Api\PernikahanApiController;
|
||||
use App\Http\Controllers\Api\UlangTahunApiController;
|
||||
use App\Http\Controllers\Api\KhitanApiController;
|
||||
use App\Http\Controllers\Api\TemplateApiController;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| API Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here is where you can register API routes for your application. These
|
||||
| routes are loaded by the RouteServiceProvider and all of them will
|
||||
| be assigned to the "api" middleware group. Make something great!
|
||||
|
|
||||
*/
|
||||
// Form API (user)
|
||||
Route::post('form/pernikahan', [PernikahanApiController::class, 'store']);
|
||||
Route::post('form/ulang-tahun', [UlangTahunApiController::class, 'store']);
|
||||
Route::post('form/khitan', [KhitanApiController::class, 'store']);
|
||||
|
||||
// API Kategori hanya read-only
|
||||
Route::get('kategoris', [KategoriApiController::class, 'index']);
|
||||
Route::get('kategoris/{kategori}', [KategoriApiController::class, 'show']);
|
||||
|
||||
// API Reviews
|
||||
Route::apiResource('reviews', ReviewController::class);
|
||||
|
||||
// API Templates
|
||||
Route::get('templates', [TemplateApiController::class, 'index']);
|
||||
Route::get('templates/{template}', [TemplateApiController::class, 'show']);
|
||||
|
||||
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
|
||||
return $request->user();
|
||||
});
|
||||
|
@ -1,18 +1,83 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\AdminAuthController;
|
||||
use App\Http\Controllers\KategoriController;
|
||||
use App\Http\Controllers\FiturController;
|
||||
use App\Http\Controllers\TemplateController;
|
||||
use App\Http\Controllers\PelangganController;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Web Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here is where you can register web routes for your application. These
|
||||
| routes are loaded by the RouteServiceProvider and all of them will
|
||||
| be assigned to the "web" middleware group. Make something great!
|
||||
|
|
||||
*/
|
||||
|
||||
//Login
|
||||
Route::get('/', function () {
|
||||
return view('welcome');
|
||||
return redirect()->route('admin.login');
|
||||
});
|
||||
Route::prefix('admin')->name('admin.')->group(function () {
|
||||
Route::middleware('guest:admin')->group(function () {
|
||||
Route::get('/login', [AdminAuthController::class, 'showLogin'])->name('login');
|
||||
Route::post('/login', [AdminAuthController::class, 'login'])->name('login.post');
|
||||
});
|
||||
|
||||
Route::middleware('auth:admin')->group(function () {
|
||||
Route::get('/dashboard', function () {
|
||||
return view('admin.dashboard');
|
||||
})->name('dashboard');
|
||||
|
||||
Route::post('/logout', [AdminAuthController::class, 'logout'])->name('logout');
|
||||
});
|
||||
});
|
||||
|
||||
//Kategori
|
||||
Route::prefix('admin')->name('admin.')->group(function () {
|
||||
Route::resource('kategori', KategoriController::class);
|
||||
});
|
||||
|
||||
// Route Admin Fitur
|
||||
Route::prefix('admin')->name('admin.')->group(function () {
|
||||
Route::resource('fitur', FiturController::class);
|
||||
});
|
||||
|
||||
|
||||
// Template
|
||||
Route::prefix('admin')->group(function () {
|
||||
Route::get('templates', [TemplateController::class, 'index'])->name('templates.index');
|
||||
Route::post('templates', [TemplateController::class, 'store'])->name('templates.store');
|
||||
Route::put('templates/{template}', [TemplateController::class, 'update'])->name('templates.update');
|
||||
Route::delete('templates/{template}', [TemplateController::class, 'destroy'])->name('templates.destroy');
|
||||
Route::get('templates/kategori/{id}', [TemplateController::class, 'byKategori'])->name('templates.byKategori');
|
||||
});
|
||||
|
||||
|
||||
// Route Admin Pelanggan
|
||||
Route::prefix('admin')->name('admin.')->group(function () {
|
||||
Route::resource('pelanggan', PelangganController::class)->only([
|
||||
'index',
|
||||
'show',
|
||||
'destroy'
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
use App\Http\Controllers\Api\ReviewController;
|
||||
use App\Models\Review;
|
||||
|
||||
Route::prefix('admin')->name('admin.')->middleware('auth:admin')->group(function () {
|
||||
// Halaman daftar ulasan
|
||||
Route::get('/ulasan', function () {
|
||||
$reviews = \App\Models\Review::latest()->get();
|
||||
return view('admin.reviews.index', compact('reviews'));
|
||||
})->name('reviews.index');
|
||||
|
||||
// Tambah ulasan
|
||||
Route::post('/ulasan', [ReviewController::class, 'store'])->name('reviews.store');
|
||||
|
||||
// Update ulasan
|
||||
Route::put('/ulasan/{review}', [ReviewController::class, 'update'])->name('reviews.update');
|
||||
|
||||
// Hapus ulasan
|
||||
Route::delete('/ulasan/{review}', function (Review $review) {
|
||||
$review->delete();
|
||||
return redirect()->route('admin.reviews.index')->with('success', 'Ulasan berhasil dihapus');
|
||||
})->name('reviews.destroy');
|
||||
});
|
||||
|
@ -1 +1,4 @@
|
||||
@import "tailwindcss";
|
||||
@import "tailwindcss";
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
133
proyek-frontend/app/components/landing-page/aboutsection.vue
Normal file
@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<section id="tentang-kami" class="about-section">
|
||||
<div class="container">
|
||||
<!-- Judul -->
|
||||
<h2 class="section-title">Tentang Kami</h2>
|
||||
|
||||
<!-- Layout gambar + teks -->
|
||||
<div class="about-layout">
|
||||
<div class="about-image">
|
||||
<img src="/logo1.png" alt="Tentang Kami - Undangan Digital" />
|
||||
</div>
|
||||
<div class="about-text">
|
||||
<p>
|
||||
"Abbauf Tech memudahkan pembuatan undangan digital yang praktis dan elegan, siap digunakan untuk berbagai acara spesial Anda."
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Box -->
|
||||
<div class="about-info">
|
||||
<div class="info-card" v-for="(item, index) in infoList" :key="index">
|
||||
<div class="icon">
|
||||
<component :is="item.icon" />
|
||||
</div>
|
||||
<h3>{{ item.title }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { h } from "vue";
|
||||
import { User, Headphones, Layout } from "lucide-vue-next";
|
||||
|
||||
// Data untuk kotak informasi
|
||||
const infoList = [
|
||||
{
|
||||
title: "300+ Template",
|
||||
icon: () => h(Layout, { size: 36, color: "#0d6efd" }),
|
||||
},
|
||||
{
|
||||
title: "24/7 Support",
|
||||
icon: () => h(Headphones, { size: 36, color: "#0d6efd" }),
|
||||
},
|
||||
{
|
||||
title: "100+ Client",
|
||||
icon: () => h(User, { size: 36, color: "#0d6efd" }),
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.about-section {
|
||||
padding: 80px 0;
|
||||
background-color: #ffffff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 2.9rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 50px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.about-layout {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.about-image img {
|
||||
max-width: 400px;
|
||||
height: auto;
|
||||
margin-bottom: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.about-text {
|
||||
max-width: 500px;
|
||||
font-size: 1.4rem;
|
||||
color: #000;
|
||||
line-height: 1.6;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.about-info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
max-width: 250px;
|
||||
border: 3px solid #0d6efd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.info-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.info-card h3 {
|
||||
margin-top: 15px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<section id="template" class="feature-section">
|
||||
<div class="featured-header">
|
||||
<h2>Templat Unggulan</h2>
|
||||
<p>"Tersedia berbagai desain undangan pernikahan, khitan, ulang tahun, dan lainnya."</p>
|
||||
</div>
|
||||
|
||||
<!-- Grid Template -->
|
||||
<div class="template-grid">
|
||||
<div class="template-card" v-for="i in 6" :key="i">
|
||||
<div class="template-image">
|
||||
<img src="/templat.jpg" alt="Template" />
|
||||
</div>
|
||||
|
||||
<div class="template-body">
|
||||
<h3 class="template-title">Golf Party</h3>
|
||||
<p class="template-price">Rp.89.000</p>
|
||||
|
||||
<select class="template-select">
|
||||
<option>Fitur Terbaik</option>
|
||||
<option>Fitur Lengkap</option>
|
||||
</select>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="btn btn-preview">Preview</button>
|
||||
<button class="btn btn-order">Order</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="see-more">
|
||||
<NuxtLink to="/template">Lihat Selengkapnya...</NuxtLink>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.featured-section {
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.featured-header h2 {
|
||||
font-size: 2.9rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
.featured-header p {
|
||||
color: #555;
|
||||
margin-bottom: 40px;
|
||||
font-size: 17px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 24px;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.template-card {
|
||||
background: #f2f2f2;
|
||||
border-radius: 5px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.template-image {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.template-image img {
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.template-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.template-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.template-price {
|
||||
color: #008000;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.template-select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
padding: 10px 0;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.btn-preview {
|
||||
background: #e5e5e5;
|
||||
}
|
||||
|
||||
.btn-preview:hover {
|
||||
background: #d6d6d6;
|
||||
}
|
||||
|
||||
.btn-order {
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-order:hover {
|
||||
background: #1d4ed8;
|
||||
}
|
||||
|
||||
.see-more {
|
||||
margin-top: 30px;
|
||||
text-align: right;
|
||||
max-width: 1100px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.see-more a {
|
||||
color: #2563eb;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
|
||||
}
|
||||
|
||||
.see-more a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
207
proyek-frontend/app/components/landing-page/footer.vue
Normal file
@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<footer class="site-footer">
|
||||
<div class="container footer-content">
|
||||
<div class="footer-brand">
|
||||
<img src="/ABBAUF.png" alt="Abbauf Tech Logo" class="footer-logo">
|
||||
</div>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Layanan</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="#">Template Undangan Pernikahan</a></li>
|
||||
<li><a href="#">Template Undangan Khitan</a></li>
|
||||
<li><a href="#">Template Undangan Ulang Tahun</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-column">
|
||||
<h4>Ikuti Kami</h4>
|
||||
<ul class="footer-contact-list">
|
||||
<li>
|
||||
<a href="#" class="social-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v2.385z"/></svg>
|
||||
<span>ABBAUF TECH</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="social-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.85s-.011 3.584-.069 4.85c-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07s-3.584-.012-4.85-.07c-3.252-.148-4.771-1.691-4.919-4.919-.058-1.265-.069-1.645-.069-4.85s.011-3.584.069-4.85c.149-3.225 1.664-4.771 4.919-4.919 1.266-.057 1.644-.069 4.85-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948s.014 3.667.072 4.947c.2 4.359 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072s3.667-.014 4.947-.072c4.359-.2 6.78-2.618 6.98-6.98.058-1.281.072-1.689.072-4.948s-.014-3.667-.072-4.947c-.2-4.359-2.618-6.78-6.98-6.98-1.281-.058-1.689-.072-4.948-.072zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.162 6.162 6.162 6.162-2.759 6.162-6.162-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4s1.791-4 4-4 4 1.79 4 4-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.441 1.441 1.441 1.441-.645 1.441-1.441-.645-1.44-1.441-1.44z"/></svg>
|
||||
<span>@abbauf_tech</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="social-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M4.98 3.5c0 1.381-1.11 2.5-2.48 2.5s-2.48-1.119-2.48-2.5c0-1.38 1.11-2.5 2.48-2.5s2.48 1.12 2.48 2.5zm.02 4.5h-5v16h5v-16zm7.982 0h-4.968v16h4.969v-8.399c0-4.67 6.029-5.052 6.029 0v8.399h4.988v-10.131c0-7.88-8.922-7.593-11.018-3.714v-2.155z"/></svg>
|
||||
<span>ABBAUF TECH</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-column footer-contact">
|
||||
<h4>Kontak Kami</h4>
|
||||
<ul class="footer-contact-list">
|
||||
<li>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M0 3v18h24v-18h-24zm21.518 2l-9.518 7.713-9.518-7.713h19.036zm-19.518 14v-11.817l10 8.104 10-8.104v11.817h-20z"/></svg>
|
||||
<a href="mailto:contact@abbauf.com">contact@abbauf.com</a>
|
||||
</li>
|
||||
<li>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M20 22.621l-3.521-6.795c-.008.004-1.974.97-2.064 1.011-2.24 1.086-6.799-3.473-5.712-5.713.041-.09 1.011-2.064 1.011-2.064l-6.795-3.521-2.918 2.918c-1.603 1.603-1.425 4.933 1.011 7.37 4.301 4.301 9.962 3.593 12.301.954l2.688-2.355-2.356-2.688z"/></svg>
|
||||
<a href="tel:02127617679">(021) 2761-7679</a>
|
||||
</li>
|
||||
<li>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M.057 24l1.687-6.163c-1.041-1.804-1.588-3.849-1.587-5.946.003-6.556 5.338-11.891 11.893-11.891 3.181.001 6.167 1.24 8.413 3.488 2.245 2.248 3.481 5.236 3.48 8.414-.003 6.557-5.338 11.892-11.894 11.892-1.99-.001-3.951-.5-5.688-1.448l-6.305 1.654zm6.597-3.807c1.676.995 3.276 1.591 5.392 1.592 5.448 0 9.886-4.434 9.889-9.885.002-5.462-4.415-9.89-9.881-9.892-5.452 0-9.887 4.434-9.889 9.884-.001 2.225.651 3.891 1.746 5.634l-.999 3.648 3.742-.981zm11.387-5.464c-.074-.124-.272-.198-.57-.347-.297-.149-1.758-.868-2.031-.967-.272-.099-.47-.149-.669.149-.198.297-.768.967-.941 1.165-.173.198-.347.223-.644.074-.297-.149-1.255-.462-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.297-.347.446-.521.151-.172.2-.296.3-.495.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01s-.52.074-.792.372c-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.626.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.695.248-1.29.173-1.414z"/></svg>
|
||||
<a href="#">+62 878-7711-7847</a>
|
||||
</li>
|
||||
<li>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-4.198 0-8 3.403-8 7.602 0 4.198 3.469 9.21 8 16.398 4.531-7.188 8-12.2 8-16.398 0-4.199-3.801-7.602-8-7.602zm0 11c-1.657 0-3-1.343-3-3s1.343-3 3-3 3 1.343 3 3-1.343 3-3 3z"/></svg>
|
||||
<div>
|
||||
<strong>Alamat Kantor Pusat</strong>
|
||||
<p>APL Tower Central Park Lantai 19 Unit T7, Jalan Letjen S. Parman, Kavling 28, RT. 012/006, Kel. Tanjung Duren, Kec. Grogol Petamburan, Jakarta Barat, DKI Jakarta 11470, ID</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-4.198 0-8 3.403-8 7.602 0 4.198 3.469 9.21 8 16.398 4.531-7.188 8-12.2 8-16.398 0-4.199-3.801-7.602-8-7.602zm0 11c-1.657 0-3-1.343-3-3s1.343-3 3-3 3 1.343 3 3-1.343 3-3 3z"/></svg>
|
||||
<div>
|
||||
<strong>Alamat Studio</strong>
|
||||
<p>Jl. Adhi Karya No. 57 RT 003 RW 015, Kel. Depok, Kec. Pancoran Mas, Depok 16431</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 ABBAUF TECH - Hak cipta dilindungi undang-undang</p>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// Tidak ada script yang dibutuhkan untuk footer statis ini
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.site-footer {
|
||||
background-color: #f0f2f5; /* Warna abu-abu muda */
|
||||
color: #333;
|
||||
padding: 50px 0 20px 0;
|
||||
font-family: 'Inter', sans-serif; /* Menggunakan font yang terlihat modern */
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1.5fr 1.5fr 2.5fr; /* Mengatur lebar kolom */
|
||||
gap: 40px;
|
||||
padding-bottom: 30px;
|
||||
border-bottom: 1px solid #d9dce1;
|
||||
}
|
||||
|
||||
.footer-logo {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.footer-column h4 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.footer-links, .footer-contact-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer-links li {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.footer-links a, .social-link {
|
||||
text-decoration: none;
|
||||
color: #555;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-links a:hover, .social-link:hover {
|
||||
color: #0d6efd; /* Biru Primer */
|
||||
}
|
||||
|
||||
.footer-contact-list li {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 18px;
|
||||
gap: 15px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.footer-contact-list svg {
|
||||
flex-shrink: 0;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.footer-contact-list a {
|
||||
text-decoration: none;
|
||||
color: #555;
|
||||
}
|
||||
.footer-contact-list a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.footer-contact-list strong {
|
||||
display: block;
|
||||
color: #111;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.footer-contact-list p {
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.social-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
text-align: center;
|
||||
padding-top: 20px;
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Penyesuaian untuk layar kecil (Mobile) */
|
||||
@media (max-width: 992px) {
|
||||
.footer-content {
|
||||
grid-template-columns: 1fr 1fr; /* 2 kolom di tablet */
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.footer-content {
|
||||
grid-template-columns: 1fr; /* 1 kolom di mobile */
|
||||
text-align: center;
|
||||
}
|
||||
.footer-brand {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.footer-logo {
|
||||
margin: 0 auto; /* Logo di tengah */
|
||||
}
|
||||
.footer-contact-list li {
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -2,12 +2,13 @@
|
||||
<header class="main-header">
|
||||
<nav class="container">
|
||||
<div class="logo">
|
||||
<img src="/logo1.png" alt="Abbauf Tech Logo" class="logo-icon">
|
||||
<span>ABBAUF TECH</span>
|
||||
<NuxtLink to="/" class="logo-link"> <img src="/ABBAUF.png" alt="Abbauf Tech Logo" class="logo-icon">
|
||||
<span>ABBAUF TECH</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li v-for="link in navLinks" :key="link.name">
|
||||
<a :href="link.path">{{ link.name }}</a>
|
||||
<NuxtLink :to="link.path">{{ link.name }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -18,18 +19,21 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
const navLinks = ref([
|
||||
{ name: 'Beranda', path: '#' },
|
||||
{ name: 'Tentang Kami', path: '#' },
|
||||
{ name: 'Templat', path: '#' },
|
||||
{ name: 'Panduan', path: '#' },
|
||||
{ name: 'Keistimewaan', path: '#' },
|
||||
{ name: 'Testimoni', path: '#' },
|
||||
// Path untuk Beranda diubah menjadi / agar selalu kembali ke halaman utama
|
||||
{ name: 'Beranda', path: '/' },
|
||||
// Path ini agar berfungsi dari halaman lain (seperti /template)
|
||||
{ name: 'Tentang Kami', path: '/#tentang-kami' },
|
||||
// INI BAGIAN YANG PALING PENTING: Path diubah ke /template
|
||||
{ name: 'Templat', path: '/template' },
|
||||
{ name: 'Panduan', path: '/#cara' },
|
||||
{ name: 'Keistimewaan', path: '/#keistimewaan' },
|
||||
{ name: 'Testimoni', path: '/#testi' },
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.main-header {
|
||||
background-color: #eaf2ff; /* Warna biru muda */
|
||||
background-color: #eaf2ff;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #d4e3ff;
|
||||
width: 100%;
|
||||
@ -44,23 +48,24 @@ const navLinks = ref([
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
.logo a.logo-link { /* Style untuk logo link */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
color: #0d6efd; /* Biru Primer */
|
||||
color: #0d6efd;
|
||||
text-decoration: none; /* Hilangkan garis bawah default */
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 40px; /* Sesuaikan ukuran logo */
|
||||
width: 40px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
gap: 30px; /* Jarak antar menu */
|
||||
gap: 30px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@ -73,6 +78,6 @@ const navLinks = ref([
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
color: #0d6efd; /* Warna biru saat di-hover */
|
||||
color: #0d6efd;
|
||||
}
|
||||
</style>
|
@ -1,30 +1,41 @@
|
||||
<template>
|
||||
<section class="hero-section">
|
||||
<div class="container hero-layout">
|
||||
<div class="hero-text">
|
||||
<h1>
|
||||
Buat Undangan Digital Praktis
|
||||
<span class="highlight">Untuk Semua</span>
|
||||
<section id="beranda" class="py-20 px-5">
|
||||
<div class="container mx-auto flex flex-col items-center gap-12 lg:flex-row">
|
||||
|
||||
<div class="w-full text-center lg:w-1/2">
|
||||
|
||||
<h1 class="text-5xl font-extrabold leading-tight text-gray-800 lg:text-6xl">
|
||||
Buat Undangan Digital Praktis Untuk
|
||||
|
||||
<div class="h-24 flex items-center justify-center">
|
||||
<Transition name="fade" mode="out-in">
|
||||
<span class="text-blue-600" :key="currentWord">{{ currentWord }}</span>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
Tanpa Ribet
|
||||
</h1>
|
||||
<p>
|
||||
|
||||
<p class="mt-4 mb-8 text-lg text-gray-600">
|
||||
Coba undangan digital PRAKTIS untuk berbagai acara. Pilih template praktis atau premium sesuai kebutuhanmu. Praktis, cepat, dan bisa langsung digunakan.
|
||||
</p>
|
||||
<div class="cta-buttons">
|
||||
<a href="#" class="btn btn-whatsapp">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.2239 4.7761C17.1659 2.7181 14.6599 1.5 11.9999 1.5C5.9249 1.5 0.9999 6.425 0.9999 12.5C0.9999 14.717 1.6219 16.814 2.7669 18.57L1.4179 22.582L5.5949 21.274C7.2729 22.3 9.2279 22.9 11.2999 22.9H11.9999C18.0749 22.9 22.9999 17.975 22.9999 11.9C22.9999 9.24 21.7819 6.734 19.7239 4.676L19.2239 4.7761Z" fill="#25D366"/>
|
||||
<path d="M16.7441 14.968C16.5331 15.538 15.4211 16.039 14.8811 16.191C14.3411 16.342 13.6261 16.355 13.0611 16.151C12.4961 15.947 11.7581 15.688 10.1581 14.18C8.2541 12.378 7.0981 10.217 6.8481 9.689C6.5991 9.16 5.8611 7.78 5.8611 6.94C5.8611 6.103 6.3051 5.689 6.5541 5.452C6.8031 5.215 7.2021 5.122 7.5121 5.122C7.6561 5.122 7.7951 5.132 7.9251 5.143C8.2851 5.179 8.4711 5.196 8.6511 5.568C8.8611 6.004 9.4211 7.42 9.4911 7.572C9.5611 7.724 9.6111 7.928 9.5111 8.132C9.4111 8.336 9.3521 8.428 9.2111 8.58C9.0711 8.732 8.9411 8.855 8.7911 9.006C8.6711 9.129 8.5511 9.253 8.6811 9.48C8.8111 9.707 9.3011 10.512 10.1211 11.332C11.1611 12.372 11.9611 12.75 12.3011 12.911C12.6411 13.072 12.9861 13.03 13.2361 12.78C13.5611 12.455 13.8861 12.01 14.3061 11.59C14.5411 11.355 14.8361 11.29 15.1361 11.39C15.4361 11.49 16.6361 12.09 16.9961 12.26C17.3561 12.43 17.5761 12.53 17.6561 12.65C17.7361 12.77 17.7411 13.06 17.7111 13.16L17.7161 14.008C17.7161 14.008 17.7111 14.003 17.6811 14.038C17.5911 14.128 17.3011 14.24 16.9511 14.39L16.7441 14.968Z" fill="white"/>
|
||||
<div class="flex flex-col justify-center gap-4 sm:flex-row">
|
||||
<a href="#" class="inline-flex items-center justify-center rounded-lg border-2 border-green-500 bg-white px-8 py-3 font-bold text-gray-800 shadow-sm transition-transform duration-300 hover:-translate-y-1 hover:shadow-md">
|
||||
<svg class="mr-2" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.2239 4.7761C17.1659 2.7181 14.6599 1.5 11.9999 1.5...Z" fill="#25D366"/>
|
||||
<path d="M16.7441 14.968C16.5331 15.538...Z" fill="white"/>
|
||||
</svg>
|
||||
<span>Hubungi Kami</span>
|
||||
</a>
|
||||
<a href="#" class="btn btn-primary">Lihat Templat</a>
|
||||
<a href="#" class="inline-flex items-center justify-center rounded-lg bg-blue-600 px-8 py-3 font-bold text-white shadow-sm transition-transform duration-300 hover:-translate-y-1 hover:shadow-md hover:bg-blue-700">
|
||||
Lihat Templat
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hero-image-wrapper">
|
||||
<img src="/logo2.png" alt="Tampilan Undangan Digital di Ponsel">
|
||||
<div class="testimonial">
|
||||
<div class="w-full lg:w-1/2">
|
||||
<img src="/logo2.png" alt="Tampilan Undangan Digital di Ponsel" class="mx-auto max-w-full">
|
||||
<div class="mt-6 text-center italic text-gray-500">
|
||||
<p>"Abadikan momen indahmu dengan undangan digital yang elegan."</p>
|
||||
<p>"Satu aplikasi untuk semua momen spesialmu."</p>
|
||||
</div>
|
||||
@ -34,97 +45,35 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// Script bisa dikosongkan jika tidak ada logika dinamis
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
// Bagian script ini tidak perlu diubah
|
||||
const words = ['Semua', 'Pernikahan', 'Khitan', 'Ulang Tahun'];
|
||||
const currentWordIndex = ref(0);
|
||||
const currentWord = ref(words[0]);
|
||||
let intervalId = null;
|
||||
|
||||
onMounted(() => {
|
||||
intervalId = setInterval(() => {
|
||||
currentWordIndex.value = (currentWordIndex.value + 1) % words.length;
|
||||
currentWord.value = words[currentWordIndex.value];
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(intervalId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hero-section {
|
||||
padding: 60px 0;
|
||||
/* Bagian style ini tidak perlu diubah */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.hero-layout {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 50px;
|
||||
}
|
||||
|
||||
.hero-text {
|
||||
flex: 1; /* Mengambil ruang yang tersedia */
|
||||
}
|
||||
|
||||
.hero-text h1 {
|
||||
font-size: 3.5rem; /* Ukuran font besar */
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hero-text h1 .highlight {
|
||||
color: #0d6efd; /* Warna biru untuk "Untuk Semua" */
|
||||
}
|
||||
|
||||
.hero-text p {
|
||||
font-size: 1.1rem;
|
||||
color: #555;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.cta-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 15px 30px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.btn-whatsapp {
|
||||
background-color: #fff;
|
||||
border: 2px solid #25D366; /* Warna hijau WhatsApp */
|
||||
color: #222;
|
||||
}
|
||||
.btn-whatsapp svg {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #0d6efd; /* Warna biru */
|
||||
color: #fff;
|
||||
border: 2px solid #0d6efd;
|
||||
}
|
||||
|
||||
.hero-image-wrapper {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-image-wrapper img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.testimonial {
|
||||
margin-top: 20px;
|
||||
font-style: italic;
|
||||
color: #444;
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
112
proyek-frontend/app/components/landing-page/howtosection.vue
Normal file
@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<section id="cara" class="howto-section">
|
||||
<div class="howto-header">
|
||||
<h2>Cara Membuat Undangan Digital</h2>
|
||||
<p>"Buat undanganmu dengan simple, mudah dan praktis."</p>
|
||||
</div>
|
||||
|
||||
<div class="howto-grid">
|
||||
<div class="howto-card" v-for="(item, index) in steps" :key="index">
|
||||
<div class="card-icon">
|
||||
<img :src="`/event-list${index}.png`" :alt="item.title" />
|
||||
</div>
|
||||
<h3>{{ item.title }}</h3>
|
||||
<p>{{ item.desc }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const steps = [
|
||||
{
|
||||
title: "Mulai dari Kategori",
|
||||
desc: "Pilih Kategory sesuai kebutuhan."
|
||||
},
|
||||
{
|
||||
title: "Tentukan Template",
|
||||
desc: "Sesuaikan desain sesuai selera."
|
||||
},
|
||||
{
|
||||
title: "Isi Data",
|
||||
desc: "Nama, tanggal, lokasi, dan detail acara, dll."
|
||||
},
|
||||
{
|
||||
title: "Bagikan Undangan",
|
||||
desc: "Lewat WhatsApp, link, atau media sosial."
|
||||
},
|
||||
{
|
||||
title: "Lihat Hasil",
|
||||
desc: "Pratinjau undangan sebelum simpan."
|
||||
},
|
||||
{
|
||||
title: "Tambahkan Media",
|
||||
desc: "Foto, musik, dan peta lokasi."
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.howto-section {
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.howto-header h2 {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.howto-header p {
|
||||
color: #555;
|
||||
font-size: 15px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.howto-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 24px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.howto-card {
|
||||
background: #f8f9fc;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.howto-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-icon img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.howto-card h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.howto-card p {
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<section class="bg-white py-20 px-5 text-center">
|
||||
<div class="container mx-auto">
|
||||
<h2 class="text-4xl font-extrabold text-gray-800 mb-2">
|
||||
Keistimewaan Kami
|
||||
</h2>
|
||||
<p class="text-lg text-gray-600 mb-16">
|
||||
"Membuat undangan digitalmu terlihat lebih hidup dan istimewa."
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-4">
|
||||
|
||||
<div class="flex flex-col items-center rounded-xl border border-gray-200 bg-white p-8 text-center shadow-md transition-transform duration-300 hover:-translate-y-2 hover:shadow-xl">
|
||||
<div class="mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-blue-100">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-blue-600"><path d="M3 18v-6a9 9 0 0 1 18 0v6"></path><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"></path></svg>
|
||||
</div>
|
||||
<h3 class="mb-4 text-xl font-bold text-gray-800">
|
||||
Layanan Customer Care Setiap Hari
|
||||
</h3>
|
||||
<p class="text-base text-gray-600">
|
||||
Tim admin siap membantu dari jam 07:00 hingga 20:00 WIB tanpa hari libur.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center rounded-xl border border-gray-200 bg-white p-8 text-center shadow-md transition-transform duration-300 hover:-translate-y-2 hover:shadow-xl">
|
||||
<div class="mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-blue-100">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-blue-600"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-2.6-2.6a1 1 0 0 0-1.4 0l-1.6 1.6a1 1 0 0 0 0 1.4l3.77 3.77a6 6 0 0 1-7.94 7.94l-2.6-2.6a1 1 0 0 0-1.4 0L.3 21.7a1 1 0 0 0 0 1.4l.7.7a1 1 0 0 0 1.4 0l2.6-2.6a6 6 0 0 1 7.94-7.94l3.77-3.77z"></path><line x1="12" y1="12" x2="16" y2="16"></line></svg>
|
||||
</div>
|
||||
<h3 class="mb-4 text-xl font-bold text-gray-800">
|
||||
Desain Personal Sesuai Gaya Anda
|
||||
</h3>
|
||||
<p class="text-base text-gray-600">
|
||||
Pilih dari ratusan tema menarik dan ubah warna, font, serta tata letaknya dengan mudah.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center rounded-xl border border-gray-200 bg-white p-8 text-center shadow-md transition-transform duration-300 hover:-translate-y-2 hover:shadow-xl">
|
||||
<div class="mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-blue-100">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-blue-600"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
|
||||
</div>
|
||||
<h3 class="mb-4 text-xl font-bold text-gray-800">
|
||||
Undangan Fleksibel
|
||||
</h3>
|
||||
<p class="text-base text-gray-600">
|
||||
Nikmati kemudahan membagikan undangan dengan jumlah tamu sesuai kebutuhan Anda.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center rounded-xl border border-gray-200 bg-white p-8 text-center shadow-md transition-transform duration-300 hover:-translate-y-2 hover:shadow-xl">
|
||||
<div class="mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-blue-100">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-blue-600"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
|
||||
</div>
|
||||
<h3 class="mb-4 text-xl font-bold text-gray-800">
|
||||
Tema Eksklusif & Bisa Dikustomisasi
|
||||
</h3>
|
||||
<p class="text-base text-gray-600">
|
||||
Pilih ratusan desain menarik, ubah warna, font, hingga tata letak. Buat undangan yang benar-benar mencerminkan gayamu.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// Tidak ada script yang dibutuhkan untuk bagian ini
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Kosong! Semua styling sudah diatur oleh Tailwind di dalam template. */
|
||||
</style>
|
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<section id="testimoni" class="bg-slate-50 py-20 px-5">
|
||||
<div class="container mx-auto text-center">
|
||||
<h2 class="text-4xl font-extrabold text-gray-800 mb-2">
|
||||
Apa Kata Mereka?
|
||||
</h2>
|
||||
<p class="text-lg text-gray-600 mb-16">
|
||||
Kisah sukses dari para pengguna yang telah mempercayakan momen spesialnya kepada kami.
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div
|
||||
v-for="testimonial in testimonials"
|
||||
:key="testimonial.id"
|
||||
class="flex flex-col rounded-xl bg-white p-8 text-left shadow-lg transition-transform duration-300 hover:-translate-y-2 hover:shadow-2xl"
|
||||
>
|
||||
<div class="mb-4 flex items-center">
|
||||
<svg v-for="n in 5" :key="n" class="h-5 w-5" :class="n <= testimonial.rating ? 'text-yellow-400' : 'text-gray-300'" fill="currentColor" viewBox="0 0 20 20"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path></svg>
|
||||
</div>
|
||||
|
||||
<p class="mb-6 flex-grow text-gray-600 italic">"{{ testimonial.text }}"</p>
|
||||
|
||||
<div class="flex items-center">
|
||||
<img class="h-12 w-12 rounded-full object-cover" :src="testimonial.avatar" :alt="testimonial.name">
|
||||
<div class="ml-4">
|
||||
<h4 class="font-bold text-gray-800">{{ testimonial.name }}</h4>
|
||||
<p class="text-sm text-gray-500">{{ testimonial.role }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
const testimonials = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: 'Rizky & Anisa',
|
||||
role: 'Pengantin Baru',
|
||||
avatar: 'https://i.pravatar.cc/100?u=rizky',
|
||||
rating: 5,
|
||||
text: 'Desainnya elegan dan modern! Proses pembuatannya juga cepat banget. Semua tamu memuji undangannya. Terima kasih Abbauf Tech!'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Budi Santoso',
|
||||
role: 'Event Organizer',
|
||||
avatar: 'https://i.pravatar.cc/100?u=budi',
|
||||
rating: 5,
|
||||
text: 'Sebagai EO, kami butuh platform yang efisien dan hasilnya premium. Abbauf Tech menjawab semua kebutuhan itu. Klien kami sangat puas.'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Citra Lestari',
|
||||
role: 'Ulang Tahun Anak',
|
||||
avatar: 'https://i.pravatar.cc/100?u=citra',
|
||||
rating: 4,
|
||||
text: 'Fitur RSVP dan pengingat sangat membantu. Tema-tema ulang tahunnya juga lucu dan bisa dikustomisasi. Sangat direkomendasikan!'
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Kosong, semua diatur oleh Tailwind */
|
||||
</style>
|
@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-8">
|
||||
<NuxtLink
|
||||
to="/"
|
||||
class="text-blue-600 hover:text-blue-800 font-semibold inline-flex items-center"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Kembali ke Beranda
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<h1 class="text-3xl md:text-4xl font-bold text-center text-gray-800">
|
||||
Pilih Template Favoritmu
|
||||
</h1>
|
||||
<p class="mt-2 text-center text-gray-500">
|
||||
"Tersedia berbagai desain undangan pernikahan, khitan, ulang tahun, dan lainnya."
|
||||
</p>
|
||||
|
||||
<div class="mt-12 grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
@click="onCategoryClick(category.nama)"
|
||||
class="group cursor-pointer overflow-hidden rounded-lg shadow-lg hover:shadow-2xl transition-all duration-300"
|
||||
>
|
||||
<img :src="category.foto" :alt="category.nama" class="w-full h-48 object-cover group-hover:scale-110 transition-transform duration-300">
|
||||
<div class="p-6 bg-white">
|
||||
<h3 class="text-xl font-semibold text-gray-800">{{ category.nama }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const emit = defineEmits(['category-selected']);
|
||||
|
||||
const categories = ref([])
|
||||
|
||||
// Ambil data kategori dari Laravel API pakai $fetch
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
categories.value = await $fetch('http://localhost:8000/api/kategoris')
|
||||
} catch (error) {
|
||||
console.error('Gagal ambil kategori:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const onCategoryClick = (categoryName) => {
|
||||
emit('category-selected', categoryName);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchCategories()
|
||||
})
|
||||
</script>
|
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex items-center mb-8">
|
||||
<button @click="$emit('back')" class="text-blue-600 hover:text-blue-800 font-semibold mr-4">
|
||||
← Kembali ke Kategori
|
||||
</button>
|
||||
<h1 class="text-3xl md:text-4xl font-bold text-gray-800">
|
||||
Pilih Template {{ category }} Favoritmu
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div v-for="tpl in templates" :key="tpl.id" class="border rounded-lg p-4 text-center">
|
||||
<img :src="tpl.foto" :alt="tpl.nama" class="bg-gray-200 h-40 mb-4 rounded-md object-cover w-full" />
|
||||
<h4 class="font-semibold">{{ tpl.nama }}</h4>
|
||||
<p class="text-gray-500">Rp {{ tpl.harga }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
category: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
id_category: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const templates = ref([])
|
||||
|
||||
const fetchTemplates = async () => {
|
||||
try {
|
||||
templates.value = await $fetch(`http://localhost:8000/api/templates/category/${props.id_category}`)
|
||||
} catch (error) {
|
||||
console.error('Gagal ambil template:', error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchTemplates()
|
||||
})
|
||||
|
||||
defineEmits(['back']);
|
||||
</script>
|
@ -2,10 +2,39 @@
|
||||
<div>
|
||||
<LandingPageHeader />
|
||||
<LandingPageHero />
|
||||
|
||||
<section id="tentang-kami">
|
||||
<AboutSection />
|
||||
</section>
|
||||
|
||||
<section id="templat">
|
||||
<FeaturedTemplates />
|
||||
</section>
|
||||
|
||||
<section id="panduan">
|
||||
<HowToSection />
|
||||
</section>
|
||||
|
||||
<section id="keistimewaan">
|
||||
<PrivilegeSection />
|
||||
</section>
|
||||
|
||||
<section id="testimoni">
|
||||
<TestimoniSection />
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// Bagian script Anda sudah benar, tidak perlu diubah.
|
||||
import LandingPageHeader from '~/components/landing-page/header.vue';
|
||||
import LandingPageHero from '~/components/landing-page/hero.vue';
|
||||
import AboutSection from '~/components/landing-page/aboutsection.vue';
|
||||
import FeaturedTemplates from '~/components/landing-page/featuredtemplates.vue';
|
||||
import HowToSection from '~/components/landing-page/howtosection.vue';
|
||||
import PrivilegeSection from '~/components/landing-page/privilegesection.vue';
|
||||
import Footer from '~/components/landing-page/footer.vue';
|
||||
import TestimoniSection from '~/components/landing-page/testimonisection.vue';
|
||||
</script>
|
31
proyek-frontend/app/pages/template.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-16">
|
||||
<TemplatePageCategorySelection
|
||||
v-if="!selectedCategory"
|
||||
@category-selected="handleCategorySelect"
|
||||
/>
|
||||
|
||||
<TemplatePageTemplateGrid
|
||||
v-else
|
||||
:category="selectedCategory"
|
||||
@back="goBack"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
// State untuk menyimpan kategori yang sedang dipilih
|
||||
const selectedCategory = ref(null);
|
||||
|
||||
// Fungsi ini akan menangkap event dari CategorySelection
|
||||
const handleCategorySelect = (categoryName) => {
|
||||
selectedCategory.value = categoryName;
|
||||
};
|
||||
|
||||
// Fungsi ini akan menangkap event 'back' dari TemplateGrid
|
||||
const goBack = () => {
|
||||
selectedCategory.value = null;
|
||||
};
|
||||
</script>
|
10
proyek-frontend/package-lock.json
generated
@ -13,6 +13,7 @@
|
||||
"@nuxt/image": "^1.11.0",
|
||||
"@tailwindcss/vite": "^4.1.12",
|
||||
"eslint": "^9.34.0",
|
||||
"lucide-vue-next": "^0.542.0",
|
||||
"nuxt": "^4.0.3",
|
||||
"tailwindcss": "^4.1.12",
|
||||
"vue": "^3.5.20",
|
||||
@ -10426,6 +10427,15 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-vue-next": {
|
||||
"version": "0.542.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.542.0.tgz",
|
||||
"integrity": "sha512-cJfyhFoneDgYTouHwUJEutXaCW5EQuRrBsvfELudWnMiwfqvcEtpZTFZLdZ5Nrqow+znzn+Iyhu3KeYIfa3mEg==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"vue": ">=3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz",
|
||||
|
@ -16,6 +16,7 @@
|
||||
"@nuxt/image": "^1.11.0",
|
||||
"@tailwindcss/vite": "^4.1.12",
|
||||
"eslint": "^9.34.0",
|
||||
"lucide-vue-next": "^0.542.0",
|
||||
"nuxt": "^4.0.3",
|
||||
"tailwindcss": "^4.1.12",
|
||||
"vue": "^3.5.20",
|
||||
|
BIN
proyek-frontend/public/ABBAUF.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
proyek-frontend/public/event-list0.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
proyek-frontend/public/event-list1.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
proyek-frontend/public/event-list2.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
proyek-frontend/public/event-list3.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
proyek-frontend/public/event-list4.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
proyek-frontend/public/event-list5.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 3.0 MiB After Width: | Height: | Size: 3.0 MiB |
Before Width: | Height: | Size: 456 KiB After Width: | Height: | Size: 456 KiB |
BIN
proyek-frontend/public/templat.jpg
Normal file
After Width: | Height: | Size: 1.4 MiB |