카테고리 없음

Vue, firestore DB를 사용한 프론트엔드 공부 - 계정 설정 보기

그랜파 개발자 2024. 12. 9. 03:11

Vue로 PWA 개발 - 그랜파 개발자.

1. 계정 설정 정보 보기

로그아웃하지 않고 앱을 종료하면 다음에 접속할 때 자동 로그인을 합니다.

로그인을 하면 user객체의 uid를 이용하여 회원 정보를 state의 상태 변수 profile에 저장을 합니다.

로그아웃 상태에서 앱을 종료한 후 다음에 접속을 하면 로그아웃 상태입니다.

이때 로그인을 할 수 있습니다. 당연히 로그인을 하면 회원 정보를 로드하여 state의 상태 변수 profile에 저장을 합니다.

이미 회원 정보는 로드되어 있는 상태이므로 계정 설정 페이지를 열면 이미 등록된 정보라면 저장된 회원의 정보가 나타나고,

아직 회원 정보를 저장하지 않은 상태라면 이메일 외에는 공란으로 나타날 것입니다.

 

fetchProfile 은 ChatGPT가 제공한 코드를 수정한 것입니다.

async fetchProfile({ commit }, user) {
    try {
      // Firestore에서 'uids' 배열에 UID가 포함된 문서 검색
      const q = query(
        collection(db, "profiles"),
        where("uids", "array-contains", user.uid)
      );
      const querySnapshot = await getDocs(q);

      // 결과를 users 배열에 저장
      let profiles = [];
      profiles = querySnapshot.docs.map((doc) => ({
        id: doc.id,
        ...doc.data(),
      }));

      if (profiles.length === 0) {
        // 새로운 회원 정보
        const profile = {
          email:user.email,
          name: '',    
          blogName: '',    
          aboutMe: '',    
          createdAt: '',    
          uids: [user.uid]
        } 
        commit("setProfile",profile);
       commit("setIsNewProfile",true); // 새로운 회원 정보 
      } else {
        // 이미 등록된 회원 정보
        commit("setProfile", profiles[0]);
        commit("setIsNewProfile", false); // 이미 등록된 회원 정보
      }
    } catch (error) {
      alert("Failed to fetch users. : " + error.message);
    }
  },

 

그림 38-1

2. 계정 설정 정보 로드

1. 자동 로그인

앱 접속을 하면 main.js에서 initializeAuth를 호출합니다.

src/main.js

// src/main.js
. . .
new Vue({
 . . .
  created() {
    // Set up Firebase auth state change listener
    const { dispatch } = this.$store;
    // Initialize Firebase authentication to check for the logged-in user
    dispatch('auth/initializeAuth');
  }
}).$mount('#app')

2. fetchProfile

auth.js에서 자동 로그인을 하면 fetchProfile을 호출합니다.
fetchProfile은 profiles 컬렉션에서 로그인한 회원의 계정 설정 정보를 찾아 로드합니다.
회원 계정 설정 정보가 등록된 경우가 아니라면 새로운 계정 설정 정보를 구성합니다.

src/store/modules/auth.js

// src/store/modules/auth.js
. . .
const actions = {
  // -- 앱을 시작하면 자동 로그인을 설정한다.
  async initializeAuth({ commit, dispatch }) {
    onAuthStateChanged(auth, (user) => {
      if (user) {        
        commit("setUser", user);
        // user.uid로 profile을 가져와야 한다.
        //console.log(user);
        dispatch("fetchProfile", user);
      } else {
        commit("setUser", null);
      }
    });
  },
 . . .
  async fetchProfile({ commit }, user) {
    try {
      // Firestore에서 'uids' 배열에 UID가 포함된 문서 검색
      const q = query(
        collection(db, "profiles"),
        where("uids", "array-contains", user.uid)
      );
      const querySnapshot = await getDocs(q);

      // 결과를 users 배열에 저장
      let profiles = [];
      profiles = querySnapshot.docs.map((doc) => ({
        id: doc.id,
        ...doc.data(),
      }));

      if (profiles.length === 0) {
        // 새로운 회원 정보
        const profile = {
          email:user.email,
          name: '',    
          blogName: '',    
          aboutMe: '',    
          createdAt: '',    
          uids: [user.uid]
        } 
        commit("setProfile",profile);
       commit("setIsNewProfile",true); // 새로운 회원 정보 
      } else {
        // 이미 등록된 회원 정보
        commit("setProfile", profiles[0]);
        commit("setIsNewProfile", false); // 이미 등록된 회원 정보
      }
    } catch (error) {
      alert("Failed to fetch users. : " + error.message);
    }
  },
. . .

3. 회원 계정 설정 정보 보기

ProfileView.vue

<!-- src/views/ProfileView.vue -->
<template>
  <v-container>
    <v-row>
      <v-col>
        <v-card class="pa-2">
          <v-card-title>
            <span class="text-h6">계정 정보</span>
          </v-card-title>

          <v-card-text>
            <v-form ref="form" v-model="valid" lazy-validation>

              <!-- Email Field (Non-Editable) -->
              <v-text-field label="이메일" v-model="editableUserInfo.email" disabled readonly></v-text-field>
              <!-- Name Field -->
              <v-text-field label="이름" v-model="editableUserInfo.name" :rules="[rules.required]"></v-text-field>
              <!-- blogName Field -->
              <v-text-field label="블로그 이름" v-model="editableUserInfo.blogName" :rules="[rules.required]"></v-text-field>
              <!-- aboutMe Field -->
              <v-text-field label="소개" v-model="editableUserInfo.aboutMe" :rules="[rules.required]"></v-text-field>
            </v-form>
          </v-card-text>

          <v-card-actions class="mt-n8">
            <v-spacer></v-spacer>
            <v-btn color="primary" @click="saveChanges" :disabled="!valid">저장</v-btn>
            <v-btn color="grey" @click="cancelEdit">취소</v-btn>
          </v-card-actions>

          <v-card-actions class="mt-2">
            <v-btn @click="showForm = !showForm">
              <v-icon left>mdi-lock</v-icon>비밀번호 변경
            </v-btn>
            <v-spacer></v-spacer>
            <v-btn color="red" @click="doGoogleAccount" dark>  
              <v-icon left>mdi-google</v-icon>Google 연동
            </v-btn>            
          </v-card-actions>

        </v-card>
      </v-col>
    </v-row>

    <v-row v-if="showForm">
      <v-col>
        <v-card class="pa-2">
          <v-card-title style="font-size:1em" class="mt-n2">
            비밀번호 변경
          </v-card-title>

          <v-card-text  class="mt-n4">
            <v-form ref="form" v-model="valid" lazy-validation> 
              <v-text-field v-model="oldPassword" :rules="passwordRules" label="현재 비밀번호" prepend-icon="mdi-lock" type="password" required></v-text-field>
              <v-text-field v-model="newPassword" :rules="passwordRules" label="새 비밀번호" prepend-icon="mdi-lock" type="password" required></v-text-field>
            </v-form>
          </v-card-text>

          <v-card-actions class="mt-n8">
            <v-spacer></v-spacer>
            <v-btn @click="doChangePassword" :disabled="!valid">
              <v-icon left>mdi-lock</v-icon>비밀번호 변경
            </v-btn>       
          </v-card-actions>

        </v-card>       
      </v-col>
    </v-row>

  </v-container>
</template>

<script>
import { mapActions, mapState } from "vuex";

export default {
  data() {
    return {
      editableUserInfo: {
        email: "",
        name: "",
        blogName: "",
        aboutMe: "",
      },
      showForm: false,
      oldPassword:"",
      newPassword:"",
      valid: false,
      validPassword: false,
      rules: {
        required: (value) => !!value || "Required.",
        numeric: (value) => !isNaN(parseInt(value)) || "Must be a number.",
      },
      passwordRules: [
        (v) => !!v || 'Password is required',
        (v) => (!!v && v.length >= 6) || 'Password must be at least 6 characters',
      ],
    };
  },
  computed: {
    ...mapState('auth', ["user", "profile"]),
    getUserInfo() {
      return this.profile;
    },
  },
  watch: {
    getUserInfo: {
      handler(newValue) {
        this.editableUserInfo = { ...newValue }; // Clone the userInfo to editableUserInfo
      },
      immediate: true,
    },
  },
  methods: {

  },
  created() {

  },
};
</script>

<style scoped>
.my-alert {
  margin: 20px 0;
}
</style>

 

Vue 프로젝트 Beta Test : mylog, 일상의 기록