Skip to content

Commit 2e3bdc9

Browse files
authored
Merge pull request #652 from pycontw/feat/597
feat: add Reviewers section to main page layout
2 parents b73b203 + 21c2708 commit 2e3bdc9

File tree

12 files changed

+378
-11
lines changed

12 files changed

+378
-11
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { genI18nMessages } from '~/utils/i18n.utils'
2+
3+
export default genI18nMessages({
4+
'en-us': {
5+
reviewers: 'Reviewers',
6+
},
7+
'zh-hant': {
8+
reviewers: '審稿人員',
9+
},
10+
})

components/reviewers/Reviewers.vue

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
<template>
2+
<div class="Reviewers">
3+
<core-h2
4+
:title="$t('reviewers')"
5+
:is-bulleted="isBulleted"
6+
class="intro-h2"
7+
/>
8+
9+
<div class="reviewerGroup">
10+
<div
11+
v-for="(member, i) in staffsData"
12+
:key="`reviewer_${i}`"
13+
class="reviewer"
14+
@click="openModal(member)"
15+
>
16+
<div class="reviewerPhoto">
17+
<img :src="getPhotoUrl(member)" />
18+
</div>
19+
<div class="reviewerName">
20+
{{ member.full_name }}
21+
</div>
22+
</div>
23+
</div>
24+
25+
<!-- 彈出視窗 -->
26+
<div v-if="selectedReviewer" class="modal" @click.self="closeModal">
27+
<div class="modal-content">
28+
<img
29+
src="~@/static/img/reviewer/Clip-LR.svg"
30+
class="absolute left-0 top-[108px] rotate-0"
31+
/>
32+
<img
33+
src="~@/static/img/reviewer/Clip-LR.svg"
34+
class="absolute right-0 top-[232px] rotate-180"
35+
/>
36+
<img
37+
src="~@/static/img/reviewer/Clip-TB.svg"
38+
class="absolute right-[120px] top-0 rotate-0"
39+
/>
40+
<img
41+
src="~@/static/img/reviewer/Clip-TB.svg"
42+
class="absolute bottom-0 left-[129px] rotate-180"
43+
/>
44+
<img
45+
src="~@/static/img/reviewer/DE-01.svg"
46+
class="absolute left-[495px] top-[571px]"
47+
/>
48+
<img
49+
src="~@/static/img/reviewer/DE-02.svg"
50+
class="absolute left-[514px] top-[105px]"
51+
/>
52+
<img
53+
src="~@/static/img/reviewer/DE-03.svg"
54+
class="absolute left-[96px] top-[194px]"
55+
/>
56+
<button
57+
class="modal-close-button"
58+
aria-label="關閉"
59+
@click="closeModal"
60+
>
61+
×
62+
</button>
63+
<div class="modal-content-header">
64+
<img
65+
class="selectedReviewerPhoto"
66+
:src="getPhotoUrl(selectedReviewer)"
67+
alt="photo"
68+
/>
69+
<p class="selectedReviewerName">
70+
{{ selectedReviewer.full_name }}
71+
</p>
72+
<div class="reviewerProfile">
73+
<div
74+
v-if="!!selectedReviewer.facebook_profile_url"
75+
class="selectedReviewerExtLink"
76+
>
77+
<ext-link
78+
:href="selectedReviewer.facebook_profile_url"
79+
>
80+
<facebook-icon />
81+
</ext-link>
82+
</div>
83+
<div
84+
v-if="!!selectedReviewer.github_profile_url"
85+
class="selectedReviewerExtLink"
86+
>
87+
<ext-link
88+
:href="selectedReviewer.github_profile_url"
89+
>
90+
<github-icon />
91+
</ext-link>
92+
</div>
93+
<div
94+
v-if="!!selectedReviewer.twitter_profile_url"
95+
class="selectedReviewerExtLink"
96+
>
97+
<ext-link
98+
:href="selectedReviewer.twitter_profile_url"
99+
>
100+
<twitter-icon />
101+
</ext-link>
102+
</div>
103+
</div>
104+
</div>
105+
<div class="modal-content-body">
106+
<p>{{ selectedReviewer.bio }}</p>
107+
</div>
108+
</div>
109+
</div>
110+
</div>
111+
</template>
112+
113+
<script>
114+
import CoreH2 from '@/components/core/titles/H2'
115+
import ExtLink from '@/components/core/links/ExtLink.vue'
116+
import FacebookIcon from '@/components/core/icons/FacebookIcon'
117+
import GithubIcon from '@/components/core/icons/GithubIcon'
118+
import TwitterIcon from '@/components/core/icons/TwitterIcon'
119+
import i18n from './Reviewers.i18n'
120+
121+
export default {
122+
i18n,
123+
name: 'Reviewers',
124+
components: {
125+
CoreH2,
126+
ExtLink,
127+
FacebookIcon,
128+
GithubIcon,
129+
TwitterIcon,
130+
},
131+
props: {
132+
isBulleted: {
133+
type: Boolean,
134+
default: true,
135+
},
136+
},
137+
data() {
138+
return { selectedReviewer: null }
139+
},
140+
computed: {
141+
staffsData() {
142+
return this.$store.state.reviewerData
143+
},
144+
},
145+
methods: {
146+
openModal(staff) {
147+
this.selectedReviewer = staff
148+
},
149+
closeModal() {
150+
this.selectedReviewer = null
151+
},
152+
getPhotoUrl(member) {
153+
if (member && member.photo_url && member.photo_url.trim() !== '') {
154+
return member.photo_url
155+
}
156+
return require('@/static/img/staff/default.jpg')
157+
},
158+
},
159+
}
160+
</script>
161+
162+
<style lang="postcss" scoped>
163+
.reviewerGroup {
164+
@apply grid grid-cols-3 sm:grid-cols-4 md:grid-cols-4;
165+
@apply lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-8;
166+
@apply gap-x-4 gap-y-8 md:gap-x-8 md:gap-y-4;
167+
@apply ml-0.5 lg:ml-14;
168+
}
169+
170+
.reviewerPhoto {
171+
@apply aspect-w-1 aspect-h-1 mb-3 w-full;
172+
}
173+
174+
.reviewerPhoto img {
175+
@apply rounded-full object-cover;
176+
}
177+
178+
.reviewerName {
179+
@apply font-serif text-xs text-white md:text-sm;
180+
@apply p-0.5 text-center;
181+
}
182+
183+
.modal {
184+
@apply fixed left-0 top-0 z-[1000] h-screen w-screen;
185+
@apply box-border flex items-center justify-center p-4;
186+
@apply bg-[#000000] bg-opacity-50 backdrop-blur-sm;
187+
}
188+
189+
.modal-close-button {
190+
@apply absolute right-2 top-2;
191+
@apply h-12 w-12 text-4xl font-bold text-[#E099E1];
192+
@apply cursor-pointer;
193+
}
194+
195+
.modal-content {
196+
@apply relative w-[724px] bg-[#121023] text-white;
197+
@apply rounded-[24px] border-[3px] border-[#E099E1] text-center;
198+
@apply h-[678px] max-h-[90vh] shadow-xl;
199+
@apply flex flex-col items-center justify-center;
200+
}
201+
202+
.modal-content-header {
203+
@apply relative flex flex-col items-center justify-between;
204+
}
205+
206+
.modal-content-header .selectedReviewerPhoto {
207+
@apply mx-auto h-[143px] w-[143px] rounded-[50%] object-cover;
208+
}
209+
210+
.modal-content-body {
211+
@apply mt-[62px] px-[57px];
212+
}
213+
214+
.selectedReviewerName {
215+
@apply my-3 text-center font-serif font-black;
216+
}
217+
218+
.reviewerProfile {
219+
@apply flex justify-center;
220+
}
221+
222+
.selectedReviewerExtLink {
223+
@apply mx-2 font-bold text-pink-700;
224+
}
225+
226+
.selectedReviewerExtLink:hover {
227+
@apply text-primary-500;
228+
}
229+
230+
.selectedReviewerExtLink svg {
231+
@apply fill-primary-300;
232+
}
233+
</style>

components/reviewers/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Reviewers from './Reviewers'
2+
3+
export { Reviewers }

db.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2187,5 +2187,39 @@
21872187
"location": "6-r2",
21882188
"event_type": "talk"
21892189
}
2190+
],
2191+
"reviewers": [
2192+
{
2193+
"full_name": "蘇文鈺",
2194+
"bio": "在資訊量爆炸的現在,任何媒體甚至是電商都需要一個能過濾雜訊,將有效的資訊正確傳遞給各個不同用戶的推薦系統。 \r\n\r\n標題中的 modern 指的是一個包含召回、排序兩階段推薦的現代化作法,而 large content 則是要談如何透過 pyspark 建立可規模化的推薦系統框架。最後要和大家分享,一個擁有數億篇文章與數千萬每日瀏覽量的部落格網站如何從無到有建立這一套作法。",
2195+
"photo_url": "",
2196+
"facebook_profile_url": "https://google.com",
2197+
"twitter_profile_url": "https://google.com",
2198+
"github_profile_url": "https://google.com"
2199+
},
2200+
{
2201+
"full_name": "馬誼郎",
2202+
"bio": "出生於南非的美國企業家 \r\n\r\n出生於南非的美國企業家出生於南非的美國企業家出生於南非的美國企業家",
2203+
"photo_url": "",
2204+
"facebook_profile_url": "https://google.com",
2205+
"twitter_profile_url": "https://google.com",
2206+
"github_profile_url": "https://google.com"
2207+
},
2208+
{
2209+
"full_name": "GTB",
2210+
"bio": "GTB is a security researcher and open-source enthusiast from Taiwan. A paranoid Pythonista and CPython contributor who now focuses on Android reverse engineering and malware analysis. And as a member of the PyCon Taiwan Program Committee, previously presented at Black Hat, DEFCON, HITB, ROOTCON, GrayHat, PyCon Europe/TW/KR/MY/IN. He’s the co-founder of Quark-Engine and the Quark package maintainer on Kali Linux, leading Quark to participate in the GSoC under the Honeynet Project since 2021.",
2211+
"photo_url": "https://proxy.duckduckgo.com/iu/?u=https://i.imgur.com/xiAfNVQ.jpeg",
2212+
"facebook_profile_url": "https://google.com",
2213+
"twitter_profile_url": "https://google.com",
2214+
"github_profile_url": "https://google.com"
2215+
},
2216+
{
2217+
"full_name": "Yucheng",
2218+
"bio": "I'm a successful business man. Better than Mark.",
2219+
"photo_url": "https://proxy.duckduckgo.com/iu/?u=https://i.imgur.com/xiAfNVQ.jpeg",
2220+
"facebook_profile_url": "https://google.com",
2221+
"twitter_profile_url": "https://google.com",
2222+
"github_profile_url": "https://google.com"
2223+
}
21902224
]
21912225
}

pages/index.vue

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@
103103
</sponsor-card-collection>
104104
</div>
105105
</I18nPageWrapper>
106+
<I18nPageWrapper>
107+
<Reviewers :is-bulleted="isBulleted" />
108+
</I18nPageWrapper>
106109
<transition name="fade">
107110
<modal
108111
v-if="isOpened"
@@ -118,7 +121,7 @@
118121
</template>
119122

120123
<script>
121-
import { mapState } from 'vuex'
124+
// import { mapState } from 'vuex'
122125
import i18n from '@/i18n/index.i18n'
123126
import { landingButtonConfig } from '@/configs/pageLanding'
124127
import I18nPageWrapper from '@/components/core/i18n/PageWrapper'
@@ -129,8 +132,9 @@ import SponsorCard from '@/components/sponsors/SponsorCard'
129132
import Modal from '@/components/core/modal/Modal'
130133
import SponsorCardCollection from '@/components/sponsors/SponsorCardCollection'
131134
import Intro from '@/components/intro/Intro'
132-
import swal from 'sweetalert2'
135+
import Swal from 'sweetalert2'
133136
import 'sweetalert2/dist/sweetalert2.css'
137+
import Reviewers from '@/components/reviewers/Reviewers'
134138
135139
export default {
136140
i18n,
@@ -144,14 +148,25 @@ export default {
144148
SponsorCardCollection,
145149
I18nPageWrapper,
146150
Intro,
151+
Reviewers,
147152
},
148153
async asyncData({ store, payload }) {
149-
if (payload) return { sponsorsData: payload }
150-
await store.dispatch('$getSponsorsData')
151-
// const sponsorsData = store.state.sponsorsData
152-
// return {
153-
// sponsorsData,
154-
// }
154+
if (payload) {
155+
return {
156+
sponsorsData: payload.sponsorsData || [],
157+
staffsData: payload.staffsData || [],
158+
}
159+
}
160+
await Promise.all([
161+
store.dispatch('$getSponsorsData'),
162+
store.dispatch('$getReviewerData'),
163+
])
164+
const sponsorsData = store.state.sponsorsData
165+
const staffsData = store.state.staffsData
166+
return {
167+
sponsorsData,
168+
staffsData,
169+
}
155170
},
156171
data() {
157172
return {
@@ -162,7 +177,7 @@ export default {
162177
}
163178
},
164179
computed: {
165-
...mapState(['sponsorsData']),
180+
// ...mapState(['sponsorsData']),
166181
isBulleted() {
167182
if (process.client) {
168183
const width = window.innerWidth
@@ -187,6 +202,7 @@ export default {
187202
},
188203
created() {
189204
this.$store.dispatch('$getSponsorsData')
205+
this.$store.dispatch('$getReviewerData')
190206
},
191207
methods: {
192208
showModal(sponsor) {
@@ -199,7 +215,8 @@ export default {
199215
return data[attributeName]
200216
},
201217
showSwal() {
202-
return new swal({ // eslint-disable-line
218+
return new Swal({
219+
// eslint-disable-line
203220
title: this.$i18n.t('typhoonInfoTitle'),
204221
html: this.$i18n
205222
.t('typhoonInfo')

routes.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
"/api/events/speeches/\\?event_types=:event_type": "/speeches/?event_type=:event_type",
66
"/api/events/speeches/category/:category": "/speeches/?category=:category",
77
"/api/events/speeches/:event_type/:id": "/speeches/?event_type=:event_type&id=:id&singular=1",
8-
"/api/events/schedule": "/schedules"
8+
"/api/events/schedule": "/schedules",
9+
"/api/users?role=Reviewer": "/reviewers"
910
}

0 commit comments

Comments
 (0)