mirror of
https://github.com/FoggedLens/deflock.git
synced 2026-02-12 15:02:45 +00:00
enable feature flags for app, add docs
This commit is contained in:
71
webapp/src/composables/useFeatureFlags.ts
Normal file
71
webapp/src/composables/useFeatureFlags.ts
Normal 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
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user