Frontend Library Usage Examples
Copy-paste patterns for @productifyfw/core and its framework bindings (@productifyfw/vue, @productifyfw/react): showing the signed-in user, gating features on settings, switching language, and testing components against a fake window.__PRODUCTIFY__. Every example assumes the app is served through the Productify proxy; for local development without it see the dev shim.
Reading Your Own Settings
The settings you define in the Manager reach the browser as config.key_value_pairs, a list of { key, value } pairs whose values are strings. pfy.getConfig(key) and pfy.isConfigEnabled(key) read top-level keys of config (such as default_theme), so they do not see those pairs. The examples on this page use a small helper instead:
// settings.ts
import { getProductifyClient } from "@productifyfw/core";
function pairs(): Record<string, string> {
return Object.fromEntries(
getProductifyClient().getKeyValuePairs().map((p) => [p.key, p.value])
);
}
/** A string setting, or `fallback` when it is not set. */
export function kvString(key: string, fallback: string): string {
return pairs()[key] ?? fallback;
}
/** A numeric setting, or `fallback` when it is missing or not a number. */
export function kvNumber(key: string, fallback: number): number {
const raw = pairs()[key];
const n = Number(raw);
return raw !== undefined && Number.isFinite(n) ? n : fallback;
}
/** A boolean setting: only the string "true" counts as on. */
export function kvFlag(key: string): boolean {
return pairs()[key] === "true";
}Basic Examples
User-Aware UI
import { ProductifyClient } from "@productifyfw/core";
const pfy = new ProductifyClient();
function showGreeting() {
const user = pfy.getUser();
if (user) {
document.getElementById("greeting").textContent = `Welcome, ${user.name}!`;
}
}Sign-Out Link
import { getProductifyClient } from "@productifyfw/core";
// getLogoutUrl() returns the injected logout_url, or /_tenant/logout when the
// proxy injected none. Never hard-code the path.
const link = document.getElementById("sign-out") as HTMLAnchorElement;
link.href = getProductifyClient().getLogoutUrl();In Vue use the useLogoutUrl() composable from @productifyfw/vue; in React the useLogoutUrl() hook from @productifyfw/react.
Feature Flag Usage
import { kvFlag } from "./settings";
// Toggle a feature on a key/value setting ("newCheckoutFlow" = "true")
function initializeApp() {
const useNewCheckout = kvFlag("newCheckoutFlow");
if (useNewCheckout) {
import("./checkout-v2").then((m) => m.init());
} else {
import("./checkout-v1").then((m) => m.init());
}
}Dynamic Configuration
import { kvFlag, kvNumber } from "./settings";
// Get pagination settings
const itemsPerPage = kvNumber("itemsPerPage", 20);
const enableInfiniteScroll = kvFlag("infiniteScroll");
// Configure data grid
const gridConfig = {
pageSize: itemsPerPage,
infiniteScroll: enableInfiniteScroll,
};Vue 3 Examples
Conditional Rendering
<script setup lang="ts">
import { computed } from "vue";
import { useProductify } from "@productifyfw/vue";
import { kvFlag } from "../settings";
const pfy = useProductify();
const canEdit = computed(() => kvFlag("editingEnabled"));
const hasReports = computed(() => pfy.isModuleEnabled("reports"));
</script>
<template>
<div class="content">
<button v-if="canEdit" @click="edit">Edit</button>
<ReportsWidget v-if="hasReports" />
</div>
</template>Settings-Based UI
<script setup lang="ts">
import { computed } from "vue";
import { useProductify } from "@productifyfw/vue";
import { kvFlag } from "../settings";
const pfy = useProductify();
const theme = computed(() => pfy.getDefaultTheme() ?? "light");
const locale = computed(() => pfy.getDefaultLocale() ?? "en");
const compactMode = computed(() => kvFlag("compactView"));
</script>
<template>
<div
:class="['app', `theme-${theme}`, { compact: compactMode }]"
:lang="locale"
>
<!-- Your app content -->
</div>
</template>User Profile Display
<script setup lang="ts">
import { useProductify } from "@productifyfw/vue";
const pfy = useProductify();
const user = pfy.getUser();
const tenant = pfy.getTenant();
</script>
<template>
<div v-if="user" class="user-profile">
<img :src="`/avatars/${user.id}`" :alt="user.name" />
<div>
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<span v-if="tenant" class="badge">{{ tenant.name }}</span>
</div>
</div>
</template>Language Switcher
<script setup lang="ts">
import { ref } from "vue";
import { createI18nComposable } from "@productifyfw/vue";
import { i18n } from "../plugins/i18n";
const useProductifyI18n = createI18nComposable(i18n);
const { changeLocale, enabledLocales, defaultLocale } = useProductifyI18n();
const selected = ref(defaultLocale);
async function switchLanguage() {
await changeLocale(selected.value);
}
</script>
<template>
<select v-model="selected" @change="switchLanguage">
<option v-for="loc in enabledLocales" :key="loc" :value="loc">
{{ loc.toUpperCase() }}
</option>
</select>
</template>React Examples
Feature-Gated Route
import { Navigate } from "react-router-dom";
import { useProductify } from "@productifyfw/react";
import { kvFlag } from "./settings";
interface FeatureRouteProps {
children: React.ReactNode;
requiredModule?: string;
requiredFlag?: string;
}
function FeatureRoute({
children,
requiredModule,
requiredFlag,
}: FeatureRouteProps) {
const pfy = useProductify();
const hasAccess = requiredModule
? pfy.isModuleEnabled(requiredModule)
: requiredFlag
? kvFlag(requiredFlag)
: true;
if (!hasAccess) {
return <Navigate to="/not-available" replace />;
}
return <>{children}</>;
}
// Usage
<Route
path="/reports"
element={
<FeatureRoute requiredModule="reports">
<ReportsDashboard />
</FeatureRoute>
}
/>;Settings Hook
import { useMemo } from "react";
import { useProductify, useProductifyConfig } from "@productifyfw/react";
import { kvFlag, kvNumber } from "./settings";
function useAppSettings() {
const pfy = useProductify();
// useProductifyConfig reads a top-level `config` key and re-renders on change
const defaultTheme = useProductifyConfig<string>("default_theme", {
defaultValue: "light",
});
return useMemo(
() => ({
theme: defaultTheme ?? "light",
locale: pfy.getDefaultLocale() ?? "en",
itemsPerPage: kvNumber("itemsPerPage", 20),
enableAnimations: kvFlag("animations"),
debugMode: kvFlag("debug"),
}),
[pfy, defaultTheme]
);
}
// Usage in component
function MyComponent() {
const settings = useAppSettings();
return (
<div className={`theme-${settings.theme}`}>
{settings.debugMode && <DebugPanel />}
<ItemList itemsPerPage={settings.itemsPerPage} />
</div>
);
}Conditional Feature Rendering
import { useProductify } from "@productifyfw/react";
import { kvFlag } from "./settings";
function Dashboard() {
const pfy = useProductify();
const showNewFeature = kvFlag("betaFeatures");
const hasAnalytics = pfy.isModuleEnabled("analytics");
return (
<div>
<h1>Dashboard</h1>
{showNewFeature && <NewFeatureWidget />}
{hasAnalytics && <AnalyticsDashboard />}
</div>
);
}Advanced Examples
Module Availability Check
import { getProductifyClient } from "@productifyfw/core";
const pfy = getProductifyClient();
class ModuleManager {
async loadModule(name: string) {
if (!pfy.isModuleEnabled(name)) {
console.warn(`Module ${name} not enabled`);
return null;
}
// Enabled modules are formatted "name@version"
const entry = pfy
.getEnabledModules()
.find((m) => m.split("@")[0] === name);
console.log(`Loading ${entry}`);
// Load module dynamically
const module = await import(`./modules/${name}`);
return module.default;
}
}
// Usage
const manager = new ModuleManager();
const checkout = await manager.loadModule("checkout");
if (checkout) {
checkout.initialize();
}Multi-Tenant Theme Switcher
import { ProductifyClient } from "@productifyfw/core";
class ThemeManager {
private currentTheme: string | null = null;
private pfy: ProductifyClient;
constructor() {
this.pfy = new ProductifyClient({
onChange: () => this.applyTheme(),
});
}
initialize() {
this.applyTheme();
}
private applyTheme() {
const theme = this.pfy.getDefaultTheme();
const tenantId = this.pfy.getTenantId();
if (!theme || !tenantId || theme === this.currentTheme) return;
// Load CSS variables
import(`./themes/${tenantId}/${theme}.css`);
document.documentElement.setAttribute("data-theme", theme);
document.documentElement.setAttribute("data-tenant", tenantId);
this.currentTheme = theme;
}
// Call pfy.refresh() when configuration may have changed at runtime;
// onChange fires only when the content actually differs
refresh() {
this.pfy.refresh();
}
}Feature Flag Rollout
import { getProductifyClient } from "@productifyfw/core";
import { kvFlag, kvNumber } from "./settings";
const pfy = getProductifyClient();
class FeatureFlagManager {
isFeatureEnabled(featureName: string): boolean {
// Check global flag ("<feature>" = "true")
if (!kvFlag(featureName)) return false;
// Check percentage rollout ("<feature>_rollout" = "25")
const rolloutPercentage = kvNumber(`${featureName}_rollout`, 100);
const userId = pfy.getUserId();
if (!userId) return false;
// Simple hash-based percentage check
const hash = this.hashUserId(userId);
return hash % 100 < rolloutPercentage;
}
private hashUserId(userId: string): number {
let hash = 0;
for (let i = 0; i < userId.length; i++) {
hash = (hash << 5) - hash + userId.charCodeAt(i);
hash = hash & hash;
}
return Math.abs(hash);
}
}
// Usage
const flags = new FeatureFlagManager();
if (flags.isFeatureEnabled("newCheckout")) {
// Use new checkout flow
}Testing Examples
Mock Setup for Unit Tests
// test-utils.ts
import type { ProductifyFrontendConfig } from "@productifyfw/core";
export function mockProductifyConfig(
overrides?: Partial<ProductifyFrontendConfig>
) {
const defaultConfig: ProductifyFrontendConfig = {
user: {
id: "test-user-id",
email: "test@example.com",
name: "Test User",
username: "testuser",
},
tenant: {
id: "test-tenant",
name: "Test Tenant",
},
application: {
id: "test-app-id",
},
auth_type: "oauth",
config: {
enabled_modules: [],
default_theme: "light",
default_locale: "en",
},
};
window.__PRODUCTIFY__ = { ...defaultConfig, ...overrides };
}
export function clearProductifyConfig() {
delete window.__PRODUCTIFY__;
}Component Test with Mock
// MyComponent.test.ts
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { mount } from "@vue/test-utils";
import { resetProductifyClient } from "@productifyfw/core";
import { mockProductifyConfig, clearProductifyConfig } from "./test-utils";
import MyComponent from "./MyComponent.vue";
describe("MyComponent", () => {
beforeEach(() => {
mockProductifyConfig({
config: {
enabled_modules: ["reports@1.0.0"],
key_value_pairs: [{ key: "maxItems", value: "50" }],
},
});
resetProductifyClient(); // Reset the singleton between tests
});
afterEach(() => {
clearProductifyConfig();
});
it("shows reports widget when the module is enabled", () => {
const wrapper = mount(MyComponent);
expect(wrapper.find(".reports-widget").exists()).toBe(true);
});
it("uses settings from config", () => {
const wrapper = mount(MyComponent);
expect(wrapper.vm.maxItems).toBe(50);
});
});Error Handling Examples
Graceful Degradation
import { ProductifyClient } from "@productifyfw/core";
import { kvNumber } from "./settings";
function initializeApp() {
const pfy = new ProductifyClient();
if (!pfy.isConfigured()) {
console.warn("Running without Productify configuration");
// Use fallback configuration
return {
theme: "light",
locale: "en",
itemsPerPage: 20,
isAuthenticated: false,
};
}
return {
theme: pfy.getDefaultTheme() ?? "light",
locale: pfy.getDefaultLocale() ?? "en",
itemsPerPage: kvNumber("itemsPerPage", 20),
isAuthenticated: pfy.getUserId() !== null,
};
}Safe Configuration Access
getConfig, getKeyValuePairs and the helpers above never throw: a missing window.__PRODUCTIFY__ behaves like an empty configuration unless you construct the client with strict: true. Centralise your settings in one place so a renamed key is a one-line change:
import { kvFlag, kvNumber, kvString } from "./settings";
export function getAppConfig() {
return {
theme: kvString("theme", "light"),
maxItems: kvNumber("maxItemsPerPage", 20),
enableCache: kvFlag("enableCache"),
};
}Related
- Frontend Integration Guide - what
window.__PRODUCTIFY__contains - Frontend Integration - @productifyfw/core - full library reference