🐾 Cyberjaya & Putrajaya

Leaving Home?
Let Me Care for
Your Cats! 🐱

Loving, reliable cat sitting while you're away. Your fur babies are treated like family β€” with patience, affection, and daily updates!

🏠
Home VisitWe come to your place
πŸ“Έ
Daily Photo UpdatesKnow your cat is happy
❀️
Trusted & CaringTreated like family
⭐
Max 7 CatsPersonal attention always
🏠Home Visit Service

We come to your home so your cats stay comfortable

πŸ›‘οΈSafe & Reliable

Fully trusted, responsible care with your cat's wellbeing first

πŸ“·Photo/Video Updates

Daily updates sent directly to you so you never worry

πŸ’Treated Like Family

Your fur babies get love, play time & extra attention

🐾 Services Include

Everything your cat needs to stay happy & healthy while you're away

🧹

Litter Box Cleaning

Scoop & clean every visit to keep it fresh and hygienic

🍽️

Food & Water Refill

Fresh food and clean water at all times

✨

Clean Feeding Area

Wipe down and tidy the feeding spot after every visit

🏠

Light Cage Cleaning

Basic cage tidying to keep your cat comfortable

🎾

Playtime & Affection

Short play sessions and cuddles so cats stay stimulated

🩺

Wellbeing Check

Basic health observation β€” eating, behaviour & condition noted

Affordable rates β€” always transparent, no hidden fees

🐱
1–2 Cats
RM25
Per visit
🐱🐱🐱
6–7 Cats (max)
RM35
Per visit

Serving Cyberjaya & Putrajaya areas

πŸ“

Cyberjaya

FREE

No transport charge

πŸ“

Putrajaya

RM2/km

Distance-based fee

🏑Balik Kampung

Going hometown? Your cats are safe with us

✈️Short Trips / Vacations

Weekend getaway or longer trips β€” we've got you

πŸ’ΌBusy Working Schedule

Long hours at work? Book a visit for your cat

Latest updates β€” see how happy the fur babies are! πŸ’•

🐾 Loading updates...

Fill in your details and Aisyah will confirm your booking!

πŸ“… Available Days: Weekends Only (Sat & Sun)

πŸ’¬ Get In Touch

DM or call to confirm your slot β€” Aisyah usually replies within the hour!

πŸ“ž Call: 0132388706 βœ‰οΈ SMS Us
βœ… Done!
// ── ADMIN AUTH ── // Change this password to whatever you want! const ADMIN_PASSWORD = 'aisyah2024'; let isAdminLoggedIn = sessionStorage.getItem('adminLoggedIn') === 'true'; let pendingAdminAction = null; function requireAdmin(action) { if (isAdminLoggedIn) { // Already logged in β€” run action directly if (action === 'updates') toggleAdmin(); if (action === 'bookings') toggleBookingsAdmin(); return; } pendingAdminAction = action; document.getElementById('admin-password').value = ''; document.getElementById('admin-error').style.display = 'none'; document.getElementById('admin-login-overlay').classList.add('open'); setTimeout(() => document.getElementById('admin-password').focus(), 300); } function checkAdminLogin() { const pwd = document.getElementById('admin-password').value; if (pwd === ADMIN_PASSWORD) { isAdminLoggedIn = true; sessionStorage.setItem('adminLoggedIn', 'true'); document.getElementById('admin-login-overlay').classList.remove('open'); document.getElementById('admin-nav-badge').classList.add('show'); document.body.classList.add('admin-mode'); document.getElementById('avail-admin-controls').style.display = 'flex'; document.getElementById('admin-dash-link').style.display = 'inline-flex'; showToast('βœ… Admin logged in!'); loadUpdates(); loadAdminPhotos(); if (pendingAdminAction === 'updates') toggleAdmin(); if (pendingAdminAction === 'bookings') toggleBookingsAdmin(); pendingAdminAction = null; } else { document.getElementById('admin-error').style.display = 'block'; document.getElementById('admin-password').value = ''; document.getElementById('admin-password').focus(); } } function closeAdminLogin() { document.getElementById('admin-login-overlay').classList.remove('open'); pendingAdminAction = null; } function adminLogout() { isAdminLoggedIn = false; sessionStorage.removeItem('adminLoggedIn'); document.getElementById('admin-nav-badge').classList.remove('show'); document.body.classList.remove('admin-mode'); document.getElementById('avail-admin-controls').style.display = 'none'; document.getElementById('admin-panel').classList.remove('open'); document.getElementById('bookings-table-wrap').style.display = 'none'; showAdmin = false; showBookingsAdmin = false; showToast('πŸ‘‹ Admin logged out'); } // Restore admin session on page load if (isAdminLoggedIn) { document.getElementById('admin-nav-badge').classList.add('show'); document.body.classList.add('admin-mode'); document.getElementById('avail-admin-controls').style.display = 'flex'; } // ── TOAST ── function showToast(msg, isError = false) { const t = document.getElementById('toast'); t.textContent = msg; t.className = 'toast' + (isError ? ' error' : ''); t.classList.add('show'); setTimeout(() => t.classList.remove('show'), 3500); } // ── MODAL ── function closeModal() { document.getElementById('modal').classList.remove('open'); } // ── AVAILABILITY SYSTEM ── // 'weekend' = Sat+Sun only | 'weekday' = Mon-Fri only | 'all' = all days let availability = localStorage.getItem('bookingAvailability') || 'weekend'; function setAvailability(mode) { availability = mode; localStorage.setItem('bookingAvailability', mode); applyAvailabilityUI(); showToast('βœ… Availability updated!'); } function applyAvailabilityUI() { const labels = { weekend: 'Weekends Only (Sat & Sun)', weekday: 'Weekdays Only (Mon–Fri)', all: 'All Days Open' }; document.getElementById('avail-label').textContent = labels[availability]; ['weekend','weekday','all'].forEach(m => { const btn = document.getElementById('btn-'+m); if (btn) btn.classList.toggle('avail-active', m === availability); }); } function isDateAllowed(dateStr) { if (!dateStr) return true; const day = new Date(dateStr + 'T12:00:00').getDay(); // 0=Sun,6=Sat if (availability === 'weekend') return day === 0 || day === 6; if (availability === 'weekday') return day >= 1 && day <= 5; return true; // all } function validateDate(fieldId) { const val = document.getElementById(fieldId).value; const errId = fieldId + '-err'; const errEl = document.getElementById(errId); if (val && !isDateAllowed(val)) { errEl.style.display = 'block'; document.getElementById(fieldId).value = ''; } else { errEl.style.display = 'none'; } if (fieldId === 'b-start') { document.getElementById('b-end').min = val; } } // ── DATE SETUP ── const today = new Date().toISOString().split('T')[0]; document.getElementById('b-start').min = today; document.getElementById('b-end').min = today; applyAvailabilityUI(); // ── PREFILL BOOKING ── function prefillBooking(cats) { document.getElementById('b-cats').value = cats; document.getElementById('booking').scrollIntoView({ behavior: 'smooth' }); } // ── SUBMIT BOOKING β€” Fixed WhatsApp + Admin Notification ── async function submitBooking() { const name = document.getElementById('b-name').value.trim(); const phone = document.getElementById('b-phone').value.trim(); const cats = document.getElementById('b-cats').value; const area = document.getElementById('b-area').value; const start = document.getElementById('b-start').value; const end = document.getElementById('b-end').value; const time = document.getElementById('b-time').value; const visits= document.getElementById('b-visits').value; const notes = document.getElementById('b-notes').value.trim(); if (!name || !phone || !cats || !area || !start || !time) { showToast('⚠️ Please fill in all required fields!', true); return; } if (!isDateAllowed(start)) { showToast('⚠️ Please select an available day for start date!', true); return; } const btn = document.getElementById('book-btn'); btn.disabled = true; btn.textContent = '⏳ Saving...'; try { const { error } = await db.from('bookings').insert([{ name, phone, cats, area, start_date: start, end_date: end, visit_time: time, visits_per_day: visits, notes, status: 'pending' }]); if (error) throw error; // ── Build WhatsApp message ── const catsNum = parseInt(cats); const price = catsNum <= 2 ? 25 : catsNum <= 5 ? 30 : 35; const waMsg = [ '🐾 *New Booking Request!*', '', `πŸ‘€ *Name:* ${name}`, `πŸ“± *Phone:* ${phone}`, `🐱 *Cats:* ${cats} cat(s)`, `πŸ“ *Area:* ${area}`, `πŸ“… *Start:* ${start}`, end ? `πŸ“… *End:* ${end}` : '', `⏰ *Time:* ${time}`, `πŸ” *Visits/day:* ${visits}`, `πŸ’° *Est. Price:* RM${price}/visit`, `πŸ“ *Notes:* ${notes || 'None'}`, '', 'Please confirm this booking! 😊' ].filter(Boolean).join('\n'); // ── Fixed WhatsApp URL β€” open directly ── const waNumber = '60132388706'; const waUrl = `https://wa.me/${waNumber}?text=${encodeURIComponent(waMsg)}`; // Clear form ['b-name','b-phone','b-notes'].forEach(id => document.getElementById(id).value = ''); ['b-cats','b-area','b-time'].forEach(id => document.getElementById(id).selectedIndex = 0); document.getElementById('b-start').value = ''; document.getElementById('b-end').value = ''; document.getElementById('b-visits').selectedIndex = 0; // Show success modal document.getElementById('modal-msg').innerHTML = `Thank you, ${name}! Your booking is saved. ` + `Aisyah will contact you at ${phone} to confirm. πŸ’•

` + `` + `πŸ’¬ Tap here if WhatsApp didn't open automatically`; document.getElementById('modal').classList.add('open'); // Open WhatsApp β€” works on mobile & desktop window.open(waUrl, '_blank'); if (document.getElementById('bookings-table-wrap').style.display !== 'none') loadBookings(); } catch (err) { console.error(err); showToast('❌ Error saving booking. Please try again.', true); } finally { btn.disabled = false; btn.innerHTML = '🐾 Send Booking Request'; } } // ── LOAD BOOKINGS (ADMIN) ── let showBookingsAdmin = false; async function toggleBookingsAdmin() { showBookingsAdmin = !showBookingsAdmin; document.getElementById('bookings-table-wrap').style.display = showBookingsAdmin ? 'block' : 'none'; if (showBookingsAdmin) loadBookings(); } async function loadBookings() { const tbody = document.getElementById('bookings-tbody'); tbody.innerHTML = '
🐾 Loading bookings...
'; try { const { data, error } = await db.from('bookings').select('*').order('created_at', { ascending: false }); if (error) throw error; if (!data.length) { tbody.innerHTML = '
πŸ“‹No bookings yet
'; return; } tbody.innerHTML = data.map(b => ` ${b.name}
${new Date(b.created_at).toLocaleDateString('en-MY')} ${b.phone} ${b.cats} cats ${b.area} ${b.start_date}${b.end_date ? '
β†’ '+b.end_date : ''} ${b.visit_time} `).join(''); } catch (err) { console.error(err); tbody.innerHTML = '
❌Error loading bookings
'; } } async function updateBookingStatus(id, status) { await db.from('bookings').update({ status }).eq('id', id); showToast('βœ… Status updated!'); } async function deleteBooking(id) { if (!confirm('Delete this booking?')) return; await db.from('bookings').delete().eq('id', id); showToast('πŸ—‘οΈ Booking deleted'); loadBookings(); } // ── CAT UPDATES ── let showAdmin = false; let selectedPhotoFile = null; function toggleAdmin() { showAdmin = !showAdmin; document.getElementById('admin-panel').classList.toggle('open', showAdmin); if (showAdmin) loadAdminPhotos(); } function previewPhoto(input) { if (input.files && input.files[0]) { selectedPhotoFile = input.files[0]; const reader = new FileReader(); reader.onload = e => { const preview = document.getElementById('u-preview'); preview.src = e.target.result; preview.style.display = 'block'; }; reader.readAsDataURL(input.files[0]); } } async function postUpdate() { const catname = document.getElementById('u-catname').value.trim(); const msg = document.getElementById('u-msg').value.trim(); const status = document.getElementById('u-status').value; if (!catname || !msg) { showToast('⚠️ Please fill in cat name and message!', true); return; } const btn = document.getElementById('post-btn'); btn.disabled = true; btn.textContent = '⏳ Posting...'; try { let photoUrl = null; if (selectedPhotoFile) { const ext = selectedPhotoFile.name.split('.').pop(); const fileName = `cat-${Date.now()}.${ext}`; const { error: uploadError } = await db.storage .from('cat-photos').upload(fileName, selectedPhotoFile, { cacheControl: '3600', upsert: false }); if (!uploadError) { const { data: urlData } = db.storage.from('cat-photos').getPublicUrl(fileName); photoUrl = urlData.publicUrl; } } const { error } = await db.from('cat_updates').insert([{ cat_name: catname, message: msg, status, photo_url: photoUrl }]); if (error) throw error; document.getElementById('u-catname').value = ''; document.getElementById('u-msg').value = ''; document.getElementById('u-photo').value = ''; document.getElementById('u-preview').style.display = 'none'; selectedPhotoFile = null; showToast('βœ… Cat update posted!'); loadUpdates(); loadAdminPhotos(); } catch (err) { console.error(err); showToast('❌ Error posting update.', true); } finally { btn.disabled = false; btn.textContent = 'πŸ“€ Post Update'; } } // ── LOAD UPDATES (public view) ── async function loadUpdates() { const container = document.getElementById('update-cards'); container.innerHTML = '
🐾 Loading updates...
'; try { const { data, error } = await db.from('cat_updates').select('*').order('created_at', { ascending: false }).limit(12); if (error) throw error; const statusMap = { happy: { label: '😸 Happy & Active', cls: 'status-happy' }, eating: { label: '🍽️ Eating Well', cls: 'status-eating' }, sleeping:{ label: '😴 Resting Well', cls: 'status-sleeping' }, playing: { label: '🎾 Playing', cls: 'status-playing' } }; if (!data.length) { container.innerHTML = '
πŸ“ΈNo updates yet β€” check back soon!
'; return; } container.innerHTML = data.map(u => { const s = statusMap[u.status] || statusMap.happy; const timeStr = new Date(u.created_at).toLocaleString('en-MY', { day:'numeric', month:'short', hour:'2-digit', minute:'2-digit', hour12:true }); const imgSection = u.photo_url ? `
Cat photo
` : `
🐱
`; return `
${imgSection}
${u.cat_name}
${timeStr}
${u.message}
${s.label}
`; }).join(''); } catch (err) { console.error(err); container.innerHTML = '
❌Error loading updates
'; } } // ── DELETE ENTIRE UPDATE + PHOTO ── async function deleteUpdate(id, photoUrl) { if (!confirm('Delete this cat update?')) return; try { if (photoUrl) { const fileName = extractFileName(photoUrl); const { error: storageErr } = await db.storage.from('cat-photos').remove([fileName]); if (storageErr) console.warn('Storage delete error:', storageErr.message); } const { error: dbErr } = await db.from('cat_updates').delete().eq('id', id); if (dbErr) throw dbErr; showToast('πŸ—‘οΈ Update deleted!'); loadUpdates(); loadAdminPhotos(); } catch (err) { console.error('deleteUpdate error:', err); showToast('❌ Error: ' + err.message, true); } } // ── DELETE ONLY PHOTO FROM UPDATE (keep update text) ── async function deleteUpdatePhoto(updateId, photoUrl) { if (!confirm('Remove photo from this update?')) return; try { const fileName = extractFileName(photoUrl); const { error: storageErr } = await db.storage.from('cat-photos').remove([fileName]); if (storageErr) console.warn('Storage delete error:', storageErr.message); const { error: dbErr } = await db.from('cat_updates').update({ photo_url: null }).eq('id', updateId); if (dbErr) throw dbErr; showToast('πŸ—‘οΈ Photo removed!'); loadUpdates(); loadAdminPhotos(); } catch (err) { console.error('deleteUpdatePhoto error:', err); showToast('❌ Error: ' + err.message, true); } } // ── LOAD ALL PHOTOS IN STORAGE (admin panel) ── async function loadAdminPhotos() { if (!isAdminLoggedIn) return; const grid = document.getElementById('photo-grid-admin'); grid.innerHTML = '
🐾 Loading photos...
'; try { const { data, error } = await db.storage.from('cat-photos').list('', { limit: 100, offset: 0, sortBy: { column: 'created_at', order: 'desc' } }); if (error) throw error; const files = (data || []).filter(f => f.name && f.name !== '.emptyFolderPlaceholder'); if (!files.length) { grid.innerHTML = '
πŸ“ΈNo photos yet
'; return; } grid.innerHTML = files.map(file => { const { data: urlData } = db.storage.from('cat-photos').getPublicUrl(file.name); return `
${file.name}
`; }).join(''); } catch (err) { console.error('loadAdminPhotos error:', err); grid.innerHTML = '
❌Error loading photos
'; } } // ── DELETE PHOTO FROM STORAGE ONLY ── async function deleteStoragePhoto(fileName) { if (!confirm('Delete this photo from storage?')) return; try { const { error } = await db.storage.from('cat-photos').remove([fileName]); if (error) throw error; showToast('πŸ—‘οΈ Photo deleted!'); loadAdminPhotos(); loadUpdates(); } catch (err) { console.error('deleteStoragePhoto error:', err); showToast('❌ Error: ' + err.message, true); } } // ── BULK UPLOAD PHOTOS ── async function bulkUploadPhotos(input) { if (!input.files.length) return; const files = Array.from(input.files); let uploaded = 0; showToast(`⏳ Uploading ${files.length} photo(s)...`); for (const file of files) { const ext = file.name.split('.').pop(); const fileName = `cat-${Date.now()}-${Math.random().toString(36).substr(2,5)}.${ext}`; const { error } = await db.storage.from('cat-photos').upload(fileName, file, { cacheControl: '3600', upsert: false }); if (!error) uploaded++; else console.warn('Upload error:', error.message); } input.value = ''; showToast(`βœ… ${uploaded} of ${files.length} photo(s) uploaded!`); loadAdminPhotos(); } // ── HELPER: extract clean filename from Supabase URL ── function extractFileName(url) { try { // URL format: .../storage/v1/object/public/cat-photos/FILENAME const parts = url.split('/cat-photos/'); if (parts.length > 1) { // Remove any query params return parts[1].split('?')[0]; } // fallback return url.split('/').pop().split('?')[0]; } catch(e) { return url.split('/').pop().split('?')[0]; } } // ── INIT ── loadUpdates();