Skip to content

Commit 27bf3ad

Browse files
authored
Merge pull request #63 from traPtitech/feat/contest_new_page
/contests/new
2 parents 17e6b41 + 933e73a commit 27bf3ad

File tree

5 files changed

+209
-19
lines changed

5 files changed

+209
-19
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<script lang="ts" setup>
2+
import RequiredChip from '/@/components/UI/Required.vue'
3+
interface Props {
4+
label: string
5+
required?: boolean
6+
}
7+
defineProps<Props>()
8+
</script>
9+
10+
<template>
11+
<div>
12+
<div :class="$style.labelContainer">
13+
<label :class="$style.label">{{ label }}</label>
14+
<required-chip v-if="required" />
15+
</div>
16+
<div :class="$style.form">
17+
<slot />
18+
</div>
19+
</div>
20+
</template>
21+
22+
<style lang="scss" module>
23+
.labelContainer {
24+
display: flex;
25+
align-items: center;
26+
gap: 0.5rem;
27+
margin-bottom: 0.5rem;
28+
}
29+
.label {
30+
font-weight: bold;
31+
font-size: 20px;
32+
}
33+
.form {
34+
margin-left: 0.5rem;
35+
}
36+
</style>

src/pages/ContestCreate.vue

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/pages/ContestNew.vue

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<script lang="ts" setup>
2+
import ContentHeader from '/@/components/Layout/ContentHeader.vue'
3+
import PageContainer from '/@/components/Layout/PageContainer.vue'
4+
import BaseButton from '/@/components/UI/BaseButton.vue'
5+
import apis, { CreateContestRequest } from '/@/lib/apis'
6+
import { RouterLink, useRouter } from 'vue-router'
7+
import { computed, reactive, ref } from 'vue'
8+
import LabeledForm from '/@/components/Form/LabeledForm.vue'
9+
import FormInput from '/@/components/UI/FormInput.vue'
10+
import FormTextArea from '/@/components/UI/FormTextArea.vue'
11+
import FormDuration from '/@/components/UI/FormDuration.vue'
12+
import { isValidDuration, isValidLength, isValidUrl } from '/@/use/validate'
13+
14+
const router = useRouter()
15+
16+
const formValues = reactive<Required<CreateContestRequest>>({
17+
name: '',
18+
link: '',
19+
description: '',
20+
duration: {
21+
since: '',
22+
until: ''
23+
}
24+
})
25+
const isSending = ref(false)
26+
const canSubmit = computed(
27+
() =>
28+
!isSending.value &&
29+
isValidLength(formValues.name, 1, 32) &&
30+
(formValues.link !== '' ? isValidUrl(formValues.link) : true) &&
31+
isValidDuration(formValues.duration) &&
32+
isValidLength(formValues.description, 1, 256)
33+
)
34+
35+
const createContest = async () => {
36+
isSending.value = true
37+
try {
38+
const requestData: CreateContestRequest = {
39+
...formValues,
40+
link: formValues.link || undefined,
41+
duration: {
42+
since: formValues.duration.since + ':00Z',
43+
until: formValues.duration.until + ':00Z'
44+
}
45+
}
46+
const res = await apis.createContest(requestData)
47+
//eslint-disable-next-line no-console
48+
console.log('追加しました') // todo:トーストとかに変えたい
49+
router.push(`/contests/${res.data.id}`)
50+
} catch {
51+
//eslint-disable-next-line no-console
52+
console.log('追加に失敗しました')
53+
}
54+
isSending.value = false
55+
}
56+
</script>
57+
58+
<template>
59+
<page-container>
60+
<div :class="$style.headerContainer">
61+
<content-header
62+
icon-name="mdi:trophy-outline"
63+
:header-texts="[
64+
{ title: 'Contests', url: '/contests' },
65+
{ title: 'New', url: '/contests/new' }
66+
]"
67+
detail="コンテストを作成します。"
68+
:class="$style.header"
69+
/>
70+
</div>
71+
<form>
72+
<labeled-form label="コンテスト名" required :class="$style.labeledForm">
73+
<form-input
74+
v-model="formValues.name"
75+
placeholder="コンテスト名を入力"
76+
:limit="32"
77+
/>
78+
</labeled-form>
79+
<labeled-form label="開催日時" required :class="$style.labeledForm">
80+
<form-duration v-model="formValues.duration" />
81+
</labeled-form>
82+
<labeled-form label="リンク" :class="$style.labeledForm">
83+
<form-input
84+
v-model="formValues.link"
85+
placeholder="https://"
86+
has-anchor
87+
/>
88+
</labeled-form>
89+
<labeled-form label="説明" required :class="$style.labeledForm">
90+
<form-text-area
91+
v-model="formValues.description"
92+
placeholder="説明を入力"
93+
:rows="3"
94+
:limit="256"
95+
/>
96+
</labeled-form>
97+
</form>
98+
<div :class="$style.buttonContainer">
99+
<router-link to="/contests" :class="$style.link">
100+
<base-button
101+
:class="$style.backButton"
102+
type="secondary"
103+
icon="mdi:arrow-left"
104+
>
105+
Back
106+
</base-button>
107+
</router-link>
108+
<base-button
109+
:is-disabled="!canSubmit"
110+
type="primary"
111+
icon="mdi:plus"
112+
@click="createContest"
113+
>
114+
Create
115+
</base-button>
116+
</div>
117+
</page-container>
118+
</template>
119+
120+
<style lang="scss" module>
121+
.headerContainer {
122+
display: flex;
123+
justify-content: space-between;
124+
align-items: center;
125+
}
126+
.header {
127+
margin: 4rem 0 2rem;
128+
}
129+
.labeledForm {
130+
margin-bottom: 2rem;
131+
}
132+
.link {
133+
text-decoration: none;
134+
color: inherit;
135+
}
136+
.buttonContainer {
137+
display: flex;
138+
justify-content: space-between;
139+
align-items: center;
140+
margin-top: 4rem;
141+
}
142+
.backButton {
143+
margin-left: 0.5rem;
144+
}
145+
</style>

src/router/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const Events = () => import('/@/pages/Events.vue')
99
const Event = () => import('/@/pages/Event.vue')
1010
const Contests = () => import('/@/pages/Contests.vue')
1111
const Contest = () => import('/@/pages/Contest.vue')
12-
const ContestCreate = () => import('/@/pages/ContestCreate.vue')
12+
const ContestNew = () => import('/@/pages/ContestNew.vue')
1313

1414
const routes = [
1515
{
@@ -48,9 +48,9 @@ const routes = [
4848
component: Contest
4949
},
5050
{
51-
path: '/contests/create',
52-
name: 'ContestCreate',
53-
component: ContestCreate
51+
path: '/contests/new',
52+
name: 'ContestNew',
53+
component: ContestNew
5454
},
5555
{
5656
path: '/users',

src/use/validate.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Duration } from '/@/lib/apis'
2+
3+
export const isValidLength = (value: string, min: number, max: number) => {
4+
if (value.length < min) return false
5+
if (value.length > max) return false
6+
return true
7+
}
8+
9+
export const isValidUrl = (link: string) => {
10+
let url
11+
try {
12+
url = new URL(link)
13+
} catch {
14+
return false
15+
}
16+
return url.protocol === 'http:' || url.protocol === 'https:'
17+
}
18+
19+
export const isValidDuration = (duration: Duration) => {
20+
if (duration.since && duration.until) {
21+
return duration.since <= duration.until
22+
}
23+
return false
24+
}

0 commit comments

Comments
 (0)