Skip to content

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:

typescript
// 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 ​

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}!`;
  }
}
typescript
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 ​

typescript
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 ​

typescript
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 ​

vue
<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 ​

vue
<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 ​

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 ​

tsx
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 ​

tsx
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 ​

tsx
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 ​

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";
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 ​

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"],
        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 ​

typescript
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:

typescript
import { kvFlag, kvNumber, kvString } from "./settings";

export function getAppConfig() {
  return {
    theme: kvString("theme", "light"),
    maxItems: kvNumber("maxItemsPerPage", 20),
    enableCache: kvFlag("enableCache"),
  };
}