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 을 호출해서 사용하면 된다.