Files
pichunt/app/components/TrackDistanceCard.vue
Pascal 61cc516f47
Docker Image CI / test (push) Failing after 41s
Docker Image CI / deploy (push) Skipped
Tests / unit (push) Failing after 36s
Tests / integration (push) Successful in 1m34s
feat(hunt): track walking distance via GPS, show live locations on map
Add foreground-only GPS distance tracking for hunts
2026-09-27 20:55:58 +02:00

110 lines
2.9 KiB
Vue

<script setup lang="ts">
import {
type TrackableHunt,
useGeoTracking
} from '~/composables/useGeoTracking';
const props = defineProps<{
hunt: TrackableHunt;
seedDistance?: number;
}>();
const { t, locale } = useI18n();
const { formatDistance } = useFormatDistance();
const {
distance,
lastFix,
paused,
consented,
eligible,
gate,
isSupported,
start,
stop,
toggleConsent
} = useGeoTracking(props.hunt, props.seedDistance ?? 0);
// Ticking clock so the "last fix … ago" label stays live while the card is open.
const nowTs = ref(Date.now());
let clock: ReturnType<typeof setInterval> | undefined;
onMounted(() => {
clock = setInterval(() => (nowTs.value = Date.now()), 1000);
});
onScopeDispose(() => {
if (clock !== undefined) clearInterval(clock);
});
const lastFixAgo = computed(() => {
if (!lastFix.value) return '';
const seconds = Math.max(
0,
Math.round((nowTs.value - lastFix.value.getTime()) / 1000)
);
return new Intl.RelativeTimeFormat(locale.value || 'en', {
numeric: 'auto'
}).format(-seconds, 'second');
});
</script>
<template>
<VaCard
v-if="eligible"
stripe
:stripe-color="gate && !paused ? 'success' : 'primary'"
>
<VaCardTitle>{{ t('page.hunt.tracking.title') }}</VaCardTitle>
<VaCardContent>
<div class="flex flex-col gap-3">
<VaAlert v-if="!isSupported" color="warning">
{{ t('page.hunt.tracking.unsupported') }}
</VaAlert>
<VaSwitch
:model-value="consented"
:disabled="!isSupported"
:label="t('page.hunt.tracking.consent')"
@update:model-value="toggleConsent"
/>
<p class="text-sm opacity-70">
{{ t('page.hunt.tracking.consent_explain') }}
</p>
<template v-if="consented">
<div class="grid grid-cols-2 gap-2 text-lg md:grid-cols-3">
<VaChip :color="paused ? 'secondary' : 'success'">
{{
t(
paused
? 'page.hunt.tracking.status_paused'
: 'page.hunt.tracking.status_tracking'
)
}}
</VaChip>
<span class="font-bold">{{ formatDistance(distance) }}</span>
<span
v-if="lastFixAgo"
class="col-span-2 text-sm opacity-70 md:col-span-1"
>
{{ t('page.hunt.tracking.last_fix', { ago: lastFixAgo }) }}
</span>
</div>
<VaButton
:disabled="!isSupported"
:icon="paused ? 'play_arrow' : 'pause'"
@click="paused ? start() : stop()"
>
{{
t(
paused
? 'page.hunt.tracking.resume'
: 'page.hunt.tracking.pause'
)
}}
</VaButton>
</template>
</div>
</VaCardContent>
</VaCard>
</template>