사용자 프로필과 로그인 방식에 맞는 계정 관리 HTML을 만듭니다.
1396 {
1397 var accountPath = $"{routePrefix}/account";
1398 var signoutPath = $"{routePrefix}/signout";
1399 var provider =
string.IsNullOrWhiteSpace(user.
Provider) ?
"Unknown" : user.
Provider;
1400 var email =
string.IsNullOrWhiteSpace(user.
Email) ?
"제공되지 않음" : user.
Email;
1401 var isLocal =
string.Equals(user.
Provider, LocalProvider, StringComparison.OrdinalIgnoreCase);
1402 var avatar =
string.IsNullOrWhiteSpace(user.
AvatarUrl)
1403 ? "<div class=\"avatar-fallback\">U</div>"
1404 : $
"""<img class="avatar
" src="{
Html(user.
AvatarUrl)}
" alt="" />""";
1405 var passwordSection = isLocal
1406 ? $$"""
1407 <section>
1408 <h2>비밀번호 변경</h2>
1409 <form method="post" action="{{accountPath}}">
1410 <input type="hidden
" name="returnUrl
" value="{{
Html(returnUrl)}}
" />
1411 <input type="hidden" name="accountAction" value="password" />
1412 <label for="currentPassword">현재 비밀번호</label>
1413 <input id="currentPassword" name="currentPassword" type="password" autocomplete="current-password" required />
1414 <label for="newPassword">새 비밀번호</label>
1415 <input id="newPassword" name="newPassword" type="password" autocomplete="new-password" required minlength="8" />
1416 <label for="confirmPassword">새 비밀번호 확인</label>
1417 <input id="confirmPassword" name="confirmPassword" type="password" autocomplete="new-password" required minlength="8" />
1418 <button type="submit">비밀번호 변경</button>
1419 </form>
1420 </section>
1421 """
1422 : $$"""
1423 <section>
1424 <h2>비밀번호</h2>
1425 <p class="note">{{Html(provider)}} 로그인 계정은 CodeMaru에 별도 비밀번호가 없습니다. 비밀번호 변경은 해당 로그인 제공자에서 진행해 주세요.</p>
1426 </section>
1427 """;
1428
1429 var builder = new StringBuilder();
1430 builder.Append($$"""
1431 <!DOCTYPE html>
1432 <html lang="ko">
1433 <head>
1434 <meta charset="utf-8" />
1435 <meta name="viewport" content="width=device-width, initial-scale=1" />
1436 <title>내 계정 | Dreamine Identity</title>
1437 <style>
1438 :root { color-scheme: dark; }
1439 * { box-sizing: border-box; }
1440 body {
1441 margin: 0;
1442 min-height: 100vh;
1443 display: grid;
1444 place-items: center;
1445 font-family: "Segoe UI", Arial, sans-serif;
1446 background: #0f172a;
1447 color: #e5e7eb;
1448 }
1449 main {
1450 width: min(480px, calc(100vw - 32px));
1451 padding: 28px;
1452 border: 1px solid rgba(148, 163, 184, .28);
1453 border-radius: 8px;
1454 background: #111827;
1455 box-shadow: 0 24px 80px rgba(0, 0, 0, .36);
1456 }
1457 .head { display: flex; align-items: center; gap: 14px; margin-bottom: 18px; }
1458 .avatar, .avatar-fallback {
1459 width: 54px;
1460 height: 54px;
1461 border-radius: 50%;
1462 object-fit: cover;
1463 display: grid;
1464 place-items: center;
1465 background: #1e293b;
1466 color: #cbd5e1;
1467 font-weight: 800;
1468 }
1469 h1 { margin: 0; font-size: 28px; letter-spacing: 0; }
1470 h2 { margin: 0 0 12px; font-size: 18px; letter-spacing: 0; }
1471 p { margin: 6px 0 0; color: #94a3b8; line-height: 1.5; }
1472 section { margin-top: 22px; padding-top: 20px; border-top: 1px solid #263244; }
1473 dl { display: grid; gap: 10px; margin: 20px 0; }
1474 div.row {
1475 display: grid;
1476 grid-template-columns: 110px minmax(0, 1fr);
1477 gap: 12px;
1478 padding: 10px 0;
1479 border-bottom: 1px solid #263244;
1480 }
1481 dt { color: #94a3b8; }
1482 dd { margin: 0; overflow-wrap: anywhere; }
1483 label { display: block; margin: 16px 0 6px; color: #cbd5e1; font-size: 14px; }
1484 input {
1485 width: 100%;
1486 height: 44px;
1487 padding: 0 12px;
1488 border: 1px solid #334155;
1489 border-radius: 6px;
1490 background: #0b1220;
1491 color: #f8fafc;
1492 font-size: 15px;
1493 }
1494 .actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 18px; }
1495 button, a.button {
1496 display: flex;
1497 height: 44px;
1498 align-items: center;
1499 justify-content: center;
1500 border-radius: 6px;
1501 text-decoration: none;
1502 font-weight: 700;
1503 }
1504 button { border: 0; background: #e5e7eb; color: #0f172a; cursor: pointer; }
1505 a.button { border: 1px solid #334155; color: #e5e7eb; }
1506 form > button { width: 100%; margin-top: 18px; }
1507 .note {
1508 padding: 12px;
1509 border: 1px solid #334155;
1510 border-radius: 6px;
1511 background: #0b1220;
1512 }
1513 .message, .error {
1514 padding: 10px 12px;
1515 border-radius: 6px;
1516 margin: 14px 0;
1517 line-height: 1.45;
1518 }
1519 .message { background: rgba(14, 165, 233, .12); color: #bae6fd; }
1520 .error { background: rgba(239, 68, 68, .14); color: #fecaca; }
1521 </style>
1522 </head>
1523 <body>
1524 <main>
1525 <div class="head">
1526 {{avatar}}
1527 <div>
1528 <h1>내 계정</h1>
1529 <p>CodeMaru 및 Dreamine 계열 서비스에서 사용할 기본 정보를 관리합니다.</p>
1530 </div>
1531 </div>
1532 """);
1533
1534 if (!string.IsNullOrWhiteSpace(message))
1535 {
1536 builder.Append($"""<div class="message">{Html(message)}</div>""");
1537 }
1538
1539 if (!string.IsNullOrWhiteSpace(error))
1540 {
1541 builder.Append($"""<div class="error">{Html(error)}</div>""");
1542 }
1543
1544 builder.Append($$"""
1545 <dl>
1546 <div class="row"><dt>로그인 방식</dt><dd>{{Html(provider)}}</dd></div>
1547 <div class="row"><dt>이메일</dt><dd>{{Html(email)}}</dd></div>
1548 </dl>
1549 <form method="post" action="{{accountPath}}">
1550 <input type="hidden
" name="returnUrl
" value="{{
Html(returnUrl)}}
" />
1551 <input type="hidden" name="accountAction" value="profile" />
1552 <label for="displayName">표시 이름</label>
1553 <input id="displayName
" name="displayName
" autocomplete="name
" required value="{{
Html(user.
DisplayName)}}
" />
1554 <div class="actions">
1555 <button type="submit">저장</button>
1556 <a class="button
" href="{{
Html(returnUrl)}}
">돌아가기</a>
1557 <a class="button
" href="{{signoutPath}}?returnUrl={{
Url(returnUrl)}}
">로그아웃</a>
1558 </div>
1559 </form>
1560 {{passwordSection}}
1561 </main>
1562 </body>
1563 </html>
1564 """);
1565
1566 return builder.ToString();
1567 }
static string Url(string? value)
static string Html(string? value)