Skip to content

Commit 71a40f0

Browse files
authored
Merge pull request #19 from TaskFlow-CLAP/CLAP-103
CLAP-103 로그인 페이지 및 공통 모달 컴포넌트 UI 제작
2 parents 4be5449 + 68ec8c5 commit 71a40f0

File tree

9 files changed

+446
-0
lines changed

9 files changed

+446
-0
lines changed

src/App.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
<script setup lang="ts">
2+
3+
import { RouterView } from 'vue-router'
4+
import TopMenu from './components/TopMenu.vue'
25
import TheView from './layout/TheView.vue'
6+
37
</script>
48

59
<template>
10+
<TopMenu />
611
<TheView />
712
</template>

src/assets/styles.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,19 @@ body {
5252
.filter-dropdown-option {
5353
@apply text-xs p-2 rounded text-center cursor-pointer;
5454
}
55+
56+
.button-large {
57+
@apply flex w-full py-3 font-bold border rounded items-center justify-center min-w-[138px];
58+
}
59+
.button-large-default {
60+
@apply button-large bg-white text-zinc-400 border-zinc-400;
61+
}
62+
.button-large-red {
63+
@apply button-large bg-red-1 text-white border-red-1;
64+
}
65+
.button-large-primary {
66+
@apply button-large bg-primary1 text-white border-primary1;
67+
}
68+
.input-box {
69+
@apply block w-full px-4 py-4 border border-zinc-300 rounded focus:outline-none;
70+
}

src/components/ModalView.vue

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<template>
2+
<div
3+
v-if="isOpen"
4+
class="fixed inset-0 bg-black bg-opacity-15 flex justify-center items-center z-50"
5+
@click.self="closeModal">
6+
<div class="bg-white rounded-lg shadow-lg px-8 py-8 min-w-[364px]">
7+
<div class="mb-2 min-h-16">
8+
<div
9+
v-if="type == 'successType'"
10+
class="flex ic justify-center">
11+
<CommonIcons :name="successIcon" />
12+
</div>
13+
<div
14+
v-if="type == 'failType' || type == 'inputType'"
15+
class="flex ic justify-center">
16+
<CommonIcons :name="failIcon" />
17+
</div>
18+
<div
19+
v-if="type == 'warningType'"
20+
class="flex ic justify-center">
21+
<CommonIcons :name="warningIcon" />
22+
</div>
23+
</div>
24+
25+
<div>
26+
<div class="flex text-2xl font-bold justify-center mb-2">
27+
<slot name="header"></slot>
28+
</div>
29+
<div
30+
v-if="type != 'inputType'"
31+
class="flex text-sm font-bold text-body justify-center">
32+
<slot name="body"></slot>
33+
</div>
34+
</div>
35+
36+
<textarea
37+
v-if="type == 'inputType'"
38+
v-model="textValue"
39+
placeholder="거부 사유를 입력해주세요"
40+
class="flex border w-full border-zinc-300 px-4 py-3 focus:outline-none resize-none mt-6 h-[120px]" />
41+
<div class="mt-8">
42+
<button
43+
class="button-large-primary"
44+
v-if="type == 'successType'"
45+
@click="closeModal">
46+
확인
47+
</button>
48+
49+
<button
50+
class="button-large-default"
51+
v-if="type == 'failType'"
52+
@click="closeModal">
53+
확인
54+
</button>
55+
56+
<div
57+
class="flex mt-8 items-center"
58+
v-if="type == 'warningType' || type == 'inputType'">
59+
<div class="mr-6">
60+
<button
61+
class="button-large-default"
62+
@click="closeModal">
63+
취소
64+
</button>
65+
</div>
66+
<div>
67+
<button
68+
class="button-large-red"
69+
@click="closeModal">
70+
{{ type === 'inputType' ? '거부' : '삭제' }}
71+
</button>
72+
</div>
73+
</div>
74+
</div>
75+
</div>
76+
</div>
77+
</template>
78+
79+
<script setup lang="ts">
80+
import { ref, watch } from 'vue'
81+
import CommonIcons from './common/CommonIcons.vue'
82+
import { successIcon, failIcon, warningIcon } from '../constants/iconPath'
83+
84+
const props = defineProps<{
85+
isOpen: boolean
86+
type?: string
87+
modelValue?: string
88+
}>()
89+
90+
const emit = defineEmits<{
91+
(e: 'close'): void
92+
(e: 'click'): void
93+
(e: 'update:modelValue', value: string): void
94+
}>()
95+
96+
const textValue = ref(props.modelValue || '')
97+
98+
watch(textValue, newValue => {
99+
emit('update:modelValue', newValue)
100+
})
101+
102+
const closeModal = () => {
103+
emit('close')
104+
}
105+
</script>

src/components/TopMenu.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<template>
2+
<header
3+
class="flex justify-center w-full bg-white text-black py-6 px-28 border-b-border-1 border-b-[2px]">
4+
<div class="container flex w-full justify-between items-center">
5+
<!-- 로고 -->
6+
<div class="text-xl font-semibold">TASKFLOW</div>
7+
</div>
8+
</header>
9+
</template>
10+
11+
<script lang="ts">
12+
export default {
13+
name: 'TopMenu' // 컴포넌트 이름
14+
}
15+
</script>

src/router/index.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,29 @@ import MyRequestView from '@/views/MyRequestView.vue'
44
const router = createRouter({
55
history: createWebHistory(import.meta.env.BASE_URL),
66
routes: [
7+
{
8+
path: '/login',
9+
name: 'login',
10+
component: () => import('../views/Login.vue')
11+
},
12+
13+
{
14+
path: '/pw-change',
15+
name: 'pwChange',
16+
component: () => import('../views/PwChange.vue')
17+
},
18+
{
19+
path: '/pw-change-email',
20+
name: 'pwChangeEmail',
21+
component: () => import('../views/PwChangeEmail.vue'),
22+
meta: { requiresAuth: true }
23+
},
24+
{
25+
path: '/pw-check',
26+
name: 'PWCheckView',
27+
component: () => import('../views/PwCheck.vue'),
28+
meta: { requiresAuth: true }
29+
},
730
{
831
path: '/my-request',
932
name: 'MyRequest',
@@ -22,4 +45,18 @@ const router = createRouter({
2245
]
2346
})
2447

48+
router.beforeEach((to, from, next) => {
49+
if (to.path === '/pw-change') {
50+
if (from.path === '/pw-check' || from.path === '/pw-change-email') {
51+
next()
52+
} else {
53+
alert('비밀번호를 먼저 확인해주세요.')
54+
next('/login')
55+
return
56+
}
57+
} else {
58+
next()
59+
}
60+
})
61+
2562
export default router

src/views/Login.vue

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<template>
2+
<div class="max-w-md mx-auto p-10">
3+
<div class="py-16">
4+
<div class="text-4xl font-bold text-center">
5+
<p class="pb-2">TaskFlow</p>
6+
<p class="pb-2">로그인</p>
7+
</div>
8+
<p class="text-center font-bold text-body">아이디와 비밀번호를 입력해주세요</p>
9+
</div>
10+
<form
11+
@submit.prevent="handleLogin"
12+
class="mb-2">
13+
<div class="mb-6">
14+
<input
15+
type="text"
16+
id="nickname"
17+
v-model="nickname"
18+
placeholder="닉네임을 입력해주세요"
19+
required
20+
class="input-box" />
21+
</div>
22+
<div class="mb-8">
23+
<input
24+
type="password"
25+
id="password"
26+
v-model="password"
27+
placeholder="비밀번호를 입력해주세요"
28+
required
29+
class="input-box" />
30+
</div>
31+
<button
32+
type="submit"
33+
class="button-large-primary">
34+
로그인
35+
</button>
36+
</form>
37+
<div class="flex w-full justify-center">
38+
<RouterLink
39+
class="text-body font-bold text-[12px]"
40+
to="/pw-change-email"
41+
>비밀번호 재설정</RouterLink
42+
>
43+
</div>
44+
</div>
45+
</template>
46+
47+
<script setup lang="ts">
48+
import { ref } from 'vue'
49+
50+
const nickname = ref('')
51+
const password = ref('')
52+
53+
const handleLogin = async () => {
54+
// 로그인 API 필요
55+
}
56+
</script>

src/views/PwChange.vue

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<template>
2+
<div class="max-w-md mx-auto p-10">
3+
<ModalView
4+
:isOpen="isModalOpen"
5+
:type="'checkType'"
6+
@close="pwChange">
7+
<template #header> 비밀번호가 변경 되었습니다 </template>
8+
<template #body> 다시 로그인 해주세요 </template>
9+
</ModalView>
10+
<div class="py-16">
11+
<div class="text-4xl font-bold text-center">
12+
<p class="pb-2">비밀번호</p>
13+
<p class="pb-2">재설정</p>
14+
</div>
15+
<p class="text-center font-bold text-body">초기 비밀번호를 변경해주세요</p>
16+
</div>
17+
<form
18+
@submit.prevent="handleChange"
19+
class="mb-2">
20+
<div class="mb-6">
21+
<input
22+
type="password"
23+
id="newPw"
24+
v-model="newPw"
25+
placeholder="새 비밀번호를 입력해주세요"
26+
required
27+
class="input-box" />
28+
</div>
29+
<div class="mb-8">
30+
<input
31+
type="password"
32+
id="checkPw"
33+
v-model="checkPw"
34+
placeholder="새 비밀번호를 다시 입력해주세요"
35+
required
36+
class="input-box" />
37+
</div>
38+
<button
39+
type="submit"
40+
class="button-large-primary">
41+
비밀번호 재설정
42+
</button>
43+
</form>
44+
<div class="flex w-full justify-center"></div>
45+
</div>
46+
</template>
47+
48+
<script setup lang="ts">
49+
import { ref } from 'vue'
50+
import { useRouter } from 'vue-router'
51+
import ModalView from '../components/ModalView.vue'
52+
53+
const newPw = ref('')
54+
const checkPw = ref('')
55+
const isModalOpen = ref(false)
56+
const router = useRouter()
57+
58+
const toggleModal = () => {
59+
isModalOpen.value = !isModalOpen.value
60+
}
61+
62+
const handleChange = () => {
63+
if (newPw.value === checkPw.value) {
64+
console.log('비밀번호 변경 성공!')
65+
toggleModal()
66+
// 비밀번호 재설정 API 호출 필요
67+
} else {
68+
alert('비밀번호가 일치하지 않습니다.')
69+
}
70+
}
71+
72+
const pwChange = () => {
73+
router.push('/login')
74+
}
75+
</script>

0 commit comments

Comments
 (0)