196 lines
9.6 KiB
PHP
196 lines
9.6 KiB
PHP
@extends('admin.layouts.app')
|
|
|
|
@section('title', '내 정보')
|
|
@section('page_title', '내 정보')
|
|
@section('page_desc', '프로필 / 연락처 / 보안 상태')
|
|
|
|
@push('head')
|
|
<style>
|
|
/* me page only */
|
|
.me-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
|
|
@media (max-width: 980px){ .me-grid{grid-template-columns:1fr;} }
|
|
|
|
.me-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap;margin-bottom:12px;}
|
|
.me-head__title{font-weight:900;font-size:16px;}
|
|
.me-head__desc{margin-top:4px;font-size:12px;}
|
|
|
|
.me-formgrid{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
|
|
@media (max-width: 720px){ .me-formgrid{grid-template-columns:1fr;} }
|
|
|
|
.me-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:12px;}
|
|
|
|
.lbtn{padding:8px 12px;font-size:13px;border-radius:12px;line-height:1.1;text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;
|
|
border:1px solid rgba(255,255,255,.10);background:rgba(255,255,255,.06);color:inherit;cursor:pointer;}
|
|
.lbtn:hover{background:rgba(255,255,255,.10);text-decoration:none;}
|
|
.lbtn--primary{background:rgba(59,130,246,.88);border-color:rgba(59,130,246,.95);color:#fff;}
|
|
.lbtn--primary:hover{background:rgba(59,130,246,.98);}
|
|
.lbtn--danger{background:rgba(244,63,94,.18);border-color:rgba(244,63,94,.35);}
|
|
.lbtn--ghost{background:transparent;}
|
|
.lbtn--wide{padding:10px 14px;font-weight:800;}
|
|
|
|
.pill{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;font-size:12px;
|
|
border:1px solid rgba(255,255,255,.10);background:rgba(255,255,255,.06);}
|
|
.pill--ok{border-color:rgba(34,197,94,.35);background:rgba(34,197,94,.12);}
|
|
.pill--warn{border-color:rgba(245,158,11,.35);background:rgba(245,158,11,.12);}
|
|
.pill--bad{border-color:rgba(244,63,94,.35);background:rgba(244,63,94,.10);}
|
|
.pill--muted{opacity:.9}
|
|
|
|
.kv{display:grid;grid-template-columns:140px 1fr;gap:10px 14px;}
|
|
@media (max-width: 720px){ .kv{grid-template-columns:1fr;} }
|
|
.kv__k{color:rgba(255,255,255,.65);font-size:12px;}
|
|
.kv__v{font-weight:700;}
|
|
.kv__sub{font-weight:500;color:rgba(255,255,255,.65);font-size:12px;margin-top:4px;}
|
|
|
|
.chipwrap{display:flex;flex-wrap:wrap;gap:8px;}
|
|
.chip{display:inline-flex;align-items:center;gap:8px;padding:8px 10px;border-radius:999px;font-size:12px;
|
|
border:1px solid rgba(255,255,255,.10);background:rgba(255,255,255,.06);}
|
|
.chip__sub{opacity:.7;font-size:11px;padding:3px 8px;border-radius:999px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.08);}
|
|
|
|
.divider{margin:14px 0;opacity:.15;}
|
|
</style>
|
|
@endpush
|
|
|
|
@section('content')
|
|
@php
|
|
$hasSecret = !empty($me->totp_secret_enc);
|
|
$isVerified = !empty($me->totp_verified_at);
|
|
$isEnabled = (int)($me->totp_enabled ?? 0) === 1;
|
|
|
|
$modeLabel = $isEnabled ? 'Google OTP(TOTP)' : 'SMS 인증';
|
|
|
|
$totpLabel = '미등록';
|
|
$totpPill = 'pill--muted';
|
|
if ($hasSecret && $isVerified) { $totpLabel='등록됨'; $totpPill='pill--ok'; }
|
|
elseif ($hasSecret && !$isVerified) { $totpLabel='등록 진행중'; $totpPill='pill--warn'; }
|
|
@endphp
|
|
|
|
<section class="a-page">
|
|
<div class="me-grid">
|
|
|
|
{{-- 기본 정보 --}}
|
|
<article class="a-card" style="padding:16px;">
|
|
<div class="me-head">
|
|
<div>
|
|
<div class="me-head__title">기본 정보</div>
|
|
<div class="me-head__desc a-muted">이메일은 변경할 수 없습니다.</div>
|
|
</div>
|
|
<div class="pill pill--muted">프로필</div>
|
|
</div>
|
|
|
|
<form method="POST"
|
|
action="{{ route('admin.me.update') }}"
|
|
class="a-form"
|
|
onsubmit="this.querySelector('button[type=submit]').disabled=true;">
|
|
@csrf
|
|
|
|
<div class="a-field" style="margin-bottom:12px;">
|
|
<label class="a-label">이메일</label>
|
|
<input class="a-input" value="{{ $me->email }}" disabled>
|
|
</div>
|
|
|
|
<div class="me-formgrid">
|
|
<div class="a-field">
|
|
<label class="a-label" for="nickname">닉네임</label>
|
|
<input class="a-input" id="nickname" name="nickname"
|
|
placeholder="예: super admin"
|
|
value="{{ old('nickname', $me->nickname ?? '') }}">
|
|
@error('nickname')<div class="a-error">{{ $message }}</div>@enderror
|
|
</div>
|
|
|
|
<div class="a-field">
|
|
<label class="a-label" for="name">성명(본명)</label>
|
|
<input class="a-input" id="name" name="name"
|
|
value="{{ old('name', $me->name ?? '') }}">
|
|
@error('name')<div class="a-error">{{ $message }}</div>@enderror
|
|
</div>
|
|
|
|
<div class="a-field" style="grid-column: 1 / -1;">
|
|
<label class="a-label" for="phone">휴대폰</label>
|
|
<input class="a-input" id="phone" name="phone"
|
|
placeholder="01012345678"
|
|
value="{{ old('phone', $phone_plain ?? '') }}">
|
|
<div class="a-muted" style="margin-top:6px;font-size:12px;">숫자만 입력 권장</div>
|
|
@error('phone')<div class="a-error">{{ $message }}</div>@enderror
|
|
</div>
|
|
</div>
|
|
|
|
<div class="me-actions">
|
|
<button class="lbtn lbtn--primary lbtn--wide" type="submit">저장</button>
|
|
<span class="a-muted" style="font-size:12px;">* 저장 시 즉시 반영됩니다.</span>
|
|
</div>
|
|
</form>
|
|
</article>
|
|
|
|
{{-- 보안 --}}
|
|
<article class="a-card" style="padding:16px;">
|
|
<div class="me-head">
|
|
<div>
|
|
<div class="me-head__title">보안</div>
|
|
<div class="me-head__desc a-muted">비밀번호 변경 및 2FA 상태</div>
|
|
</div>
|
|
<div class="pill {{ $isEnabled ? 'pill--ok' : 'pill--muted' }}">2FA</div>
|
|
</div>
|
|
|
|
<div class="kv">
|
|
<div class="kv__k">2FA 모드</div>
|
|
<div class="kv__v">
|
|
<span class="pill {{ $isEnabled ? 'pill--ok' : 'pill--muted' }}">● {{ $modeLabel }}</span>
|
|
<div class="kv__sub">로그인 시 추가 인증 방식입니다.</div>
|
|
</div>
|
|
|
|
<div class="kv__k">TOTP 상태</div>
|
|
<div class="kv__v">
|
|
<span class="pill {{ $totpPill }}">● {{ $totpLabel }}</span>
|
|
@if($hasSecret && $isVerified)
|
|
<div class="kv__sub">등록일: {{ $me->totp_verified_at }}</div>
|
|
@elseif($hasSecret && !$isVerified)
|
|
<div class="kv__sub">QR 등록 후, 인증코드 확인이 필요합니다.</div>
|
|
@else
|
|
<div class="kv__sub">Google Authenticator(또는 호환 앱)로 등록할 수 있습니다.</div>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="kv__k">내 역할</div>
|
|
<div class="kv__v">
|
|
<div class="chipwrap">
|
|
@forelse(($roles ?? []) as $r)
|
|
<span class="chip">
|
|
{{ $r['name'] }}
|
|
<span class="chip__sub">{{ $r['code'] }}</span>
|
|
</span>
|
|
@empty
|
|
<span class="a-muted">부여된 역할이 없습니다.</span>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kv__k">최근 로그인</div>
|
|
<div class="kv__v">
|
|
{{ $me->last_login_at ? $me->last_login_at : '-' }}
|
|
<div class="kv__sub">최근 로그인 시간 기준입니다.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="divider">
|
|
|
|
<div class="me-actions">
|
|
<a class="lbtn lbtn--primary lbtn--wide" href="{{ route('admin.me.password.form') }}">
|
|
비밀번호 변경
|
|
</a>
|
|
|
|
@if(!$hasSecret)
|
|
<a class="lbtn" href="{{ route('admin.security') }}">Google OTP 등록</a>
|
|
@else
|
|
<a class="lbtn lbtn--ghost" href="{{ route('admin.security') }}">2FA 방식 변경 / OTP 관리</a>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="a-muted" style="font-size:12px; margin-top:10px; line-height:1.6;">
|
|
* OTP를 등록해도, 최종 로그인 방식(OTP/SMS)은 “2FA 방식 변경”에서 선택할 수 있습니다.
|
|
</div>
|
|
</article>
|
|
|
|
</div>
|
|
</section>
|
|
@endsection
|