Compare commits
4 Commits
61bb1ccc39
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 77cd655112 | |||
| 109489b191 | |||
| 7a14c80eec | |||
| 4fe4b13f39 |
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
707
adventure-rental-app/package-lock.json
generated
@@ -20,7 +20,7 @@
|
|||||||
"@eslint/js": "^9.30.1",
|
"@eslint/js": "^9.30.1",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"eslint": "^9.30.1",
|
"eslint": "^9.30.1",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
@@ -28,6 +28,6 @@
|
|||||||
"globals": "^16.3.0",
|
"globals": "^16.3.0",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"vite": "^4.5.2"
|
"vite": "^7.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,264 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin="" />
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
as="style"
|
|
||||||
onload="this.rel='stylesheet'"
|
|
||||||
href="https://fonts.googleapis.com/css2?display=swap&family=Noto+Sans%3Awght%40400%3B500%3B700%3B900&family=Plus+Jakarta+Sans%3Awght%40400%3B500%3B700%3B800"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<title>Stitch Design</title>
|
|
||||||
<link rel="icon" type="image/x-icon" href="data:image/x-icon;base64," />
|
|
||||||
|
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="relative flex size-full min-h-screen flex-col bg-[#fcf8f8] group/design-root overflow-x-hidden" style='font-family: "Plus Jakarta Sans", "Noto Sans", sans-serif;'>
|
|
||||||
<div class="layout-container flex h-full grow flex-col">
|
|
||||||
<header class="flex items-center justify-between whitespace-nowrap border-b border-solid border-b-[#f3e7e8] px-10 py-3">
|
|
||||||
<div class="flex items-center gap-8">
|
|
||||||
<div class="flex items-center gap-4 text-[#1b0e0e]">
|
|
||||||
<div class="size-4">
|
|
||||||
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M44 4H30.6666V17.3334H17.3334V30.6666H4V44H44V4Z" fill="currentColor"></path></svg>
|
|
||||||
</div>
|
|
||||||
<h2 class="text-[#1b0e0e] text-lg font-bold leading-tight tracking-[-0.015em]">CampRent</h2>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-9">
|
|
||||||
<a class="text-[#1b0e0e] text-sm font-medium leading-normal" href="#">Beranda</a>
|
|
||||||
<a class="text-[#1b0e0e] text-sm font-medium leading-normal" href="#">Peralatan</a>
|
|
||||||
<a class="text-[#1b0e0e] text-sm font-medium leading-normal" href="#">Tentang Kami</a>
|
|
||||||
<a class="text-[#1b0e0e] text-sm font-medium leading-normal" href="#">Kontak</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-1 justify-end gap-8">
|
|
||||||
<label class="flex flex-col min-w-40 !h-10 max-w-64">
|
|
||||||
<div class="flex w-full flex-1 items-stretch rounded-lg h-full">
|
|
||||||
<div
|
|
||||||
class="text-[#994d51] flex border-none bg-[#f3e7e8] items-center justify-center pl-4 rounded-l-lg border-r-0"
|
|
||||||
data-icon="MagnifyingGlass"
|
|
||||||
data-size="24px"
|
|
||||||
data-weight="regular"
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor" viewBox="0 0 256 256">
|
|
||||||
<path
|
|
||||||
d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
placeholder="Search"
|
|
||||||
class="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-[#1b0e0e] focus:outline-0 focus:ring-0 border-none bg-[#f3e7e8] focus:border-none h-full placeholder:text-[#994d51] px-4 rounded-l-none border-l-0 pl-2 text-base font-normal leading-normal"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<button
|
|
||||||
class="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 bg-[#e92932] text-[#fcf8f8] text-sm font-bold leading-normal tracking-[0.015em]"
|
|
||||||
>
|
|
||||||
<span class="truncate">Masuk</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 bg-[#f3e7e8] text-[#1b0e0e] text-sm font-bold leading-normal tracking-[0.015em]"
|
|
||||||
>
|
|
||||||
<span class="truncate">Daftar</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="px-40 flex flex-1 justify-center py-5">
|
|
||||||
<div class="layout-content-container flex flex-col max-w-[960px] flex-1">
|
|
||||||
<div class="@container">
|
|
||||||
<div class="@[480px]:p-4">
|
|
||||||
<div
|
|
||||||
class="flex min-h-[480px] flex-col gap-6 bg-cover bg-center bg-no-repeat @[480px]:gap-8 @[480px]:rounded-lg items-center justify-center p-4"
|
|
||||||
style='background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.4) 100%), url("https://lh3.googleusercontent.com/aida-public/AB6AXuCWyBJbobacHRpR2rZFfGuiAHDut1lUwnrS7Ne1qIwJmVoHIZ9jSH8pBbiNxfQVxaolM_fOzTrnkl3gJf7cvEZ82ca9FDwOAWghh3tHnGt7nmYJJwwBa_JEBLu_ZX32TtrwIevABNmTkTsFrVR_lREK-3BBHvfEfNa-B3xStitvuMhiaQ0uemjEQZ8dj18r8epa8t9Ub17dok8VigDsim9rif4kOgg7eIVn_ssoTCj5Wy-rvkUMYOE5yXcm-EdNoY2ktTTgA--gV5Q");'
|
|
||||||
>
|
|
||||||
<div class="flex flex-col gap-2 text-center">
|
|
||||||
<h1
|
|
||||||
class="text-white text-4xl font-black leading-tight tracking-[-0.033em] @[480px]:text-5xl @[480px]:font-black @[480px]:leading-tight @[480px]:tracking-[-0.033em]"
|
|
||||||
>
|
|
||||||
Sewa Peralatan Camping Berkualitas
|
|
||||||
</h1>
|
|
||||||
<h2 class="text-white text-sm font-normal leading-normal @[480px]:text-base @[480px]:font-normal @[480px]:leading-normal">
|
|
||||||
Nikmati petualangan alam bebas dengan peralatan camping terbaik. Sewa sekarang dan rasakan pengalaman camping yang tak terlupakan.
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 @[480px]:h-12 @[480px]:px-5 bg-[#e92932] text-[#fcf8f8] text-sm font-bold leading-normal tracking-[0.015em] @[480px]:text-base @[480px]:font-bold @[480px]:leading-normal @[480px]:tracking-[0.015em]"
|
|
||||||
>
|
|
||||||
<span class="truncate">Lihat Peralatan</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h2 class="text-[#1b0e0e] text-[22px] font-bold leading-tight tracking-[-0.015em] px-4 pb-3 pt-5">Penawaran Spesial</h2>
|
|
||||||
<div class="flex overflow-y-auto [-ms-scrollbar-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
|
||||||
<div class="flex items-stretch p-4 gap-3">
|
|
||||||
<div class="flex h-full flex-1 flex-col gap-4 rounded-lg min-w-60">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-video bg-cover rounded-lg flex flex-col"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuAvRp6hsxMTXE1tJYF0MjKVRm0zysHK3ClRz5HwJfP34ELGDJvktXlsjUJQ9Fv1VPsUwEE-CU6w25CN9geU18xHw_yTh3jzpJheLyDwrnMZOChC3HaBlYmv28UKhGV76P2LHMDvu5dEtobTm0QD3YLAvwT6L7SK0m33paUlXxna9CuR2MMgKZ17P_a46q2zBwCSnYf1GuZEZNzePW-HPWd0tyuJ9UYOblL2T3_w-d44UkbiHKT9lIKk6y4piJTESqj7LuO0NfHHe7E");'
|
|
||||||
></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[#1b0e0e] text-base font-medium leading-normal">Tenda Camping Kapasitas 4 Orang</p>
|
|
||||||
<p class="text-[#994d51] text-sm font-normal leading-normal">Sewa mulai dari Rp 50.000/hari</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex h-full flex-1 flex-col gap-4 rounded-lg min-w-60">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-video bg-cover rounded-lg flex flex-col"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuC5BQ5uUUuMKq6VhjKep8tyvrmeGT0nvgx86fyEVrUqTTGzcZDO8z2ayqxSxnzdk8gjpgGmjLbhZL-HnpOV-5r-GwGs3FssAhhgcBi4HoCthNTn2zUYytotzBWVDDXylxGeKAzhOw2dgbM6oD_AfknM_6kWWr6CBobDasznxJuC2JNbzV-PEDmhPT_8qzeKh6mWYY8-KXDM3jHcVZZhhEvde6tBRwBzpHGlkrDrog_6C_Yiagmz9JN9lVE4xHuAWtFLX1IQRvGfwak");'
|
|
||||||
></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[#1b0e0e] text-base font-medium leading-normal">Kompor Camping Portable</p>
|
|
||||||
<p class="text-[#994d51] text-sm font-normal leading-normal">Sewa mulai dari Rp 25.000/hari</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex h-full flex-1 flex-col gap-4 rounded-lg min-w-60">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-video bg-cover rounded-lg flex flex-col"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuDow9uli5aTA-tdn0N14A0jP3UNtUVlAlo6rbp-rV-YlTVoQq_A4uTMqbRo9HoIVi_K1xBfxfRYrWxF0w9ZCDh_6rydQLIHbmaqwUOeTGs0irjrBfPRa2hWAbtOFRic0JBuHPAMm_Hm7hotKO5-wj7uIg0aMMmuyE2Y92toM47T2yH0-eRtaBLUgSTNP90FWAS7Z95iOEslTdb3tdsXFC9KTc55u4vP9COi09M-GVD0PBEnSWz4PzAcCyNGfpGSs4E18CIwg9L32I8");'
|
|
||||||
></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[#1b0e0e] text-base font-medium leading-normal">Ransel Camping 60L</p>
|
|
||||||
<p class="text-[#994d51] text-sm font-normal leading-normal">Sewa mulai dari Rp 30.000/hari</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h2 class="text-[#1b0e0e] text-[22px] font-bold leading-tight tracking-[-0.015em] px-4 pb-3 pt-5">Peralatan Terbaru</h2>
|
|
||||||
<div class="grid grid-cols-[repeat(auto-fit,minmax(158px,1fr))] gap-3 p-4">
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-lg"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuAv4vQr6Z0wpBiZoy5iTs_t1XGlkBeai4R1tIzBmjAIM9gIZKrRcT8-4JDlsLvKDfZvIQYl8loCnkO7lIFP9S2mPQVyozPsjsKkPFmSCF5wjMOBhTs1_d7D5aW6JKsAsxe89mtb2muBN9oXK-xBZHnROjxTxkNMOYJMcTwBm80Qkrvnf27WztRK_49JXRJB4Q1YJ2zLR32yuGD-6rCx5kUm8vu3fZuK-iSUxbAD232TW2A7EU54GGtPRDubJNHdeBVjR2JccH1aG_Y");'
|
|
||||||
></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[#1b0e0e] text-base font-medium leading-normal">Sleeping Bag</p>
|
|
||||||
<p class="text-[#994d51] text-sm font-normal leading-normal">Nyaman dan hangat untuk tidur di alam</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-lg"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuBUg3yW3gYWdIVJPyX4CV_RdT5hghqLHRTcqsAbL5SCGAHCO9P8g44Kim-ul3zrjOnPqmOg8gH-kMAtlG7DsoB5JbSwJ9_uVnpV1TVCmJEpvcYO8P3lurZNVr19zFejVvvohLzehcwy0CxSQcCuKy38ljowG5vSjqc3iP8D0KPHC6Gn44Onh3PTe6E1m7EyX-89LdFbV6yGB1_Vj_XJF2_5wcQlt7yJ362WJlJFVXjONywcWNRvh22uuj498k4yMk9ZOslxX_JRmCk");'
|
|
||||||
></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[#1b0e0e] text-base font-medium leading-normal">Matras Camping</p>
|
|
||||||
<p class="text-[#994d51] text-sm font-normal leading-normal">Alas tidur yang empuk dan isolatif</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-lg"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuASMEBXbQxfu1_7DcxvyUWeYt70VrkXJp8jYf_vI5-NDsvAyvnARncM3N1BP_RxnUeOR-mPihZzOZW_AmJBRbWBquoaoSsxV4RYpCGBVWQnyWLStHBXQNspO39JdHMF0SnEzNGv8TKGcd3IPFShYf0A26-Xr59HP99s5UErJb-d3DM_9T1lec9aiCPQOqcrmauDQDlOqLVLzyIX3jcArLSOwFkhyxG-ksvQb0-UgnuE2SKJ0ZKKX2obPJmK-7124grO84UipPJZ_UM");'
|
|
||||||
></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[#1b0e0e] text-base font-medium leading-normal">Lampu Tenda</p>
|
|
||||||
<p class="text-[#994d51] text-sm font-normal leading-normal">Penerangan yang praktis untuk tenda</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-lg"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuB_a0CqFMmJkN-KIpw5ACcEZ5mjuOHwxr_j73KyC89fRjx6okZb178lwTQz7zrGXbAsAe4Xdf52EVns7ROW3TF2OYxOtE2dQI_ldoXD0JeXLi9CvKKL_s697Xk9nC7-UmwaJ5UK4fxSNFbxS4ABmMbmvHTwJ82aIHaNdNzJzjlnWSQVCxC29fGtj3UaU7uX7GTWXuXJMJLXBUqo7EDY8iujDHj_JQZ3mFN1ydxR764qk1rnWmACbXiCA-tBk5j5GVpCeHmhphKMhyw");'
|
|
||||||
></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[#1b0e0e] text-base font-medium leading-normal">Peralatan Masak</p>
|
|
||||||
<p class="text-[#994d51] text-sm font-normal leading-normal">Set lengkap untuk memasak di luar ruangan</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h2 class="text-[#1b0e0e] text-[22px] font-bold leading-tight tracking-[-0.015em] px-4 pb-3 pt-5">Kategori Populer</h2>
|
|
||||||
<div class="grid grid-cols-[repeat(auto-fit,minmax(158px,1fr))] gap-3 p-4">
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-lg"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuDI6Hr5q3ThScjaCpB8TwRqtKk2nljIxFOtDR2xcZs7X5i3P1Fn86QsPm30m2bd_qKW_v15nErkTgj5RGMXR8bTJ1w_nLgQKDS8jzhcwJGmAf-2fv7rTSNMf4-odTSEQI0tza0bberwHeuW1vy2Thsha9IugC5bGX_AC12PIqhLOGn85KWEhi4uCsnNXuAwt1nuli81M9ew2ILQqMjco64DJpdozvGHeDmu8-MAsy6_kCuQQkEC7dBpB8S5pO3uoTVbOAnmhdByoss");'
|
|
||||||
></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[#1b0e0e] text-base font-medium leading-normal">Tenda</p>
|
|
||||||
<p class="text-[#994d51] text-sm font-normal leading-normal">Berbagai pilihan tenda untuk semua kebutuhan</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-lg"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuAGrYFPtDbX3TZObxkWWwy8FgZlSrrK7WxCnzMuDzQgrL2a_VeqpPy7D1-XCxOYk_P_ys4l475lvBTeZqFy8zosbb433EdiiyBrRmyIG4Wgq0N8B_tjSccIO7WAsCoa7FyMV2ttdP_8QTtXOT5Y6JDoSlxxqzlQ_qHxu-v477d7QQexUuwmnpEV3oiFrmbmoJmAUrASUzIDuA3cuaRF9tJ9o6nKNLTIs0uLyMHiskMjhQKJHEnpkrG4Q0e-Y2V-0ksI9yPX6BY2vTM");'
|
|
||||||
></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[#1b0e0e] text-base font-medium leading-normal">Ransel</p>
|
|
||||||
<p class="text-[#994d51] text-sm font-normal leading-normal">Ransel dengan kapasitas dan fitur lengkap</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-lg"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuBGVtDm8LwfWVZfEkxJUZFSWPkp8BfQsc9L__99XArHJE4CKYXw-0BXlGuQ7BwLTPFDgTqz-9keOWyk9SR-XJBJx7TwkL22uC76w86RcSBzvBmJsymWkKbqNZgHgYiGhszhoiFY08lcj2mUAzS88LjBNeVhWwEeBgKCDr6Y9VgDRuHA-vH6GRSzMZUU89AOBlgu5jw9xtkjILSq3NgUfBP2mmz6ntCLGLRSuH6QGv0VtsDYGzyCW8bk63XW9bzs1A-aROyELH1Yu84");'
|
|
||||||
></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[#1b0e0e] text-base font-medium leading-normal">Peralatan Masak</p>
|
|
||||||
<p class="text-[#994d51] text-sm font-normal leading-normal">Set peralatan masak praktis dan ringan</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-lg"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuC3s14he8A8hsfHyQjfRX2mrYgNBXXXnndXjkC3UK8SPV7qXuZ58ZIU1DIZrDNxKJifgFAfpPcUs2h9sAWxBphoMqk1nCuABUxcqvzw3WvWFSjIoEDvmQ5_bnmOA8vklUUzq-E26X06ngQEUcqH1PyFWLAPJuPzCk9tMHgRJl9dyBq4SsMa9Q985SSzAxDmjjXzx3lUL7NNFYG9Gtoe-BPMC0rzIeWbD_WlFe-KlrsyzcbjyOedp_x8jzFZSs57vLARipoU7aPxnIY");'
|
|
||||||
></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[#1b0e0e] text-base font-medium leading-normal">Peralatan Tidur</p>
|
|
||||||
<p class="text-[#994d51] text-sm font-normal leading-normal">Sleeping bag dan matras untuk tidur nyaman</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<footer class="flex justify-center">
|
|
||||||
<div class="flex max-w-[960px] flex-1 flex-col">
|
|
||||||
<footer class="flex flex-col gap-6 px-5 py-10 text-center @container">
|
|
||||||
<div class="flex flex-wrap items-center justify-center gap-6 @[480px]:flex-row @[480px]:justify-around">
|
|
||||||
<a class="text-[#994d51] text-base font-normal leading-normal min-w-40" href="#">Tentang Kami</a>
|
|
||||||
<a class="text-[#994d51] text-base font-normal leading-normal min-w-40" href="#">Kebijakan Privasi</a>
|
|
||||||
<a class="text-[#994d51] text-base font-normal leading-normal min-w-40" href="#">Syarat dan Ketentuan</a>
|
|
||||||
<a class="text-[#994d51] text-base font-normal leading-normal min-w-40" href="#">Kontak</a>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap justify-center gap-4">
|
|
||||||
<a href="#">
|
|
||||||
<div class="text-[#994d51]" data-icon="InstagramLogo" data-size="24px" data-weight="regular">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor" viewBox="0 0 256 256">
|
|
||||||
<path
|
|
||||||
d="M128,80a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160ZM176,24H80A56.06,56.06,0,0,0,24,80v96a56.06,56.06,0,0,0,56,56h96a56.06,56.06,0,0,0,56-56V80A56.06,56.06,0,0,0,176,24Zm40,152a40,40,0,0,1-40,40H80a40,40,0,0,1-40-40V80A40,40,0,0,1,80,40h96a40,40,0,0,1,40,40ZM192,76a12,12,0,1,1-12-12A12,12,0,0,1,192,76Z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a href="#">
|
|
||||||
<div class="text-[#994d51]" data-icon="TwitterLogo" data-size="24px" data-weight="regular">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor" viewBox="0 0 256 256">
|
|
||||||
<path
|
|
||||||
d="M247.39,68.94A8,8,0,0,0,240,64H209.57A48.66,48.66,0,0,0,168.1,40a46.91,46.91,0,0,0-33.75,13.7A47.9,47.9,0,0,0,120,88v6.09C79.74,83.47,46.81,50.72,46.46,50.37a8,8,0,0,0-13.65,4.92c-4.31,47.79,9.57,79.77,22,98.18a110.93,110.93,0,0,0,21.88,24.2c-15.23,17.53-39.21,26.74-39.47,26.84a8,8,0,0,0-3.85,11.93c.75,1.12,3.75,5.05,11.08,8.72C53.51,229.7,65.48,232,80,232c70.67,0,129.72-54.42,135.75-124.44l29.91-29.9A8,8,0,0,0,247.39,68.94Zm-45,29.41a8,8,0,0,0-2.32,5.14C196,166.58,143.28,216,80,216c-10.56,0-18-1.4-23.22-3.08,11.51-6.25,27.56-17,37.88-32.48A8,8,0,0,0,92,169.08c-.47-.27-43.91-26.34-44-96,16,13,45.25,33.17,78.67,38.79A8,8,0,0,0,136,104V88a32,32,0,0,1,9.6-22.92A30.94,30.94,0,0,1,167.9,56c12.66.16,24.49,7.88,29.44,19.21A8,8,0,0,0,204.67,80h16Z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a href="#">
|
|
||||||
<div class="text-[#994d51]" data-icon="FacebookLogo" data-size="24px" data-weight="regular">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor" viewBox="0 0 256 256">
|
|
||||||
<path
|
|
||||||
d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm8,191.63V152h24a8,8,0,0,0,0-16H136V112a16,16,0,0,1,16-16h16a8,8,0,0,0,0-16H152a32,32,0,0,0-32,32v24H96a8,8,0,0,0,0,16h24v63.63a88,88,0,1,1,16,0Z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<p class="text-[#994d51] text-base font-normal leading-normal">@2024 CampRent. All rights reserved.</p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin="" />
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
as="style"
|
|
||||||
onload="this.rel='stylesheet'"
|
|
||||||
href="https://fonts.googleapis.com/css2?display=swap&family=Noto+Sans%3Awght%40400%3B500%3B700%3B900&family=Plus+Jakarta+Sans%3Awght%40400%3B500%3B700%3B800"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<title>Stitch Design</title>
|
|
||||||
<link rel="icon" type="image/x-icon" href="data:image/x-icon;base64," />
|
|
||||||
|
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="relative flex size-full min-h-screen flex-col bg-[#fbf9f9] group/design-root overflow-x-hidden" style='font-family: "Plus Jakarta Sans", "Noto Sans", sans-serif;'>
|
|
||||||
<div class="layout-container flex h-full grow flex-col">
|
|
||||||
<header class="flex items-center justify-between whitespace-nowrap border-b border-solid border-b-[#f1e9ea] px-10 py-3">
|
|
||||||
<div class="flex items-center gap-8">
|
|
||||||
<div class="flex items-center gap-4 text-[#191011]">
|
|
||||||
<div class="size-4">
|
|
||||||
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M44 4H30.6666V17.3334H17.3334V30.6666H4V44H44V4Z" fill="currentColor"></path></svg>
|
|
||||||
</div>
|
|
||||||
<h2 class="text-[#191011] text-lg font-bold leading-tight tracking-[-0.015em]">CampRent</h2>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-9">
|
|
||||||
<a class="text-[#191011] text-sm font-medium leading-normal" href="#">Beranda</a>
|
|
||||||
<a class="text-[#191011] text-sm font-medium leading-normal" href="#">Kategori</a>
|
|
||||||
<a class="text-[#191011] text-sm font-medium leading-normal" href="#">Pemesanan</a>
|
|
||||||
<a class="text-[#191011] text-sm font-medium leading-normal" href="#">Tentang Kami</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-1 justify-end gap-8">
|
|
||||||
<label class="flex flex-col min-w-40 !h-10 max-w-64">
|
|
||||||
<div class="flex w-full flex-1 items-stretch rounded-xl h-full">
|
|
||||||
<div
|
|
||||||
class="text-[#8b5b5d] flex border-none bg-[#f1e9ea] items-center justify-center pl-4 rounded-l-xl border-r-0"
|
|
||||||
data-icon="MagnifyingGlass"
|
|
||||||
data-size="24px"
|
|
||||||
data-weight="regular"
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor" viewBox="0 0 256 256">
|
|
||||||
<path
|
|
||||||
d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
placeholder="Search"
|
|
||||||
class="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-xl text-[#191011] focus:outline-0 focus:ring-0 border-none bg-[#f1e9ea] focus:border-none h-full placeholder:text-[#8b5b5d] px-4 rounded-l-none border-l-0 pl-2 text-base font-normal leading-normal"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<button
|
|
||||||
class="flex max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-xl h-10 bg-[#f1e9ea] text-[#191011] gap-2 text-sm font-bold leading-normal tracking-[0.015em] min-w-0 px-2.5"
|
|
||||||
>
|
|
||||||
<div class="text-[#191011]" data-icon="Heart" data-size="20px" data-weight="regular">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="currentColor" viewBox="0 0 256 256">
|
|
||||||
<path
|
|
||||||
d="M178,32c-20.65,0-38.73,8.88-50,23.89C116.73,40.88,98.65,32,78,32A62.07,62.07,0,0,0,16,94c0,70,103.79,126.66,108.21,129a8,8,0,0,0,7.58,0C136.21,220.66,240,164,240,94A62.07,62.07,0,0,0,178,32ZM128,206.8C109.74,196.16,32,147.69,32,94A46.06,46.06,0,0,1,78,48c19.45,0,35.78,10.36,42.6,27a8,8,0,0,0,14.8,0c6.82-16.67,23.15-27,42.6-27a46.06,46.06,0,0,1,46,46C224,147.61,146.24,196.15,128,206.8Z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="flex max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-xl h-10 bg-[#f1e9ea] text-[#191011] gap-2 text-sm font-bold leading-normal tracking-[0.015em] min-w-0 px-2.5"
|
|
||||||
>
|
|
||||||
<div class="text-[#191011]" data-icon="ShoppingCart" data-size="20px" data-weight="regular">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="currentColor" viewBox="0 0 256 256">
|
|
||||||
<path
|
|
||||||
d="M222.14,58.87A8,8,0,0,0,216,56H54.68L49.79,29.14A16,16,0,0,0,34.05,16H16a8,8,0,0,0,0,16h18L59.56,172.29a24,24,0,0,0,5.33,11.27,28,28,0,1,0,44.4,8.44h45.42A27.75,27.75,0,0,0,152,204a28,28,0,1,0,28-28H83.17a8,8,0,0,1-7.87-6.57L72.13,152h116a24,24,0,0,0,23.61-19.71l12.16-66.86A8,8,0,0,0,222.14,58.87ZM96,204a12,12,0,1,1-12-12A12,12,0,0,1,96,204Zm96,0a12,12,0,1,1-12-12A12,12,0,0,1,192,204Zm4-74.57A8,8,0,0,1,188.1,136H69.22L57.59,72H206.41Z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-center bg-no-repeat aspect-square bg-cover rounded-full size-10"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuBxaF5v6Zbu9bB82x_wP5pkjogVV5iFabxe8ySILcRyEdf47hhsJZXw1sLOwUUS88fTlb7fgkXL_uMZqqrl23CGFaQ_TXb11ZHBSSe-_nnX8OA8nNEzJCM558mTSUEQDfd08OlgUOycu_eRnnWYkwz7j4Pq5F7aPyjpTRPvs1mA9us4DhS_EYCCUDaU6lfHerzOvHS3yP0JMS6aAGm1WLQkXoj36__9PePJvdNhbRcRVRuizP7t3Ugb70q3mtiJ8nsWCOtxRPSmZdE");'
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="px-40 flex flex-1 justify-center py-5">
|
|
||||||
<div class="layout-content-container flex flex-col max-w-[960px] flex-1">
|
|
||||||
<div class="flex flex-wrap justify-between gap-3 p-4"><p class="text-[#191011] tracking-light text-[32px] font-bold leading-tight min-w-72">Kategori Peralatan</p></div>
|
|
||||||
<div class="flex gap-3 p-3 flex-wrap pr-4">
|
|
||||||
<button class="flex h-8 shrink-0 items-center justify-center gap-x-2 rounded-xl bg-[#f1e9ea] pl-4 pr-2">
|
|
||||||
<p class="text-[#191011] text-sm font-medium leading-normal">Harga</p>
|
|
||||||
<div class="text-[#191011]" data-icon="CaretDown" data-size="20px" data-weight="regular">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="currentColor" viewBox="0 0 256 256">
|
|
||||||
<path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button class="flex h-8 shrink-0 items-center justify-center gap-x-2 rounded-xl bg-[#f1e9ea] pl-4 pr-2">
|
|
||||||
<p class="text-[#191011] text-sm font-medium leading-normal">Merek</p>
|
|
||||||
<div class="text-[#191011]" data-icon="CaretDown" data-size="20px" data-weight="regular">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="currentColor" viewBox="0 0 256 256">
|
|
||||||
<path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button class="flex h-8 shrink-0 items-center justify-center gap-x-2 rounded-xl bg-[#f1e9ea] pl-4 pr-2">
|
|
||||||
<p class="text-[#191011] text-sm font-medium leading-normal">Kapasitas</p>
|
|
||||||
<div class="text-[#191011]" data-icon="CaretDown" data-size="20px" data-weight="regular">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="currentColor" viewBox="0 0 256 256">
|
|
||||||
<path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-[repeat(auto-fit,minmax(158px,1fr))] gap-3 p-4">
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-xl"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuCNHokO6q-y0WWuIHxzj0NwuqG6bj2wHCfFp5C925XV6UC9pzZDqunT5gN3LBK645qCCXzyqxn6-eY7k8b_zRBPm4LzV8huqbjEmoa2KfQmS2FxGkTLNyPUdBx9s27nIS1QvHo_1xRSRCdQvwUz8W-7Ff544DTny-I7nKmoLnKp4q_MhP0pr5UmHdMCWwAhAmfJop8afuMpeR2cwgJb8U-_GgLi11JRJcanuH9fVOqqqxsbuAoQXOjJPWiJ6P8UhpFmGJJpet1_AzA");'
|
|
||||||
></div>
|
|
||||||
<p class="text-[#191011] text-base font-medium leading-normal">Tenda</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-xl"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuDrxeYAqxgHvXBLRxyW6GONxUV4CreNviEp49SxXd6r_PHkczD1Hl5f_qkFAvhnjD4BzOPXvkBALnESILNgTVoH8SRDj2655B-UgV1EeydVlo7_vqXLPL00EOej920wYxn3ciRqc8T8T5NSiwyvFzHWcY6QLJUYT2cQRemIPDpSFJR76cPAyHk2umMOh_WKXrBOoZJ7vE9kaSZOshh-PkxUi92Pi3bolxcOm7eiRXB8K8e6yqhOqLMV5KtxP5UmZg05c0IevCQYsBk");'
|
|
||||||
></div>
|
|
||||||
<p class="text-[#191011] text-base font-medium leading-normal">Sleeping Bag</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-xl"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuCWwcGTGRxXLPLB4NE3vT-cPBnJs0rs2JlyPQn0YSKWIaZkp5__D-P05aaUTSha1lfgbPeyIIseEOVlX30J5zkTf3oMZXYZV9HlV1ckh_3PJJULcIQJ6QdlQuxiTHEpF-qRkbhwXrsQhPLgqvXyB3eaRUxcFu179dHzmkU4OfspLCye8U6LHjdp30zT1DDn87a_SgnLm77wlIpwS9PhjbGKdC9jy_g2Kgt_Wdn-OgHwnefT9Hl7KiRfdZ2RwPz-J5dcAw6gwYEUm1I");'
|
|
||||||
></div>
|
|
||||||
<p class="text-[#191011] text-base font-medium leading-normal">Matras</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-xl"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuD4aZqSSgxUwH2PtTZoWZkHJyBnImCvJ3E5_XDv1fYv_2mcnq8P3dMOLoARlHsJC4aeRhxxlr58hkUk120Mq_Uk5unxMAbDFYNn4TM_h5QEm0ZBIqOKgEAx4QKf93XE68uVLwT4sB_6dnMHs9gjxMS5vdalwAPTCbwlANQrQELLiBA-055ZYSa-8Kwop__Ygzyp2OrxA4cwYcPC5wlgogD7R5BSVq-vOnKUtrcfLezPUu49wwdufZYdJ9C33pUske_FQLhLA2pHrJQ");'
|
|
||||||
></div>
|
|
||||||
<p class="text-[#191011] text-base font-medium leading-normal">Kompor</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-xl"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuDO6RTDKia5PJXnjXf1HFAmayyakeUrXzX7OWUANvGHTUYfzrb8YlZxLm3PwBud6k9uc_r4YER-cA7SXx3kWjK5eMD9clyFU4_NdIfr4865_FLDUmM4tUmRShNQMuis0aFoVGgkiIgsRVhXqLmp2W836eIFHix1UqUxcKhGuKRu9GbPTAgQZlEo4PMntizkCPEOsNYpnLFpYtbUeD0afJ8erKPZQU3vnpxeP_G8atJBAVV1MEgHNd2W6FOMPqfVV2XhgTiu3WrIqZo");'
|
|
||||||
></div>
|
|
||||||
<p class="text-[#191011] text-base font-medium leading-normal">Peralatan Masak</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-xl"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuDymPy_KGw4A3sbMoc2a1_u51Tgtpl2UFR9BSb8ywke8yk3d34Qz8B75ty20CinoBsqxRxOs0JsHWqBV0X7n-BRmMEfi2HAxWUptg6gh5NZZHxdSXNamBtzhhflifOM06t2A_EWDu79T_6vDDPlSG76fSwdTm_4mC_ei85AuW8mFxGBIqOxldQhZESsg9SWM0p3XPUUy6tYFxtXf7zWDb3mpU1D1XPZC5_po3yXYksmJVqngFAmV8DKOgkyPWOks88pkyc0SQks6YE");'
|
|
||||||
></div>
|
|
||||||
<p class="text-[#191011] text-base font-medium leading-normal">Lampu</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-xl"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuBklJDG4ebGr2t_CncSfmiA7gZEABiAjzzuSBFQIPn3vDjwkHDIJjKDjEW3SU7Mrz9QexsdDpbR3i5uuWFAr-ZhARMooHtK20wK1dmkJ-tGSYuwpWfWDQYuPnoSsGCWY2c3V7Zs_oACjHjl4tVYS_giJblk24wHx2nlVBrBGdbsPCRrcIl7wAkJvYT0TUvyQmG69ijfmjeGuoq41s3u8_wxh_dILQiaPxXScvalC_oXgM9ScT6HsYMe02uPOKSk9yG8ERhmVofTyhM");'
|
|
||||||
></div>
|
|
||||||
<p class="text-[#191011] text-base font-medium leading-normal">Tas Gunung</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3 pb-3">
|
|
||||||
<div
|
|
||||||
class="w-full bg-center bg-no-repeat aspect-square bg-cover rounded-xl"
|
|
||||||
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuAkCyY-0wbXffws3P_0WZUFYBmPCLO4R8uFjLuPPoHykdg3djqLJkO7wgZ6P95PY_3ZMoklu8TynW3gU46rvYC-co3yvMLuYv0VZ7p5QknLqVDUBGHbcVS3QqNz5VOtw4bq8WtW_OK8-3nzePCawaZWD4N5SFByCXQ4d9MO6UwhOIfmBQP5N7xDkNIlvjxYyJDzY_rJ7NF5d1FlS0py72Bi8yFEHSLGP4k1YFji8in5T-6ZnJ4O_fNGOJ2-EveQHoR68DvH_oRvKbs");'
|
|
||||||
></div>
|
|
||||||
<p class="text-[#191011] text-base font-medium leading-normal">Sepatu Gunung</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-end overflow-hidden px-5 pb-5">
|
|
||||||
<button
|
|
||||||
class="flex max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-xl h-14 bg-[#e8b4b7] text-[#191011] text-base font-bold leading-normal tracking-[0.015em] min-w-0 px-2 gap-4 pl-4 pr-6"
|
|
||||||
>
|
|
||||||
<div class="text-[#191011]" data-icon="ChatCircleDots" data-size="24px" data-weight="regular">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor" viewBox="0 0 256 256">
|
|
||||||
<path
|
|
||||||
d="M140,128a12,12,0,1,1-12-12A12,12,0,0,1,140,128ZM84,116a12,12,0,1,0,12,12A12,12,0,0,0,84,116Zm88,0a12,12,0,1,0,12,12A12,12,0,0,0,172,116Zm60,12A104,104,0,0,1,79.12,219.82L45.07,231.17a16,16,0,0,1-20.24-20.24l11.35-34.05A104,104,0,1,1,232,128Zm-16,0A88,88,0,1,0,51.81,172.06a8,8,0,0,1,.66,6.54L40,216,77.4,203.53a7.85,7.85,0,0,1,2.53-.42,8,8,0,0,1,4,1.08A88,88,0,0,0,216,128Z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<footer class="flex justify-center">
|
|
||||||
<div class="flex max-w-[960px] flex-1 flex-col">
|
|
||||||
<footer class="flex flex-col gap-6 px-5 py-10 text-center @container">
|
|
||||||
<div class="flex flex-wrap items-center justify-center gap-6 @[480px]:flex-row @[480px]:justify-around">
|
|
||||||
<a class="text-[#8b5b5d] text-base font-normal leading-normal min-w-40" href="#">Kebijakan Privasi</a>
|
|
||||||
<a class="text-[#8b5b5d] text-base font-normal leading-normal min-w-40" href="#">Syarat dan Ketentuan</a>
|
|
||||||
<a class="text-[#8b5b5d] text-base font-normal leading-normal min-w-40" href="#">Pusat Bantuan</a>
|
|
||||||
</div>
|
|
||||||
<p class="text-[#8b5b5d] text-base font-normal leading-normal">@2024 CampRent. All rights reserved.</p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
Tentu, saya akan merevisi PRD Anda untuk mencerminkan penggunaan Create React App (CRA) sebagai tool utama untuk frontend React Anda. Perubahan utamanya akan ada di bagian teknologi yang digunakan dan beberapa penyesuaian di alur pengguna, meskipun tidak signifikan karena fokus tetap pada React.
|
|
||||||
|
|
||||||
Product Requirement Document (PRD) - Webapp Rental Peralatan Camping (Revisi untuk Create React App)
|
|
||||||
1. Pendahuluan
|
|
||||||
Dokumen ini menguraikan persyaratan fungsional dan non-fungsional untuk pengembangan aplikasi web rental peralatan camping. Tujuannya adalah menyediakan platform yang mudah digunakan bagi pelanggan untuk mencari, menyewa, dan mengembalikan peralatan camping. Bagi pemilik bisnis, ini akan membantu mengelola inventaris, pesanan, dan pelanggan secara efisien. Proyek ini akan dibangun menggunakan Create React App (CRA) untuk frontend React, n8n untuk backend, dan PostgreSQL sebagai database.
|
|
||||||
|
|
||||||
2. Sasaran
|
|
||||||
Meningkatkan efisiensi operasional bisnis rental.
|
|
||||||
|
|
||||||
Mempermudah proses pemesanan bagi pelanggan dengan visualisasi ketersediaan melalui kalender.
|
|
||||||
|
|
||||||
Memperluas jangkauan pelanggan melalui platform online.
|
|
||||||
|
|
||||||
Memberikan visibilitas inventaris real-time.
|
|
||||||
|
|
||||||
Mengurangi kesalahan manual dalam pengelolaan pesanan dan inventaris.
|
|
||||||
|
|
||||||
3. Audiens
|
|
||||||
Pengguna Akhir (Penyewa): Individu atau kelompok yang mencari peralatan camping untuk disewa.
|
|
||||||
|
|
||||||
Admin/Pemilik Bisnis: Pengelola bisnis rental yang bertanggung jawab atas inventaris, pesanan, dan operasional.
|
|
||||||
|
|
||||||
4. Fitur Utama
|
|
||||||
4.1. Modul Pengguna (Penyewa)
|
|
||||||
4.1.1. Pencarian & Penemuan Peralatan
|
|
||||||
Pencarian Berdasarkan Kategori: Pengguna dapat mencari peralatan berdasarkan kategori (misalnya, tenda, sleeping bag, alat masak, dll.).
|
|
||||||
|
|
||||||
Pencarian Berdasarkan Kata Kunci: Pengguna dapat mencari peralatan dengan memasukkan kata kunci.
|
|
||||||
|
|
||||||
Filter & Sortir:
|
|
||||||
|
|
||||||
Filter: Berdasarkan harga, ketersediaan, kapasitas (untuk tenda), merek, dll.
|
|
||||||
|
|
||||||
Sortir: Berdasarkan harga terendah/tertinggi, popularitas, ulasan, dll.
|
|
||||||
|
|
||||||
Detail Produk: Menampilkan informasi lengkap setiap peralatan, termasuk:
|
|
||||||
|
|
||||||
Nama produk
|
|
||||||
|
|
||||||
Deskripsi lengkap
|
|
||||||
|
|
||||||
Harga sewa (per hari/paket)
|
|
||||||
|
|
||||||
Gambar produk (galeri)
|
|
||||||
|
|
||||||
Ketersediaan (Integrasi Kalender): Menampilkan kalender interaktif yang menunjukkan tanggal-tanggal ketersediaan dan ketidaktersediaan untuk setiap item. Pengguna dapat langsung memilih tanggal sewa dari kalender ini.
|
|
||||||
|
|
||||||
Spesifikasi (ukuran, bahan, kapasitas)
|
|
||||||
|
|
||||||
Ulasan dan rating pelanggan
|
|
||||||
|
|
||||||
4.1.2. Proses Pemesanan
|
|
||||||
Pilihan Tanggal Sewa (Melalui Kalender): Pengguna dapat memilih tanggal mulai dan selesai sewa langsung dari kalender di halaman detail produk atau keranjang belanja. Sistem harus secara otomatis memperbarui ketersediaan dan harga.
|
|
||||||
|
|
||||||
Keranjang Belanja: Pengguna dapat menambahkan beberapa peralatan.
|
|
||||||
|
|
||||||
Ringkasan Pesanan: Menampilkan total biaya, durasi sewa, dan daftar peralatan yang disewa.
|
|
||||||
|
|
||||||
Formulir Data Diri: Pengguna harus mengisi data diri (nama, alamat, nomor telepon, email).
|
|
||||||
|
|
||||||
Metode Pembayaran: Integrasi dengan berbagai metode pembayaran (transfer bank, e-wallet, kartu kredit).
|
|
||||||
|
|
||||||
Konfirmasi Pesanan: Pengguna menerima konfirmasi pesanan melalui email/notifikasi dalam aplikasi.
|
|
||||||
|
|
||||||
4.1.3. Manajemen Akun Pengguna
|
|
||||||
Registrasi & Login: Pengguna dapat mendaftar dengan email/nomor telepon atau melalui media sosial.
|
|
||||||
|
|
||||||
Profil Pengguna: Mengelola informasi pribadi, alamat, dan preferensi.
|
|
||||||
|
|
||||||
Riwayat Pesanan: Melihat daftar pesanan yang pernah dibuat (aktif, selesai, dibatalkan).
|
|
||||||
|
|
||||||
Notifikasi: Pembaruan status pesanan, promosi, pengingat pengembalian.
|
|
||||||
|
|
||||||
4.2. Modul Admin/Pemilik Bisnis
|
|
||||||
4.2.1. Manajemen Inventaris
|
|
||||||
Tambah/Edit/Hapus Peralatan: Admin dapat menambahkan produk baru dengan detail lengkap (nama, deskripsi, harga, stok, gambar, kategori, spesifikasi).
|
|
||||||
|
|
||||||
Manajemen Kategori: Tambah/edit/hapus kategori peralatan.
|
|
||||||
|
|
||||||
Pelacakan Stok Real-time (Terintegrasi Kalender):
|
|
||||||
|
|
||||||
Stok otomatis berkurang saat disewa dan bertambah saat dikembalikan.
|
|
||||||
|
|
||||||
Admin dapat melihat kalender inventaris global yang menunjukkan kapan setiap item atau kategori item tersedia atau sedang disewa.
|
|
||||||
|
|
||||||
Kemampuan untuk memblokir tanggal tertentu untuk perawatan atau alasan lain.
|
|
||||||
|
|
||||||
Status Peralatan: Menandai peralatan sebagai "tersedia," "disewa," "dalam perawatan," atau "rusak."
|
|
||||||
|
|
||||||
4.2.2. Manajemen Pesanan
|
|
||||||
Daftar Pesanan: Melihat semua pesanan dengan status (pending, dikonfirmasi, sedang disewa, selesai, dibatalkan).
|
|
||||||
|
|
||||||
Detail Pesanan: Melihat detail lengkap setiap pesanan, termasuk informasi pelanggan, daftar peralatan yang disewa, tanggal sewa, dan total pembayaran.
|
|
||||||
|
|
||||||
Perubahan Status Pesanan: Admin dapat mengubah status pesanan secara manual (misalnya, dari "pending" ke "dikonfirmasi" setelah pembayaran diterima). Perubahan status ini harus memicu pembaruan ketersediaan di kalender.
|
|
||||||
|
|
||||||
Pencarian & Filter Pesanan: Berdasarkan tanggal, status, nama pelanggan.
|
|
||||||
|
|
||||||
4.2.3. Manajemen Pelanggan
|
|
||||||
Daftar Pelanggan: Melihat daftar semua pelanggan yang terdaftar.
|
|
||||||
|
|
||||||
Detail Pelanggan: Melihat riwayat pesanan pelanggan.
|
|
||||||
|
|
||||||
4.2.4. Manajemen Pengembalian
|
|
||||||
Pencatatan Pengembalian: Admin dapat mencatat peralatan yang telah dikembalikan.
|
|
||||||
|
|
||||||
Inspeksi Peralatan: Fitur untuk mencatat kondisi peralatan saat dikembalikan (baik, rusak minor, rusak parah).
|
|
||||||
|
|
||||||
Penalti/Denda: Jika ada kerusakan atau keterlambatan pengembalian, sistem dapat menghitung dan mencatat penalti/denda.
|
|
||||||
|
|
||||||
4.2.5. Laporan & Analitik
|
|
||||||
Laporan Pendapatan: Ringkasan pendapatan harian, mingguan, bulanan.
|
|
||||||
|
|
||||||
Laporan Inventaris: Peralatan paling populer, peralatan yang jarang disewa, peralatan yang sering rusak.
|
|
||||||
|
|
||||||
Laporan Ketersediaan: Prediksi ketersediaan di masa mendatang berdasarkan data kalender.
|
|
||||||
|
|
||||||
5. Persyaratan Non-Fungsional
|
|
||||||
5.1. Kinerja
|
|
||||||
Waktu muat halaman yang memadai.
|
|
||||||
|
|
||||||
Dapat menangani hingga 100 transaksi bersamaan tanpa penurunan kinerja yang signifikan.
|
|
||||||
|
|
||||||
5.2. Keamanan
|
|
||||||
Enkripsi SSL/TLS untuk semua komunikasi data.
|
|
||||||
|
|
||||||
Perlindungan terhadap serangan XSS dan SQL Injection.
|
|
||||||
|
|
||||||
Autentikasi dan otorisasi pengguna yang kuat.
|
|
||||||
|
|
||||||
Penyimpanan data sensitif pelanggan yang aman.
|
|
||||||
|
|
||||||
5.3. Skalabilitas
|
|
||||||
Mampu mengakomodasi peningkatan jumlah pengguna dan inventaris di masa mendatang.
|
|
||||||
|
|
||||||
5.4. Ketersediaan
|
|
||||||
Uptime minimal 99,5%.
|
|
||||||
|
|
||||||
5.5. Usability (Kemudahan Penggunaan)
|
|
||||||
Antarmuka yang intuitif dan responsif untuk desktop dan perangkat mobile.
|
|
||||||
|
|
||||||
Proses pemesanan yang sederhana dan jelas, terutama interaksi dengan kalender.
|
|
||||||
|
|
||||||
5.6. Kompatibilitas
|
|
||||||
Kompatibel dengan browser web modern (Chrome, Firefox, Safari, Edge).
|
|
||||||
|
|
||||||
6. Integrasi
|
|
||||||
Payment Gateway: Integrasi dengan penyedia layanan pembayaran lokal (misalnya, Midtrans, Xendit, Duitku). n8n akan menjadi orkestrator untuk memproses webhook dari payment gateway dan memperbarui status pesanan di database.
|
|
||||||
|
|
||||||
Email Service Provider: Untuk pengiriman notifikasi transaksi dan promosi. n8n dapat digunakan untuk memicu pengiriman email berdasarkan event tertentu (misalnya, pesanan baru, konfirmasi pembayaran, pengingat pengembalian).
|
|
||||||
|
|
||||||
7. Teknologi yang Digunakan
|
|
||||||
Frontend: Create React App (CRA) (untuk setup awal proyek React, pembangunan UI, dan development server bawaan)
|
|
||||||
|
|
||||||
Backend: n8n (sebagai orkestrator workflow, API endpoint, dan penghubung ke database PostgreSQL)
|
|
||||||
|
|
||||||
Database: PostgreSQL (untuk menyimpan data inventaris, pesanan, pengguna, dan informasi ketersediaan kalender)
|
|
||||||
|
|
||||||
Hosting: Server pribadi Anda
|
|
||||||
|
|
||||||
8. Alur Pengguna (Contoh Sederhana)
|
|
||||||
8.1. Alur Pemesanan (Penyewa)
|
|
||||||
Pengguna membuka webapp React yang di-build dengan CRA.
|
|
||||||
|
|
||||||
Mencari peralatan (misal: "tenda dome").
|
|
||||||
|
|
||||||
Di halaman detail tenda, komponen kalender React menampilkan ketersediaan. Data ketersediaan ini ditarik dari API yang diekspos oleh n8n (yang kemudian mengambil data dari PostgreSQL).
|
|
||||||
|
|
||||||
Pengguna memilih tanggal sewa dari kalender.
|
|
||||||
|
|
||||||
Menambahkan ke keranjang belanja.
|
|
||||||
|
|
||||||
Melanjutkan ke checkout.
|
|
||||||
|
|
||||||
Mengisi data diri.
|
|
||||||
|
|
||||||
Memilih metode pembayaran.
|
|
||||||
|
|
||||||
Melakukan pembayaran.
|
|
||||||
|
|
||||||
Frontend React mengirim data pesanan ke API n8n.
|
|
||||||
|
|
||||||
n8n menerima data pesanan, memprosesnya (misalnya, memverifikasi stok, membuat entri pesanan di PostgreSQL), dan memicu pengiriman konfirmasi email ke pengguna.
|
|
||||||
|
|
||||||
n8n juga memperbarui status ketersediaan item di database PostgreSQL untuk mencerminkan item yang sedang disewa, yang akan terlihat di kalender.
|
|
||||||
|
|
||||||
8.2. Alur Pengelolaan Pesanan (Admin)
|
|
||||||
Admin login ke dashboard React yang di-build dengan CRA.
|
|
||||||
|
|
||||||
Melihat daftar pesanan baru. Data ini ditarik dari API n8n (yang mengambil dari PostgreSQL).
|
|
||||||
|
|
||||||
Admin memverifikasi pembayaran pesanan.
|
|
||||||
|
|
||||||
Admin mengubah status pesanan melalui UI React, yang mengirim permintaan ke API n8n.
|
|
||||||
|
|
||||||
n8n menerima permintaan, memperbarui status pesanan di PostgreSQL, dan memicu pembaruan ketersediaan di kalender secara otomatis.
|
|
||||||
|
|
||||||
Saat pelanggan mengembalikan peralatan, admin mencatat pengembalian melalui UI React.
|
|
||||||
|
|
||||||
Permintaan dikirim ke n8n, yang kemudian memperbarui status inventaris di PostgreSQL dan mengembalikan ketersediaan item di kalender.
|
|
||||||
|
|
||||||
Jika ada kerusakan atau keterlambatan, n8n dapat memicu workflow untuk menghitung denda dan mengirim notifikasi.
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||||
import AuthPage from './pages/AuthPage';
|
import AuthPage from './AuthPage';
|
||||||
import ChatbotPage from './pages/ChatbotPage';
|
import ChatbotPage from './ChatbotPage';
|
||||||
import DashboardLayout from './layouts/DashboardLayout';
|
|
||||||
import DashboardOverview from './pages/dashboard/DashboardOverview';
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -10,7 +8,6 @@ function App() {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/admin" element={<AuthPage />} />
|
<Route path="/admin" element={<AuthPage />} />
|
||||||
<Route path="/" element={<ChatbotPage />} />
|
<Route path="/" element={<ChatbotPage />} />
|
||||||
<Route path="/dashboard" element={<DashboardLayout><DashboardOverview /></DashboardLayout>} />
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import LoginForm from '../components/auth/LoginForm';
|
import LoginForm from './LoginForm';
|
||||||
import SignupForm from '../components/auth/SignupForm';
|
import SignupForm from './SignupForm';
|
||||||
import OtpForm from '../components/auth/OtpForm';
|
import OtpForm from './OtpForm';
|
||||||
import ForgotPasswordForm from '../components/auth/ForgotPasswordForm';
|
import ForgotPasswordForm from './ForgotPasswordForm';
|
||||||
import ResetPasswordForm from '../components/auth/ResetPasswordForm';
|
import ResetPasswordForm from './ResetPasswordForm';
|
||||||
|
|
||||||
const AuthPage = () => {
|
const AuthPage = () => {
|
||||||
const [view, setView] = useState('login'); // 'login', 'signup', 'otp', 'forgotPassword', 'resetPassword'
|
const [view, setView] = useState('login'); // 'login', 'signup', 'otp', 'forgotPassword', 'resetPassword'
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
const DashboardLayout = ({ children }) => {
|
|
||||||
return (
|
|
||||||
<div className="flex h-screen bg-gray-100">
|
|
||||||
<div className="w-64 bg-gray-800 text-white">
|
|
||||||
{/* Sidebar */}
|
|
||||||
<div className="p-4">
|
|
||||||
<h1 className="text-2xl font-bold">Admin</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 p-10">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DashboardLayout;
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import App from './App.jsx';
|
import App from './App.jsx'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
const DashboardOverview = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-bold">Dashboard Overview</h1>
|
|
||||||
<p>Welcome to the admin dashboard.</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DashboardOverview;
|
|
||||||
82
bookoomoo-app/landing-page-brief.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
Copywriting Landing Page Bookoomoo
|
||||||
|
Hero Section (Attention)
|
||||||
|
Judul Utama:
|
||||||
|
Berikan Cerita, Sebarkan Bahagia
|
||||||
|
|
||||||
|
Sub Judul:
|
||||||
|
Setiap buku yang kamu buat untuk anakmu, akan menjadi hadiah untuk anak lain yang belum pernah punya buku cerita sendiri.
|
||||||
|
Nama anakmu akan jadi tokoh utama, dan wajahnya akan hadir di bagian serial number buku donasi — sebagai penanda siapa yang berbagi kebahagiaan ini.
|
||||||
|
|
||||||
|
CTA Button:
|
||||||
|
[Buat Buku + Donasi Sekarang]
|
||||||
|
|
||||||
|
Section 1 – Problem & Agitate (P – A dari PAS)
|
||||||
|
Banyak anak di pelosok Indonesia tumbuh tanpa buku cerita berwarna.
|
||||||
|
Tidak ada tokoh yang mereka kenal, tidak ada cerita yang membuat mereka tertawa atau bermimpi.
|
||||||
|
Sementara di kota, buku anak hadir berlimpah, tapi sering kali hanya berhenti di rak-rak rumah kita.
|
||||||
|
|
||||||
|
Section 2 – Solution (S dari PAS)
|
||||||
|
Bookoomoo mengubah setiap cerita yang kamu buat menjadi dua hal:
|
||||||
|
|
||||||
|
Buku personal untuk anakmu, dengan nama mereka sebagai tokoh utama.
|
||||||
|
|
||||||
|
Buku donasi fisik untuk anak lain, lengkap dengan ilustrasi AI wajah anakmu di bagian serial number — agar penerima tahu, ada teman dari jauh yang berbagi cerita untuknya.
|
||||||
|
|
||||||
|
Section 3 – Features & Advantages (F – A dari FAB)
|
||||||
|
Fitur Utama:
|
||||||
|
|
||||||
|
Cerita personal bilingual (Indonesia & Inggris) dengan nama anakmu.
|
||||||
|
|
||||||
|
Ilustrasi AI wajah anakmu untuk identitas donasi.
|
||||||
|
|
||||||
|
Cetak fisik premium + PDF digital.
|
||||||
|
|
||||||
|
QR tracking untuk melihat perjalanan buku donasi.
|
||||||
|
|
||||||
|
Keunggulan:
|
||||||
|
|
||||||
|
Setiap transaksi otomatis berdampak sosial.
|
||||||
|
|
||||||
|
Buku donasi dibuat dari kertas & cetakan berkualitas, tahan lama di tangan anak-anak.
|
||||||
|
|
||||||
|
Transparansi penuh lewat halaman tracking QR.
|
||||||
|
|
||||||
|
Section 4 – Benefits (B dari FAB)
|
||||||
|
Untuk Anakmu:
|
||||||
|
|
||||||
|
Merasa istimewa membaca buku dengan namanya sendiri sebagai tokoh utama.
|
||||||
|
|
||||||
|
Mendapatkan cerita edukatif yang memupuk imajinasi.
|
||||||
|
|
||||||
|
Untuk Anak Penerima Donasi:
|
||||||
|
|
||||||
|
Mendapat buku berwarna, gratis, dan berkualitas.
|
||||||
|
|
||||||
|
Melihat wajah “teman pemberi buku” di halaman serial number, membuat donasi terasa lebih personal.
|
||||||
|
|
||||||
|
Untuk Kamu:
|
||||||
|
|
||||||
|
Rasa bangga karena membuat dampak nyata.
|
||||||
|
|
||||||
|
Menjadi bagian dari gerakan berbagi literasi anak di Indonesia.
|
||||||
|
|
||||||
|
Section 5 – AIDA (Desire & Action)
|
||||||
|
Desire:
|
||||||
|
Bayangkan, anakmu tertawa membaca cerita tentang dirinya sendiri.
|
||||||
|
Di saat yang sama, seorang anak di pelosok juga membuka buku yang sama, dan tersenyum melihat wajah anakmu di halaman depan.
|
||||||
|
Dua anak, dua senyum, satu buku yang menyatukan.
|
||||||
|
|
||||||
|
Action:
|
||||||
|
[Klik tombol di bawah untuk mulai membuat buku]
|
||||||
|
[Buat Buku + Donasi Sekarang]
|
||||||
|
|
||||||
|
Section 6 – Social Proof & Testimoni
|
||||||
|
"Waktu saya lihat foto anak penerima buku dengan wajah anak saya di halaman depan, rasanya haru banget." – Rina, Jakarta
|
||||||
|
"Anak saya senang bukunya, tapi dia malah lebih bangga karena bisa memberi ke anak lain." – Bima, Surabaya
|
||||||
|
|
||||||
|
Section 7 – CTA Penutup
|
||||||
|
Tagline:
|
||||||
|
Bukan sekadar buku. Ini cerita yang menyatukan hati.
|
||||||
|
CTA Button:
|
||||||
|
[Buat Buku + Donasi Sekarang]
|
||||||
|
|
||||||
68
bookoomoo-app/list-page.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Sitemap & Daftar Halaman Bookoomoo
|
||||||
|
|
||||||
|
## A. Halaman Publik
|
||||||
|
1. **Landing Page (Donasi-First)**
|
||||||
|
- Hero
|
||||||
|
- Problem → Agitate
|
||||||
|
- Solution
|
||||||
|
- Cara Kerja (3 Langkah)
|
||||||
|
- Fitur & Keunggulan
|
||||||
|
- Pilihan Skema Donasi
|
||||||
|
- Preview Buku (interaktif)
|
||||||
|
- Testimoni & Dampak Sosial
|
||||||
|
- FAQ
|
||||||
|
- CTA Penutup
|
||||||
|
- Footer
|
||||||
|
|
||||||
|
2. **Tentang Kami (About)**
|
||||||
|
- Visi & Misi
|
||||||
|
- Cerita di Balik Bookoomoo
|
||||||
|
- Tim & Partner
|
||||||
|
|
||||||
|
3. **Program Donasi**
|
||||||
|
- Penjelasan Buy 1 Donate 1 & Full Donation
|
||||||
|
- Laporan & Statistik Donasi
|
||||||
|
- Foto dokumentasi (privasi terjaga)
|
||||||
|
|
||||||
|
4. **Kontak**
|
||||||
|
- Form kontak
|
||||||
|
- Email, WhatsApp, media sosial
|
||||||
|
|
||||||
|
5. **Kebijakan**
|
||||||
|
- Kebijakan Privasi
|
||||||
|
- Syarat & Ketentuan Layanan
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## B. Halaman Fungsional / User
|
||||||
|
6. **Form Pembuatan Buku**
|
||||||
|
- Input nama anak
|
||||||
|
- Pilih tema
|
||||||
|
- Preview singkat
|
||||||
|
- Pilih skema donasi
|
||||||
|
|
||||||
|
7. **Checkout**
|
||||||
|
- Ringkasan pesanan
|
||||||
|
- Metode pembayaran
|
||||||
|
- Alamat (jika ada pengiriman fisik)
|
||||||
|
|
||||||
|
8. **Konfirmasi Pesanan**
|
||||||
|
- Detail pesanan
|
||||||
|
- Nomor order
|
||||||
|
- Estimasi cetak & kirim
|
||||||
|
- Link tracking QR donasi
|
||||||
|
|
||||||
|
9. **Tracking Donasi**
|
||||||
|
- Status perjalanan buku donasi
|
||||||
|
- Foto penerima (opsional, privasi terjaga)
|
||||||
|
- Lokasi tujuan (umum)
|
||||||
|
|
||||||
|
10. **Login / Register**
|
||||||
|
- Form login & daftar akun
|
||||||
|
- Integrasi media sosial (opsional)
|
||||||
|
|
||||||
|
11. **Dashboard User**
|
||||||
|
- Riwayat pesanan
|
||||||
|
- Status donasi
|
||||||
|
- Saldo token (jika ada)
|
||||||
|
- Akses ke PDF yang sudah dibeli
|
||||||
@@ -9,10 +9,11 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"framer-motion": "^12.23.12",
|
||||||
|
"lucide-react": "^0.469.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-router-dom": "^6.26.2"
|
||||||
"lucide-react": "^0.469.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
@@ -22,4 +23,3 @@
|
|||||||
"vite": "^5.4.8"
|
"vite": "^5.4.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
bookoomoo-app/public/hero-bg.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
1
bookoomoo-app/public/hero-image.png
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300"><rect width="400" height="300" fill="#e0f2f1"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="sans-serif" font-size="24px" fill="#374151">Hero Image Placeholder</text></svg>
|
||||||
|
After Width: | Height: | Size: 296 B |
14
juragankos-app/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env.local
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# Editors
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
77
juragankos-app/CODEX.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# CODEX Context
|
||||||
|
|
||||||
|
Ringkasan konteks kerja untuk meneruskan pengembangan aplikasi chatbot pengusaha kos.
|
||||||
|
|
||||||
|
## Ringkasan Proyek
|
||||||
|
- Tujuan: Aplikasi chatbot untuk pengusaha kos-kosan.
|
||||||
|
- Fokus utama: Antarmuka chat sebagai halaman utama, login admin, dan dashboard analitik performa chatbot.
|
||||||
|
|
||||||
|
## Stack & Tools
|
||||||
|
- Frontend: React 18 + TypeScript (Vite).
|
||||||
|
- Styling: Tailwind CSS + shadcn/ui (preset CSS vars + utilities) + tailwindcss-animate.
|
||||||
|
- Ikon: lucide-react.
|
||||||
|
- Routing: react-router-dom.
|
||||||
|
|
||||||
|
## Struktur Halaman & Routing
|
||||||
|
- `/` → Chat (UI percakapan + input pesan, respons asisten masih disimulasikan).
|
||||||
|
- `/admin/login` → Halaman login admin (stub login di sisi klien).
|
||||||
|
- `/admin/dashboard` → Dashboard analitik sederhana (KPI dummy + daftar percakapan terbaru).
|
||||||
|
|
||||||
|
Router didefinisikan di `src/main.tsx` dengan `createBrowserRouter`, layout root di `src/App.tsx` dan `Outlet` untuk konten halaman.
|
||||||
|
|
||||||
|
## UI Kit & Styling (shadcn/ui)
|
||||||
|
- Tailwind dikonfigurasi untuk shadcn: `darkMode: ['class']`, warna via CSS variables, radius, dan animasi accordion.
|
||||||
|
- File penting:
|
||||||
|
- `tailwind.config.js` (termasuk `tailwindcss-animate`).
|
||||||
|
- `src/index.css` (layer base + CSS variables tema light/dark shadcn).
|
||||||
|
- `components.json` (config shadcn, alias `src/components` dan `src/lib/utils`).
|
||||||
|
- Helper util: `src/lib/utils.ts` berisi `cn()` (clsx + tailwind-merge).
|
||||||
|
|
||||||
|
## Komponen yang Tersedia
|
||||||
|
- `Button` (`src/components/ui/button.tsx`) – berbasis CVA + Radix Slot, varian: default, destructive, outline, secondary, ghost, link; size: default, sm, lg, icon.
|
||||||
|
- `Input` (`src/components/ui/input.tsx`).
|
||||||
|
- `Card` (`src/components/ui/card.tsx`) + `CardHeader`, `CardTitle`, `CardContent`.
|
||||||
|
- Ikon: gunakan dari `lucide-react`, contoh: `Home`, `Moon`, `Send`, dsb.
|
||||||
|
|
||||||
|
## Halaman yang Ada
|
||||||
|
- `src/pages/Chat.tsx`: daftar pesan, auto scroll, input + tombol kirim, balasan asisten disimulasikan (setTimeout). Siap dihubungkan ke backend (SSE/WebSocket/HTTP stream).
|
||||||
|
- `src/pages/admin/Login.tsx`: form email/password; menggunakan `services/auth` untuk stub login.
|
||||||
|
- `src/pages/admin/Dashboard.tsx`: guard sederhana (redirect ke login jika belum auth), KPI dummy dan list percakapan terbaru.
|
||||||
|
|
||||||
|
## Auth (sementara)
|
||||||
|
- `src/services/auth.ts` menyimpan token dummy di `localStorage`. Fungsi: `login`, `logout`, `isAuthenticated`.
|
||||||
|
- Perlu diganti ke autentikasi riil (JWT/session) dan proteksi route yang lebih kuat.
|
||||||
|
|
||||||
|
## Perintah NPM
|
||||||
|
- `npm run dev` — jalankan Vite dev server.
|
||||||
|
- `npm run build` — build produksi ke `dist/`.
|
||||||
|
- `npm run preview` — preview hasil build.
|
||||||
|
|
||||||
|
## Berkas Penting
|
||||||
|
- `index.html` — entry HTML.
|
||||||
|
- `vite.config.ts` — plugin React.
|
||||||
|
- `tsconfig.json`, `tsconfig.node.json` — TypeScript config.
|
||||||
|
- `src/App.tsx` — layout + navigasi (Chat, Dashboard) + placeholder toggle tema.
|
||||||
|
- `src/main.tsx` — bootstrap React + Router.
|
||||||
|
|
||||||
|
## Rencana Lanjutan / TODO
|
||||||
|
- [ ] Implementasi toggle dark mode (persist di `localStorage`, set class `dark` di `document.documentElement`).
|
||||||
|
- [ ] Integrasi backend chat (SSE/WebSocket/HTTP) + streaming token respons.
|
||||||
|
- [ ] Ganti stub auth dengan API riil (JWT/refresh) + guard router (loader/ProtectedRoute).
|
||||||
|
- [ ] Dashboard analitik: metrik nyata (total percakapan, CSAT, latency), grafik (mis. Recharts), filter rentang waktu.
|
||||||
|
- [ ] Persistensi percakapan (riwayat, metadata: intent, kepuasan, sumber lead).
|
||||||
|
- [ ] A11y chat: roles/aria proper, fokus kembali ke input setelah kirim, shortcut keyboard.
|
||||||
|
- [ ] ESLint + Prettier + Vitest sesuai pedoman repo; tambahkan test dasar untuk komponen UI dan routing.
|
||||||
|
- [ ] ENV config (`.env.local`) untuk URL API (`VITE_API_URL`) dan kunci lain (tanpa commit rahasia).
|
||||||
|
|
||||||
|
## Catatan Implementasi
|
||||||
|
- Tema mengikuti shadcn: gunakan kelas Tailwind yang merujuk CSS variables (`bg-background`, `text-foreground`, dll.).
|
||||||
|
- Animasi: `tailwindcss-animate` sudah aktif; siap untuk komponen seperti Accordion/Dialog apabila ditambahkan.
|
||||||
|
- Navigasi header ada di `src/App.tsx` (link ke Chat dan Dashboard), tombol tema placeholder.
|
||||||
|
|
||||||
|
## Cara Menjalankan
|
||||||
|
1. `npm install`
|
||||||
|
2. `npm run dev` (development) atau `npm run build && npm run preview` (produksi lokal).
|
||||||
|
|
||||||
|
---
|
||||||
|
Jika Anda ingin saya melanjutkan, prioritas disarankan: dark mode toggle → integrasi alur chat ke backend (streaming) → auth riil → dashboard metrik & grafik → testing & linting.
|
||||||
17
juragankos-app/components.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "default",
|
||||||
|
"rsc": false,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.js",
|
||||||
|
"css": "src/index.css",
|
||||||
|
"baseColor": "slate",
|
||||||
|
"cssVariables": true
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "src/components",
|
||||||
|
"utils": "src/lib/utils"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
13
juragankos-app/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>JuraganKos App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
3086
juragankos-app/package-lock.json
generated
Normal file
32
juragankos-app/package.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "juragankos-app",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "^0.539.0",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-router-dom": "^7.8.0",
|
||||||
|
"tailwind-merge": "^3.3.1",
|
||||||
|
"tailwindcss-animate": "^1.0.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.2.43",
|
||||||
|
"@types/react-dom": "^18.2.17",
|
||||||
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"postcss": "^8.4.41",
|
||||||
|
"tailwindcss": "^3.4.10",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"vite": "^5.0.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
juragankos-app/postcss.config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
1
juragankos-app/public/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
54
juragankos-app/src/App.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { Button } from './components/ui/button'
|
||||||
|
import { Home, Moon, BarChart3, MessageSquare } from 'lucide-react'
|
||||||
|
import { Link, Outlet, useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const { pathname } = useLocation()
|
||||||
|
const isChat = pathname === '/'
|
||||||
|
|
||||||
|
if (isChat) {
|
||||||
|
return (
|
||||||
|
<div className="relative min-h-screen overflow-hidden bg-gradient-to-br from-sky-100 via-rose-50 to-emerald-100 dark:from-slate-900 dark:via-slate-950 dark:to-indigo-950 text-foreground">
|
||||||
|
<div className="pointer-events-none absolute inset-0 opacity-70">
|
||||||
|
<div className="absolute -top-32 left-1/2 h-[60vh] w-[80vw] -translate-x-1/2 rounded-full bg-[radial-gradient(circle_at_center,theme(colors.sky.400)/35%,transparent_60%)] blur-3xl" />
|
||||||
|
<div className="absolute bottom-[-20%] left-[-10%] h-[50vh] w-[50vw] rounded-full bg-[radial-gradient(circle_at_center,theme(colors.emerald.400)/25%,transparent_60%)] blur-3xl" />
|
||||||
|
<div className="absolute bottom-[-30%] right-[-10%] h-[55vh] w-[55vw] rounded-full bg-[radial-gradient(circle_at_center,theme(colors.purple.400)/20%,transparent_60%)] blur-3xl" />
|
||||||
|
</div>
|
||||||
|
<main className="relative grid min-h-screen place-items-center px-4 py-6">
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background text-foreground">
|
||||||
|
<header className="border-b bg-background/60 supports-[backdrop-filter]:backdrop-blur">
|
||||||
|
<div className="mx-auto max-w-5xl px-6 py-4 flex items-center gap-4">
|
||||||
|
<Link to="/" className="inline-flex items-center gap-2">
|
||||||
|
<Home className="h-5 w-5" />
|
||||||
|
<span className="text-xl font-semibold">JuraganKos</span>
|
||||||
|
</Link>
|
||||||
|
<nav className="flex items-center gap-2 text-sm">
|
||||||
|
<Link to="/" className="inline-flex items-center gap-1 text-muted-foreground hover:text-foreground">
|
||||||
|
<MessageSquare className="h-4 w-4" /> Chat
|
||||||
|
</Link>
|
||||||
|
<Link to="/admin/dashboard" className="inline-flex items-center gap-1 text-muted-foreground hover:text-foreground">
|
||||||
|
<BarChart3 className="h-4 w-4" /> Dashboard
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
<div className="ml-auto">
|
||||||
|
<Button variant="secondary" size="sm">
|
||||||
|
<Moon className="mr-2 h-4 w-4" />
|
||||||
|
Toggle theme
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main className="mx-auto max-w-5xl px-6 py-6">
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
53
juragankos-app/src/components/ui/button.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import { Slot } from '@radix-ui/react-slot'
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
import { cn } from '../../lib/utils'
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
||||||
|
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
||||||
|
outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
||||||
|
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
||||||
|
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||||
|
link: 'text-primary underline-offset-4 hover:underline',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: 'h-9 px-4 py-2',
|
||||||
|
sm: 'h-8 rounded-md px-3 text-xs',
|
||||||
|
lg: 'h-10 rounded-md px-8',
|
||||||
|
icon: 'h-9 w-9',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
size: 'default',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : 'button'
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Button.displayName = 'Button'
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
||||||
|
|
||||||
21
juragankos-app/src/components/ui/card.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import { cn } from '../../lib/utils'
|
||||||
|
|
||||||
|
function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return <div className={cn('rounded-lg border bg-card text-card-foreground shadow-sm', className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return <div className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
|
||||||
|
return <h3 className={cn('text-2xl font-semibold leading-none tracking-tight', className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return <div className={cn('p-6 pt-0', className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Card, CardHeader, CardTitle, CardContent }
|
||||||
|
|
||||||
22
juragankos-app/src/components/ui/input.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import { cn } from '../../lib/utils'
|
||||||
|
|
||||||
|
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
|
|
||||||
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
className={cn(
|
||||||
|
'flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
Input.displayName = 'Input'
|
||||||
|
|
||||||
|
export { Input }
|
||||||
|
|
||||||
74
juragankos-app/src/index.css
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--primary: 222.2 47.4% 11.2%;
|
||||||
|
--primary-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--secondary: 210 40% 96.1%;
|
||||||
|
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
|
--muted: 210 40% 96.1%;
|
||||||
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
|
|
||||||
|
--accent: 210 40% 96.1%;
|
||||||
|
--accent-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--border: 214.3 31.8% 91.4%;
|
||||||
|
--input: 214.3 31.8% 91.4%;
|
||||||
|
--ring: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 222.2 84% 4.9%;
|
||||||
|
--foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--card: 222.2 84% 4.9%;
|
||||||
|
--card-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--popover: 222.2 84% 4.9%;
|
||||||
|
--popover-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--primary: 210 40% 98%;
|
||||||
|
--primary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
|
--secondary-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--muted: 217.2 32.6% 17.5%;
|
||||||
|
--muted-foreground: 215 20.2% 65.1%;
|
||||||
|
|
||||||
|
--accent: 217.2 32.6% 17.5%;
|
||||||
|
--accent-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--border: 217.2 32.6% 17.5%;
|
||||||
|
--input: 217.2 32.6% 17.5%;
|
||||||
|
--ring: 212.7 26.8% 83.9%;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
juragankos-app/src/lib/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { type ClassValue, clsx } from 'clsx'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
|
|
||||||
26
juragankos-app/src/main.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import './index.css'
|
||||||
|
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
|
||||||
|
import Chat from './pages/Chat'
|
||||||
|
import AdminLogin from './pages/admin/Login'
|
||||||
|
import AdminDashboard from './pages/admin/Dashboard'
|
||||||
|
import App from './App'
|
||||||
|
|
||||||
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <App />,
|
||||||
|
children: [
|
||||||
|
{ index: true, element: <Chat /> },
|
||||||
|
{ path: 'admin/login', element: <AdminLogin /> },
|
||||||
|
{ path: 'admin/dashboard', element: <AdminDashboard /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</React.StrictMode>,
|
||||||
|
)
|
||||||
91
juragankos-app/src/pages/Chat.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { Button } from '../components/ui/button'
|
||||||
|
import { Send, Bot, User } from 'lucide-react'
|
||||||
|
|
||||||
|
type Message = {
|
||||||
|
id: string
|
||||||
|
role: 'user' | 'assistant'
|
||||||
|
content: string
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Chat() {
|
||||||
|
const [messages, setMessages] = useState<Message[]>([{
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
role: 'assistant',
|
||||||
|
content: 'Halo! Saya asisten virtual untuk pengusaha kos. Ada yang bisa saya bantu hari ini?',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
}])
|
||||||
|
const [input, setInput] = useState('')
|
||||||
|
const scrollerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
scrollerRef.current?.scrollTo({ top: scrollerRef.current.scrollHeight, behavior: 'smooth' })
|
||||||
|
}, [messages.length])
|
||||||
|
|
||||||
|
const sendMessage = () => {
|
||||||
|
const trimmed = input.trim()
|
||||||
|
if (!trimmed) return
|
||||||
|
const userMsg: Message = { id: crypto.randomUUID(), role: 'user', content: trimmed, timestamp: Date.now() }
|
||||||
|
setMessages((m) => [...m, userMsg])
|
||||||
|
setInput('')
|
||||||
|
|
||||||
|
// Simulate assistant reply
|
||||||
|
setTimeout(() => {
|
||||||
|
const reply: Message = {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
role: 'assistant',
|
||||||
|
content: `Terima kasih! Saya catat: "${trimmed}". Fitur backend akan menjawab secara cerdas nanti.`,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
}
|
||||||
|
setMessages((m) => [...m, reply])
|
||||||
|
}, 600)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-2xl h-[75vh] flex flex-col rounded-2xl border bg-background/45 shadow-2xl supports-[backdrop-filter]:backdrop-blur-xl backdrop-blur-xl">
|
||||||
|
<div className="flex items-center gap-3 border-b bg-background/40 px-4 py-3">
|
||||||
|
<div className="relative h-8 w-8 shrink-0 overflow-hidden rounded-full border bg-secondary">
|
||||||
|
<Bot className="h-full w-full p-1" />
|
||||||
|
<span className="absolute bottom-0 right-0 h-2 w-2 rounded-full bg-emerald-500 ring-2 ring-background" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-medium">JuraganKos Assistant</div>
|
||||||
|
<div className="text-xs text-muted-foreground">Online • Siap membantu kos Anda</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ref={scrollerRef} className="flex-1 space-y-4 overflow-auto p-4">
|
||||||
|
{messages.map((msg) => {
|
||||||
|
const isUser = msg.role === 'user'
|
||||||
|
return (
|
||||||
|
<div key={msg.id} className={`flex items-start gap-3 ${isUser ? 'flex-row-reverse' : ''}`}>
|
||||||
|
<div className={`mt-1 rounded-full p-1 ${isUser ? 'bg-primary text-primary-foreground' : 'bg-secondary'}`}>
|
||||||
|
{isUser ? <User className="h-4 w-4" /> : <Bot className="h-4 w-4" />}
|
||||||
|
</div>
|
||||||
|
<div className={`max-w-[75%] rounded-lg border px-3 py-2 text-sm ${isUser ? 'bg-primary text-primary-foreground border-transparent' : 'bg-background'}`}>
|
||||||
|
{msg.content}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
sendMessage();
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2 border-t p-3 bg-background/30"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="flex-1 h-10 rounded-md border bg-background/60 px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring backdrop-blur-sm"
|
||||||
|
placeholder="Tulis pesan..."
|
||||||
|
value={input}
|
||||||
|
onChange={(e) => setInput(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button type="submit" size="sm" disabled={!input.trim()}>
|
||||||
|
<Send className="mr-2 h-4 w-4" /> Kirim
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
51
juragankos-app/src/pages/admin/Dashboard.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { Button } from '../../components/ui/button'
|
||||||
|
import { LogOut, MessageSquare, TrendingUp, Clock } from 'lucide-react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { isAuthenticated, logout } from '../../services/auth'
|
||||||
|
|
||||||
|
export default function AdminDashboard() {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAuthenticated()) navigate('/admin/login')
|
||||||
|
}, [navigate])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h2 className="text-2xl font-bold">Dashboard</h2>
|
||||||
|
<Button variant="outline" onClick={() => { logout(); navigate('/admin/login') }}>
|
||||||
|
<LogOut className="mr-2 h-4 w-4" /> Keluar
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||||
|
<StatCard icon={<MessageSquare className="h-4 w-4" />} label="Total Percakapan" value="124" />
|
||||||
|
<StatCard icon={<TrendingUp className="h-4 w-4" />} label="CSAT (mingguan)" value="92%" />
|
||||||
|
<StatCard icon={<Clock className="h-4 w-4" />} label="Rata respon" value="1.8s" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border bg-card p-4">
|
||||||
|
<h3 className="mb-2 font-semibold">Percakapan Terbaru</h3>
|
||||||
|
<ul className="divide-y text-sm">
|
||||||
|
{['Booking kamar putri', 'Tanya harga bulanan', 'Kebijakan tamu', 'Ketersediaan parkir'].map((t, i) => (
|
||||||
|
<li key={i} className="flex items-center justify-between py-2">
|
||||||
|
<span>{t}</span>
|
||||||
|
<span className="text-muted-foreground">{new Date().toLocaleDateString()}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatCard({ icon, label, value }: { icon: React.ReactNode; label: string; value: string }) {
|
||||||
|
return (
|
||||||
|
<div className="rounded-lg border bg-card p-4 shadow-sm">
|
||||||
|
<div className="flex items-center gap-2 text-muted-foreground">{icon}<span className="text-xs">{label}</span></div>
|
||||||
|
<div className="mt-2 text-2xl font-semibold">{value}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
48
juragankos-app/src/pages/admin/Login.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { Button } from '../../components/ui/button'
|
||||||
|
import { LogIn } from 'lucide-react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { login } from '../../services/auth'
|
||||||
|
|
||||||
|
export default function AdminLogin() {
|
||||||
|
const [email, setEmail] = useState('')
|
||||||
|
const [password, setPassword] = useState('')
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const onSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setLoading(true)
|
||||||
|
setError(null)
|
||||||
|
try {
|
||||||
|
await login(email, password)
|
||||||
|
navigate('/admin/dashboard')
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err?.message ?? 'Gagal login')
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto max-w-sm rounded-lg border bg-card p-6 shadow-sm">
|
||||||
|
<h2 className="mb-4 text-xl font-semibold">Admin Login</h2>
|
||||||
|
<form className="space-y-3" onSubmit={onSubmit}>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label htmlFor="email" className="text-sm text-muted-foreground">Email</label>
|
||||||
|
<input id="email" type="email" autoComplete="email" required className="h-10 w-full rounded-md border bg-background px-3 text-sm" value={email} onChange={(e)=>setEmail(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label htmlFor="password" className="text-sm text-muted-foreground">Password</label>
|
||||||
|
<input id="password" type="password" autoComplete="current-password" required className="h-10 w-full rounded-md border bg-background px-3 text-sm" value={password} onChange={(e)=>setPassword(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
{error && <p className="text-sm text-red-600">{error}</p>}
|
||||||
|
<Button type="submit" className="w-full" disabled={loading}>
|
||||||
|
<LogIn className="mr-2 h-4 w-4" /> {loading ? 'Memproses...' : 'Masuk'}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
21
juragankos-app/src/services/auth.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const KEY = 'jk_admin_token'
|
||||||
|
|
||||||
|
export async function login(email: string, password: string) {
|
||||||
|
await sleep(300)
|
||||||
|
if (!email || !password) throw new Error('Email dan password wajib diisi')
|
||||||
|
// Placeholder: accept any values. Replace with real API later.
|
||||||
|
localStorage.setItem(KEY, 'ok')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logout() {
|
||||||
|
localStorage.removeItem(KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAuthenticated() {
|
||||||
|
return !!localStorage.getItem(KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms: number) {
|
||||||
|
return new Promise((res) => setTimeout(res, ms))
|
||||||
|
}
|
||||||
|
|
||||||
71
juragankos-app/tailwind.config.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
darkMode: ['class'],
|
||||||
|
content: ['./index.html', './src/**/*.{ts,tsx}'],
|
||||||
|
theme: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
padding: '2rem',
|
||||||
|
screens: {
|
||||||
|
'2xl': '1400px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
border: 'hsl(var(--border))',
|
||||||
|
input: 'hsl(var(--input))',
|
||||||
|
ring: 'hsl(var(--ring))',
|
||||||
|
background: 'hsl(var(--background))',
|
||||||
|
foreground: 'hsl(var(--foreground))',
|
||||||
|
primary: {
|
||||||
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
|
foreground: 'hsl(var(--primary-foreground))',
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
|
foreground: 'hsl(var(--secondary-foreground))',
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
|
foreground: 'hsl(var(--destructive-foreground))',
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
|
foreground: 'hsl(var(--muted-foreground))',
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
|
foreground: 'hsl(var(--accent-foreground))',
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
|
foreground: 'hsl(var(--popover-foreground))',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: 'hsl(var(--card))',
|
||||||
|
foreground: 'hsl(var(--card-foreground))',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: 'var(--radius)',
|
||||||
|
md: 'calc(var(--radius) - 2px)',
|
||||||
|
sm: 'calc(var(--radius) - 4px)',
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
'accordion-down': {
|
||||||
|
from: { height: '0' },
|
||||||
|
to: { height: 'var(--radix-accordion-content-height)' },
|
||||||
|
},
|
||||||
|
'accordion-up': {
|
||||||
|
from: { height: 'var(--radix-accordion-content-height)' },
|
||||||
|
to: { height: '0' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||||
|
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [require('tailwindcss-animate')],
|
||||||
|
}
|
||||||
17
juragankos-app/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
|
|
||||||
12
juragankos-app/tsconfig.node.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
|
|
||||||
1
juragankos-app/tsconfig.tsbuildinfo
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"root":["./src/App.tsx","./src/main.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/input.tsx","./src/lib/utils.ts","./src/pages/Chat.tsx","./src/pages/admin/Dashboard.tsx","./src/pages/admin/Login.tsx","./src/services/auth.ts"],"version":"5.9.2"}
|
||||||
7
juragankos-app/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
})
|
||||||
|
|
||||||
BIN
kloowear-app/assets/img/about.jpg
Executable file
|
After Width: | Height: | Size: 78 KiB |
BIN
kloowear-app/assets/img/apple-touch-icon.png
Executable file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
kloowear-app/assets/img/clients/bri.png
Executable file
|
After Width: | Height: | Size: 127 KiB |
BIN
kloowear-app/assets/img/clients/client-1.webp
Executable file
|
After Width: | Height: | Size: 12 KiB |
BIN
kloowear-app/assets/img/clients/client-2-l.webp
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
kloowear-app/assets/img/clients/client-2.png
Executable file
|
After Width: | Height: | Size: 100 KiB |
BIN
kloowear-app/assets/img/clients/gb.png
Executable file
|
After Width: | Height: | Size: 58 KiB |
BIN
kloowear-app/assets/img/clients/ic.webp
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
kloowear-app/assets/img/clients/ll.png
Executable file
|
After Width: | Height: | Size: 462 KiB |
BIN
kloowear-app/assets/img/clients/mh.png
Executable file
|
After Width: | Height: | Size: 139 KiB |
BIN
kloowear-app/assets/img/clients/mi.png
Executable file
|
After Width: | Height: | Size: 55 KiB |
BIN
kloowear-app/assets/img/clients/omo.png
Executable file
|
After Width: | Height: | Size: 38 KiB |
BIN
kloowear-app/assets/img/clients/uniska.png
Executable file
|
After Width: | Height: | Size: 302 KiB |
BIN
kloowear-app/assets/img/cta-bg-bak.jpg
Executable file
|
After Width: | Height: | Size: 334 KiB |
BIN
kloowear-app/assets/img/cta-bg.jpg
Executable file
|
After Width: | Height: | Size: 58 KiB |
BIN
kloowear-app/assets/img/favicon.png
Executable file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
kloowear-app/assets/img/hero-bg.jpg
Executable file
|
After Width: | Height: | Size: 53 KiB |
BIN
kloowear-app/assets/img/logo.png
Executable file
|
After Width: | Height: | Size: 36 KiB |
BIN
kloowear-app/assets/img/portfolio/IMG_0025 (2).JPG
Executable file
|
After Width: | Height: | Size: 5.6 MiB |
BIN
kloowear-app/assets/img/portfolio/IMG_0569 (2).JPG
Executable file
|
After Width: | Height: | Size: 4.5 MiB |
BIN
kloowear-app/assets/img/portfolio/IMG_0603.JPG
Executable file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
kloowear-app/assets/img/portfolio/IMG_1056.JPG
Executable file
|
After Width: | Height: | Size: 4.1 MiB |
BIN
kloowear-app/assets/img/portfolio/IMG_2933.JPG
Executable file
|
After Width: | Height: | Size: 5.5 MiB |
BIN
kloowear-app/assets/img/portfolio/IMG_6232.JPG
Executable file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
kloowear-app/assets/img/portfolio/Sekenples Paracord 137.jpg
Executable file
|
After Width: | Height: | Size: 266 KiB |
BIN
kloowear-app/assets/img/portfolio/app-1.jpg
Executable file
|
After Width: | Height: | Size: 66 KiB |
BIN
kloowear-app/assets/img/portfolio/app-2.jpg
Executable file
|
After Width: | Height: | Size: 72 KiB |
BIN
kloowear-app/assets/img/portfolio/app-3.jpg
Executable file
|
After Width: | Height: | Size: 41 KiB |
BIN
kloowear-app/assets/img/portfolio/b1.jpeg
Executable file
|
After Width: | Height: | Size: 87 KiB |
BIN
kloowear-app/assets/img/portfolio/b2.jpeg
Executable file
|
After Width: | Height: | Size: 57 KiB |
BIN
kloowear-app/assets/img/portfolio/b2.jpg
Executable file
|
After Width: | Height: | Size: 347 KiB |
BIN
kloowear-app/assets/img/portfolio/b3.jpeg
Executable file
|
After Width: | Height: | Size: 43 KiB |
BIN
kloowear-app/assets/img/portfolio/b4.JPG
Executable file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
kloowear-app/assets/img/portfolio/b5.JPG
Executable file
|
After Width: | Height: | Size: 675 KiB |
BIN
kloowear-app/assets/img/portfolio/b6.JPG
Executable file
|
After Width: | Height: | Size: 913 KiB |
BIN
kloowear-app/assets/img/portfolio/b7.JPG
Executable file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
kloowear-app/assets/img/portfolio/books-1.jpg
Executable file
|
After Width: | Height: | Size: 78 KiB |
BIN
kloowear-app/assets/img/portfolio/books-2.jpg
Executable file
|
After Width: | Height: | Size: 59 KiB |
BIN
kloowear-app/assets/img/portfolio/books-3.jpg
Executable file
|
After Width: | Height: | Size: 89 KiB |
BIN
kloowear-app/assets/img/portfolio/branding-1.jpg
Executable file
|
After Width: | Height: | Size: 25 KiB |
BIN
kloowear-app/assets/img/portfolio/branding-2.jpg
Executable file
|
After Width: | Height: | Size: 51 KiB |
BIN
kloowear-app/assets/img/portfolio/branding-3.jpg
Executable file
|
After Width: | Height: | Size: 76 KiB |
BIN
kloowear-app/assets/img/portfolio/k.JPG
Executable file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
kloowear-app/assets/img/portfolio/k1.jpg
Executable file
|
After Width: | Height: | Size: 19 KiB |
BIN
kloowear-app/assets/img/portfolio/k2.jpg
Executable file
|
After Width: | Height: | Size: 352 KiB |
BIN
kloowear-app/assets/img/portfolio/k3.JPG
Executable file
|
After Width: | Height: | Size: 587 KiB |
BIN
kloowear-app/assets/img/portfolio/l1.jpg
Executable file
|
After Width: | Height: | Size: 55 KiB |
BIN
kloowear-app/assets/img/portfolio/l2.jpg
Executable file
|
After Width: | Height: | Size: 306 KiB |
BIN
kloowear-app/assets/img/portfolio/l3.png
Executable file
|
After Width: | Height: | Size: 68 KiB |
BIN
kloowear-app/assets/img/portfolio/product-1.jpg
Executable file
|
After Width: | Height: | Size: 61 KiB |
BIN
kloowear-app/assets/img/portfolio/product-2.jpg
Executable file
|
After Width: | Height: | Size: 87 KiB |
BIN
kloowear-app/assets/img/portfolio/product-3.jpg
Executable file
|
After Width: | Height: | Size: 20 KiB |
BIN
kloowear-app/assets/img/portfolio/w1.jpg
Executable file
|
After Width: | Height: | Size: 8.1 MiB |
BIN
kloowear-app/assets/img/portfolio/z1.png
Executable file
|
After Width: | Height: | Size: 84 KiB |