feat(auth): iOS 애플 로그인 버튼 — Sign in with Apple(App Store 4.8 대응)
- @capacitor-community/apple-sign-in + LoginModal Apple 버튼(iOS 전용, HIG 검은 버튼) - appleAuth 네이티브 래퍼 + authApi/auth store appleLogin - SPM capacitor-swift-pm 8.x 로 패치(patch-package) — @capawesome 구글 플러그인과 iOS 공존 - App.entitlements(applesignin) 준비 — Xcode 'Sign in with Apple' Capability 추가 시 사용 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import { useAuthStore } from '@/stores/auth'
|
||||
import { useUiStore } from '@/stores/ui'
|
||||
import { authApi } from '@/api/authApi'
|
||||
import { isNativeGoogle, nativeGoogleIdToken } from '@/native/googleAuth'
|
||||
import { isNativeApple, nativeAppleCredential } from '@/native/appleAuth'
|
||||
import { ID_LOGIN_ENABLED } from '@/config/features'
|
||||
|
||||
const auth = useAuthStore()
|
||||
@@ -64,6 +65,33 @@ async function handleNativeGoogle() {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 애플 로그인 (iOS 전용) =====
|
||||
const isApple = isNativeApple()
|
||||
|
||||
async function handleNativeApple() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const { identityToken, name } = await nativeAppleCredential()
|
||||
loading.value = false
|
||||
if (!identityToken) return
|
||||
error.value = null
|
||||
loading.value = true
|
||||
await auth.appleLogin(identityToken, name, form.rememberMe)
|
||||
const redirect = ui.redirectPath
|
||||
ui.closeLogin()
|
||||
router.push(redirect || '/')
|
||||
} catch (e) {
|
||||
// 사용자가 취소(USER_CANCELLED/1001 등)한 경우는 조용히 무시
|
||||
const msg = `${e?.code || ''} ${e?.message || ''}`
|
||||
if (!/cancel|1001/i.test(msg)) {
|
||||
error.value = e.response?.data?.message || '애플 로그인에 실패했습니다.'
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// GIS 스크립트 로드 (한 번만). 실패하면 reject.
|
||||
function loadGisScript() {
|
||||
if (window.google?.accounts?.id) return Promise.resolve()
|
||||
@@ -208,6 +236,20 @@ onUnmounted(() => window.removeEventListener('keydown', onKeydown))
|
||||
<div v-else ref="googleBtn" class="google-btn"></div>
|
||||
</div>
|
||||
|
||||
<!-- 애플 로그인 — iOS 전용(App Store 가이드라인 4.8 대응). 서버 설정 불필요 -->
|
||||
<button
|
||||
v-if="isApple"
|
||||
type="button"
|
||||
class="apple-native"
|
||||
:disabled="loading"
|
||||
@click="handleNativeApple"
|
||||
>
|
||||
<svg class="a-logo" viewBox="0 0 384 512" width="16" height="16" aria-hidden="true">
|
||||
<path fill="currentColor" d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"/>
|
||||
</svg>
|
||||
Apple로 계속하기
|
||||
</button>
|
||||
|
||||
<button v-if="SOCIAL_LOGIN_ENABLED" type="button" class="naver" :disabled="loading" @click="handleNaverLogin">
|
||||
네이버로 로그인 (준비 중)
|
||||
</button>
|
||||
@@ -339,6 +381,32 @@ h2 {
|
||||
.google-native .g-logo {
|
||||
flex: none;
|
||||
}
|
||||
/* Apple 로그인 버튼 — HIG 권장(검은 배경 + 흰 로고/텍스트) */
|
||||
.apple-native {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin: 0.6rem auto 0;
|
||||
padding: 0.6rem 1rem;
|
||||
border: 1px solid #000;
|
||||
border-radius: 4px;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
.apple-native:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.apple-native .a-logo {
|
||||
flex: none;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user