Frontend Library Usage Examples
Practical examples for using the @productifyfw/core library and its framework bindings (@productifyfw/vue, @productifyfw/react).
Basic Examples
User-Aware UI
typescript
import { ProductifyClient } from "@productifyfw/core";
const pfy = new ProductifyClient();
function showGreeting() {
const user = pfy.getUser();
if (user) {
document.getElementById("greeting").textContent = `Welcome, ${user.name}!`;
}
}Feature Flag Usage
typescript
import { getProductifyClient } from "@productifyfw/core";
const pfy = getProductifyClient();
// Toggle feature based on a boolean configuration value
function initializeApp() {
const useNewCheckout = pfy.isConfigEnabled("newCheckoutFlow");
if (useNewCheckout) {
import("./checkout-v2").then((m) => m.init());
} else {
import("./checkout-v1").then((m) => m.init());
}
}Dynamic Configuration
typescript
const pfy = new ProductifyClient();
// Get pagination settings
const itemsPerPage = pfy.getConfig<number>("itemsPerPage", {
defaultValue: 20,
});
const enableInfiniteScroll = pfy.isConfigEnabled("infiniteScroll");
// Configure data grid
const gridConfig = {
pageSize: itemsPerPage,
infiniteScroll: enableInfiniteScroll,
};Vue 3 Examples
Conditional Rendering
vue
<script setup lang="ts">
import { computed } from "vue";
import { useProductify } from "@productifyfw/vue";
const pfy = useProductify();
const canEdit = computed(() => pfy.isConfigEnabled("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
vue
<script setup lang="ts">
import { computed } from "vue";
import { useProductify } from "@productifyfw/vue";
const pfy = useProductify();
const theme = computed(() => pfy.getDefaultTheme() ?? "light");
const locale = computed(() => pfy.getDefaultLocale() ?? "en");
const compactMode = computed(() => pfy.isConfigEnabled("compactView"));
</script>
<template>
<div
:class="['app', `theme-${theme}`, { compact: compactMode }]"
:lang="locale"
>
<!-- Your app content -->
</div>
</template>User Profile Display
vue
<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
vue
<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
typescript
import { Navigate } from "react-router-dom";
import { useProductify } from "@productifyfw/react";
interface FeatureRouteProps {
children: React.ReactNode;
requiredModule?: string;
requiredConfig?: string;
}
function FeatureRoute({
children,
requiredModule,
requiredConfig,
}: FeatureRouteProps) {
const pfy = useProductify();
const hasAccess = requiredModule
? pfy.isModuleEnabled(requiredModule)
: requiredConfig
? pfy.isConfigEnabled(requiredConfig)
: true;
if (!hasAccess) {
return <Navigate to="/not-available" replace />;
}
return <>{children}</>;
}
// Usage
<Route
path="/reports"
element={
<FeatureRoute requiredModule="reports">
<ReportsDashboard />
</FeatureRoute>
}
/>;Settings Hook
typescript
import { useMemo } from "react";
import { useProductify, useProductifyConfig } from "@productifyfw/react";
function useAppSettings() {
const pfy = useProductify();
const itemsPerPage = useProductifyConfig<number>("itemsPerPage", {
defaultValue: 20,
});
return useMemo(
() => ({
theme: pfy.getDefaultTheme() ?? "light",
locale: pfy.getDefaultLocale() ?? "en",
itemsPerPage,
enableAnimations: pfy.isConfigEnabled("animations"),
debugMode: pfy.isConfigEnabled("debug"),
}),
[pfy, itemsPerPage]
);
}
// 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
typescript
import { useProductify } from "@productifyfw/react";
function Dashboard() {
const pfy = useProductify();
const showNewFeature = pfy.isConfigEnabled("betaFeatures");
const hasAnalytics = pfy.isModuleEnabled("analytics");
return (
<div>
<h1>Dashboard</h1>
{showNewFeature && <NewFeatureWidget />}
{hasAnalytics && <AnalyticsDashboard />}
</div>
);
}Advanced Examples
Module Availability Check
typescript
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
typescript
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
typescript
import { getProductifyClient } from "@productifyfw/core";
const pfy = getProductifyClient();
class FeatureFlagManager {
isFeatureEnabled(featureName: string): boolean {
// Check global flag
const globalEnabled = pfy.isConfigEnabled(featureName);
if (!globalEnabled) return false;
// Check percentage rollout
const rolloutPercentage = pfy.getConfig<number>(`${featureName}_rollout`, {
defaultValue: 100,
});
const userId = pfy.getUserId();
if (!userId) return false;
// Simple hash-based percentage check
const hash = this.hashUserId(userId);
return hash % 100 < (rolloutPercentage ?? 0);
}
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
typescript
// 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
typescript
// 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"],
maxItems: 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
typescript
import { ProductifyClient } from "@productifyfw/core";
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: pfy.getConfig<number>("itemsPerPage", { defaultValue: 20 }),
isAuthenticated: pfy.getUserId() !== null,
};
}Safe Configuration Access
typescript
function getAppConfig(pfy: ProductifyClient) {
const safeConfig = <T>(key: string, defaultValue: T): T => {
try {
return pfy.getConfig<T>(key, { defaultValue }) ?? defaultValue;
} catch (error) {
console.error(`Error reading configuration ${key}:`, error);
return defaultValue;
}
};
return {
theme: safeConfig("theme", "light"),
maxItems: safeConfig("maxItemsPerPage", 20),
enableCache: safeConfig("enableCache", true),
};
}