Vue3, Firebase 프로젝트 - 채팅앱 VSignal

27. Vue 3 + Vuetify + Pinia + Firebase Firestore 기반의 계정 설정

그랜파 개발자 2025. 5. 3. 10:00

계정 설정

우리는 createUserWithEmailAndPassword 메서드로
이메일과 패스워드만으로 계정을 생성하였습니다.
계정 또한 Firebase Auth에 저장됩니다.

 

앱에서는 사용자의 계정 등록 정보 외에 사용자 이름 등의 추가적인 정보가 필요합니다.
또, OAuth를 이용한 로그인을 허용한다면 한 사람의 사용자가 여러 계정으로 로그인할 수 있습니다.
OAuth의 여러 계정이 같은 사용자라면 한 사람의 사용자로 통합하여야 합니다.

 

앱에서는 이를 위하여 계정과는 별도의 계정 설정 정보를 저장합니다.
사용자의 이름, 현재 상태, 사용자 소개 등의 정보를 Firestore의 profiles 컬렉션에 저장합니다.

 

profiles 컬렉션의 각 user 문서에는 uids 배열 항목이 있습니다.
이것은 각 다른 OAuth를 이용하여 로그인하는 사용자의 정보를 통합하기 위한 것으로
uids 배열에 같은 사용자의 각 다른 계정 uid를 저장하여 같은 사용자임을 보증합니다..

 

각 사용자의 계정 정보 문서 항목은 다음과 같습니다.

 

userId : 로그인 Id입니다. 계정 만들기에 등록된 정보입니다.
email : 로그인할 때 이메일입니다. 이미 계정 생성에서 등록된 정보이므로 계정 설정에서는 입력하지 않습니다.
name : 사용자의 이름 또는 별명입니다.
status: 사용자의 현재 상태입니다..
aboutMe : 사용자가 자신을 소개하는 말입니다..
createdAt : 계정 정보 생성일입니다. 계정 설정 정보를 저장할 때 시스템 시간을 자동으로 얻어 저장합니다.
uids[] : 여러 OAuth를 이용한 로그인을 위한 것입니다.

🔧 계정 만들기 시 프로필 정보 저장 로직 확인

이미 authStore에 해당 로직이 대부분 구현되어 있고,
계정 생성 시 registerProfile을 통해 프로필 정보를 저장할 수 있도록 수정합니다.

 

authStore.js에서 register 함수:

async function register(email, password) {
  try {
    const userCredential = await createUserWithEmailAndPassword(auth, email, password);
    user.value = userCredential.user;

    const profileData = {
      userId: userCredential.user.uid,
      email,
      name: '',
      status: '',
      aboutMe: '',
      createdAt: new Date(),
      uids: [userCredential.user.uid],
    };
    await registerProfile(profileData);

    router.push('/profile');
  } catch (error) {
    alert('register: ' + error);
  }
}

  // 계정 설정
  const registerProfile = async (profileData) => {
    try {
      await addDoc(collection(db, 'vsignalUsers'), profileData);
      await fetchProfiles();
    } catch (error) {
      alert('Failed to register user: ' + error);
    }
  };

  // 계정 설정 수정
  const updateProfile = async (updatedProfile) => {
    try {
      loading.value = true;
      const profileDocRef = doc(db, 'profiles', updatedProfile.id);
      await updateDoc(profileDocRef, {
        name: updatedProfile.name,
        status: updatedProfile.status,
        aboutMe: updatedProfile.aboutMe,
        // 다른 필드도 필요에 따라 추가 가능
      });
      profile.value = { ...updatedProfile }; // 로컬 상태도 반영
      loading.value = false;
      alert('계정 정보를 성공적으로 수정했습니다.');
    } catch (error) {
      loading.value = false;
      alert('계정 정보 수정 중 오류 발생: ' + error.message);
    }
  }

  const fetchProfile = async (profileId) => {
    try {
      const profileRef = doc(db, 'vsignalUsers', profileId);
      const profileSnap = await getDoc(profileRef);
      if (profileSnap.exists()) {
        profile.value = { id: profileId, ...profileSnap.data() };
      } else {
        console.log(`fetchProfile: ${profileId} 계정 설정 정보가 없습니다.`);
      }
    } catch (error) {
      alert('Failed to fetch user: ' + error.message);
    }
  };

✔ 이미 계정 생성 시 userId, email, createdAt, uids[]가 저장되고 있음
➡ name, status, aboutMe는 사용자 입력값으로 UI에서 입력 받음

🖥 계정 설정 컴포넌트

Vue 3 + Vuetify + Pinia + Firebase Firestore 기반 계정 설정 페이지 입니다.
사용자가 자신의 계정 설정 정보를 수정할 수 있으며,
정보는 Firestore의 profiles 컬렉션에 저장됩니다.

Profile.vue

<!-- src/views/Profile.vue -->
<template>
  <v-container class="fill-height" fluid>
    <v-row align="center" justify="center">
      <v-col cols="12" sm="10" md="6">
        <v-card elevation="10" class="pa-4">
          <v-card-title class="text-h6 font-weight-bold">계정 설정</v-card-title>
          <v-card-text>
            <v-form @submit.prevent="onUpdate" v-model="formValid" ref="formRef">
              <v-text-field
                v-model="auth.profile.name"
                label="이름 또는 별명"
                prepend-inner-icon="mdi-account"
                required
              />
              <v-text-field
                v-model="auth.profile.status"
                label="상태 메시지"
                prepend-inner-icon="mdi-comment"
              />
              <v-textarea
                v-model="auth.profile.aboutMe"
                label="자기소개"
                auto-grow
                prepend-inner-icon="mdi-information-outline"
              />
              <v-text-field
                v-model="auth.profile.email"
                label="이메일"
                prepend-inner-icon="mdi-email"
                disabled
              />
              <v-btn :loading="auth.loading" type="submit" color="primary" block class="mt-4">
                저장하기
              </v-btn>
            </v-form>
          </v-card-text>
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { useAuthStore } from '@/stores/authStore';

const auth = useAuthStore();
const formValid = ref(false);
const formRef = ref(null);

onMounted(() => {
  if (!auth.profile) {
    auth.fetchProfiles();
  }
});

const onUpdate = () => {
  if (formRef.value?.validate()) {
    auth.updateProfile(auth.profile);
  }
};
</script>

<style scoped>
.fill-height {
  min-height: 100vh;
}
</style>