UNIVERSITAS GUNADARMA FAKULTAS TEKNOLOGI INDUSTRI
LAPORAN AKHIR
PROYEK WEBSITE TOKO TAS SABLON EXPRESS Nama Anggota:
1. 50421549 | Geren Haekal Hafizh 2. 50421663 | Irfan Nur Sasmito 3. 50421769 | M Nur Luthfi
4. 51421091 | Muhammad Thariq El Deuzan 5. 51421341 | Rizki Faizal Iriansyah
Kelas : 4IA02
Jurusan : Informatika
Dosen : Dr. Dharmayanti S.T., MMSI
Ditulis Guna Melengkapi Sebagian Syarat Project Pengolahan Proyek Perangkat Lunak
Jakarta
2025
KATA PENGANTAR
Segala puji dan syukur penulis panjatkan ke hadirat Tuhan Yang Maha Esa atas limpahan rahmat dan karunia-Nya, sehingga tim 3 berhasil menyelesaikan proyek dan laporan akhir ini dengan baik dan lancar. Laporan akhir yang berjudul PROYEK WEBSITE TOKO TAS SABLON EXPRESS disusun sebagai salah satu syarat untuk menyelesaikan tugas proyek dalam pengolahan perangkat lunak.
Laporan ini disajikan menggunakan konsep dan bahasa yang sederhana agar dapat memudahkan pembaca dalam memahami isi laporan.
Kami juga menyampaikan rasa terima kasih yang sebesar-besarnya kepada semua pihak yang telah memberikan dukungan, ide, dan kontribusi dalam penyusunan laporan ini. Harapan kami, laporan ini dapat memberikan manfaat bagi pembaca dan menjadi referensi yang berguna. Kami sangat mengapresiasi kritik, saran, serta masukan dari berbagai pihak demi perbaikan di masa depan.
Jakarta, 10 Januari 2025
Tim 3
DAFTAR ISI
KATA PENGANTAR ... 2
DAFTAR ISI ... 3
DAFTAR GAMBAR ... 5
DAFTAR TABEL ... 6
DAFTAR LAMPIRAN ... 7
BAB 1 PENDAHULUAN ... 8
1.1 Latar Belakang ... 8
1.2 Tujuan ... 8
1.3 Rumusan Masalah ... 9
1.4 Batasan Masalah ... 9
BAB 2 PEMBAHASAN ... 10
2.1 Gambaran Umum Aplikasi ... 10
2.2 Perancangan Aplikasi ... 10
2.2.1 Perancangan Struktur navigasi... 10
2.2.1.1 Struktur Navigasi Admin ... 10
2.2.1.2 Struktur Navigasi Pengguna ... 11
2.2.2 Perancangan Database ... 12
2.2.2.1 Struktur Database Admin ... 12
2.2.2.2 Struktur Database Testimoni Pengguna ... 13
2.2.2.3 Struktur Database Items ... 14
2.2.2.4 Struktur Database Categories ... 14
2.2.3 Desain tampilan aplikasi ... 15
2.2.3.1 Desain tampilan Landing page ... 15
2.2.3.2 Desain tampilan categories ... 17
2.2.3.3 Desain tampilan About ... 18
2.2.3.4 Desain tampilan Header ... 19
2.2.3.5 Desain tampilan Footer ... 20
2.2.3.6 Desain tampilan Login Admin ... 21
2.2.4 Uji Coba ... 23
BAB 3 PENUTUP ... 25
3.1 Kesimpulan ... 25
3.2 Saran ... 25
LAMPIRAN ... 26
DAFTAR GAMBAR
Gambar 2. 1 Struktur Navigasi Admin ... 11
Gambar 2. 2 Struktur Navigasi Pengguna ... 12
Gambar 2. 3 Struktur Database Admin ... 13
Gambar 2. 4 Struktur Database Testimoni Pengguna ... 13
Gambar 2. 5 Struktur Database Items ... 14
Gambar 2. 6 Struktur Database Categories ... 14
Gambar 2. 7 Desain Tampilan Aplikasi ... 15
Gambar 2. 8 Desain tampilan categories ... 17
Gambar 2. 9 Desain tampilan About ... 18
Gambar 2. 10 Desain tampilan Header ... 19
Gambar 2. 11 Desain tampilan Footer ... 20
Gambar 2. 12 Desain tampilan Login Admin ... 21
Gambar 2. 13 Desain tampilan Dashboard Admin ... 21
DAFTAR TABEL
Tabel 2. 1 Uji Coba ... 24
DAFTAR LAMPIRAN
Lampiran 1 code App.Jsx ... 27
Lampiran 2 code Main.Jsx ... 27
Lampiran 3 code HomePage.Jsx ... 36
Lampiran 4 code Categories.Jsx ... 38
Lampiran 5 code Catalogue.Jsx ... 39
Lampiran 6 code About.Jsx ... 43
Lampiran 7 code Dashboard.Jsx ... 50
Lampiran 8 code Login.Jsx ... 52
Lampiran 9 code Server.js ... 58
BAB 1 PENDAHULUAN
1.1 Latar Belakang
Di era globalisasi saat ini, perkembangan teknologi semakin pesat seiring waktu. Kemajuan teknologi informasi memiliki peran yang sangat penting dalam berbagai aspek kehidupan manusia. Beragam aktivitas yang sebelumnya dilakukan secara langsung kini dapat dijalankan hanya dengan memanfaatkan teknologi yang ada di genggaman kita. Salah satu sektor yang sangat dipengaruhi oleh perkembangan teknologi ini adalah sektor bisnis.
Toko Tas sablon Express Layanan produksi berbagai jenis tas custom yang dapat digunakan untuk berbagai keperluan, seperti seminar, souvenir, ulang tahun, wedding, produksi promosi perusahaan, dan lainnya. Tas-tas ini dibuat dari berbagai jenis bahan berkualitas, dengan berbagai ukuran, dan bisa dipesan sesuai kebutuhan pelanggan, termasuk untuk keperluan khusus seperti tas sekolah, tas bidan, tas wedding, dan sebagainya.
1.2 Tujuan
Tujuan yang hendak dicapai dalam proyek ini adalah :
1. Mempermudah Toko Tas Sablon Express dalam memperluas
jangkauan pemasaran untuk menjangkau lebih banyak wilayah pembelian.
2. Mendukung Toko Tas Sablon Express dalam menghemat biaya promosi serta meningkatkan efisiensi waktu dalam memperkenalkan produk tas sablon berbahan beragam.
3. Membantu Toko Tas Sablon Express dalam mempromosikan berbagai jenis tas sablon dengan pilihan bahan yang beragam kepada calon pelanggan.
4. Memudahkan calon pelanggan yang berminat terhadap produk Toko Tas Sablon Express untuk mendapatkan informasi terkait pemesanan dan kontak usaha.
1.3 Rumusan Masalah
Dalam upaya meningkatkan jangkauan pemasaran dan efisiensi operasional, Toko Tas Sablon Express menghadapi beberapa tantangan, yaitu:
1. Kesulitan dalam menjangkau calon pelanggan di wilayah yang lebih luas tanpa dukungan teknologi pemasaran yang memadai.
2. Pengeluaran biaya promosi yang tinggi dan memakan waktu dalam memperkenalkan produk kepada pelanggan.
3. Keterbatasan media promosi untuk memberikan informasi lengkap tentang produk, bahan, harga, dan cara pemesanan kepada calon pelanggan.
4. Kurangnya platform yang memungkinkan calon pelanggan untuk dengan mudah mendapatkan informasi terkait kontak usaha dan melakukan pemesanan secara langsung.
1.4 Batasan Masalah
Agar proyek ini lebih terarah, terdapat beberapa batasan masalah yang ditetapkan, yaitu:
1. Proyek ini hanya mencakup pembuatan website berbasis web untuk mempromosikan produk Toko Tas Sablon Express.
2. Website yang dikembangkan dirancang untuk operasional offline, tanpa fitur
e-commerce atau transaksi online.
3. Fungsi website dibatasi pada penyajian katalog produk, informasi detail produk, dan kontak usaha.
4. Perancangan antarmuka pengguna difokuskan pada kemudahan navigasi untuk admin dan pengguna.
5. Pengembangan hanya mencakup pengelolaan data produk, testimoni, dan informasi pelanggan tanpa integrasi dengan sistem eksternal lainnya.
BAB 2 PEMBAHASAN
2.1 Gambaran Umum Aplikasi
Aplikasi ini dirancang sebagai platform berbasis web untuk mendukung operasional Toko Tas Sablon Express. Dengan tampilan antarmuka yang user- friendly, aplikasi ini memungkinkan pengguna untuk melihat katalog produk, memesan tas custom, serta mendapatkan informasi lengkap mengenai bahan, harga, dan waktu produksi.
2.2 Perancangan Aplikasi
Pada tahap ini dijelaskan mengenai proses perancangan website toko sablon express dalam mencapai tujuan yang diharapkan. Terdapat 3 tahapan perancangan yang dimulai dari perancangan struktur navigasi, perancangan database dan desain tampilan aplikasi.
2.2.1 Perancangan Struktur navigasi
Pada tahap ini menjelaskan perancangan struktur navigasi dalam membuat aplikasi website toko sablon express. Pada tahap ini struktur navigasi dibedakan menjadi struktur navigasi admin dan pengguna
2.2.1.1 Struktur Navigasi Admin
Struktur navigasi admin yang ditujukan untuk mempermudah admin dalam
mengolah data pada website. Terdapat 3 menu dalam struktur navigasi admin
yaitu dashboard, data produk dan data testimoni.
Gambar 2. 1 Struktur Navigasi Admin
Gambar tersebut menjelaskan bahwa ketiga struktur navigasi memiliki fungsi yang berbeda. Pada bagian data pengguna, admin dapat mengelola akun admin dengan melakukan penambahan, perubahan, dan penghapusan data admin.
Navigasi data produk digunakan untuk mengelola produk yang ada di website, termasuk penambahan, pengubahan, dan penghapusan produk. Sementara itu, navigasi data pengguna digunakan untuk mengelola testimoni yang diberikan oleh pengguna di website, dengan opsi untuk menambah, mengubah, dan menghapus testimoni.
2.2.1.2 Struktur Navigasi Pengguna
Struktur navigasi pengguna yang ditujukan untuk mempermudah pengguna
dalam menggunakan website. Terdapat 4 menu dalam struktur navigasi admin
yaitu home, detail produk, testimoni dan about us.
Gambar 2. 2 Struktur Navigasi Pengguna
Pada gambar menjelaskan mengenai struktur navigasi pengguna yaitu pada menu home terdapat penjelasan mengenai toko sablon express. Menu detail product terdapat penjelasan produk berupa harga,ukuran dan bahan produk toko sablom express. Pada Menu testimoni terdapat testimoni dari beberapa konsumen mengenai pelayanan dan produk dari toko sablon express. Menu about terdapat informasi , alamat dan kontak toko sablon express.
2.2.2 Perancangan Database
pada tahap ini menjelaskan proses perancangan backend untuk pembuatan website toko sablon express. pada tahap ini dibagi menjadi 3 bagian yaitu struktur database admin, testimoni pengguna dan categories.
2.2.2.1 Struktur Database Admin
Struktur Database Admin digunakan untuk menyimpan data akun admin pada
website, pada struktur ini terdapat empat field yaitu id,username, password, dan
created_at
Gambar 2. 3 Struktur Database Admin
pada gambar menjelaskan mengenai struktur database admin yang memiliki empat field yaitu id dengan type int, panjang 11 karakter dan auto increment, username dengan type varchar dan panjang 25 karakter, password dengan type varchar dan panjang 10 karakter dan created_At
2.2.2.2 Struktur Database Testimoni Pengguna
Struktur Database Testimoni Pengguna digunakan untuk menyimpan data testimoni yang diberikan pengguna mengenai toko sablon express, pada struktur ini terdapat dua field yaitu isi_testimoni dan nama_pengguna.
Gambar 2. 4 Struktur Database Testimoni Pengguna
pada gambar menjelaskan mengenai struktur database testimoni pengguna yang
memiliki dua field yaitu isi_testimoni dengan type text dan nama_pengguna
dengan type varchar dan panjang 25 karakter.
2.2.2.3 Struktur Database Items
Struktur Database Items digunakan untuk menyimpan data produk di website, pada struktur ini terdapat tiga field yaitu id, name dan image.
Gambar 2. 5 Struktur Database Items
pada gambar menjelaskan mengenai struktur database items yang memiliki tiga field yaitu id dengan type int, panjang 11 karakter dan auto increment, name dengan type varchar dan panjang 25 karakter dan image dengan type vaarchar dan panjang 255 karakter.
2.2.2.4 Struktur Database Categories
Struktur Database Categories digunakan untuk menyimpan data untuk akun admin, pada struktur ini terdapat tiga field yaitu id, name dan image.
Gambar 2. 6 Struktur Database Categories
pada gambar menjelaskan mengenai struktur database categories yang memiliki
empat field yaitu id dengan type int, panjang 11 karakter dan auto increment, title
dengan type varchar dan panjang 255 karakter, description dengan type text dan image dengan type varchar dan panjang 255 karakter.
2.2.3 Desain tampilan aplikasi
Perancangan tampilan aplikasi adalah tahap yang menjelaskan proses-proses dalam merancang tampilan aplikasi. Pada tahap ini, tampilan dibuat dengan desain yang sederhana agar pengguna dapat mengoperasikan aplikasi dengan mudah dan sesuai dengan keinginan klien.
2.2.3.1 Desain tampilan Landing page
Gambar 2. 7 Desain Landing page
Halaman ini dibuat untuk memudahkan pengguna memahami produk yang tersedia, dengan menonjolkan personalisasi tas tote bag yang unik dan berkualitas.
Pengguna dapat menjelajahi pilihan kategori lainnya, seperti tote bag custom dan
pouch custom, yang dilengkapi dengan deskripsi singkat untuk mempermudah
dalam menentukan pilihan. Bagian keunggulan produk menampilkan poin-poin
penting, seperti proses cepat, harga terjangkau, dan kualitas yang terjamin,
disajikan secara visual dan informatif. Selain itu, testimoni pelanggan juga
disertakan sebagai bukti kepercayaan dan pengalaman positif dari pengguna
sebelumnya. Desain halaman ini mengutamakan kesederhanaan dan efektivitas,
dengan navigasi yang tertata rapi untuk kenyamanan pengguna.
2.2.3.2 Desain tampilan categories
Gambar 2. 8 Desain tampilan categories
Halaman kategori menyajikan berbagai produk yang tersedia disertai
deskripsi singkat untuk setiap kategori. Pengguna dapat melihat gambar yang
menggambarkan masing-masing kategori, dilengkapi informasi detail seperti
material, dimensi, dan fitur produk. Semua kategori diatur dengan rapi untuk
memudahkan pengguna dalam menjelajahi dan menemukan produk yang
diinginkan.
2.2.3.3 Desain tampilan About
Gambar 2. 9 Desain tampilan About
Halaman ini dirancang untuk memudahkan pengguna memahami layanan Goodie Bag Tas Sablon Express, yang menyediakan berbagai jenis tas custom untuk keperluan seminar, souvenir, acara perusahaan, hingga pernikahan. Produk yang ditawarkan hadir dengan bahan berkualitas, desain menarik, dan dapat disesuaikan dengan kebutuhan pengguna. Bagian pilihan bahan memberikan informasi detail mengenai jenis bahan seperti spunbond, baby canvas, drill, mika, dan dacron, lengkap dengan keunggulan masing-masing.
Bagian keunggulan produk menyoroti aspek penting, seperti kualitas bahan yang tahan lama, opsi kustomisasi untuk memenuhi preferensi pelanggan, dan harga yang terjangkau sesuai anggaran. Selain itu, informasi lokasi juga disertakan dengan peta interaktif untuk memudahkan pengguna menemukan alamat kami.
Desain halaman ini mengutamakan kesederhanaan dan kejelasan, dengan elemen informatif yang disusun secara terorganisir untuk kenyamanan pengguna.
2.2.3.4 Desain tampilan Header
Gambar 2. 10 Desain tampilan Header
Bagian navbar diisi dengan paduan warna antara putih dan warna beige digunakan
disetiap halaman sebagai header, yang memiliki isi seperti logo toko, home,
categories dan about.
2.2.3.5 Desain tampilan Footer
Gambar 2. 11 Desain tampilan Footer
Footer tersebut dirancang untuk memberikan kesan toko yang memiliki layanan pembuatan tas custom, seperti pouch, tote bag, dan goodie bag. Dengan slogan
"Your Promotional Packaging Solution," mereka menawarkan desain kreatif dan
kualitas terbaik untuk memenuhi kebutuhan pelanggan. Terdapat informasi layanan
yang mencakup pembuatan pouch, tote bag, dan goodie bag, serta kontak untuk
informasi lebih lanjut, seperti nomor telepon (+6289534151742), email
([email protected]), dan alamat fisik (Jalan Arthayasa No. 50, Kecamatan Limo,
Depok 16515). Footer ini juga menonjolkan tautan ke Instagram mereka,
memperlihatkan pendekatan visual untuk menarik pelanggan dan menampilkan
portofolio. Desainnya sederhana namun informatif, dengan latar warna coklat yang
memberi kesan hangat dan profesional.
2.2.3.6 Desain tampilan Login Admin
Gambar 2. 12 Desain tampilan Login Admin
Halaman login ditampilkan memiliki desain sederhana namun estetik.
Tampilan ini terbagi menjadi dua bagian utama: di sebelah kiri, terdapat gambar tas kanvas putih yang diletakkan di atas latar rerumputan kering dan elemen alam seperti ranting dan daun, yang mencerminkan tema ramah lingkungan atau natural.
Di sebelah kanan, terdapat formulir login untuk administrator yang mencakup judul
"Login Admin," pesan selamat datang yang berbunyi "Selamat datang di portal admin, login untuk memulai," serta dua kolom input untuk memasukkan username dan password.
Kolom input memiliki ikon masing-masing untuk membantu identifikasi (username dan password), yang memudahkan pengguna memahami fungsinya.
Tombol "LOGIN" berwarna coklat yang konsisten dengan elemen desain
sebelumnya memberikan sentuhan profesional. Keseluruhan desain memberikan
kesan minimalis dengan fokus pada fungsi serta daya tarik visual, terutama melalui
penggunaan elemen gambar yang relevan dengan konteks bisnis tas custom atau
ramah lingkungan.
2.2.3.7 Desain tampilan Dashboard admin
Gambar 2. 13 Desain tampilan Dashboard Admin
Halaman ini merupakan dashboard admin untuk mengelola kategori dan item produk pada aplikasi berbasis web. Di bagian atas terdapat pesan sambutan dengan latar biru muda. Tengah halaman menampilkan dua bagian utama: Jumlah Kategori:
3 dan Jumlah Items: 12. Masing-masing berisi daftar data dengan ID unik, dilengkapi tombol Edit dan Hapus untuk mempermudah pengelolaan.
Footer memuat informasi kontak, alamat, serta layanan seperti "Sablon Pouch" dan
"Sablon GoodieBag". Dengan desain yang sederhana dan terstruktur, halaman ini
memprioritaskan kemudahan admin dalam mengelola data, meskipun akan lebih
baik jika dilengkapi fitur pencarian atau elemen visual tambahan untuk
meningkatkan kenyamanan navigasi.
2.2.4 Uji Coba Aktivitas
Pengujian
Realisasi Yang Diharapkan
Hasil Pengujian Kesimpulan
Dapat menuju halaman “Home”
Sistem akan menuju ke halaman “Home”
sebagai halaman awal
Menampilkan halaman “Home”
Valid
Dapat menuju halaman
“Categories”
Sistem akan menuju ke halaman
“Categories”
Menampilkan halaman
“Categories”
Valid
Dapat menuju halaman “About”
Sistem akan menuju ke halaman “About”
Menampilkan halaman “About”
Valid
Klik tombol
“Pesan Sekarang”
Membuka formulir
pemesanan Toko Tas Sablon Express
Membuka formulir pemesanan
Valid
Klik tombol
“Explore”
Sistem akan menuju ke halaman
“Categories”
Menampilkan halaman
“Categories”
Valid
Input nama dan review pada form review
Saat review dikirim sistem akan
menampilkan nama dan review
Nama dan review tampil di text field review
Valid
yang telah di input
Klik tombol “See our Instagram”
Membuka halaman
Instagram Toko Tas Sablon Express
Membuka halaman
Instagram Toko Tas Sablon Express
Valid
Klik tombol
“Catalogue”
Sistem akan menuju ke halaman
“Catalogue”
Menampilkan halaman
“Catalogue”
Valid
Klik tombol
“Lihat peta lebih besar” di peta lokasi
Membuka Google Maps lokasi Toko Tas Sablon Express
Membuka lokasi Toko Tas Sablon Express di Google Maps
Valid
Klik icon login admin
Sistem akan menuju ke halaman login admin
Menampilkan halaman login admin
Valid
Memasukan username dan password untuk masuk ke
dashboard admin
Sistem akan menuju ke halaman
dashboard admin jika username dan password benar
Menampilkan halaman
dashboard admin jika username dan password yang dimasukan benar
Valid
Tabel 2. 1 Uji Coba
BAB 3 PENUTUP
3.1 Kesimpulan
Proyek pengembangan website Toko Tas Sablon Express berhasil memberikan solusi praktis untuk mempromosikan produk tas custom. Fitur-fitur yang dirancang mempermudah pelanggan dalam menjelajahi katalog produk, mendapatkan informasi detail, dan menghubungi toko. Website ini juga membantu meningkatkan efisiensi promosi dan memperluas jangkauan pemasaran. Selain itu, proses pengembangan memanfaatkan navigasi dan desain antarmuka yang sederhana, sehingga website ini mudah digunakan baik oleh admin maupun pengguna.
3.2 Saran
Disarankan untuk menambahkan fitur e-commerce agar transaksi online
lebih mudah, memperkuat keamanan website pada login admin, serta
mengoptimalkan desain agar responsif di berbagai perangkat. Selain itu, visibilitas
website dapat ditingkatkan melalui SEO, dan pengembangan aplikasi mobile juga
dapat dipertimbangkan untuk memperluas aksesibilitas pengguna.
LAMPIRAN LAMPIRAN
App.Jsx
import { useState } from "react";
import { Routes, Route, useLocation } from "react-router- dom"
import NavbarComponent from "./components/NavbarComponent"
import FooterComponent from "./components/FooterComponent"
import HomePage from "./pages/HomePage"
import Categories from "./pages/Categories"
import About from "./pages/About"
import Login from "./auth/login"
import Dashboard from './pages/Dashboard';
import Catalogue from "./pages/Catalogue";
function App() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const location = useLocation();
return <div>
{location.pathname !== "/login" && <NavbarComponent isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} />}
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/homepage" element={<HomePage />} />
<Route path="/categories" element={<Categories />} />
<Route path="/about" element={<About/>} />
<Route path="/login" element={<Login setIsLoggedIn={setIsLoggedIn} />} />
<Route path="/dashboard" element={<Dashboard isLoggedIn={isLoggedIn} />} />
<Route path="/catalogue" element={<Catalogue />} />
</Routes>
{location.pathname !== "/login" && <FooterComponent/>}
</div>
}
export default App
Lampiran 1 code App.Jsx
Main.Jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import App from './App.jsx'
import 'bootstrap/dist/css/bootstrap.min.css';
import "./dist/css/main.css"
import { BrowserRouter} from "react-router-dom"
createRoot(document.getElementById('root')).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>, )
Lampiran 2 code Main.Jsx
HomePage.Jsx
import React, { useState, useEffect } from 'react';
import '../dist/css/main.css';
import produkHeader from '../assets/img/produk- jumbotron.jpg';
import totebagImg from '../assets/img/tote-bag.jpg';
import potchImg from '../assets/img/potch.jpg';
import bestImg from '../assets/img/best.jpg';
import fastImg from '../assets/img/fast.png';
import cheapImg from '../assets/img/cheap.png';
import qualityImg from '../assets/img/quality.png';
import tokoImg from '../assets/img/toko.jpg';
import { Container, Row, Col } from 'react-bootstrap';
import { FaArrowUpRightFromSquare, FaTrash } from "react- icons/fa6";
const HomePage = () => {
const [isHovered, setIsHovered] = useState(false);
const [isVisible, setIsVisible] = useState(false);
const [review, setReview] = useState('');
const [customerName, setCustomerName] = useState('');
const [testimonials, setTestimonials] = useState([]);
useEffect(() => {
const timer = setTimeout(() => setIsVisible(true), 500);
return () => clearTimeout(timer);
}, []);
useEffect(() => {
fetch('http://localhost:3000/testimoni') .then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => setTestimonials(data))
.catch(error => console.error('Error fetching testimonials:', error));
}, []);
const handleReviewChange = (e) =>
setReview(e.target.value);
const handleNameChange = (e) =>
setCustomerName(e.target.value);
const handleReviewSubmit = () => {
const forbiddenWords = ['anjing', 'babi', 'jelek'];
const containsForbiddenWord = forbiddenWords.some(word
=> review.includes(word));
if (containsForbiddenWord) {
alert('Review Anda mengandung kata yang tidak diperbolehkan.');
return;
}
if (!review) {
alert('Masukkan review Anda terlebih dahulu');
return;
}
if (!customerName) {
alert('Masukkan nama Anda terlebih dahulu');
return;
}
fetch('http://localhost:3000/testimoni', { method: 'POST',
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nama_pengguna: customerName, isi_testimoni: review }),
})
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => {
setTestimonials([...testimonials, data]);
setReview('');
setCustomerName('');
})
.catch(error => console.error('Error submitting review:', error));
};
const handleDeleteTestimonial = (nama_pengguna, isi_testimoni) => {
fetch(`http://localhost:3000/testimoni`, { method: 'DELETE',
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nama_pengguna, isi_testimoni }),
})
.then(response => { if (!response.ok) {
return response.json().then(err => {
throw new Error(err.message || 'Terjadi kesalahan saat menghapus testimoni.');
});
}
setTestimonials(testimonials.filter(testimonial =>
!(testimonial.nama_pengguna === nama_pengguna &&
testimonial.isi_testimoni === isi_testimoni) ));
})
.catch(error => {
console.error('Error deleting testimonial:', error);
alert(error.message);
});
};
return (
<div className="homepage">
<header className="w-100 min-vh-100 d-flex align- items-center">
<Container>
<Row style={{ display: 'flex', alignItems:
'flex-start', marginLeft: '0px', position: 'relative', zIndex: 1, marginTop: '10px' }}>
<Col style={{ textAlign: 'left', padding:
'20px', marginLeft: '-150px', marginTop: '-100px' }}>
<h1 style={{ fontWeight: 'bold', fontSize:
'50px' }}>
Personalisasi Gaya Anda dengan Tote Bag Unik dan Eksklusif
</h1>
<p style={{ fontWeight: 'inherit', fontSize:
'17px' }}>
Sablon Custom, Kualitas Premium - Buat Ceritamu Tertulis di Setiap Tas
</p>
<a
href="https://forms.gle/kg9hD6JwoTMpeqoe6" target="_blank"
rel="noopener noreferrer" style={{ textDecoration: 'none' }}>
<button
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
backgroundColor: isHovered ? '#7A5A3B' : '#A68B5B',
padding: '10px 12px', width: '133px',
height: '48px', display: 'flex',
justifyContent: 'center', alignItems: 'center', border: 'none',
borderRadius: '8px', color: '#FAFAFA', fontSize: '14px', fontWeight: 'inherit',
transition: 'background-color 0.5s' }}
>
Pesan Sekarang </button>
</a>
</Col>
<Col xs={12} md={6} style={{ display: 'flex', justifyContent: 'flex-end', marginLeft: '170px',
marginTop: '-100px' }}>
<img
src={produkHeader}
alt="Produk"
style={{
width: '350px', height: '380px', borderRadius: '40px',
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.2)',
}}
/>
</Col>
</Row>
</Container>
<div className={`ellipse ${isVisible ? 'fade-in' : ''}`} style={{ marginLeft: '80px', position: 'absolute', zIndex: 0 }}>
<Row className="greeting" style={{ display:
'flex', alignItems: 'flex-start', marginLeft: '20px', position: 'relative', zIndex: 1, marginTop: '20px' }}>
<Col style={{ textAlign: 'left', padding:
'20px' }}>
<h1 style={{ fontWeight: 'bold', fontSize:
'50px' }}>
Personalisasi Gaya Anda dengan Tote Bag Unik dan Eksklusif
</h1>
<p style={{ fontWeight: 'inherit', fontSize:
'17px' }}>
Sablon Custom, Kualitas Premium - Buat Ceritamu Tertulis di Setiap Tas
</p>
</Col>
</Row>
</div>
</header>
<div className="custom-category w-2 min-vh-20">
<Container>
<Row>
<Col style={{ textAlign: 'left', padding:
'20px', marginLeft: '-150px' }}>
<h2 style={{ fontWeight: 'bold' }}>OTHER CUSTOM CATEGORY</h2>
</Col>
</Row>
<Row>
<Col style={{ display: 'flex', alignItems:
'center', padding: '20px' }} className="category-item">
<div style={{ border: '1px solid #A68B5B', borderRadius: '8px', padding: '10px', flex: 1, display:
'flex', alignItems: 'center' }}>
<div style={{ flex: 1 }}>
<h3 style={{ fontWeight: 'inherit' }}>Custom Tote Bag</h3>
<p>Ekspresikan diri dengan tote bag custom yang stylish dan fungsional, dibuat khusus untuk Anda.</p>
<button className="button-explore"
onClick={() => window.location.href='/categories#tote- bag'}>
EXPLORE TOTE BAG
<FaArrowUpRightFromSquare />
</button>
</div>
<img src={totebagImg} alt="Custom Tote Bag" style={{ width: '150px', height: '150px',
borderRadius: '8px', marginLeft: '5px', objectFit: 'cover' }} />
</div>
</Col>
<Col style={{ display: 'flex', alignItems:
'center', padding: '20px' }} className="category-item">
<div style={{ border: '1px solid #A68B5B', borderRadius: '8px', padding: '10px', flex: 1, display:
'flex', alignItems: 'center' }}>
<div style={{ flex: 1 }}>
<h3 style={{ fontWeight: 'inherit' }}>Custom Pouch</h3>
<p>Pouch multifungsi yang bisa
disesuaikan dengan selera Anda. Praktis, elegan, dan penuh karakter!</p>
<button className="button-explore"
onClick={() => window.location.href='/categories#pouch'}>
EXPLORE POUCH
<FaArrowUpRightFromSquare />
</button>
</div>
<img src={potchImg} alt="Custom Pouch"
style={{ width: '150px', height: '150px', borderRadius:
'8px', marginLeft: '5px', objectFit: 'cover' }} />
</div>
</Col>
</Row>
</Container>
</div>
<div className="best-choice w-100 min-vh-50 mt-5">
<Container>
<Row>
<Col style={{ textAlign: 'left', padding:
'20px', marginLeft: '-150px' }}>
<h2 style={{ fontWeight: 'bold' }}>WHAT MAKES US THE BEST CHOICE</h2>
</Col>
</Row>
<Row>
<Col xs={12} md={6} style={{ display: 'flex', alignItems: 'center', padding: '20px' }}>
<img
src={bestImg}
alt="Best Choice"
style={{
width: '527px', height: '449px', borderRadius: '24px', marginRight: '20px', objectFit: 'cover', marginLeft: '-150px',
transition: 'transform 0.3s' }}
onMouseEnter={(e) =>
e.currentTarget.style.transform = 'scale(1.05)'}
onMouseLeave={(e) =>
e.currentTarget.style.transform = 'scale(1)'}
/>
</Col>
<Col xs={12} md={6} style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
<div style={{ display: 'flex', alignItems:
'center', marginBottom: '20px' }}>
<img src={fastImg} alt="Cepat" style={{
width: '120px', height: '120px', marginRight: '24px' }} />
<div>
<h3 style={{ fontWeight: 'bold' }}>Cepat</h3>
<p>Pesanan Anda diproses dengan cepat dan tepat waktu, tanpa mengurangi kualitas.</p>
</div>
</div>
<div style={{ display: 'flex', alignItems:
'center', marginBottom: '20px' }}>
<img src={cheapImg} alt="Murah" style={{
width: '120px', height: '120px', marginRight: '24px' }} />
<div>
<h3 style={{ fontWeight: 'bold' }}>Murah</h3>
<p>Dapatkan produk custom berkualitas dengan harga yang ramah di kantong.</p>
</div>
</div>
<div style={{ display: 'flex', alignItems:
'center' }}>
<img src={qualityImg} alt="Berkualitas"
style={{ width: '120px', height: '120px', marginRight:
'24px' }} />
<div>
<h3 style={{ fontWeight: 'bold' }}>Berkualitas</h3>
<p>Menggunakan material terbaik dan sablon awet untuk hasil maksimal yang tahan lama.</p>
</div>
</div>
</Col>
</Row>
</Container>
</div>
<div className="review-section w-100 min-vh-50 mt-5"
style={{ marginLeft: '-150px' }}>
<Container>
<Row>
<Col style={{ textAlign: 'left' }}>
<h1 style={{ fontWeight: 'bold', marginBottom:'50px' }}>Tinggalkan Review Anda</h1>
<div style={{
marginBottom: '10px', padding: '10px',
border: '1px solid #A68B5B', borderRadius: '8px',
backgroundColor: '#fff', maxHeight: '150px',
overflowY: 'auto', scrollbarWidth: 'thin',
scrollbarColor: '#A68B5B transparent' }} className="review-scroll">
{testimonials.map((testimonial) => ( <div
key={`${testimonial.nama_pengguna}-
${testimonial.isi_testimoni}`} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<p>
<strong>{testimonial.nama_pengguna}:</strong>
{testimonial.isi_testimoni}
</p>
<FaTrash
onClick={() =>
handleDeleteTestimonial(testimonial.nama_pengguna, testimonial.isi_testimoni)}
style={{ cursor: 'pointer', color: 'red' }}
/>
</div>
))}
</div>
<input
type="text"
value={customerName}
onChange={handleNameChange}
style={{ width: '100%', padding: '10px', borderRadius: '8px', border: '1px solid #A68B5B',
marginBottom: '10px' }}
placeholder="Masukkan nama Anda..."
/>
<textarea rows="4"
value={review}
onChange={handleReviewChange}
style={{ width: '100%', padding: '10px', borderRadius: '8px', border: '1px solid #A68B5B' }}
placeholder="Tulis review Anda di sini..."
/>
<button
onClick={handleReviewSubmit}
style={{ marginTop: '10px', padding: '10px 20px', backgroundColor: '#A68B5B', color: '#FAFAFA',
border: 'none', borderRadius: '8px' }}
>
Kirim Review </button>
</Col>
</Row>
</Container>
</div>
<div className="sosmed-section" style={{ position:
'relative', backgroundImage: `url(${tokoImg})`,
backgroundSize: 'cover', padding: '120px 0', marginTop:
'50px' }}>
<div style={{
position: 'absolute', top: 0,
left: 0, right: 0, bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)', zIndex: 1
}} />
<Container style={{ position: 'relative', zIndex:
2 }}>
<Row>
<Col style={{ textAlign: 'center' }}>
<h2 style={{ color: '#fff', fontWeight:
'bold' }}>WHAT MAKES US THE BEST CHOICE</h2>
<p style={{ color: '#fff' }}>Temukan berbagai tote bag dan pouch custom yang siap mendukung gaya dan kebutuhan Anda. Dari desain unik hingga kualitas terbaik, kami hadir untuk Anda!</p>
<a
href="https://www.instagram.com/tas_sablon_express?utm_sou
rce=ig_web_button_share_sheet&igsh=ZDNlZDc0MzIxNw=="
target="_blank" rel="noopener noreferrer">
<button
onMouseEnter={(e) =>
e.currentTarget.style.backgroundColor = 'gray'}
onMouseLeave={(e) =>
e.currentTarget.style.backgroundColor = 'transparent'}
style={{ backgroundColor: 'transparent', color: '#FAFAFA', padding: '10px 20px', border: '2px solid
#FAFAFA', borderRadius: '8px' }}
>
SEE OUR INSTAGRAM
<FaArrowUpRightFromSquare style={{ marginLeft: '5px' }} />
</button>
</a>
</Col>
</Row>
</Container>
</div>
</div>
);
}
export default HomePage;
Lampiran 3 code HomePage.Jsx
Categories.Jsx
import '../dist/css/main.css';
import { Container, Row, Col, Card, ListGroup, Button } from 'react-bootstrap';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
const Categories = () => {
const [categories, setCategories] = useState([]);
const navigate = useNavigate();
useEffect(() => {
const fetchCategories = async () => { const response = await
fetch('http://localhost:3000/categories');
const data = await response.json();
setCategories(data);
};
fetchCategories();
}, []);
const handleCatalogueClick = () => { navigate('/catalogue');
};
const handleShowModal = (category) => { // Implementasi handleShowModal
};
const handleDeleteCategory = (id) => { // Implementasi handleDeleteCategory };
const renderCards = () => {
return categories.map((category) => ( <Col key={category.id} xs={12} style={{
marginBottom: '20px' }}>
<Card style={{ width: '100%', borderRadius:
'24px', border: 'none', padding: '20px' }}>
<Card.Img
variant="top"
src={category.image}
alt={category.title}
style={{ objectFit: 'cover', height: '350px', borderRadius: '24px' }}
/>
<Card.Body style={{ display: 'flex',
flexDirection: 'column', alignItems: 'flex-start' }}>
<Card.Title style={{ fontSize: '24px', fontWeight: 'bold' }}>{category.title}</Card.Title>
<Card.Text style={{ fontSize: '16px', marginBottom: '10px', whiteSpace: 'pre-line' }}>
Deskripsi: {category.description}
</Card.Text>
<Card.Text style={{ fontSize: '16px', marginBottom: '10px' }}>
{category.material}
</Card.Text>
<Card.Text style={{ fontSize: '16px', marginBottom: '10px' }}>
{category.size}
</Card.Text>
<button className="catalogue-button"
onClick={handleCatalogueClick} style={{
width: '244px', height: '50px',
backgroundColor: '#AF8E45', color: '#FFFFFF',
border: 'none',
borderRadius: '20px', cursor: 'pointer' }}>
Catalogue </button>
</Card.Body>
</Card>
</Col>
));
};
return (
<div className="categories w-100 min-vh-100" style={{
marginTop: '80px' }}>
<Container>
<h1>Categories</h1>
<Row>
<Col>
<Row style={{ padding: '40px' }}>
{renderCards()}
</Row>
</Col>
</Row>
</Container>
</div>
);
};
export default Categories;
Lampiran 4 code Categories.Jsx
Catalogue.Jsx
import React, { useEffect, useState } from 'react';
import { Container, Row, Col, Card } from 'react- bootstrap';
const Catalogue = () => {
const [items, setItems] = useState([]);
useEffect(() => {
const fetchItems = async () => { const response = await
fetch('http://localhost:3000/items');
const data = await response.json();
setItems(data);
};
fetchItems();
}, []);
return (
<Container>
<Row>
<Col xs={12} style={{ marginBottom: '20px', marginTop: '50px' }}>
<h1>Catalogue</h1>
</Col>
</Row>
<Row style={{ marginTop: '20px' }}>
{items.map(item => (
<Col key={item.id} md={4} className="mb-4">
<Card style={{ width: '300px', height: '350px' }}>
<Card.Img
variant="top"
src={item.image}
alt={item.name}
style={{ width: '100%', height: '250px', objectFit: 'fill' }}
/>
<Card.Body>
<Card.Title>{item.name}</Card.Title>
</Card.Body>
</Card>
</Col>
))}
</Row>
</Container>
);
};
export default Catalogue;
Lampiran 5 code Catalogue.Jsx
About.Jsx
import React from 'react';
import '../dist/css/main.css';
import { Container, Row, Col, Card } from 'react- bootstrap';
import aboutImg from '../assets/img/white-totebag.jpg';
import terbaikImg from '../assets/img/tas-about.jpg';
const About = () => { return (
<div className="aboutpage">
<Container style={{ textAlign: 'center', padding:
'20px', position: 'relative' }}>
<Row>
<Col>
<div style={{ borderRadius: '24px', overflow:
'hidden', height: '453px' }}>
<img src={aboutImg} alt="About" style={{
width: '100%', height: '100%', objectFit: 'cover' }} />
</div>
<div style={{ position: 'absolute', top:
'20%', left: '50%', transform: 'translateX(-50%)', zIndex:
1 }}>
<h1 style={{fontWeight:'bolder'}}>Tas Sablon Express</h1>
<p style={{ fontWeight: '500',marginTop:
'20px' }}>
Layanan produksi berbagai jenis tas custom yang dapat digunakan untuk berbagai keperluan, seperti seminar, souvenir, ulang tahun, wedding, produksi promosi perusahaan, dan lainnya.
Tas-tas ini dibuat dari berbagai jenis bahan berkualitas, dengan berbagai ukuran, dan bisa dipesan sesuai kebutuhan pelanggan, termasuk untuk keperluan khusus seperti tas sekolah, tas bidan, tas wedding, dan sebagainya.
</p>
</div>
</Col>
</Row>
</Container>
<div className='pilihan-bahan w-2 min-vh-20'>
<Container
style={{textAlign:'center',position:'relative'}}>
<Row style={{ marginTop: '40px', justifyContent:
'center' }}>
<h1>Pilihan Bahan</h1>
<Col md={3} style={{ margin: '10px' }}>
<Card style={{ height: '100%', border: '2px solid #A0855B' }}>
<Card.Body style={{ padding: '30px' }}>
<Card.Title style={{ textAlign: 'left', fontWeight: 'bold' }}>Spunbond (75 gram dan 100
gram)</Card.Title>
<Card.Text style={{ textAlign: 'left' }}>
Bahan ringan, ramah lingkungan, dan cocok untuk keperluan tas sekali pakai atau tas promosi.
</Card.Text>
</Card.Body>
</Card>
</Col>
<Col md={3} style={{ margin: '10px' }}>
<Card style={{ height: '100%', border: '2px solid #A0855B' }}>
<Card.Body style={{ padding: '30px' }}>
<Card.Title style={{ textAlign: 'left', fontWeight: 'bold' }}>Baby Canvas & Canvas</Card.Title>
<Card.Text style={{ textAlign: 'left' }}>
Tekstur kain yang kuat dan tahan lama, memberikan tampilan yang lebih mewah.
</Card.Text>
</Card.Body>
</Card>
</Col>
<Col md={3} style={{ margin: '10px' }}>
<Card style={{ height: '100%', border: '2px solid #A0855B' }}>
<Card.Body style={{ padding: '30px' }}>
<Card.Title style={{ textAlign: 'left', fontWeight: 'bold' }}>Blacu</Card.Title>
<Card.Text style={{ textAlign: 'left' }}>
Kain yang kuat dengan tampilan yang sederhana dan alami.
</Card.Text>
</Card.Body>
</Card>
</Col>
</Row>
<Row style={{ justifyContent: 'center' }}>
<Col md={3} style={{ margin: '10px' }}>
<Card style={{ height: '100%', border: '2px solid #A0855B' }}>
<Card.Body style={{ padding: '30px' }}>
<Card.Title style={{ textAlign: 'left', fontWeight: 'bold' }}>Mika</Card.Title>
<Card.Text style={{ textAlign: 'left' }}>
Transparan, cocok untuk pouch atau tas kosmetik.
</Card.Text>
</Card.Body>
</Card>
</Col>
<Col md={3} style={{ margin: '10px' }}>
<Card style={{ height: '100%', border: '2px solid #A0855B' }}>
<Card.Body style={{ padding: '30px' }}>
<Card.Title style={{ textAlign: 'left', fontWeight: 'bold' }}>Dinir (D300 & D600)</Card.Title>
<Card.Text style={{ textAlign: 'left' }}>
D300: Tekstur lembut, sertai kecil, tahan lama dan sulit robek.
D600: Tekstur lebih besar, wrinkle- free, lebih tahan lama, dan kuat.
</Card.Text>
</Card.Body>
</Card>
</Col>
</Row>
</Container>
</div>
<div className='pilihan-terbaik w-2 min-vh-20 mb-5'>
<Container style={{ textAlign: 'center', position:
'relative' }}>
<Row style={{ marginTop: '40px', justifyContent:
'flex-start' }}>
<h1>Pilihan Terbaik Untuk Kebutuhan Anda</h1>
</Row>
<Row style={{ marginTop: '20px' }}>
<Col md={6} style={{ margin: '10px', display:
'flex', justifyContent: 'center' }}>
<div style={{ borderRadius: '24px', overflow: 'hidden' }}>
<img src={terbaikImg} alt="Pilihan Terbaik" style={{ width: '651px', height: '815px', objectFit: 'cover', borderRadius: '24px' }} />
</div>
</Col>
<Col md={3} style={{ margin: '100px', textAlign: 'left', display: 'flex', flexDirection:
'column', justifyContent: 'center', alignItems: 'center' }}>
<div style={{ marginBottom: '80px' }}>
<h2 style={{ color: '#806A49' }}>Kualitas</h2>
<p>Bahan kuat, tahan lama, dan tersedia berbagai pilihan sesuai kebutuhan dan anggaran.</p>
</div>
<div style={{ marginBottom: '80px' }}>
<h2 style={{ color: '#806A49' }}>Customizable</h2>
<p>Bisa disesuaikan ukuran, warna, bahan, dan jenis sablon atau bordiran sesuai preferensi.</p>
</div>
<div style={{ marginBottom: '80px' }}>
<h2 style={{ color: '#806A49' }}>Harga Terjangkau</h2>
<p>Menawarkan berbagai pilihan harga yang bisa disesuaikan dengan budget pelanggan.</p>
</div>
</Col>
</Row>
</Container>
</div>
<div className='google-maps w-2 min-vh-20 mb-5' style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<h1 style={{ marginBottom: '40px', textAlign:
'center' }}>Lokasi Kami</h1>
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1 d4726.794481922771!2d106.77336628844577!3d-
6.368575505378205!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!
3m3!1m2!1s0x2e69eed94194c9fd%3A0xc56241d049ee54c8!2sJl.%20 Kec.%20No.50%2C%20Limo%2C%20Kec.%20Limo%2C%20Kota%20Depok%
2C%20Jawa%20Barat%2016514!5e0!3m2!1sid!2sid!4v173436394752 3!5m2!1sid!2sid"
width="1152px"
height="453px"
style={{ border: 0, borderRadius: '24px', marginBottom: '20px' }}
allowFullScreen loading="lazy"
referrerPolicy="no-referrer-when-downgrade">
</iframe>
<h5 style={{ textAlign: 'left', alignSelf: 'flex- start', marginLeft: '20%' }}>
LOCATION<br />
Jalan Arthyasa<br />
No. 50, Kec. Limo, Depok 16515 </h5>
</div>
</div>
);
}
export default About;
Lampiran 6 code About.Jsx
Dashboard.Jsx
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Container, Row, Col, ListGroup, Alert, Button, Modal, Form } from 'react-bootstrap';
const Dashboard = ({ isLoggedIn }) => {
const [categories, setCategories] = useState([]);
const [items, setItems] = useState([]);
const [showModal, setShowModal] = useState(false);
const [currentCategory, setCurrentCategory] = useState(null);
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [image, setImage] = useState(null);
const [showItemModal, setShowItemModal] = useState(false);
const [currentItem, setCurrentItem] = useState(null);
const [itemName, setItemName] = useState('');
const [itemImage, setItemImage] = useState(null);
const navigate = useNavigate();
useEffect(() => { if (!isLoggedIn) {
window.alert('Anda harus login terlebih dahulu!');
navigate('/login');
} else {
// Mengambil data kategori
fetch('http://localhost:3000/categories') .then(response => response.json()) .then(data => setCategories(data))
.catch(error => console.error('Error fetching categories:', error));
// Mengambil data items
fetch('http://localhost:3000/items') .then(response => response.json()) .then(data => setItems(data))
.catch(error => console.error('Error fetching items:', error));
}
}, [isLoggedIn, navigate]);
const handleShowModal = (category) => { if (category) {
setTitle(category.title);
setDescription(category.description);
setImage(null);
setCurrentCategory(category);
} else {
setTitle('');
setDescription('');
setImage(null);
setCurrentCategory(null);
}
setShowModal(true);
};
const handleCloseModal = () => { setCurrentCategory(null);
setShowModal(false);
};
const handleSaveCategory = () => { const formData = new FormData();
formData.append('title', title);
formData.append('description', description);
if (image) {
formData.append('image', image);
}
if (currentCategory) {
// Update kategori yang sudah ada
fetch(`http://localhost:3000/categories/${currentCategory.
id}`, {
method: 'PUT', body: formData, })
.then(response => response.json()) .then(data => {
setCategories(categories.map(cat => (cat.id ===
data.id ? data : cat)));
handleCloseModal();
})
.catch(error => console.error('Error updating category:', error));
} else {
// Tambah kategori baru
fetch('http://localhost:3000/categories', { method: 'POST',
body: formData, })
.then(response => response.json()) .then(data => {
setCategories([...categories, data]);
handleCloseModal();
})
.catch(error => console.error('Error adding category:', error));
} };
const handleDeleteCategory = (id) => {
fetch(`http://localhost:3000/categories/${id}`, { method: 'DELETE',
})
.then(response => { if (response.ok) {
setCategories(categories.filter(category =>
category.id !== id));
} else {
console.error('Error deleting category');
} })
.catch(error => console.error('Error deleting category:', error));
};
const handleShowItemModal = (item) => { if (item) {
setItemName(item.name);
setItemImage(null);
setCurrentItem(item);
} else {
setItemName('');
setItemImage(null);
setCurrentItem(null);
}
setShowItemModal(true);
};
const handleCloseItemModal = () => { setCurrentItem(null);
setShowItemModal(false);
};
const handleSaveItem = () => { const formData = new FormData();
formData.append('name', itemName);
if (itemImage) {
formData.append('image', itemImage);
}
if (currentItem) {
// Update item yang sudah ada
fetch(`http://localhost:3000/items/${currentItem.id}`, { method: 'PUT',
body: formData, })
.then(response => response.json()) .then(data => {
setItems(items.map(item => (item.id === data.id ? data : item)));
handleCloseItemModal();
})
.catch(error => console.error('Error updating item:', error));
} else {
// Tambah item baru
fetch('http://localhost:3000/items', { method: 'POST',
body: formData, })
.then(response => response.json()) .then(data => {
setItems([...items, data]);
handleCloseItemModal();
})
.catch(error => console.error('Error adding item:', error));
} };
const handleDeleteItem = (id) => {
fetch(`http://localhost:3000/items/${id}`, { method: 'DELETE',
})
.then(response => { if (response.ok) {
setItems(items.filter(item => item.id !== id));
} else {
console.error('Error deleting item');
} })
.catch(error => console.error('Error deleting item:', error));
};
return (
<Container className="mt-4">
<Alert variant="info">Selamat datang di dashboard admin!</Alert>
<h1 className="text-center mb-4">Dashboard Admin</h1>
<h2 className="text-primary">Jumlah Kategori:
{categories.length}</h2>
<Button onClick={() => handleShowModal(null)}>Tambah Kategori</Button>
<ListGroup className="mb-4">
{categories.map(category => (
<ListGroup.Item key={category.id} className="d- flex justify-content-between align-items-center">
<div>
<strong>{category.title}</strong>
<div className="text-muted">ID:
{category.id}</div>
</div>
<div>
<Button variant="warning" onClick={() =>
handleShowModal(category)}>Edit</Button>
<Button variant="danger" onClick={() =>
handleDeleteCategory(category.id)}>Hapus</Button>
</div>
</ListGroup.Item>
))}
</ListGroup>
<h2 className="text-success">Jumlah Items:
{items.length}</h2>
<Button onClick={() =>
handleShowItemModal(null)}>Tambah Item</Button>
<ListGroup className="mb-4">
{items.map(item => (
<ListGroup.Item key={item.id} className="d-flex justify-content-between align-items-center">
<div>
<strong>{item.name}</strong>
<div className="text-muted">ID:
{item.id}</div>
</div>
<div>
<Button variant="warning" onClick={() =>
handleShowItemModal(item)}>Edit</Button>
<Button variant="danger" onClick={() =>
handleDeleteItem(item.id)}>Hapus</Button>
</div>
</ListGroup.Item>
))}
</ListGroup>
<Modal show={showModal} onHide={handleCloseModal}>
<Modal.Header closeButton>
<Modal.Title>{currentCategory ? 'Edit Kategori' : 'Tambah Kategori'}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<Form.Group controlId="formCategoryTitle">
<Form.Label>Judul Kategori</Form.Label>
<Form.Control type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
</Form.Group>
<Form.Group
controlId="formCategoryDescription">
<Form.Label>Deskripsi Kategori</Form.Label>
<Form.Control as="textarea"
rows={3}
value={description}
onChange={(e) =>
setDescription(e.target.value)}
/>
</Form.Group>
<Form.Group controlId="formCategoryImage">
<Form.Label>Unggah Gambar</Form.Label>
<Form.Control type="file"
onChange={(e) =>
setImage(e.target.files[0])}
/>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary"
onClick={handleCloseModal}>
Tutup </Button>
<Button variant="primary"
onClick={handleSaveCategory}>
Simpan </Button>
</Modal.Footer>
</Modal>
<Modal show={showItemModal}
onHide={handleCloseItemModal}>
<Modal.Header closeButton>
<Modal.Title>{currentItem ? 'Edit Item' : 'Tambah Item'}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<Form.Group controlId="formItemName">
<Form.Label>Nama Item</Form.Label>
<Form.Control type="text"
value={itemName}
onChange={(e) =>
setItemName(e.target.value)}
/>
</Form.Group>
<Form.Group controlId="formItemImage">
<Form.Label>Unggah Gambar</Form.Label>
<Form.Control type="file"
onChange={(e) =>
setItemImage(e.target.files[0])}
/>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary"
onClick={handleCloseItemModal}>
Tutup </Button>
<Button variant="primary"
onClick={handleSaveItem}>
Simpan </Button>
</Modal.Footer>
</Modal>
</Container>
);
};
export default Dashboard;
Lampiran 7 code Dashboard.Jsx
Login.Jsx
import React, { useState } from 'react';
import { Container, Row, Col, Form, Button } from 'react- bootstrap';
import loginImg from '../assets/img/login.jpg';
import loginBg from '../assets/img/bg-login.jpg';
import { useNavigate } from "react-router-dom";
import { FaEye, FaEyeSlash } from 'react-icons/fa';
const Login = ({ setIsLoggedIn }) => { const navigate = useNavigate();
const [showPassword, setShowPassword] = useState(false);
const handleTogglePassword = () => { setShowPassword(!showPassword);
};
const handleSubmit = (event) => { event.preventDefault();
const username = event.target.username.value;
const password = event.target.password.value;
fetch(`http://localhost:3000/login-
admin?username=${username}&password=${password}`) .then(response => response.json())
.then(data => {
if (data.message === 'Login admin berhasil') { alert(data.message);
setIsLoggedIn(true);
navigate('/dashboard');
} else {
alert(data.message);
} })
.catch(error => {
console.error('Error:', error);
alert('Terjadi kesalahan saat login');
});
};
return (
<div className="login" style={{ backgroundImage:
`url(${loginBg})`, backgroundSize: 'cover', backgroundPosition: 'center' }}>
<Container className="mt-5">
<Row className="justify-content-center align- items-center">
<Col md={6} className="login-image">
<img src={loginImg} alt="Login"
className="img-fluid" />
</Col>
<Col md={6} className="login-container">
<h2 className="text-center login-title font- weight-bold">Login Admin</h2>
<p className="text-center">Selamat datang di portal admin, login untuk memulai.</p>
<Form onSubmit={handleSubmit}>
<Form.Group controlId="formBasicUsername"
className="mb-3">
<Form.Label>Masukan username</Form.Label>
<Form.Control type="text" name="username"
placeholder="Masukan username" />
</Form.Group>
<Form.Group controlId="formBasicPassword"
className="mb-3">
<Form.Label>Masukan password</Form.Label>
<div className="input-group">
<Form.Control
type={showPassword ? "text" :
"password"}
name="password"
placeholder="Masukan password"
/>
<span
className="input-group-text"
onClick={handleTogglePassword}
style={{ cursor: 'pointer', border:
'none' }}
>
{showPassword ? <FaEyeSlash /> :
<FaEye />}
</span>
</div>
</Form.Group>
<Button type="submit" className="w-100 login-button custom-login-button">
LOGIN </Button>
</Form>
<Button
variant="link"
onClick={() => navigate('/')}
className="mt-3"
>
Kembali ke Homepage </Button>
</Col>
</Row>
</Container>
</div>
);
}
export default Login;
Lampiran 8 code Login.Jsx
Server.js
const express = require('express');
const mysql = require('mysql');
const bodyParser = require('body-parser');
const cors = require('cors');
const path = require('path');
const multer = require('multer');
const app = express();
const port = 3000;
// Middleware untuk parsing JSON app.use(bodyParser.json());
// Middleware untuk CORS app.use(cors());
// Middleware untuk menyajikan file statis
app.use(express.static(path.join(__dirname, 'public')));
// Konfigurasi multer untuk menyimpan file const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, path.join(__dirname, '../sablon- app/src/assets/img')); // Simpan di src/assets/img },
filename: (req, file, cb) => { cb(null, Date.now() +
path.extname(file.originalname)); // Menyimpan dengan nama unik
} });
const upload = multer({ storage });
// Konfigurasi koneksi ke database MySQL const db = mysql.createConnection({
host: 'localhost', user: 'root', password: '',
database: 'tas_sablon' });
// Koneksi ke database db.connec