114. Storing the User Form Data
npm create vue@latest
์ด๋ฒ์ ๋ฑ๋ก ์์ ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํผ ๋ฐ์ดํฐ๋ฅผ ์ฝ์
ํ๋ ๋ฐฉ๋ฒ์ ๋ํด ๋ฐฐ์๋๋ค.
๋จผ์ , ์คํฌ๋ฆฝํธ ์๋จ์์ Firebase ๋ชจ๋์ ๊ตฌ์กฐํํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ฐ์ฒด๋ฅผ ์ ํํฉ๋๋ค.
๋ฐ์ดํฐ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ถ๊ฐํ๊ธฐ ์ํ ์ฒซ ๋จ๊ณ๋ ์ฝ๋ ์
์ ์ ํํ๋ ๊ฒ์
๋๋ค.
Firebase์์ ์ฌ์ฉ๋๋ ์ฃผ์ ์ฉ์ด ์ธ ๊ฐ์ง๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
๋ฒํท(Bucket)
- ๋ฐ์ดํฐ๊ฐ ์ ์ฅ๋๋ ๋ฌผ๋ฆฌ์ ์์น๋ฅผ ์๋ฏธํฉ๋๋ค.
Firebase์์๋ ํ๋ฆฌ๋ฏธ์ ํ๋์์ ์ฌ๋ฌ ๋ฒํท์ ์์ฑํ ์ ์์ต๋๋ค.
- ๋ฐ์ดํฐ๊ฐ ์ ์ฅ๋๋ ๋ฌผ๋ฆฌ์ ์์น๋ฅผ ์๋ฏธํฉ๋๋ค.
์ปฌ๋ ์ (Collection)
- ๋ฐ์ดํฐ๋ฅผ ์กฐ์งํํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ Firestar์ ๊ฐ์ฒด์ ๋๋ค.
๋ฌธ์(Document)
- ์ปฌ๋ ์ ๋ด ๊ฐ๋ณ ๋ ์ฝ๋๋ฅผ ๋งํ๋ฉฐ, ์ค์ ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ ๋ฐ์ดํฐ์ ๋๋ค.
์ด์ Firebase ํ์ผ์์ ์ฌ์ฉ์ ์ปฌ๋ ์
์ ์ ํํ ๊ฒ์
๋๋ค.
์ฌ์ฉ์ ์ปฌ๋ ์
์ db.collection
ํจ์๋ฅผ ํตํด ์ ํ๋๋ฉฐ, ์ด ์ปฌ๋ ์
์ ์ด๋ฆ์ users
๊ฐ ๋ฉ๋๋ค.
์ด ํจ์๋ ์ฌ์ฉ์ ์ปฌ๋ ์
๊ณผ ์์
ํ๋ ๋ฐ ํ์ํ ๋ฉ์๋์ ์์ฑ์ ๊ฐ๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
์ด ์ปฌ๋ ์
์ ๋ด๋ณด๋ด์ด ๋ชจ๋ ์ปดํฌ๋ํธ์์ ์ ๊ทผํ ์ ์๋๋ก ํฉ๋๋ค.
๋ฑ๋ก ์์ ์ปดํฌ๋ํธ๋ก ๋์๊ฐ์, ์ธ์ฆ ๊ฐ์ฒด์ ๊ฐ์ ธ์ค๊ธฐ ๋ฌธ์ ์ฌ์ฉ์ ์ปฌ๋ ์
๊ฐ์ฒด๋ก ๋ณ๊ฒฝํฉ๋๋ค.
๋ฑ๋ก ํจ์์์๋ ์ฌ์ฉ์ ์ปฌ๋ ์
์ add
ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฌธ์๋ฅผ ์ถ๊ฐํฉ๋๋ค.
์ด add
ํจ์๋ ์ฝ์
ํ ๊ฐ์ฒด๋ฅผ ์ธ์๋ก ๋ฐ์ต๋๋ค.
์ฌ๊ธฐ์๋ ๋น๋ฐ๋ฒํธ์ ์ด์ฉ ์ฝ๊ด ๋์ ์ฌ๋ถ์ ๊ฐ์ ์ผ๋ถ ์์ฑ์ ์ ์ธํ ์ฌ์ฉ์ ๋ฐ์ดํฐ๊ฐ ํฌํจ๋ฉ๋๋ค.
์ด ํจ์๋ ํ๋ก๋ฏธ์ค๋ฅผ ๋ฐํํ๋ฏ๋ก async/await
๊ตฌ๋ฌธ์ ์ฌ์ฉํ์ฌ ์ฝ๋๋ฅผ ๋ณด๋ค ์ฝ๊ธฐ ์ฝ๊ฒ ๋ง๋ญ๋๋ค.
์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ์ํด try/catch
๊ตฌ๋ฌธ์ ์ฌ์ฉํฉ๋๋ค.
์ด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ฝ์
ํ๋ ๋ฐฉ๋ฒ์ ๋ง์ณค์ต๋๋ค.
์ด ์ ๋ณด๋ ์ผ๋ฐ์ ์ผ๋ก ์ธ์ฆ ์๋น์ค์์ ์ ์ฅํ ์ ์๋ ์ ๋ณด์
๋๋ค.
์ด๋ฉ์ผ์ ์ด๋ฏธ ์ธ์ฆ ์๋น์ค์ ์ ์ฅ๋์ด ์์ง๋ง, ๋ฐ์ดํฐ๋ฒ ์ด์ค์๋ ์ ์ฅํ๋ ๊ฒ์ด ์ ์ฉํฉ๋๋ค.
์ด๋ ๊ฒ ํ๋ฉด ์ด๋ฉ์ผ์ ๊ธฐ์ค์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ํํฐ๋งํ๊ณ ์ ๋ ฌํ ์ ์์ต๋๋ค.
ํ
์คํธ๋ฅผ ์ํด ๋ธ๋ผ์ฐ์ ๋ก ์ ํํ๊ณ ๊ฐ๋ฐ์ ๋๊ตฌ๋ฅผ ์ด์ด ์ค๋ฅ๊ฐ ์๋์ง ํ์ธํฉ๋๋ค.
์ ํจํ ๊ฐ์ผ๋ก ๋ฑ๋ก ์์์ ์์ฑํ๊ณ ์ ์ถํ ํ, ์ฑ๊ณต ๋ฉ์์ง๊ฐ ํ์๋๋ฉด ์ฌ์ฉ์ ์๊ฒฉ ์ฆ๋ช
์ ๋ณด๊ฐ ์ฝ์์ ๋ก๊ทธ๋์ง ์๋์ง ํ์ธํฉ๋๋ค.
๊ทธ๋ฐ ๋ค์ Firebase ์ฝ์์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ์ด์ง๋ก ์ด๋ํ์ฌ ๋ฐ์ดํฐ๊ฐ ์ฝ์
๋์๋์ง ํ์ธํฉ๋๋ค.
Firebase ์ฝ์์์๋ ์๋ก์ด ์ปฌ๋ ์
, ๋ฌธ์, ๊ทธ๋ฆฌ๊ณ ๋ฌธ์์ ๋ฐ์ดํฐ๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
๋ฌธ์๋ ๋ฌด์์ ๋ฌธ์์ด๋ก ๋ ๊ณ ์ ID๋ฅผ ๊ฐ์ง๋ฉฐ, ์ด๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฐ ๋ ์ฝ๋๋ฅผ ์ถ์ ํ๋ ๋ฐ ํ์ํฉ๋๋ค.
Firebase๋ ์๋์ผ๋ก ID๋ฅผ ์์ฑํ์ง๋ง, ํ์ํ ๊ฒฝ์ฐ ์ง์ ID๋ฅผ ์ ๊ณตํ ์๋ ์์ต๋๋ค.
์ด ๊ณผ์ ์ ํตํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ฝ์
ํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค.
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
import { getFirestore, collection } from 'firebase/firestore'
const firebaseConfig = {
apiKey: 'AIzaSyA4X8qu0cS5CeKS9sExbwWgTP4mW9crP84',
authDomain: 'vue-guide-2.firebaseapp.com',
projectId: 'vue-guide-2',
storageBucket: 'vue-guide-2.appspot.com',
// messagingSenderId: '475228612189',
appId: '1:475228612189:web:ff548967df307e9a110fb4'
}
initializeApp(firebaseConfig)
const auth = getAuth()
const db = getFirestore()
const usersCollection = collection(db, 'users')
export { auth, db, usersCollection }
<script setup lang="ts">
import { ref } from 'vue'
import { createUserWithEmailAndPassword } from 'firebase/auth'
import { auth, usersCollection } from '@/includes/firebase'
import { addDoc } from 'firebase/firestore'
const schema = ref({
name: 'required|min:3|max:100|alpha_spaces',
email: 'required|min:3|max:100|email',
age: 'required|min_value:18|max_value:100',
password: 'required|min:9|max:100|excluded:password', // password ๋จ์ด๋ฅผ ์
๋ ฅํ๋ฉด ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํต๊ณผํ ์ ์๋ค.
confirm_password: 'passwords_mismatch:@password', // ๋น๋ฐ๋ฒํธ ์
๋ ฅํ ๋, ๋น๋ฐ๋ฒํธ ์ฌํ์ธ์ฐจ ์
๋ ฅํ๋ ํ๋ // ์์ password ๋ถ๋ถ๊ณผ ์ผ์นํ๋์ง ํ์ธํจ
country: 'required|country_excluded:Antarctica',
tos: 'required'
})
const userData = ref({
country: 'USA'
})
const reg_in_submission = ref(false)
const reg_show_alert = ref(false)
const reg_alert_variant = ref('bg-blue-500')
const reg_alert_msg = ref('Please wait! Your acount is being created.')
const register = async (values: {
age: number
confirm_password: string
country: string
email: string
name: string
password: string
tos: number
}) => {
// VeeForm ์์ VeeField์ ๋ชจ๋ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํต๊ณผํด์ผ์ง values ๊ฐ์ด ๋ค์ด์จ๋ค.
// ex.
// {
// age: 31
// confirm_password: "asdf"
// country: "Germany"
// email: "sdf@sdf.com"
// name: "sfdf"
// password: "asdf"
// tos: "1"
// }
reg_show_alert.value = true
reg_in_submission.value = true
reg_alert_variant.value = 'bg-blue-500'
reg_alert_msg.value = 'Please wait! Your account is being created.'
let userCred
try {
userCred = await createUserWithEmailAndPassword(auth, values.email, values.password)
} catch (error) {
reg_in_submission.value = false
reg_alert_variant.value = 'bg-red-500'
reg_alert_msg.value = 'An unexpected error occured. Please try again later.'
return
}
try {
await addDoc(usersCollection, {
name: values.name,
email: values.email,
age: values.age,
country: values.country
})
} catch (error) {
reg_in_submission.value = false
reg_alert_variant.value = 'bg-red-500'
reg_alert_msg.value = 'An unexpected error occured. Please try again later.'
return
}
reg_alert_variant.value = 'bg-green-500'
reg_alert_msg.value = 'Success! Your account has been created.'
console.log(userCred)
}
</script>
<template>
<!-- Registration Form -->
<div
class="text-white text-center font-bold p-4 rounded mb-4"
v-if="reg_show_alert"
:class="reg_alert_variant"
>
{{ reg_alert_msg }}
</div>
<VeeForm :validation-schema="schema" @submit="register" :initial-values="userData">
<!-- Name -->
<div class="mb-3">
<label class="inline-block mb-2">Name</label>
<VeeField
type="text"
name="name"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="Enter Name"
/>
<ErrorMessage class="text-red-600" name="name" />
</div>
<!-- Email -->
<div class="mb-3">
<label class="inline-block mb-2">Email</label>
<VeeField
type="email"
name="email"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="Enter Email"
/>
<ErrorMessage class="text-red-600" name="email" />
</div>
<!-- Age -->
<div class="mb-3">
<label class="inline-block mb-2">Age</label>
<VeeField
type="number"
name="age"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
/>
<ErrorMessage class="text-red-600" name="age" />
</div>
<!-- Password -->
<div class="mb-3">
<label class="inline-block mb-2">Password</label>
<VeeField name="password" :bails="false" #default="{ field, errors }">
<input
type="password"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="password"
v-bind="field"
/>
<div class="text-red-600" v-for="error in errors" :key="error">
{{ error }}
</div>
</VeeField>
</div>
<!-- Confirm Password -->
<div class="mb-3">
<label class="inline-block mb-2">Confirm Password</label>
<VeeField
type="password"
name="confirm_password"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
placeholder="Confirm Password"
/>
<ErrorMessage class="text-red-600" name="confirm_password" />
</div>
<!-- Country -->
<div class="mb-3">
<label class="inline-block mb-2">Country</label>
<VeeField
as="select"
name="country"
class="block w-full py-1.5 px-3 text-gray-800 border border-gray-300 transition duration-500 focus:outline-none focus:border-black rounded"
>
<option value="USA">USA</option>
<option value="Mexico">Mexico</option>
<option value="Germany">Germany</option>
<option value="Antarctica">Antarctica</option>
</VeeField>
<ErrorMessage class="text-red-600" name="country" />
</div>
<!-- TOS -->
<div class="mb-3 pl-6">
<VeeField
type="checkbox"
name="tos"
value="1"
class="w-4 h-4 float-left -ml-6 mt-1 rounded"
/>
<label class="inline-block">Accept terms of service</label>
<ErrorMessage class="text-red-600 block" name="tos" />
</div>
<button
type="submit"
class="block w-full bg-purple-600 text-white py-1.5 px-3 rounded transition hover:bg-purple-700"
:disabled="reg_in_submission"
>
Submit
</button>
</VeeForm>
</template>
<style scoped></style>