Skip to content

Commit ccb6866

Browse files
committed
Updates to latest biketag version with fixes for deleteTag and getQueue. Also includes the new create round page for BikeTag Ambassadors.
1 parent 9a88242 commit ccb6866

7 files changed

Lines changed: 299 additions & 7 deletions

File tree

functions/new.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { BikeTagClient, createTagObject, Game, Tag } from 'biketag'
2+
import {
3+
acceptCorsHeaders,
4+
getBikeTagClientOpts,
5+
getImageSource,
6+
getPayloadOpts,
7+
getProfileAuthorization,
8+
log,
9+
setNewBikeTagPost,
10+
} from './common'
11+
import { ErrorMessage, HttpStatusCode } from './common/constants'
12+
13+
export default async (req: Request) => {
14+
const headers = acceptCorsHeaders()
15+
log('[new-tag] Incoming request', { method: req.method, url: req.url })
16+
17+
if (req.method === 'OPTIONS') {
18+
log('[new-tag] OPTIONS preflight handled')
19+
return new Response(undefined, {
20+
status: HttpStatusCode.NoContent,
21+
headers,
22+
})
23+
}
24+
25+
if (req.method !== 'POST') {
26+
log('[new-tag] Method not allowed', { method: req.method }, 'warn')
27+
return new Response(ErrorMessage.MethodNotAllowed, {
28+
headers,
29+
status: HttpStatusCode.MethodNotAllowed,
30+
})
31+
}
32+
33+
try {
34+
const profile = await getProfileAuthorization(req)
35+
log('[new-tag] Profile authorization', { profile: profile?.sub ?? 'none' })
36+
37+
const payload = await getPayloadOpts(req)
38+
log('[new-tag] Payload received', payload)
39+
40+
const {
41+
ambassadorId,
42+
game: gameName,
43+
tagnumber,
44+
foundPlayer,
45+
foundTime,
46+
foundLocation,
47+
foundImageUrl,
48+
mysteryPlayer,
49+
mysteryTime,
50+
mysteryImageUrl,
51+
hint,
52+
} = payload
53+
54+
if (!profile?.sub || profile.sub !== ambassadorId) {
55+
log('[new-tag] Unauthorized attempt', { profile: profile?.sub ?? 'none' }, 'warn')
56+
return new Response("you don't have permission to do that", {
57+
headers,
58+
status: HttpStatusCode.Unauthorized,
59+
})
60+
}
61+
62+
const nonAdminOpts = getBikeTagClientOpts(req, true)
63+
const nonAdminBiketag = new BikeTagClient(nonAdminOpts)
64+
const game = (await nonAdminBiketag.game(undefined, { source: 'sanity' })) as Game
65+
const imageSource = getImageSource(game)
66+
67+
if (!game?.name) {
68+
log('[new-tag] No game found', { game: gameName }, 'error')
69+
return new Response(`Game not found for: ${gameName}`, {
70+
headers,
71+
status: HttpStatusCode.BadRequest,
72+
})
73+
}
74+
75+
const updatedOpts = getBikeTagClientOpts(req, true, true, game)
76+
const adminBiketag = new BikeTagClient(updatedOpts)
77+
78+
const currentBikeTag = (await adminBiketag.getTag(undefined, { source: imageSource })).data
79+
log('[new-tag] Current tag', { current: currentBikeTag?.tagnumber ?? 'none' })
80+
81+
const newTag: Tag = createTagObject({
82+
game: game.name,
83+
tagnumber,
84+
foundPlayer,
85+
foundTime,
86+
foundLocation,
87+
foundImageUrl,
88+
mysteryPlayer,
89+
mysteryTime,
90+
mysteryImageUrl,
91+
hint,
92+
})
93+
94+
const result = await setNewBikeTagPost(
95+
game,
96+
newTag,
97+
currentBikeTag,
98+
adminBiketag,
99+
nonAdminBiketag,
100+
)
101+
102+
log('[new-tag] setNewBikeTagPost result', result)
103+
104+
const status = result.errors
105+
? HttpStatusCode.BadRequest
106+
: HttpStatusCode.Accepted
107+
108+
return new Response(JSON.stringify(result), {
109+
headers,
110+
status,
111+
})
112+
113+
} catch (err: any) {
114+
log('[new-tag] Unhandled error', err, 'error')
115+
return new Response(
116+
JSON.stringify({
117+
success: false,
118+
error: err.message ?? 'Unknown error',
119+
}),
120+
{
121+
status: HttpStatusCode.InternalServerError,
122+
headers,
123+
}
124+
)
125+
}
126+
}

package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"@vueuse/head": "^2.0.0",
3636
"ajv": "^8.17.1",
3737
"autoprefixer": "^10.4.17",
38-
"biketag": "^3.5.35",
38+
"biketag": "^3.5.37",
3939
"bootstrap": "^5.3.3",
4040
"bootstrap-vue-next": "^0.15.5",
4141
"crypto-js": "^4.2.0",
@@ -72,7 +72,7 @@
7272
"vue3-markdown-it": "^1.0.10"
7373
},
7474
"peerDependencies": {
75-
"biketag": "^3.5.35",
75+
"biketag": "^3.5.37",
7676
"pinia": "^2.0.0",
7777
"vue": "^3.0.0",
7878
"vue-router": "^4.0.0"

src/components/QueueFound.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ const setImage = async (event) => {
422422
// }
423423
const results = await exifr.parse(await input.files[0].arrayBuffer())
424424
const createDate = results?.CreateDate ?? results?.DateTimeOriginal ?? Date.now()
425+
console.log('exif data:', results)
425426
426427
if (createDate < getCurrentBikeTag.value.mysteryTime) {
427428
toast.open({
@@ -432,6 +433,7 @@ const setImage = async (event) => {
432433
})
433434
} else {
434435
const GPSData = await exifr.gps(await input.files[0].arrayBuffer())
436+
console.log('GPSData from EXIF:', GPSData)
435437
436438
if (GPSData?.latitude && GPSData?.longitude) {
437439
gps.value = {

src/router/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Leaderboard from '@/views/Leaderboard.vue'
1111
import Login from '@/views/Login.vue'
1212
import Logout from '@/views/Logout.vue'
1313
import Map from '@/views/Map.vue'
14+
import New from '@/views/New.vue'
1415
import Play from '@/views/Play.vue'
1516
import Player from '@/views/Player.vue'
1617
import Players from '@/views/Players.vue'
@@ -143,6 +144,12 @@ if (isAuthenticationEnabled()) {
143144
beforeEnter: authGuard,
144145
component: Edit,
145146
},
147+
{
148+
path: '/new',
149+
name: 'New',
150+
beforeEnter: authGuard,
151+
component: New,
152+
},
146153
]
147154
}
148155

src/views/Dashboard.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@
3838
</span>
3939
Edit Current Post
4040
</router-link>
41+
42+
<router-link to="/new" class="action-button">
43+
<span class="icon">
44+
<!-- Sketch-style plus sign -->
45+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
46+
<line x1="12" y1="5" x2="12" y2="19" stroke-linecap="round" stroke-linejoin="round" />
47+
<line x1="5" y1="12" x2="19" y2="12" stroke-linecap="round" stroke-linejoin="round" />
48+
</svg>
49+
</span>
50+
Create New Round
51+
</router-link>
4152
</div>
4253
</div>
4354
</template>

src/views/New.vue

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<!-- eslint-disable vue/multi-word-component-names -->
2+
<template>
3+
<loading
4+
v-show="submitInProgress"
5+
v-model:active="submitInProgress"
6+
:is-full-page="true"
7+
class="realign-spinner"
8+
>
9+
<img class="spinner" src="@/assets/images/SpinningBikeV1.svg" alt="Loading..." />
10+
</loading>
11+
12+
<div class="queue-page">
13+
<div v-if="submitSuccess">
14+
A new BikeTag round for {{ getGameNameProper }} has been created!
15+
<bike-tag-button @click="router.push({ name: 'Home' })">
16+
Go to the Home Page
17+
</bike-tag-button>
18+
</div>
19+
20+
<div v-else>
21+
<h2>Start a New Round for {{ getGameNameProper }}</h2>
22+
23+
<EditBikeTag :tag="newTag" @update="onFieldUpdate" />
24+
25+
<bike-tag-button variant="light" class="big-btn" @click="onSubmitClick">
26+
Submit New Round
27+
</bike-tag-button>
28+
</div>
29+
30+
<form
31+
ref="submitError"
32+
name="new-round-error"
33+
action="new-round-error"
34+
method="POST"
35+
data-netlify="true"
36+
data-netlify-honeypot="bot-field"
37+
hidden
38+
>
39+
<input type="hidden" name="form-name" value="new-round-error" />
40+
<input type="hidden" name="ambassadorId" :value="getAmbassadorId" />
41+
<input type="hidden" name="message" />
42+
<input type="hidden" name="ip" value="" />
43+
</form>
44+
</div>
45+
</template>
46+
47+
<script setup name="NewRound">
48+
import { computed, inject, onMounted, reactive, ref } from 'vue'
49+
import { useI18n } from 'vue-i18n'
50+
import { useRouter } from 'vue-router'
51+
52+
import { sendNetlifyError, sendNetlifyForm } from '@/common'
53+
import { useBikeTagStore } from '@/store/index'
54+
55+
import BikeTagButton from '@/components/BikeTagButton.vue'
56+
import EditBikeTag from '@/components/EditBikeTag.vue'
57+
import Loading from 'vue-loading-overlay'
58+
59+
const store = useBikeTagStore()
60+
const router = useRouter()
61+
const toast = inject('toast')
62+
const { t } = useI18n()
63+
64+
const submitInProgress = ref(false)
65+
const submitSuccess = ref(false)
66+
const submitError = ref(null)
67+
68+
const getGameName = computed(() => store.getGameName)
69+
const getGameNameProper = computed(() => store.getGameNameProper)
70+
const getAmbassadorId = computed(() => store.getAmbassadorId)
71+
72+
const newTag = reactive({
73+
game: getGameName.value,
74+
tagnumber: null,
75+
foundPlayer: '',
76+
foundTime: 0,
77+
foundLocation: '',
78+
foundImageUrl: '',
79+
mysteryPlayer: '',
80+
mysteryTime: 0,
81+
mysteryLocation: '',
82+
mysteryImageUrl: '',
83+
hint: '',
84+
})
85+
86+
async function onFieldUpdate({ field, value }) {
87+
newTag[field] = value
88+
}
89+
90+
async function onSubmitClick() {
91+
submitInProgress.value = true
92+
const errorAction = submitError.value.getAttribute('action')
93+
94+
const result = await store.createNewRoundTag(newTag)
95+
submitInProgress.value = false
96+
97+
if (result === true) {
98+
store.resetBikeTagCache()
99+
return sendNetlifyForm(
100+
'new-round-success',
101+
`game=${getGameName.value}`,
102+
() => {
103+
toast.open({
104+
message: 'New round submitted!',
105+
type: 'success',
106+
position: 'top',
107+
})
108+
submitSuccess.value = true
109+
},
110+
(m) => {
111+
toast.open({
112+
message: `${t('notifications.error')} ${m}`,
113+
type: 'error',
114+
duration: 10000,
115+
timeout: false,
116+
position: 'bottom',
117+
})
118+
return sendNetlifyError(m, undefined, errorAction)
119+
}
120+
)
121+
} else {
122+
const message = `Error creating new round: ${result}`
123+
toast.open({
124+
message,
125+
type: 'error',
126+
duration: 10000,
127+
timeout: false,
128+
position: 'bottom',
129+
})
130+
console.error(message)
131+
return sendNetlifyError(message, undefined, errorAction)
132+
}
133+
}
134+
135+
onMounted(async () => {
136+
await store.isReady()
137+
newTag.tagnumber = store.getCurrentBikeTag?.tagnumber ?? 1
138+
})
139+
</script>
140+
141+
<style scoped>
142+
.biketag-container {
143+
max-width: clamp(80vw, 80vw, 500px);
144+
margin: auto;
145+
}
146+
</style>

0 commit comments

Comments
 (0)