Compare commits

..

11 Commits

Author SHA1 Message Date
d078aab4b0 new logo 2025-09-03 13:52:24 +07:00
213a3874d0 image 2025-09-03 11:02:31 +07:00
e20f1fa12f fix testi 2025-09-03 10:48:20 +07:00
ef91b38fa5 box shadow 2025-09-01 16:29:59 +07:00
Farhaan4
ddea7e321f fix 2025-09-01 15:23:46 +07:00
ddae4df823 fix 2025-09-01 12:31:25 +07:00
ff19646ba7 Styling 2025-09-01 11:58:46 +07:00
49ac69876c Cara membuat undangan 2025-09-01 11:54:24 +07:00
c11fdaf58f navlink 2025-09-01 10:22:10 +07:00
b28ee832d5 Update aboutsection.vue 2025-09-01 10:05:05 +07:00
720b968c6b Tentang Kami 2025-09-01 09:41:06 +07:00
56 changed files with 147 additions and 2528 deletions

View File

@ -1,41 +0,0 @@
<?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');
}
}

View File

@ -1,21 +0,0 @@
<?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);
}
}

View File

@ -1,48 +0,0 @@
<?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);
}
}

View File

@ -1,49 +0,0 @@
<?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);
}
}

View File

@ -1,55 +0,0 @@
<?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);
}
}

View File

@ -1,21 +0,0 @@
<?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']));
}
}

View File

@ -1,51 +0,0 @@
<?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);
}
}

View File

@ -1,56 +0,0 @@
<?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!');
}
}

View File

@ -1,64 +0,0 @@
<?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!');
}
}

View File

@ -1,29 +0,0 @@
<?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!');
}
}

View File

@ -1,82 +0,0 @@
<?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'));
}
}

View File

@ -1,15 +0,0 @@
<?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'];
}

View File

@ -1,15 +0,0 @@
<?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);
}
}

View File

@ -1,15 +0,0 @@
<?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);
}
}

View File

@ -1,19 +0,0 @@
<?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);
}
}

View File

@ -1,25 +0,0 @@
<?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);
}
}

View File

@ -1,22 +0,0 @@
<?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);
}
}

View File

@ -1,18 +0,0 @@
<?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);
}
}

View File

@ -1,18 +0,0 @@
<?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',
];
}

View File

@ -1,30 +0,0 @@
<?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);
}
}

View File

@ -1,19 +0,0 @@
<?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);
}
}

View File

@ -2,39 +2,94 @@
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,
],
// Provider untuk admin
'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| 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',
@ -42,14 +97,19 @@ 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,
];

View File

@ -1,23 +0,0 @@
<?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 [
//
];
}
}

View File

@ -1,24 +0,0 @@
<?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');
}
};

View File

@ -1,22 +0,0 @@
<?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');
}
};

View File

@ -1,24 +0,0 @@
<?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 15
$table->text('message');
$table->string('name');
$table->string('city');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('reviews');
}
};

View File

@ -1,20 +0,0 @@
<?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');
}
};

View File

@ -1,25 +0,0 @@
<?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');
}
};

View File

@ -1,26 +0,0 @@
<?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');
}
};

View File

@ -1,24 +0,0 @@
<?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');
}
};

View File

@ -1,33 +0,0 @@
<?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');
}
};

View File

@ -1,33 +0,0 @@
<?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');
}
};

View File

@ -1,32 +0,0 @@
<?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');
}
};

View File

@ -2,20 +2,16 @@
namespace Database\Seeders;
use App\Models\Admin;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
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
]
);
//
}
}

View File

@ -7,10 +7,16 @@ use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
$this->call([
AdminSeeder::class,
]);
}
{
// \App\Models\User::factory(10)->create();
// \App\Models\User::factory()->create([
// 'name' => 'Test User',
// 'email' => 'test@example.com',
// ]);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,109 +0,0 @@
<!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>

View File

@ -1,54 +0,0 @@
@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

View File

@ -1,164 +0,0 @@
@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

View File

@ -1,231 +0,0 @@
@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

View File

@ -1,133 +0,0 @@
@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

View File

@ -1,119 +0,0 @@
@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

View File

@ -1,279 +0,0 @@
@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

View File

@ -1,149 +0,0 @@
<!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>

View File

@ -2,26 +2,18 @@
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;
// 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']);
/*
|--------------------------------------------------------------------------
| 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!
|
*/
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});

View File

@ -1,83 +1,18 @@
<?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;
//Login
/*
|--------------------------------------------------------------------------
| 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!
|
*/
Route::get('/', function () {
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');
return view('welcome');
});

View File

@ -7,7 +7,7 @@
<!-- Layout gambar + teks -->
<div class="about-layout">
<div class="about-image">
<img src="/logo1.png" alt="Tentang Kami - Undangan Digital" />
<img src="/Rectangle.png" alt="Tentang Kami - Undangan Digital" />
</div>
<div class="about-text">
<p>

View File

@ -30,7 +30,7 @@
</div>
<div class="see-more">
<NuxtLink to="/template">Lihat Selengkapnya...</NuxtLink>
<a href="#">Lihat Selengkapnya...</a>
</div>
</section>
</template>

View File

@ -2,13 +2,12 @@
<header class="main-header">
<nav class="container">
<div class="logo">
<NuxtLink to="/" class="logo-link"> <img src="/ABBAUF.png" alt="Abbauf Tech Logo" class="logo-icon">
<span>ABBAUF TECH</span>
</NuxtLink>
<img src="/abbauflogo.png" alt="Abbauf Tech Logo" class="logo-icon">
<span>ABBAUF TECH</span>
</div>
<ul class="nav-links">
<li v-for="link in navLinks" :key="link.name">
<NuxtLink :to="link.path">{{ link.name }}</NuxtLink>
<a :href="link.path">{{ link.name }}</a>
</li>
</ul>
</nav>
@ -19,15 +18,12 @@
import { ref } from 'vue';
const navLinks = ref([
// 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' },
{ name: 'Beranda', path: '#beranda' },
{ name: 'Tentang Kami', path: '#tentang-kami' },
{ name: 'Templat', path: '#template' },
{ name: 'Panduan', path: '#cara' },
{ name: 'Keistimewaan', path: '#keistimewaan' },
{ name: 'Testimoni', path: '#testimoni' },
]);
</script>
@ -48,17 +44,26 @@ const navLinks = ref([
align-items: center;
}
.logo a.logo-link { /* Style untuk logo link */
.logo {
display: flex;
align-items: center;
font-weight: bold;
font-size: 1.5rem;
color: #0d6efd;
text-decoration: none; /* Hilangkan garis bawah default */
}
.main-header {
position: fixed;
top: 0;
left: 0;
}
.logo-icon {
width: 40px;
width: 55px;
margin-right: 10px;
}

View File

@ -4,7 +4,7 @@
<div class="w-full text-center lg:w-1/2">
<h1 class="text-5xl font-extrabold leading-tight text-gray-800 lg:text-6xl">
<h1 class="text-5xl font-extrabold leading-tight text-gray-800 lg:text-6xl ml-14">
Buat Undangan Digital Praktis Untuk
<div class="h-24 flex items-center justify-center">
@ -16,10 +16,10 @@
Tanpa Ribet
</h1>
<p class="mt-4 mb-8 text-lg text-gray-600">
<p class="mt-4 mb-8 text-lg text-gray-600 ml-14">
Coba undangan digital PRAKTIS untuk berbagai acara. Pilih template praktis atau premium sesuai kebutuhanmu. Praktis, cepat, dan bisa langsung digunakan.
</p>
<div class="flex flex-col justify-center gap-4 sm:flex-row">
<div class="flex flex-col justify-center gap-4 sm:flex-row ml-14">
<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"/>
@ -34,7 +34,7 @@
</div>
<div class="w-full lg:w-1/2">
<img src="/logo2.png" alt="Tampilan Undangan Digital di Ponsel" class="mx-auto max-w-full">
<img src="/iphone.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>

View File

@ -1,58 +0,0 @@
<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>

View File

@ -1,49 +0,0 @@
<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">
&larr; 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>

View File

@ -1,31 +0,0 @@
<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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB