penambahan route protection

This commit is contained in:
aji@catalis.app
2025-06-13 19:36:53 +07:00
parent 9668e3ace3
commit 7be6168330
8 changed files with 365 additions and 223 deletions

View File

@@ -258,6 +258,7 @@
console.error("Logout failed:", error.message); console.error("Logout failed:", error.message);
} else { } else {
localStorage.removeItem("user"); localStorage.removeItem("user");
goto("/login"); goto("/login");
} }
} }

View File

@@ -1,12 +1,12 @@
import { redirect } from '@sveltejs/kit'; // import { redirect } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types'; // import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => { // export const load: LayoutServerLoad = async ({ locals }) => {
if (!locals.user) { // if (!locals.user) {
throw redirect(303, '/login'); // throw redirect(303, '/login');
} // }
return { // return {
user: locals.user // user: locals.user
}; // };
}; // };

View File

@@ -1,13 +1,44 @@
<script> <script lang="ts">
import { writable } from "svelte/store";
import Sidebar from "../../components/Sidebar.svelte"; import Sidebar from "../../components/Sidebar.svelte";
import { onMount } from "svelte";
import { supabase } from "$lib/supabaseClient";
import { goto } from "$app/navigation";
import type { Session } from "@supabase/supabase-js";
export let data; export let data;
let notifications = 3; // Contoh jumlah notifikasi let notifications = 3;
let user = { let user = {
name: "John Doe", name: "John Doe",
avatar: "https://i.pravatar.cc/40", // Avatar placeholder avatar: "https://i.pravatar.cc/40",
}; };
let userdata: any = null;
const session = writable<Session | null>(null);
onMount(async () => {
const {
data: { session: currentSession },
} = await supabase.auth.getSession();
session.set(currentSession || null);
supabase.auth.onAuthStateChange((event, sessionValue) => {
session.set(sessionValue);
if (!sessionValue) goto("/login");
});
const userStr = localStorage.getItem("user");
if (userStr) {
userdata = JSON.parse(userStr);
} else {
goto("/login");
}
user.name = userdata.full_name || "Guest";
user.avatar = userdata.profile_picture || "https://i.pravatar.cc/40";
});
</script> </script>
<div class="flex h-screen"> <div class="flex h-screen">

View File

@@ -1,8 +1,11 @@
import { supabase } from "$lib/supabaseClient";
import { redirect } from "@sveltejs/kit"; import { redirect } from "@sveltejs/kit";
export const load = async (event) => { import type { RequestEvent } from "@sveltejs/kit";
export const load = async (event: RequestEvent & { locals: { user?: any } }) => {
if (!event.locals.user) { if (!event.locals.user) {
console.log(event.url) console.log(event.url)
} }
} }

View File

@@ -90,7 +90,6 @@
{ label: "Guest", value: "Guest" }, { label: "Guest", value: "Guest" },
]; ];
let currentUserId: string | null = null; let currentUserId: string | null = null;
onMount(async () => { onMount(async () => {
@@ -226,8 +225,6 @@
: issue.approval : issue.approval
? "APPROVED" ? "APPROVED"
: "REJECTED", // or map as needed : "REJECTED", // or map as needed
approved_by: issue.approved_by ?? null,
approved_date: issue.approved_date ? new Date(issue.approved_date) : null,
total_hours_work: total_hours_work:
Math.abs( Math.abs(
new Date(issue.datetime_out).getTime() - new Date(issue.datetime_out).getTime() -
@@ -721,7 +718,9 @@
>Select Villa</option >Select Villa</option
> >
{#each dataVilla as villa} {#each dataVilla as villa}
<option value={villa.id}>{villa.villa_name}</option> <option value={villa.id}
>{villa.villa_name}</option
>
{/each} {/each}
</select> </select>
{#if $formErrors[col.key]} {#if $formErrors[col.key]}

View File

@@ -1,22 +1,21 @@
<script> <script>
import { supabase } from '$lib/supabaseClient'; import { supabase } from "$lib/supabaseClient";
import StarRating from '$lib/StarRating.svelte'; import StarRating from "$lib/StarRating.svelte";
import villaBugisImage from '$lib/images/villa-bugis.png'; import villaBugisImage from "$lib/images/villa-bugis.png";
const WEBHOOK_URL = "https://flow.catalis.app/webhook/vb_feedback_new";
const WEBHOOK_URL = 'https://flow.catalis.app/webhook/vb_feedback_new'; let villa_name = "";
let customer_name = "";
let villa_name = ''; let checkin_date = "";
let customer_name = ''; let checkout_date = "";
let checkin_date = ''; let feedback = "";
let checkout_date = ''; let errorMessage = "";
let feedback = ''; let book_process = "";
let errorMessage = ''; let airport_greet = "";
let book_process = ''; let arrival_greet = "";
let airport_greet = ''; let maintenance_proc = "";
let arrival_greet = ''; let bf_service = "";
let maintenance_proc = '';
let bf_service = '';
// Star ratings (1-5) // Star ratings (1-5)
let overal_star = 0; let overal_star = 0;
@@ -27,23 +26,22 @@
let become_sponsor = false; let become_sponsor = false;
async function handleSubmit() { async function handleSubmit() {
errorMessage = ''; errorMessage = "";
if (!villa_name.trim() || !customer_name.trim() || !feedback.trim()) { if (!villa_name.trim() || !customer_name.trim() || !feedback.trim()) {
errorMessage = 'Villa, Name, and feedback are required.'; errorMessage = "Villa, Name, and feedback are required.";
return; return;
} }
if (new Date(checkout_date) <= new Date(checkin_date)) { if (new Date(checkout_date) <= new Date(checkin_date)) {
errorMessage = 'Check-out date must be after check-in date.'; errorMessage = "Check-out date must be after check-in date.";
return; return;
} }
const user = (await supabase.auth.getUser()).data.user; const user = (await supabase.auth.getUser()).data.user;
const { data, error } = await supabase const { data, error } = await supabase.from("vb_feedback").insert([
.from('vb_feedback') {
.insert([{
villa_name, villa_name,
customer_name, customer_name,
checkin_date, checkin_date,
@@ -57,17 +55,18 @@
maintenance_proc, maintenance_proc,
extend_disc, extend_disc,
nextstay_disc, nextstay_disc,
become_sponsor become_sponsor,
}]); },
]);
if (error) { if (error) {
console.error('Error submitting feedback:', error.message); console.error("Error submitting feedback:", error.message);
errorMessage = 'Failed to submit feedback. Please try again.'; errorMessage = "Failed to submit feedback. Please try again.";
} else { } else {
try { try {
await fetch(WEBHOOK_URL, { await fetch(WEBHOOK_URL, {
method: 'POST', method: "POST",
headers: { 'Content-Type': 'application/json' }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({
villa_name, villa_name,
customer_name, customer_name,
@@ -82,24 +81,24 @@
overal_star, overal_star,
extend_disc, extend_disc,
nextstay_disc, nextstay_disc,
become_sponsor become_sponsor,
}) }),
}); });
} catch (webhookError) { } catch (webhookError) {
console.error('Webhook failed:', webhookError); console.error("Webhook failed:", webhookError);
} }
// Reset form // Reset form
villa_name = ''; villa_name = "";
customer_name = ''; customer_name = "";
checkin_date = ''; checkin_date = "";
checkout_date = ''; checkout_date = "";
feedback = ''; feedback = "";
book_process = ''; book_process = "";
airport_greet = ''; airport_greet = "";
arrival_greet = ''; arrival_greet = "";
maintenance_proc = ''; maintenance_proc = "";
bf_service = ''; bf_service = "";
overal_star = 0; overal_star = 0;
extend_disc = false; extend_disc = false;
nextstay_disc = false; nextstay_disc = false;
@@ -130,89 +129,180 @@
</div> </div>
</div> </div>
<form
<form on:submit|preventDefault={handleSubmit} class="flex flex-col gap-4 max-w-xl mx-auto p-6 bg-white rounded-xl shadow-md"> on:submit|preventDefault={handleSubmit}
class="flex flex-col gap-4 max-w-xl mx-auto p-6 bg-white rounded-xl shadow-md"
>
{#if errorMessage} {#if errorMessage}
<div class="text-red-600 font-semibold">{errorMessage}</div> <div class="text-red-600 font-semibold">{errorMessage}</div>
{/if} {/if}
<label for="fb_villaname" class="flex flex-col">Villa Name</label> <label for="fb_villaname" class="flex flex-col">Villa Name</label>
<input id="fb_villaname" type="text" bind:value={villa_name} placeholder="Villa Name" required class="p-2 border rounded-md" /> <input
id="fb_villaname"
type="text"
bind:value={villa_name}
placeholder="Villa Name"
required
class="p-2 border rounded-md"
/>
<label for="fb_gn" class="flex flex-col">Name</label> <label for="fb_gn" class="flex flex-col">Name</label>
<input id="fb_gn" type="text" bind:value={customer_name} placeholder="Customer Name" required class="p-2 border rounded-md" /> <input
id="fb_gn"
type="text"
bind:value={customer_name}
placeholder="Customer Name"
required
class="p-2 border rounded-md"
/>
<label for="fb_cid" class="flex flex-col">Checkin Date</label> <label for="fb_cid" class="flex flex-col">Checkin Date</label>
<input id="fb_cid" type="date" bind:value={checkin_date} required class="p-2 border rounded-md" /> <input
id="fb_cid"
type="date"
bind:value={checkin_date}
required
class="p-2 border rounded-md"
/>
<label for="fb_cod" class="flex flex-col">Checkout Date</label> <label for="fb_cod" class="flex flex-col">Checkout Date</label>
<input id="fb_cod" type="date" bind:value={checkout_date} required class="p-2 border rounded-md" /> <input
id="fb_cod"
type="date"
bind:value={checkout_date}
required
class="p-2 border rounded-md"
/>
<!-- Star Ratings --> <!-- Star Ratings -->
<label class="flex flex-col"> <label class="flex flex-col">
Booking Process: Booking Process:
<small class="text-sm text-gray-500 mb-1">Please highlights of the booking process (anything neglected by the staff or something done well by the staff)</small> <small class="text-sm text-gray-500 mb-1"
<textarea bind:value={book_process} placeholder="Write your feedback here..." required class="p-2 border rounded-md min-h-[120px]"></textarea> >Please highlights of the booking process (anything neglected by the staff
or something done well by the staff)</small
>
<textarea
bind:value={book_process}
placeholder="Write your feedback here..."
required
class="p-2 border rounded-md min-h-[120px]"
></textarea>
</label> </label>
<label class="flex flex-col"> <label class="flex flex-col">
Airport Greeting and Transportation: Airport Greeting and Transportation:
<small class="text-sm text-gray-500 mb-1">Please highlights of the airport transfer experience (anything neglected by the staff or something done well by the staff)</small> <small class="text-sm text-gray-500 mb-1"
<textarea bind:value={airport_greet} placeholder="Write your feedback here..." required class="p-2 border rounded-md min-h-[120px]"></textarea> >Please highlights of the airport transfer experience (anything neglected
by the staff or something done well by the staff)</small
>
<textarea
bind:value={airport_greet}
placeholder="Write your feedback here..."
required
class="p-2 border rounded-md min-h-[120px]"
></textarea>
</label> </label>
<label class="flex flex-col"> <label class="flex flex-col">
Arrival & Check-in: Arrival & Check-in:
<small class="text-sm text-gray-500 mb-1">Please highlights of the arrival and check-in process(anything neglected by the staff or something done well by the staff)</small> <small class="text-sm text-gray-500 mb-1"
<textarea bind:value={arrival_greet} placeholder="Write your feedback here..." required class="p-2 border rounded-md min-h-[120px]"></textarea> >Please highlights of the arrival and check-in process(anything neglected
by the staff or something done well by the staff)</small
>
<textarea
bind:value={arrival_greet}
placeholder="Write your feedback here..."
required
class="p-2 border rounded-md min-h-[120px]"
></textarea>
</label> </label>
<label class="flex flex-col"> <label class="flex flex-col">
Breakfast Service or other food and beverage service utilized: Breakfast Service or other food and beverage service utilized:
<small class="text-sm text-gray-500 mb-1">Please highlighs of the breakfast service(anything neglected by the staff or something done well by the staff)</small> <small class="text-sm text-gray-500 mb-1"
<textarea bind:value={bf_service} placeholder="Write your feedback here..." required class="p-2 border rounded-md min-h-[120px]"></textarea> >Please highlighs of the breakfast service(anything neglected by the staff
or something done well by the staff)</small
>
<textarea
bind:value={bf_service}
placeholder="Write your feedback here..."
required
class="p-2 border rounded-md min-h-[120px]"
></textarea>
</label> </label>
<label class="flex flex-col"> <label class="flex flex-col">
Housekeeping and Maintenance: Housekeeping and Maintenance:
<small class="text-sm text-gray-500 mb-1">Please highlighs of the housekeeping and maintenance service(anything neglected by the staff or something done well by the staff)</small> <small class="text-sm text-gray-500 mb-1"
<textarea bind:value={maintenance_proc} placeholder="Write your feedback here..." required class="p-2 border rounded-md min-h-[120px]"></textarea> >Please highlighs of the housekeeping and maintenance service(anything
neglected by the staff or something done well by the staff)</small
>
<textarea
bind:value={maintenance_proc}
placeholder="Write your feedback here..."
required
class="p-2 border rounded-md min-h-[120px]"
></textarea>
</label> </label>
<label class="flex flex-col"> <label class="flex flex-col">
What did you think of the villa in General: What did you think of the villa in General:
<small class="text-sm text-gray-500 mb-1">Did you have any other issue of area you would like to highligh during stay? Please let us know any other comments you have - the good, the bad, and the ugly</small> <small class="text-sm text-gray-500 mb-1"
<textarea bind:value={feedback} placeholder="Write your feedback here..." required class="p-2 border rounded-md min-h-[120px]"></textarea> >Did you have any other issue of area you would like to highligh during
stay? Please let us know any other comments you have - the good, the bad,
and the ugly</small
>
<textarea
bind:value={feedback}
placeholder="Write your feedback here..."
required
class="p-2 border rounded-md min-h-[120px]"
></textarea>
</label> </label>
<!-- Centered big star--> <!-- Centered big star-->
<div class="text-center mt-6"> <div class="text-center mt-6">
<small class="text-sm text-gray-500 block mb-2"> <small class="text-sm text-gray-500 block mb-2">
Overall Stay Please rate your overall stay. 5 stars = a great stay!<br /> Overall Stay Please rate your overall stay. 5 stars = a great stay!<br
/>
We aim to provide 5-star services to you! We aim to provide 5-star services to you!
</small> </small>
<div class="flex justify-center"> <div class="flex justify-center">
<StarRating bind:value={overal_star} name="Overall Satisfaction" size="xl" /> <StarRating
bind:value={overal_star}
name="Overall Satisfaction"
size="xl"
/>
</div> </div>
</div> </div>
<!-- Checkboxes --> <!-- Checkboxes -->
<label class="flex flex-col"> <label class="flex flex-col">
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
<input type="checkbox" bind:checked={extend_disc} /> <input type="checkbox" bind:checked={extend_disc} />
<small class="text-sm text-gray-500 mb-1">I would like to discuss extending my stay for 30% off!</small> <small class="text-sm text-gray-500 mb-1"
>I would like to discuss extending my stay for 30% off!</small
>
</span> </span>
</label> </label>
<label class="flex flex-col"> <label class="flex flex-col">
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
<input type="checkbox" bind:checked={nextstay_disc} /> <input type="checkbox" bind:checked={nextstay_disc} />
<small class="text-sm text-gray-500 mb-1">I would like to discuss booking my next stayto receive an extra discount!</small> <small class="text-sm text-gray-500 mb-1"
>I would like to discuss booking my next stayto receive an extra
discount!</small
>
</span> </span>
</label> </label>
<label class="flex flex-col"> <label class="flex flex-col">
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
<input type="checkbox" bind:checked={become_sponsor} /> <input type="checkbox" bind:checked={become_sponsor} />
<small class="text-sm text-gray-500 mb-1">I would like to know more about sponsoring a Balinese child with Damara (for only AU$150 a year!)(100% of the AU$150 goes to the childs schooling - there are no overheads with Damara!)</small> <small class="text-sm text-gray-500 mb-1"
>I would like to know more about sponsoring a Balinese child with Damara
(for only AU$150 a year!)(100% of the AU$150 goes to the childs
schooling - there are no overheads with Damara!)</small
>
</span> </span>
</label> </label>
<!-- Submit Button --> <!-- Submit Button -->
<button type="submit" class="bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition"> <button
type="submit"
class="bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition"
>
Submit Feedback Submit Feedback
</button> </button>
</form> </form>

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { supabase } from "$lib/supabaseClient"; import { supabase } from "$lib/supabaseClient";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import logo from "$lib/images/villa.png";
let email = ""; let email = "";
let password = ""; let password = "";
@@ -38,11 +39,7 @@
> >
<!-- Ilustrasi --> <!-- Ilustrasi -->
<div class="hidden md:flex w-1/2 justify-center items-center"> <div class="hidden md:flex w-1/2 justify-center items-center">
<img <img src={logo} alt="Login Illustration" class="w-full max-w-sm" />
src="/src/lib/images/villa.png"
alt="Login Illustration"
class="w-full max-w-sm"
/>
</div> </div>
<!-- Form Login --> <!-- Form Login -->

View File

@@ -0,0 +1,21 @@
<script>
// Tambahkan script jika ingin menambahkan interaktivitas di masa depan
</script>
<div
class="min-h-screen flex items-center justify-center bg-gradient-to-br from-red-100 to-red-300"
>
<div class="bg-white rounded-xl shadow-lg p-8 max-w-md w-full text-center">
<div class="text-6xl mb-4">🚫</div>
<h1 class="text-2xl font-bold text-red-600 mb-2">Akses Ditolak</h1>
<p class="text-gray-700 mb-6">
Anda tidak memiliki izin untuk mengakses halaman ini.
</p>
<a
href="/login"
class="inline-block px-6 py-2 rounded-lg bg-red-500 text-white font-semibold hover:bg-red-600 transition"
>
Kembali ke Login
</a>
</div>
</div>