JOBsDONE APP SOURCE-CODE
DATABASE MIGRATION from FIREBASE to SUPABASE

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