Files
home/index.html
dsyoon fac88b6508 Add social quick login and user sync API
Add quick provider login buttons (Auth0 connections), an API to upsert users into Postgres and gate admin via can_manage, plus schema and Node server.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 18:04:18 +09:00

335 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<title>NCue | 개인 링크 홈</title>
<meta name="description" content="개인 서비스 링크를 모아 관리하는 홈 화면" />
<link rel="stylesheet" href="./styles.css" />
<!-- Auth0 SPA SDK (정적 사이트용) -->
<!-- jsDelivr 차단/실패 시 unpkg로 자동 대체 -->
<script
src="https://cdn.jsdelivr.net/npm/@auth0/auth0-spa-js@2/dist/auth0-spa-js.production.js"
onerror="this.onerror=null;this.src='https://unpkg.com/@auth0/auth0-spa-js@2/dist/auth0-spa-js.production.js';"
></script>
<script>
// 로그인 설정 (관리 기능 잠금용)
// 1) Auth0에서 Application(SPA) 생성 후 domain/clientId를 입력하세요.
// 2) Allowed Callback URLs / Allowed Logout URLs에 현재 사이트 주소를 등록하세요.
// 예: https://drive.daewoongai.com/apps/dashboard/
window.AUTH_CONFIG = {
auth0: {
domain: "",
clientId: "",
},
// 관리 허용 이메일(대소문자 무시)
allowedEmails: [],
};
</script>
<script defer src="./script.js"></script>
</head>
<body>
<a class="skip-link" href="#main">본문으로 건너뛰기</a>
<header class="topbar">
<div class="wrap">
<div class="brand">
<div class="logo" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none">
<path
d="M10.5 13.5l3-3m-1.24-3.13l-2.86-2.86a4 4 0 0 0-5.66 5.66l2.86 2.86"
stroke="currentColor"
stroke-width="1.8"
stroke-linecap="round"
/>
<path
d="M13.5 10.5l2.86 2.86a4 4 0 0 1-5.66 5.66l-2.86-2.86"
stroke="currentColor"
stroke-width="1.8"
stroke-linecap="round"
/>
</svg>
</div>
<div class="brand-text">
<div class="brand-title">NCue</div>
<div class="brand-sub" id="subtitle">개인 링크 관리</div>
</div>
</div>
<div class="actions">
<button class="btn" id="btnAdd" type="button">
<span class="btn-ico" aria-hidden="true">+</span>
추가
</button>
<button class="btn" id="btnImport" type="button">가져오기</button>
<button class="btn" id="btnExport" type="button">내보내기</button>
<button class="btn" id="btnTheme" type="button" aria-pressed="false" title="테마 전환">
테마
</button>
<div class="user" id="user" hidden>
<span class="user-dot" aria-hidden="true"></span>
<span class="user-text" id="userText">로그인 필요</span>
</div>
<button class="btn" id="btnGoogle" type="button" hidden>구글</button>
<button class="btn" id="btnKakao" type="button" hidden>카카오</button>
<button class="btn" id="btnNaver" type="button" hidden>네이버</button>
<button class="btn" id="btnLogin" type="button">로그인</button>
<button class="btn" id="btnLogout" type="button" hidden>로그아웃</button>
</div>
</div>
</header>
<main class="wrap" id="main">
<section class="panel">
<div class="controls">
<label class="field">
<span class="field-label">검색</span>
<input
id="q"
class="input"
type="search"
placeholder="제목/도메인/태그 검색…"
autocomplete="off"
spellcheck="false"
/>
</label>
<label class="field">
<span class="field-label">정렬</span>
<select id="sort" class="select">
<option value="json">파일 순서</option>
<option value="recent">최근 수정</option>
<option value="name">이름</option>
<option value="domain">도메인</option>
<option value="favorite">즐겨찾기 우선</option>
</select>
</label>
<label class="check">
<input id="onlyFav" type="checkbox" />
<span>즐겨찾기만</span>
</label>
<div class="meta" id="meta" aria-live="polite"></div>
</div>
</section>
<section class="grid" id="grid" aria-label="링크 목록"></section>
<section class="empty" id="empty" hidden>
<div class="empty-title">표시할 링크가 없습니다.</div>
<div class="empty-sub">상단의 “추가” 버튼으로 새 링크를 등록하세요.</div>
</section>
</main>
<!-- Modal -->
<div class="modal" id="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle" hidden>
<div class="modal-backdrop" data-close="1"></div>
<div class="modal-card" role="document">
<div class="modal-head">
<div class="modal-title" id="modalTitle">링크 추가</div>
<button class="icon-btn" type="button" id="btnClose" title="닫기" aria-label="닫기">×</button>
</div>
<form id="form" class="modal-body">
<input type="hidden" id="id" />
<label class="field">
<span class="field-label">제목</span>
<input id="title" class="input" type="text" required maxlength="80" placeholder="예: Git" />
</label>
<label class="field">
<span class="field-label">URL</span>
<input
id="url"
class="input"
type="url"
required
placeholder="예: https://example.com"
inputmode="url"
autocomplete="off"
spellcheck="false"
/>
<div class="hint">http(s) URL을 권장합니다. (미입력 시 자동으로 https://가 붙습니다)</div>
</label>
<label class="field">
<span class="field-label">설명</span>
<input id="description" class="input" type="text" maxlength="140" placeholder="선택" />
</label>
<label class="field">
<span class="field-label">태그</span>
<input id="tags" class="input" type="text" placeholder="예: ncue, dev (쉼표로 구분)" />
</label>
<label class="check">
<input id="favorite" type="checkbox" />
<span>즐겨찾기</span>
</label>
<div class="modal-foot">
<button class="btn btn-ghost" type="button" id="btnCancel">취소</button>
<button class="btn btn-primary" type="submit" id="btnSave">저장</button>
</div>
</form>
</div>
</div>
<!-- Auth Config Modal -->
<div class="modal" id="authModal" role="dialog" aria-modal="true" aria-labelledby="authModalTitle" hidden>
<div class="modal-backdrop" data-auth-close="1"></div>
<div class="modal-card" role="document">
<div class="modal-head">
<div class="modal-title" id="authModalTitle">로그인 설정</div>
<button class="icon-btn" type="button" id="btnAuthClose" title="닫기" aria-label="닫기">×</button>
</div>
<form id="authForm" class="modal-body">
<label class="field">
<span class="field-label">Auth0 Domain</span>
<input id="authDomain" class="input" type="text" placeholder="예: your-tenant.us.auth0.com" />
<div class="hint">Auth0 테넌트 도메인입니다. (비밀값 아님)</div>
</label>
<label class="field">
<span class="field-label">Auth0 Client ID</span>
<input id="authClientId" class="input" type="text" placeholder="예: AbCdEf..." />
<div class="hint">Auth0 SPA Application의 Client ID입니다. (비밀값 아님)</div>
</label>
<label class="field">
<span class="field-label">허용 이메일</span>
<input id="authAllowedEmails" class="input" type="text" placeholder="예: me@example.com, admin@example.com" />
<div class="hint">쉼표로 구분합니다. 비워두면 “로그인한 모든 계정”이 관리 가능해집니다.</div>
</label>
<label class="field">
<span class="field-label">Connection 이름(선택)</span>
<input id="authConnGoogle" class="input" type="text" placeholder="Google 예: google-oauth2" />
<input id="authConnKakao" class="input" type="text" placeholder="Kakao 예: kakao" />
<input id="authConnNaver" class="input" type="text" placeholder="Naver 예: naver" />
<div class="hint">Auth0에서 설정한 connection 이름입니다. 비우면 “로그인” 버튼으로 통합 로그인 화면을 띄웁니다.</div>
</label>
<div class="modal-foot">
<button class="btn btn-ghost" type="button" id="btnAuthReset">초기화</button>
<button class="btn btn-primary" type="submit" id="btnAuthSave">저장</button>
</div>
</form>
</div>
</div>
<!-- Hidden file input for import -->
<input id="file" type="file" accept="application/json" hidden />
<!-- Toast -->
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
<!--
기본 링크 데이터(서버 없이 index.html만 열어도 동작)
이 배열 순서가 "파일 순서" 정렬 기준이 됩니다.
-->
<script id="linksData" type="application/json">
[
{
"id": "dsyoon-ncue-net",
"title": "DSYoon",
"url": "https://ncue.net/dsyoon",
"description": "개인 페이지",
"tags": ["personal", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "family-ncue-net",
"title": "Family",
"url": "https://ncue.net/family",
"description": "Family",
"tags": ["personal", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "git-ncue-net",
"title": "Git",
"url": "https://git.ncue.net/",
"description": "NCUE Git 서비스",
"tags": ["dev", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "mail-ncue-net",
"title": "Mail",
"url": "https://mail.ncue.net/",
"description": "NCUE 메일",
"tags": ["mail", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "tts-ncue-net",
"title": "TTS",
"url": "https://tts.ncue.net/",
"description": "입력한 text를 mp3로 변환",
"tags": ["text", "mp3", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "meeting-ncue-net",
"title": "Meeting",
"url": "https://meeting.ncue.net/",
"description": "NCUE 미팅",
"tags": ["meeting", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "openclaw-ncue-net",
"title": "OpenClaw",
"url": "https://openclaw.ncue.net/",
"description": "OpenClaw",
"tags": ["tool", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "link-ncue-net",
"title": "Link",
"url": "https://link.ncue.net/",
"description": "NCUE 링크 허브",
"tags": ["link", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "dreamgirl-ncue-net",
"title": "DreamGirl",
"url": "https://ncue.net/dreamgirl",
"description": "DreamGirl",
"tags": ["personal", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
}
]
</script>
<noscript>
<div class="noscript">이 페이지는 JavaScript가 필요합니다.</div>
</noscript>
</body>
</html>