1/*! ìë¨í´ ê·¸ëë L.O.V.E â © 2026 taehyun (salutth · 미ì¹ê´ì´ 구ë¦) · ë른ë¹ì¤í 리 · ì¬ííì êµì¡ì. All Rights Reserved. PROPRIETARY. */ 2/* 3 * âââ ë¡ê·¸ì¸ ì¤í (P5 MVP · Supabase) ââââââââââââââââââââââââââââââââââââââ 4 * ì ì§ ë¼ë²¨: ì´ íì¼ì "ëìíë ê»ë°ê¸°(stub)"ë¤. auth-config.js ì í¤ê° ë¹ì´ ìì¼ë©´ 5 * · ì¸ë¶(supabase-js) ë¡ë ì í¨ Â· ë¡ê·¸ì¸ ë²í¼ì "ì¤ì ë기"ë¡ ë¹íì± 6 * · ì´ë¤ ìë¬ë ëì§ì§ ìì 7 * í¤ê° ì±ìì§ë©´ ê·¸ ìê°: supabase-js ì§ì° ë¡ë â OAuth ë¡ê·¸ì¸ â ì¸ì ·ìí ë°ì. 8 * 9 * §10 íì ë°ì: ê´ê´ê°=구ê¸/ì í, ë´ë¹ì·ê´ë¦¬ì=ì¹´ì¹´ì¤(í¼í©). 1P=1ì·ì ì°ì ë°±ìë(08). 10 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 11 */ 12(function () { 13 "use strict"; 14 15 var SUPABASE_JS = "https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"; 16 var PROVIDER_LABEL = { google: "Google", apple: "Apple", kakao: "Kakao" }; 17 18 var cfg = window.SG_SUPABASE || { url: "", anonKey: "" }; 19 var providers = window.SG_AUTH_PROVIDERS || { tourist: ["google", "apple"], gov: ["kakao"] }; 20 var configured = !!(cfg.url && cfg.anonKey); 21 22 var client = null; // supabase client (ì¤ì ììë§) 23 var session = null; // íì¬ ì¸ì 24 var profile = null; // { role, nickname, ... } 25 26 // ë²ì í¬í¼ (localinfo.js ì ì ì t ì¬ì©, ìì¼ë©´ í´ë°±) 27 function T(key, fb) { return (typeof window.t === "function") ? window.t(key, fb) : fb; } 28 29 // âââ supabase-js ì§ì° ë¡ë (ì¤ì ë ê²½ì°ìë§) âââ 30 function loadSupabaseJs() { 31 return new Promise(function (resolve, reject) { 32 if (window.supabase && window.supabase.createClient) return resolve(window.supabase); 33 var s = document.createElement("script"); 34 s.src = SUPABASE_JS; s.async = true; 35 s.onload = function () { resolve(window.supabase); }; 36 s.onerror = function () { reject(new Error("supabase-js ë¡ë ì¤í¨")); }; 37 document.head.appendChild(s); 38 }); 39 } 40 41 // âââ ì¸ì ·íë¡í ì¡°í âââ 42 function refresh() { 43 if (!client) return Promise.resolve(); 44 return client.auth.getSession().then(function (r) { 45 session = (r && r.data && r.data.session) || null; 46 if (!session) { profile = null; render(); return; } 47 // profiles í ì¡°í (í¸ë¦¬ê±°ê° ìµì´ ë¡ê·¸ì¸ ì ìë ìì± â schema.sql handle_new_user) 48 return client.from("profiles").select("role,nickname").eq("id", session.user.id).single() 49 .then(function (p) { profile = (p && p.data) || null; render(); }) 50 .catch(function () { profile = null; render(); }); 51 }); 52 } 53 54 // âââ ë¡ê·¸ì¸ / ë¡ê·¸ìì âââ 55 function signIn(provider) { 56 if (!client) return; 57 client.auth.signInWithOAuth({ 58 provider: provider, 59 options: { redirectTo: window.location.origin + window.location.pathname } 60 }); 61 } 62 // ì´ë©ì¼/ë¹ë°ë²í¸ ë¡ê·¸ì¸ â ë´ë¹ì·ê´ë¦¬ìì©. ê³ì ì Supabase ëìë³´ë(AuthenticationâUsers)ìì 63 // ìë ìì±(ìê° ê°ì í¼ ìì, admin ì¹ê¸ë ìë ìì¹ê³¼ ëì¼íê² ê³ì ë°ê¸ë ìë). 64 function signInEmail(email, password) { 65 if (!client) return Promise.reject(new Error("not configured")); 66 return client.auth.signInWithPassword({ email: email, password: password }); 67 } 68 function signOut() { 69 if (!client) return; 70 client.auth.signOut().then(function () { session = null; profile = null; render(); }); 71 } 72 window.SGAuth = { 73 signIn: signIn, signInEmail: signInEmail, signOut: signOut, 74 get session() { return session; }, get profile() { return profile; }, 75 get client() { return client; }, 76 get configured() { return configured; }, 77 isSignedIn: function () { return !!(session && session.user); }, 78 isReviewer: function () { return !!(profile && (profile.role === "reviewer" || profile.role === "admin")); }, 79 isAdmin: function () { return !!(profile && profile.role === "admin"); }, 80 openLogin: function () { openModal(); }, 81 refresh: function () { return refresh(); }, 82 }; 83 84 // âââ UI âââ 85 function providerButtons(list) { 86 return list.map(function (p) { 87 return '<button class="auth-provider auth-' + p + '" onclick="SGAuth.signIn(\'' + p + '\')">' + 88 esc(PROVIDER_LABEL[p] || p) + '</button>'; 89 }).join(""); 90 } 91 92 function renderModalBody() { 93 var m = document.getElementById("authModalBody"); 94 if (!m) return; 95 if (!configured) { 96 m.innerHTML = '<p class="auth-pending">' + 97 esc(T("auth.pending", "ë¡ê·¸ì¸ì Supabase ì¤ì í íì±íë©ëë¤. (backend/README 참조)")) + "</p>"; 98 return; 99 } 100 m.innerHTML = 101 '<div class="auth-group"><div class="auth-group-h">' + esc(T("auth.grp.tourist", "ê´ê´ê°")) + "</div>" + 102 providerButtons(providers.tourist || []) + "</div>" + 103 '<div class="auth-group"><div class="auth-group-h">' + esc(T("auth.grp.gov", "ë´ë¹ì · ê´ë¦¬ì")) + "</div>" + 104 providerButtons(providers.gov || []) + 105 '<div class="auth-email-form">' + 106 '<input type="email" id="authEmailIn" class="submit-in" placeholder="' + esc(T("auth.email.ph", "ì´ë©ì¼")) + '">' + 107 '<input type="password" id="authPwIn" class="submit-in" placeholder="' + esc(T("auth.pw.ph", "ë¹ë°ë²í¸")) + '">' + 108 '<button class="auth-provider auth-email" onclick="SGAuthEmailLogin()">' + esc(T("auth.email.login", "ì´ë©ì¼ ë¡ê·¸ì¸")) + '</button>' + 109 '<p id="authEmailErr" class="auth-pending" style="display:none;"></p>' + 110 '</div>' + 111 "</div>" + 112 '<p class="auth-consent">' + esc(T("auth.consent", "ë¡ê·¸ì¸ ì ê°ì¸ì ë³´ ìì§Â·ì´ì© ë° ì¬ì§ íì©ì ëìí©ëë¤. (ì² í·ìì ê¶ ì ê³µ)")) + "</p>"; 113 } 114 115 window.SGAuthEmailLogin = function () { 116 var emailIn = document.getElementById("authEmailIn"); 117 var pwIn = document.getElementById("authPwIn"); 118 var err = document.getElementById("authEmailErr"); 119 if (!emailIn || !pwIn) return; 120 if (err) err.style.display = "none";
121 signInEmail(emailIn.value.trim(), pwIn.value).then(function (r) { 122 if (r.error) { 123 if (err) { err.textContent = r.error.message; err.style.display = ""; } 124 return; 125 } 126 closeModal(); 127 }); 128 }; 129 130 function render() { 131 var btn = document.getElementById("authBtn"); 132 if (!btn) return; 133 if (session && session.user) { 134 var name = (profile && profile.nickname) || (session.user.email || "").split("@")[0] || T("auth.member", "íì"); 135 var role = (profile && profile.role) || "tourist"; 136 var roleLabel = T("auth.role." + role, role); 137 btn.innerHTML = '<span class="auth-name">' + esc(name) + '</span>' + 138 '<span class="auth-role auth-role-' + esc(role) + '">' + esc(roleLabel) + "</span>"; 139 btn.setAttribute("title", T("auth.logout", "ë¡ê·¸ìì")); 140 btn.onclick = signOut; 141 btn.classList.add("auth-signed"); 142 btn.disabled = false; 143 } else { 144 btn.textContent = T("auth.login", "ë¡ê·¸ì¸"); 145 btn.onclick = openModal; 146 btn.classList.remove("auth-signed"); 147 btn.disabled = false; // 미ì¤ì ì´ì´ë ëë¬ì ìë´ ëª¨ë¬ì ë³¼ ì ìê² 148 btn.classList.toggle("auth-disabled", !configured); 149 } 150 // ë´ë¹ì·ê´ë¦¬ì ì ì© ì§ì ì (ê²í í) ë ¸ì¶ 151 var navRev = document.getElementById("navReview"); 152 if (navRev) navRev.style.display = window.SGAuth.isReviewer() ? "" : "none"; 153 var navAdm = document.getElementById("navAdmin"); 154 if (navAdm) navAdm.style.display = window.SGAuth.isAdmin() ? "" : "none"; 155 // í ë ¸ì¶/ë¬¸êµ¬ê° ë°ëë©´ fixed nav ëì´ë ë°ë ì ìì´ ì¬ê³ì°(모ë°ì¼ 본문 ê°ë¦¼ ë°©ì§) 156 if (typeof window.syncNavHeight === "function") window.syncNavHeight(); 157 renderModalBody(); 158 } 159 160 function openModal() { var m = document.getElementById("authModal"); if (m) m.classList.add("open"); renderModalBody(); } 161 function closeModal() { var m = document.getElementById("authModal"); if (m) m.classList.remove("open"); } 162 window.closeAuthModal = closeModal; 163 164 // âââ ì´ê¸°í âââ 165 function init() { 166 render(); // ë²í¼ ìí 먼ì (미ì¤ì ì´ë©´ ì¬ê¸°ì ë) 167 if (!configured) return; 168 loadSupabaseJs().then(function (sb) { 169 client = sb.createClient(cfg.url, cfg.anonKey); 170 client.auth.onAuthStateChange(function () { refresh(); }); 171 return refresh(); 172 }).catch(function (e) { 173 // CDN ì°¨ë¨Â·ì¤íë¼ì¸ ë± â ì¡°ì©í 미ì¤ì ì²ë¼ ì²ë¦¬ 174 try { console.warn("[SGAuth]", e && e.message); } catch (x) {} 175 configured = false; render(); 176 }); 177 } 178 179 // ì¸ì´ ë³ê²½ ì ì¬ë ë (기존 í¸ë¤ë¬ ì²´ì´ë â ë®ì´ì°ê¸° ë°©ì§) 180 (function () { 181 var prev = window.onLangChange; 182 window.onLangChange = function (lang) { 183 if (typeof prev === "function") { try { prev(lang); } catch (e) {} } 184 render(); 185 }; 186 })(); 187 188 document.addEventListener("DOMContentLoaded", init); 189})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.