enable feature flags for app, add docs

This commit is contained in:
Will Freeman
2025-10-25 14:33:09 -06:00
parent 37150211e4
commit 0089241a53
3 changed files with 119 additions and 9 deletions

View File

@@ -0,0 +1,71 @@
import { ref, readonly } from 'vue';
interface FeatureFlags {
iosApp: {
enabled: boolean;
appUrl: string;
};
}
const flags = ref<FeatureFlags | null>(null);
const isLoading = ref(false);
const error = ref<string | null>(null);
let fetchPromise: Promise<void> | null = null;
async function fetchFeatureFlags(): Promise<void> {
if (flags.value !== null) {
// Already loaded
return;
}
if (fetchPromise) {
// Already fetching, wait for the existing promise
return fetchPromise;
}
fetchPromise = (async () => {
try {
isLoading.value = true;
error.value = null;
const response = await fetch('https://cdn.deflock.me/config/flags.json');
if (!response.ok) {
throw new Error(`Failed to fetch feature flags: ${response.status}`);
}
const data = await response.json();
flags.value = data;
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error';
console.warn('Failed to load feature flags, using defaults:', error.value);
// Fallback to default values
flags.value = {
iosApp: {
enabled: false,
appUrl: ''
}
};
} finally {
isLoading.value = false;
}
})();
return fetchPromise;
}
export function useFeatureFlags() {
// Auto-fetch on first use
if (!fetchPromise) {
fetchFeatureFlags();
}
return {
flags: readonly(flags),
isLoading: readonly(isLoading),
error: readonly(error),
refresh: fetchFeatureFlags
};
}

View File

@@ -40,6 +40,20 @@
<span v-if="appLinks.android">Get on Android</span>
<span v-else>Android Coming Soon</span>
</v-btn>
<!-- Documentation Link -->
<v-btn
size="large"
variant="text"
color="white"
href="https://blog.deflock.me/deflock-mobile-guide/"
target="_blank"
prepend-icon="mdi-book-open-variant"
class="download-btn doc-btn"
>
View Mobile App Guide
<v-icon icon="mdi-open-in-new" size="small" class="ml-1" />
</v-btn>
</div>
</div>
</v-col>
@@ -217,8 +231,11 @@
<script setup lang="ts">
import Footer from '@/components/layout/Footer.vue';
import { useFeatureFlags } from '@/composables/useFeatureFlags';
import { ref, computed } from 'vue';
const { flags } = useFeatureFlags();
interface Feature {
id: number;
title: string;
@@ -252,10 +269,10 @@ interface PrivacyPrinciple {
description: string;
}
const appLinks = {
const appLinks = computed(() => ({
android: 'https://play.google.com/store/apps/details?id=me.deflock.deflockapp',
ios: undefined,
}
ios: flags.value?.iosApp.enabled ? flags.value.iosApp.appUrl : undefined,
}));
// App features
const features: Feature[] = [
@@ -475,6 +492,17 @@ const visibleScreenshots = computed(() =>
color: white !important;
}
.doc-btn {
color: white !important;
opacity: 0.9;
transition: all 0.3s ease;
}
.doc-btn:hover {
opacity: 1;
transform: translateY(-2px);
}
.hero-image {
display: flex;
justify-content: center;

View File

@@ -68,9 +68,11 @@
<v-col cols="12" md="5" class="pa-4">
<div class="app-card-container">
<v-card
class="mx-auto h-100 d-flex flex-column app-card-disabled"
class="mx-auto h-100 d-flex flex-column"
:class="{ 'app-card-disabled': !isIosAppEnabled }"
elevation="4"
:class="{ 'card-disabled': true }"
:hover="isIosAppEnabled"
:to="isIosAppEnabled ? '/app' : undefined"
>
<v-card-item class="bg-green-darken-3">
<v-card-title class="text-h5 font-weight-bold text-white">
@@ -93,16 +95,17 @@
color="green-darken-2"
variant="elevated"
size="large"
disabled
:disabled="!isIosAppEnabled"
:to="isIosAppEnabled ? '/app' : undefined"
>
Download App
{{ isIosAppEnabled ? 'Download App' : 'Download App' }}
<v-icon icon="mdi-arrow-right" end></v-icon>
</v-btn>
</v-card-actions>
</v-card>
<!-- Coming Soon Banner -->
<div class="coming-soon-banner">
<div v-if="shouldShowComingSoon" class="coming-soon-banner">
<v-chip
color="warning"
variant="elevated"
@@ -121,6 +124,14 @@
<script setup lang="ts">
import ALPRVerificationDialog from '@/components/ALPRVerificationDialog.vue';
import { useFeatureFlags } from '@/composables/useFeatureFlags';
import { computed } from 'vue';
const { flags } = useFeatureFlags();
// Computed properties for iOS app state
const isIosAppEnabled = computed(() => flags.value?.iosApp.enabled ?? false);
const shouldShowComingSoon = computed(() => !isIosAppEnabled.value);
</script>
<style scoped>
@@ -138,7 +149,7 @@ import ALPRVerificationDialog from '@/components/ALPRVerificationDialog.vue';
position: relative;
}
.card-disabled {
.app-card-disabled {
opacity: 0.7;
pointer-events: none;
cursor: not-allowed;