Skip to content

Commit

Permalink
list speed scores
Browse files Browse the repository at this point in the history
  • Loading branch information
swantzter committed Oct 31, 2021
1 parent 7fab785 commit bc9042e
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 30 deletions.
22 changes: 22 additions & 0 deletions src/apollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { setContext } from '@apollo/client/link/context'
import { persistCache } from 'apollo3-cache-persist'
import { getAuth } from 'firebase/auth'

import type { SpeedResultsQueryVariables } from './graphql/generated/graphql'

const httpLink = createHttpLink({
uri: import.meta.env.VITE_GRAPHQL_URL
})
Expand All @@ -22,6 +24,26 @@ const cache = new InMemoryCache({
User: {
merge (existing, incoming, { mergeObjects }) {
return mergeObjects(existing, incoming)
},
fields: {
speedResults: {
keyArgs: false,
merge (existing: any[] = [], incoming: any[], { readField, args }) {
const merged = existing.slice(0)
const existingIds = new Set(merged.map(doc => readField('id', doc)))

incoming = incoming.filter(doc => !existingIds.has(readField("id", doc)))
const afterIndex = merged.findIndex(doc => (args as SpeedResultsQueryVariables).startAfter === readField("createdAt", doc))


if (afterIndex >= 0) {
merged.splice(afterIndex + 1, 0, ...incoming)
} else {
merged.push(...incoming)
}
return merged
}
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/NavHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<nav class="flex">
<router-link exact-active-class="active" class="nav-link" to="/">Tricks</router-link>
<!-- <router-link active-class="active" class="nav-link" to="/speed">Speed</router-link> -->
<router-link active-class="active" class="nav-link" to="/speed">Speed</router-link>
<router-link active-class="active" class="nav-link" to="/shop">Shop</router-link>
<router-link active-class="active" class="nav-link" to="/profile" v-if="user">Profile</router-link>
<router-link active-class="active" class="nav-link" to="/auth" v-else>Sign in</router-link>
Expand Down
35 changes: 35 additions & 0 deletions src/components/SpeedBox.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<router-link
class="
block grid grid-cols-[auto,4rem] grid-rows-3
rounded border border-gray-300 hover:bg-gray-200
p-2
"
:to="`/speed/${result.id}`"
@click="$emit('navigate')"
>
<span class="col-start-2 row-span-3 flex justify-center items-center">{{ result.count }}</span>

<span class="col-start-1 row-start-1 font-bold">
{{ result.eventDefinition.name }}
</span>
<span class="col-start-1">{{ formatDateTime(result.createdAt) }}</span>
<span class="col-start-1">{{ result.name }}</span>
</router-link>
</template>

<script setup lang="ts">
import { formatDateTime } from '../helpers'
import type { PropType } from 'vue'
import type { SpeedResult } from '../graphql/generated/graphql'
defineProps({
result: {
type: Object as PropType<Pick<SpeedResult, 'id' | 'name' | 'createdAt' | 'count' | 'eventDefinition'>>,
required: true
}
})
const a = 1
</script>
2 changes: 1 addition & 1 deletion src/components/TrickBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@
import { ref, toRef } from 'vue'
import { disciplineToSlug } from "../helpers";
import useCompleteTrick from '../hooks/useCompleteTrick';
import IconCheck from 'virtual:vite-icons/mdi/check'
import IconLoading from 'virtual:vite-icons/mdi/loading'
import type { PropType } from 'vue'
import type { TricksQuery } from '../graphql/generated/graphql'
import useCompleteTrick from '../hooks/useCompleteTrick';
const props = defineProps({
trick: {
Expand Down
11 changes: 11 additions & 0 deletions src/graphql/queries/checklist.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
query Checklist {
me {
id
checklist {
id
trick {
id
}
}
}
}
8 changes: 1 addition & 7 deletions src/graphql/queries/me.gql
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
query Me ($withChecklist: Boolean!) {
query Me {
me {
id
username
name
photo
lang
checklist @include(if: $withChecklist) {
id
trick {
id
}
}
}
}
29 changes: 29 additions & 0 deletions src/graphql/queries/speedResutls.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
query SpeedResults ($limit: Int, $startAfter: Timestamp) {
me {
id
speedResults (limit: $limit, startAfter: $startAfter) {
... on SimpleSpeedResult {
id
name
createdAt
count
eventDefinition {
id
name
totalDuration
}
}
... on DetailedSpeedResult {
id
name
createdAt
count
eventDefinition {
id
name
totalDuration
}
}
}
}
}
7 changes: 7 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,10 @@ export function formatPrice (prices: PricesFormatFields | Readonly<PricesFormatF
currency
}).format(price?.unitAmount / 100)
}

export function formatDateTime (date: number | Date) {
return new Intl.DateTimeFormat(undefined, {
dateStyle: 'medium',
timeStyle: 'short'
}).format(date)
}
4 changes: 2 additions & 2 deletions src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const analytics = getAnalytics()
const firebaseUser = ref<User | null>()
let off: Unsubscribe

export default function useAuth ({ withChecklist = false } = {}) {
const userQuery = useMeQuery({ withChecklist }, { fetchPolicy: 'cache-and-network' })
export default function useAuth () {
const userQuery = useMeQuery({ fetchPolicy: 'cache-and-network' })

const user = useResult(userQuery.result, null, data => data.me)

Expand Down
11 changes: 4 additions & 7 deletions src/hooks/useCompleteTrick.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { getAnalytics, logEvent } from '@firebase/analytics'
import { MeDocument, MeQuery, MeQueryVariables, useCompleteTrickMutation } from '../graphql/generated/graphql'

import type { Ref } from 'vue'
import { ChecklistDocument, ChecklistQuery, ChecklistQueryVariables, useCompleteTrickMutation } from '../graphql/generated/graphql'

const analytics = getAnalytics()

export default function useCompleteTrick (variables?: { trickId: string, completed: boolean }) {
const mutation = useCompleteTrickMutation(() => ({
...(variables ? { variables } : {}),
update (cache, { data }) {
const cachedData = cache.readQuery<MeQuery, MeQueryVariables>({ query: MeDocument, variables: { withChecklist: true } })
const cachedData = cache.readQuery<ChecklistQuery, ChecklistQueryVariables>({ query: ChecklistDocument })

if (cachedData?.me?.checklist?.length) {
const checklist = [...cachedData.me.checklist]
Expand All @@ -21,9 +19,8 @@ export default function useCompleteTrick (variables?: { trickId: string, complet
checklist.push(data.createTrickCompletion)
}

cache.writeQuery<Partial<MeQuery> | null, MeQueryVariables>({
query: MeDocument,
variables: { withChecklist: true },
cache.writeQuery<Partial<ChecklistQuery> | null, ChecklistQueryVariables>({
query: ChecklistDocument,
data: { me: { id: cachedData?.me?.id, checklist } }
})
}
Expand Down
11 changes: 9 additions & 2 deletions src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,20 @@ const analytics = getAnalytics()

export const routes: RouteRecordRaw[] = [
{ name: 'tricktionary', path: '/', component: () => import('./views/Home.vue') },

{ name: 'trick', path: '/trick/:discipline/:slug', component: () => import('./views/Trick.vue') },

{ name: 'speed', path: '/speed', component: () => import('./views/SpeedIndex.vue') },

{ name: 'shop', path: '/shop', component: () => import('./views/Shop.vue') },
{ name: 'shop-success', path: '/shop-success', component: () => import('./views/ShopSuccess.vue') },

{ name: 'auth', path: '/auth', component: () => import('./views/Auth.vue') },
{ name: 'profile', path: '/profile', component: () => import('./views/Profile.vue') },

{ name: 'rafiki', path: '/rafiki', component: () => import('./views/Rafiki.vue') },
{ name: 'policies', path: '/policies', component: () => import('./views/Policies.vue') },
{ name: 'shop', path: '/shop', component: () => import('./views/Shop.vue') },
{ name: 'shop-success', path: '/shop-success', component: () => import('./views/ShopSuccess.vue') },

{ name: 'not_found', path: '/:catchAll(.*)*', component: () => import('./views/404.vue') }
]

Expand Down
10 changes: 6 additions & 4 deletions src/views/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import TtFooter from '../components/Footer.vue'
import Links from '../components/Links.vue'
import IconCheckbox from '../components/IconCheckbox.vue'
import { Discipline, useTricksQuery } from '../graphql/generated/graphql'
import { Discipline, useChecklistQuery, useTricksQuery } from '../graphql/generated/graphql'
import useAuth from '../hooks/useAuth'
import useSettings from '../hooks/useSettings'
import AdAdsense from '../components/AdAdsense.vue'
Expand All @@ -63,15 +63,18 @@ import BottomBar from '../components/BottomBar.vue'
const discipline = ref<Discipline>()
const settings = useSettings()
const analytics = getAnalytics()
const { firebaseUser, user } = useAuth({ withChecklist: true })
const { firebaseUser, user } = useAuth()
const tricksQuery = useTricksQuery({
discipline: discipline.value,
withLocalised: !!user.value?.lang && user.value?.lang !== 'en',
lang: !!user.value?.lang && user.value?.lang !== 'en' ? user.value.lang : undefined
})
const checklistQuery = useChecklistQuery()
const tricks = useResult(tricksQuery.result, [] as TricksQuery['tricks'], data => data?.tricks)
const checklist = ref<Set<string>>(new Set())
const checklist = useResult(checklistQuery.result, new Set() as Set<string>, data => new Set(data?.me?.checklist?.map(checklistItem => checklistItem.trick.id)))
const search = ref<string | undefined>(undefined)
const debouncedSearch = useDebounce(search, 1000)
Expand All @@ -86,7 +89,6 @@ watch(user, user => {
tricksQuery.variables.value.withLocalised = false
tricksQuery.variables.value.lang = undefined
}
checklist.value = new Set(user?.checklist?.map(checklistItem => checklistItem.trick.id))
})
watch(debouncedSearch, search => {
console.log(search)
Expand Down
60 changes: 60 additions & 0 deletions src/views/SpeedIndex.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<template>
<!-- position: fixed? -->
<!-- TODO <section class="container mx-auto p-2">filters: eventDefinition, participants</section> -->

<section class="container mx-auto p-2">
<div class="min-h-[100vh] grid grid-cols-1 gap-2">
<speed-box v-for="speedResult of speedResults" :key="speedResult.id" :result="speedResult" />
</div>

<button
ref="loadMoreRef"
class="btn full-w mt-2"
:disabled="speedResultsQuery.loading.value"
@click="loadMore()"
>
<span v-if="speedResultsQuery.loading.value">
<icon-loading class="animate-spin" />
</span>
<span v-else>Load more</span>
</button>
</section>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useResult } from '@vue/apollo-composable'
import { useIntersectionObserver, useThrottleFn } from '@vueuse/core'
import { useSpeedResultsQuery } from '../graphql/generated/graphql'
import SpeedBox from '../components/SpeedBox.vue'
import IconLoading from 'virtual:vite-icons/mdi/loading'
const loadMoreRef = ref()
const speedResultsQuery = useSpeedResultsQuery({
limit: 20,
startAfter: null
}, { fetchPolicy: 'cache-and-network' })
const speedResults = useResult(speedResultsQuery.result, [], data => data.me?.speedResults)
function loadMore() {
const lastResult = speedResults.value[speedResults.value.length - 1]
if (!lastResult) return false
speedResultsQuery.fetchMore({
variables: {
limit: 20,
startAfter: lastResult.createdAt
}
})
}
const throttledLoadMore = useThrottleFn(loadMore, 10000)
useIntersectionObserver(loadMoreRef, () => {
if (speedResultsQuery.loading.value) return
throttledLoadMore()
})
</script>
12 changes: 6 additions & 6 deletions src/views/Trick.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ import { useResult } from '@vue/apollo-composable'
import { getAnalytics, logEvent } from '@firebase/analytics'
import { useHead } from '@vueuse/head'
import { Discipline, useTrickBySlugQuery, VerificationLevel } from '../graphql/generated/graphql'
import { Discipline, useChecklistQuery, useTrickBySlugQuery, VerificationLevel } from '../graphql/generated/graphql'
import { slugToDiscipline } from '../helpers'
import useAuth from '../hooks/useAuth'
import useCompleteTrick from '../hooks/useCompleteTrick'
Expand All @@ -113,13 +113,14 @@ const router = useRouter()
const analytics = getAnalytics()
const discipline = ref(slugToDiscipline(route.params.discipline as string))
const { user } = useAuth({ withChecklist: true })
const { user } = useAuth()
const trickQuery = useTrickBySlugQuery({
discipline: discipline,
slug: route.params.slug as string,
withLocalised: !!user.value?.lang && user.value?.lang !== 'en',
lang: !!user.value?.lang && user.value?.lang !== 'en' ? user.value.lang : undefined
})
const checklistQuery = useChecklistQuery()
const { loading } = trickQuery
const trick = useResult(trickQuery.result, null, data => data?.trick)
Expand Down Expand Up @@ -165,7 +166,8 @@ onBeforeRouteUpdate((to, from) => {
}
})
trickQuery.onResult(({ data }) => {
trickQuery.onResult(({ data, loading }) => {
if (loading) return
if (!data.trick) {
router.push({
name: 'not_found',
Expand Down Expand Up @@ -201,9 +203,7 @@ async function share () {
})
}
const completed = computed(() => {
return new Set(user.value?.checklist?.map(checklistItem => checklistItem.trick.id))
})
const completed = useResult(checklistQuery.result, new Set() as Set<string>, data => new Set(data?.me?.checklist?.map(checklistItem => checklistItem.trick.id)))
// the andoird app tracks these events, so we do too
function viewNext (trick: TrickBoxFragment) {
Expand Down

0 comments on commit bc9042e

Please sign in to comment.