Front-end
구글 login 구현하기 위한 Pinia store 정의
크레비즈
2025. 2. 28. 01:11
지금 개발 환경을 package.json 파일로 참고하세요.
"axios": "^1.7.9",
"bootstrap": "^5.3.3",
"concurrently": "^8.2.2",
"cors": "^2.8.5",
"echarts": "^5.6.0",
"express": "^4.21.2",
"firebase": "^11.3.1",
"papaparse": "^5.5.2",
"pinia": "^3.0.1",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"xlsx": "^0.18.5"
Pinia 로 구성한 store 의 내용입니다.
import { defineStore } from "pinia";
import { auth } from "../firebase";
import { onAuthStateChanged } from "firebase/auth";
export const useMainStore = defineStore("main", {
// _ref()
state: () => {
return {
user: null,
loading: false,
error: null,
sidebarCollapsed: false,
};
},
// _computed()
getters: {
isAuthenticated: (state) => !!state.user,
userProfile: (state) => state.user,
},
// _funtion
actions: {
// google login return 값 저장
setUser(userData) {
this.user = {
uid: userData.uid,
email: userData.email,
displayName: userData.displayName || null,
photoURL: userData.photoURL || null,
metadata: {
creationTime: userData.metadata?.creationTime || null,
lastSignInTime: userData.metadata?.lastSignInTime || null,
},
emailVerified: userData.emailVerified || false,
providerData: userData.providerData || [],
};
},
clearUser() {
this.user = null;
},
setLoading(status) {
this.loading = status;
},
setError(error) {
this.error = error;
},
toggleSidebar() {
this.sidebarCollapsed = !this.sidebarCollapsed;
},
// Firebase Auth State 관찰자 설정
initializeAuthListener() {
return new Promise((resolve) => {
onAuthStateChanged(auth, (user) => {
if (user) {
this.setUser(user);
} else {
this.clearUser();
}
resolve(user);
});
});
},
// 로그인 상태 체크
async checkAuth() {
try {
this.setLoading(true);
await this.initializeAuthListener();
} catch (error) {
this.setError(error.message);
console.error("Auth check error:", error);
} finally {
this.setLoading(false);
}
},
// 로그아웃
async logout() {
try {
this.setLoading(true);
this.setError(null);
await auth.signOut();
this.clearUser();
return true;
} catch (error) {
this.setError(error.message);
console.error("Logout error:", error);
return false;
} finally {
this.setLoading(false);
}
},
},
});
store 에 login 정보를 관리하고 각 vue 페이지에서 로그인 여부에 따라 화면을 구성하면 된다.
<!-- 로그인된 상태일 때 -->
<div v-if="store.isAuthenticated" class="logged-in-content">
<h2>이미 로그인되어 있습니다</h2>
<p>현재 계정: {{ store.userProfile.email }}</p>
<button @click="handleLogout" class="logout-btn" :disabled="store.loading">
{{ store.loading ? '로그아웃 중...' : '로그아웃' }}
</button>
</div>
<!-- 로그인되지 않은 상태일 때 -->
<template v-else>
<h2 class="login-title">Log in to your Account</h2>
<p class="login-subtitle">Welcome back! Select method to log in:</p>
</template>
<script setup>
import { useMainStore } from '../../stores';
const store = useMainStore();
</script>
<template>
<!-- 로그인하지 않은 경우 -->
<div v-if="!isLoggedIn" class="auth-buttons">
<router-link to="/auth/login" class="login-btn">
<i class="material-icons">login</i>
<span>Login</span>
</router-link>
</div>
<!-- 로그인한 경우 -->
<div v-else class="user-profile" @click="toggleProfile">
<img :src="userProfile?.photoURL || 'https://via.placeholder.com/40'"
:alt="userProfile?.displayName || userProfile?.email" class="profile-img">
<span class="user-name">{{ userProfile?.displayName || userProfile?.email }}</span>
<i class="material-icons">arrow_drop_down</i>
<!-- 프로필 드롭다운 -->
<div v-show="showProfile" class="profile-dropdown">
<router-link to="/profile" class="dropdown-item">
<i class="material-icons">person</i>
<span>My Profile</span>
</router-link>
<router-link to="/settings" class="dropdown-item">
<i class="material-icons">settings</i>
<span>Settings</span>
</router-link>
<div class="dropdown-divider"></div>
<button @click="handleLogout" class="dropdown-item logout-btn">
<i class="material-icons">logout</i>
<span>Logout</span>
</button>
</div>
</div>
</template>
<script setup>
import { useMainStore } from '../stores';
const store = useMainStore();
const isLoggedIn = computed(() => store.isAuthenticated);
const userProfile = computed(() => store.userProfile);
</script>
이런 방식으로 store 에서 정의한 getter, action 을 호출해서 사용하면 된다.