diff --git a/webapp/src/composables/useFeatureFlags.ts b/webapp/src/composables/useFeatureFlags.ts new file mode 100644 index 0000000..9d52e34 --- /dev/null +++ b/webapp/src/composables/useFeatureFlags.ts @@ -0,0 +1,71 @@ +import { ref, readonly } from 'vue'; + +interface FeatureFlags { + iosApp: { + enabled: boolean; + appUrl: string; + }; +} + +const flags = ref(null); +const isLoading = ref(false); +const error = ref(null); + +let fetchPromise: Promise | null = null; + +async function fetchFeatureFlags(): Promise { + 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 + }; +} \ No newline at end of file diff --git a/webapp/src/views/ContactView.vue b/webapp/src/views/ContactView.vue index f5ce89c..ca10bed 100644 --- a/webapp/src/views/ContactView.vue +++ b/webapp/src/views/ContactView.vue @@ -1,46 +1,128 @@