SUPABASE SET-UP/SETTING
Project URL: https://ipjlqlsaxhvzvelcgqav.supabase.co
Client API key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImlwamxxbHNheGh2enZlbGNncWF2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzY2MDA4NTUsImV4cCI6MjA5MjE3Njg1NX0.ceuuTpDjAHS4vmwgczr6pV0Kf1DQo97p25nPfW-JLTQ
Service key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImlwamxxbHNheGh2enZlbGNncWF2Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc3NjYwMDg1NSwiZXhwIjoyMDkyMTc2ODU1fQ.7Y0YYO_7LcuxYJFKn_vI-_HQiWjCZJt1y9wpeBh29Nk
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://ipjlqlsaxhvzvelcgqav.supabase.co'
const supabaseKey = process.env.SUPABASE_KEY
const supabase = createClient(supabaseUrl, supabaseKey)
ronin-11938-default-rtdb-JobDoneData
create table public."ronin-11938-default-rtdb-JobDoneData" (
created_at timestamp with time zone not null default now(),
description text null,
month smallint null,
"monthName" character varying null,
year integer null,
"sortKey" bigint null,
id character varying null,
_key character varying null
) TABLESPACE pg_default;
<!DOCTYPE html>
<html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JobsDone App - Blogger Widget</title> <!-- STYLES: Tailwind CSS & Google Fonts --> <script src="https://cdn.tailwindcss.com"></script> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Playfair+Display:ital,wght@0,700;1,700&display=swap" rel="stylesheet"> <style type="text/tailwindcss"> @layer base { html { font-family: 'Inter', sans-serif; } .font-serif { font-family: 'Playfair Display', serif; } } @layer components { .input-field { @apply w-full p-4 text-lg border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all bg-slate-50/50; } .btn-primary { @apply h-14 bg-gradient-to-r from-blue-600 to-blue-500 hover:from-blue-700 hover:to-blue-600 text-white text-xl font-bold uppercase tracking-widest rounded-lg shadow-lg active:scale-[0.98] transition-all disabled:opacity-50 disabled:cursor-not-allowed; } } </style>
<!-- DEPENDENCIES: React, Supabase, Lucide Icons --> <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@supabase/supabase-js@2"></script> <script src="https://unpkg.com/lucide@latest"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script></head><body class="bg-white"> <!-- Application Root --> <div id="jobs-done-root"></div>
<script type="text/babel"> /** * JOBSDONE APP for Blogger * Optimized for Single-File HTML/JS Gadget usage. */ const { useState, useEffect } = React; // --- REAL-TIME DATABASE CONFIGURATION --- const SUPABASE_URL = 'https://ipjlqlsaxhvzvelcgqav.supabase.co'; const SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImlwamxxbHNheGh2enZlbGNncWF2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzY2MDA4NTUsImV4cCI6MjA5MjE3Njg1NX0.ceuuTpDjAHS4vmwgczr6pV0Kf1DQo97p25nPfW-JLTQ'; const TABLE_NAME = 'ronin-11938-default-rtdb-JobDoneData'; const supabaseClient = supabase.createClient(SUPABASE_URL, SUPABASE_KEY);
const MONTH_NAMES = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ];
function App() { const [jobs, setJobs] = useState([]); const [loading, setLoading] = useState(true); const [connStatus, setConnStatus] = useState('checking'); const [newMonth, setNewMonth] = useState(MONTH_NAMES[new Date().getMonth()]); const [newYear, setNewYear] = useState(new Date().getFullYear() + ""); const [newDesc, setNewDesc] = useState(''); const [isSaving, setIsSaving] = useState(false);
const fetchJobs = async () => { setLoading(true); try { const { data, error } = await supabaseClient .from(TABLE_NAME) .select('*') .order('sortKey', { ascending: false }); if (error) throw error; setJobs(data || []); setConnStatus('ok'); } catch (err) { console.error('Connection error:', err); setConnStatus('failed'); } finally { setLoading(false); } };
useEffect(() => { fetchJobs(); // Initialize icons setTimeout(() => lucide.createIcons(), 200); }, []);
useEffect(() => { lucide.createIcons(); }, [jobs, connStatus, loading]);
const handleAdd = async () => { if (!newDesc.trim()) { alert("Please enter a description."); return; } setIsSaving(true); const mIndex = MONTH_NAMES.indexOf(newMonth) + 1; const y = parseInt(newYear); const job = { description: newDesc, month: mIndex, monthName: MONTH_NAMES[mIndex - 1].toUpperCase(), year: y, sortKey: y * 100 + mIndex };
try { const { error } = await supabaseClient.from(TABLE_NAME).insert([job]); if (error) throw error; setNewDesc(''); fetchJobs(); } catch (err) { alert("Sync failed. Check connection."); } finally { setIsSaving(false); } };
const handleDelete = async (id) => { if (!confirm('Are you sure you want to delete this record?')) return; try { const { error } = await supabaseClient.from(TABLE_NAME).delete().eq('id', id); if (error) throw error; fetchJobs(); } catch (err) { console.error(err); } };
const todayStr = new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
return ( <div className="min-h-screen bg-white text-[#333] p-4 md:p-8 max-w-4xl mx-auto border-x border-gray-100"> <header className="mb-8 flex flex-col md:flex-row md:items-end justify-between gap-4"> <div> <h1 className="text-3xl font-bold tracking-tight text-black">JOBSDONE APP</h1> <p className="text-sm italic text-gray-400 mt-1">on {todayStr}</p> </div> <div className="flex items-center gap-2"> {connStatus === 'ok' ? ( <span className="px-3 py-1 bg-green-50 text-green-700 text-[10px] font-bold rounded-full border border-green-100 uppercase tracking-widest flex items-center gap-2"> <span className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span> Database Connected </span> ) : ( <span className="px-3 py-1 bg-red-50 text-red-700 text-[10px] font-bold rounded-full border border-red-100 uppercase tracking-widest flex items-center gap-2"> <span className="w-2 h-2 bg-red-500 rounded-full"></span> Offline Mode </span> )} </div> </header>
<div className="flex justify-center mb-10 text-[#84cc16]"> <i data-lucide="share-2" className="w-8 h-8"></i> </div>
<div className="space-y-8"> {/* INPUT SECTION */} <section className="bg-white border border-gray-200 rounded-2xl shadow-sm p-6 md:p-8 space-y-6"> <div className="flex items-center gap-3 text-xl font-bold border-b border-gray-100 pb-4 text-slate-800"> <i data-lucide="plus-circle" className="text-blue-500 w-6 h-6"></i> <span>Add Job Done</span> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="space-y-1"> <label className="text-[10px] font-bold text-gray-400 uppercase tracking-widest ml-1">Month</label> <select value={newMonth} onChange={(e) => setNewMonth(e.target.value)} className="w-full h-12 px-4 bg-slate-50 border border-gray-200 rounded-lg text-lg"> {MONTH_NAMES.map(m => <option key={m} value={m}>{m}</option>)} </select> </div> <div className="space-y-1"> <label className="text-[10px] font-bold text-gray-400 uppercase tracking-widest ml-1">Year</label> <select value={newYear} onChange={(e) => setNewYear(e.target.value)} className="w-full h-12 px-4 bg-slate-50 border border-gray-200 rounded-lg text-lg"> {Array.from({length: 10}, (_, i) => 2023 + i).map(y => <option key={y} value={y}>{y}</option>)} </select> </div> </div>
<textarea placeholder="Enter job description..." value={newDesc} onChange={(e) => setNewDesc(e.target.value)} className="input-field min-h-[140px] resize-none"></textarea>
<button onClick={handleAdd} disabled={isSaving || connStatus !== 'ok'} className="btn-primary w-full"> {isSaving ? "SYNCING..." : "SAVE TO DATABASE"} </button> </section>
{/* DATA TABLE */} <section className="bg-white border border-gray-200 rounded-2xl shadow-sm overflow-hidden"> <div className="overflow-x-auto"> <table className="w-full text-left"> <thead className="bg-slate-50/80 border-b border-gray-100 italic"> <tr> <th className="px-6 py-4 font-bold text-gray-900">DATE</th> <th className="px-6 py-4 font-bold text-gray-900 text-center">DESCRIPTION</th> <th className="px-6 py-4 font-bold text-gray-900 text-right">ACTION</th> </tr> </thead> <tbody className="divide-y divide-gray-50"> {loading && jobs.length === 0 ? ( <tr><td colSpan="3" className="py-20 text-center text-gray-400 animate-pulse font-sans italic">Connecting to Household++ Cloud...</td></tr> ) : jobs.length === 0 ? ( <tr><td colSpan="3" className="py-20 text-center text-gray-400 font-sans italic">No records found.</td></tr> ) : ( jobs.map(job => ( <tr key={job.id} className="hover:bg-slate-50/50 transition-colors"> <td className="px-6 py-6 w-40"> <div className="font-bold text-gray-900 leading-tight">{MONTH_NAMES[job.month-1]}</div> <div className="text-[10px] font-mono text-gray-400 uppercase">Year: {job.year}</div> </td> <td className="px-6 py-6 text-gray-700 leading-relaxed italic border-x border-gray-50/50"> {job.description} </td> <td className="px-6 py-6 text-right"> <button onClick={() => handleDelete(job.id)} className="p-3 text-gray-300 hover:text-red-500 hover:bg-red-50 rounded-full transition-all"> <i data-lucide="trash-2" className="w-6 h-6"></i> </button> </td> </tr> )) )} </tbody> </table> </div> </section> </div>
<footer className="mt-20 py-8 border-t border-gray-100 text-center text-[10px] font-bold text-gray-300 uppercase tracking-[0.4em]"> Household++ Productivity Systems </footer> </div> ); }
const root = ReactDOM.createRoot(document.getElementById('jobs-done-root')); root.render(<App />); </script></body></html>
Comments
Post a Comment