1+ <!DOCTYPE html>
2+ < html lang ="en ">
3+ < head >
4+ < meta charset ="UTF-8 ">
5+ < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
6+ < title > Art Proofing Portal</ title >
7+ <!-- Load Tailwind CSS for styling -->
8+ < script src ="https://cdn.tailwindcss.com "> </ script >
9+ </ head >
10+ < body class ="bg-gray-100 text-gray-900 font-sans min-h-screen p-4 md:p-8 ">
11+
12+ < div id ="app " class ="max-w-4xl mx-auto ">
13+ <!-- The JavaScript below will inject either the Admin Dashboard or Customer View here -->
14+ </ div >
15+
16+ <!-- Load Firebase Modular SDKs as an ES module -->
17+ < script type ="module ">
18+ import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js" ;
19+ import { getFirestore , collection , addDoc , onSnapshot , doc , updateDoc , getDoc } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js" ;
20+
21+ // 1. REPLACE WITH YOUR FIREBASE CONFIG
22+ const firebaseConfig = {
23+ apiKey : "YOUR_API_KEY" ,
24+ authDomain : "YOUR_AUTH_DOMAIN" ,
25+ projectId : "YOUR_PROJECT_ID" ,
26+ storageBucket : "YOUR_STORAGE_BUCKET" ,
27+ messagingSenderId : "YOUR_MESSAGING_SENDER_ID" ,
28+ appId : "YOUR_APP_ID"
29+ } ;
30+
31+ const app = initializeApp ( firebaseConfig ) ;
32+ const db = getFirestore ( app ) ;
33+
34+ // 2. Routing logic: Check if the URL has a '?id=...' parameter
35+ const urlParams = new URLSearchParams ( window . location . search ) ;
36+ const proofId = urlParams . get ( 'id' ) ;
37+ const appDiv = document . getElementById ( 'app' ) ;
38+
39+ if ( proofId ) {
40+ renderCustomerView ( proofId ) ;
41+ } else {
42+ renderAdminView ( ) ;
43+ }
44+
45+ // --- CUSTOMER VIEW ---
46+ async function renderCustomerView ( id ) {
47+ appDiv . innerHTML = `<p class="text-xl font-bold animate-pulse">Loading your proof...</p>` ;
48+ try {
49+ const docRef = doc ( db , "proofs" , id ) ;
50+ const docSnap = await getDoc ( docRef ) ;
51+
52+ if ( docSnap . exists ( ) ) {
53+ const data = docSnap . data ( ) ;
54+
55+ let statusColor = "text-yellow-600" ;
56+ if ( data . status === "Approved" ) statusColor = "text-green-600" ;
57+ if ( data . status === "Denied" ) statusColor = "text-red-600" ;
58+
59+ appDiv . innerHTML = `
60+ <div class="bg-white p-6 md:p-10 rounded-xl shadow-lg border border-gray-200">
61+ <h1 class="text-3xl font-black uppercase tracking-tight mb-2">Review Your Design</h1>
62+ <p class="mb-6 text-gray-500">Client: <span class="font-bold text-gray-900">${ data . customerName } </span></p>
63+
64+ <img src="${ data . imageUrl } " alt="Art Proof" class="w-full max-w-2xl border-4 border-gray-100 rounded shadow-sm mb-6">
65+
66+ <div class="bg-gray-50 p-4 rounded border mb-6">
67+ <p class="font-bold text-sm uppercase text-gray-500">Current Status</p>
68+ <p class="text-2xl font-black uppercase ${ statusColor } ">${ data . status } </p>
69+ </div>
70+
71+ ${ data . status === 'Pending' ? `
72+ <div class="flex flex-wrap gap-4">
73+ <button id="approveBtn" class="bg-green-600 text-white px-8 py-4 rounded font-bold uppercase tracking-wide hover:bg-green-700 transition-colors">Approve Design</button>
74+ <button id="denyBtn" class="bg-red-600 text-white px-8 py-4 rounded font-bold uppercase tracking-wide hover:bg-red-700 transition-colors">Request Changes</button>
75+ </div>
76+ ` : `
77+ <p class="text-gray-600 italic">This proof has been closed.</p>
78+ ` }
79+ </div>
80+ ` ;
81+
82+ if ( data . status === 'Pending' ) {
83+ document . getElementById ( 'approveBtn' ) . addEventListener ( 'click' , ( ) => updateStatus ( id , 'Approved' ) ) ;
84+ document . getElementById ( 'denyBtn' ) . addEventListener ( 'click' , ( ) => updateStatus ( id , 'Denied' ) ) ;
85+ }
86+ } else {
87+ appDiv . innerHTML = `<h1 class="text-2xl font-bold text-red-600">Proof not found. It may have been deleted.</h1>` ;
88+ }
89+ } catch ( e ) {
90+ console . error ( e ) ;
91+ appDiv . innerHTML = `<p class="text-red-600">Error loading database.</p>` ;
92+ }
93+ }
94+
95+ async function updateStatus ( id , newStatus ) {
96+ await updateDoc ( doc ( db , "proofs" , id ) , { status : newStatus } ) ;
97+ renderCustomerView ( id ) ; // Reload the UI to hide buttons
98+ }
99+
100+ // --- ADMIN VIEW ---
101+ function renderAdminView ( ) {
102+ appDiv . innerHTML = `
103+ <h1 class="text-4xl font-black uppercase tracking-tighter mb-8 border-b-4 border-black pb-4 inline-block">Proofing Dashboard</h1>
104+
105+ <div class="bg-white p-6 rounded-xl shadow-md border border-gray-200 mb-8">
106+ <h2 class="text-xl font-bold uppercase tracking-wide mb-4 text-gray-700">Generate New Link</h2>
107+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
108+ <input id="custName" type="text" placeholder="Customer Name" class="border-2 border-gray-200 p-3 rounded w-full outline-none focus:border-black transition-colors">
109+ <input id="imgUrl" type="url" placeholder="Direct Image URL (e.g., https://...)" class="border-2 border-gray-200 p-3 rounded w-full outline-none focus:border-black transition-colors">
110+ </div>
111+ <button id="createBtn" class="bg-black text-white px-6 py-3 rounded font-bold uppercase tracking-wide hover:bg-gray-800 transition-colors w-full md:w-auto">Create Proof</button>
112+ </div>
113+
114+ <h2 class="text-2xl font-bold uppercase tracking-tight mb-4">Recent Proofs</h2>
115+ <div id="proofList" class="flex flex-col gap-4"></div>
116+ ` ;
117+
118+ // Create new proof
119+ document . getElementById ( 'createBtn' ) . addEventListener ( 'click' , async ( ) => {
120+ const name = document . getElementById ( 'custName' ) . value ;
121+ const url = document . getElementById ( 'imgUrl' ) . value ;
122+ if ( name && url ) {
123+ await addDoc ( collection ( db , "proofs" ) , {
124+ customerName : name ,
125+ imageUrl : url ,
126+ status : 'Pending' ,
127+ createdAt : new Date ( )
128+ } ) ;
129+ document . getElementById ( 'custName' ) . value = '' ;
130+ document . getElementById ( 'imgUrl' ) . value = '' ;
131+ } else {
132+ alert ( "Please enter both a name and an image URL." ) ;
133+ }
134+ } ) ;
135+
136+ // Listen for database changes and render list
137+ onSnapshot ( collection ( db , "proofs" ) , ( snapshot ) => {
138+ const listDiv = document . getElementById ( 'proofList' ) ;
139+ listDiv . innerHTML = '' ;
140+
141+ snapshot . forEach ( docSnap => {
142+ const data = docSnap . data ( ) ;
143+ // Generate the unique link for the customer
144+ const clientLink = window . location . href . split ( '?' ) [ 0 ] + '?id=' + docSnap . id ;
145+
146+ let borderClass = "border-yellow-400" ;
147+ if ( data . status === 'Approved' ) borderClass = "border-green-500" ;
148+ if ( data . status === 'Denied' ) borderClass = "border-red-500" ;
149+
150+ listDiv . innerHTML += `
151+ <div class="bg-white p-5 rounded-lg shadow-sm border-l-8 ${ borderClass } flex justify-between items-center">
152+ <div>
153+ <p class="font-black text-xl uppercase tracking-tight">${ data . customerName } </p>
154+ <p class="text-sm font-bold text-gray-500 uppercase mb-2">Status: ${ data . status } </p>
155+ <div class="flex gap-2">
156+ <a href="${ clientLink } " target="_blank" class="text-blue-600 font-bold hover:underline text-sm uppercase">Open Portal</a>
157+ <span class="text-gray-300">|</span>
158+ <button onclick="navigator.clipboard.writeText('${ clientLink } '); alert('Link Copied!')" class="text-gray-600 hover:text-black font-bold text-sm uppercase cursor-pointer">Copy Link</button>
159+ </div>
160+ </div>
161+ <img src="${ data . imageUrl } " class="h-20 w-20 object-cover rounded shadow-sm border border-gray-200">
162+ </div>
163+ ` ;
164+ } ) ;
165+ } ) ;
166+ }
167+ </ script >
168+ </ body >
169+ </ html >
0 commit comments