Skip to content
45 changes: 33 additions & 12 deletions packages/vue-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,18 @@ If using Nuxt, wrap `<BucketProvider>` in `<ClientOnly>`. `<BucketProvider>` onl
<script setup lang="ts">
import { useFeature } from "@bucketco/vue-sdk";

const huddle = useFeature("huddle");
const { isEnabled } = useFeature("huddle");
</script>

<template>
<div v-if="huddle.isEnabled">
<button @click="huddle.track()">Start huddle!</button>
<div v-if="isEnabled">
<button>Start huddle!</button>
</div>
</template>
```

See [useFeature()](#usefeature) for a full example

## Setting `user` and `company`

Bucket determines which features are active for a given `user`, `company`, or `otherContext`.
Expand All @@ -70,7 +73,9 @@ A number of special attributes exist:
:publishable-key="publishableKey"
:user="{ id: 'user_123', name: 'John Doe', email: 'john@acme.com' }"
:company="{ id: 'acme_inc', plan: 'pro' }"
></BucketProvider>
>
<!-- your app -->
</BucketProvider>
```

To retrieve features along with their targeting information, use `useFeature(key: string)` hook (described in a section below).
Expand Down Expand Up @@ -149,26 +154,40 @@ If you want more control over loading screens, `useIsLoading()` returns a Ref<bo

### `useFeature()`

Returns the state of a given feature for the current context. The composable provides type-safe access to feature flags and their configurations.
Returns the state of a given feature for the current context. The composable provides access to feature flags and their configurations.

`useFeature()` returns an object with this shape:

```ts
{
isEnabled: boolean, // is the feature enabled
track: () => void, // send a track event when the feature is used
requestFeedback: (...) => void // open up a feedback dialog
config: {key: string, payload: any}, // remote configuration for this feature
isLoading: boolean // if you want to manage loading state at the feature level
}
```

Example:

```vue
<script setup lang="ts">
import { useFeature } from "@bucketco/vue-sdk";

const huddle = useFeature("huddle");
const { isEnabled, track, requestFeedback, config } = useFeature("huddle");
</script>

<template>
<div v-if="huddle.isLoading">Loading...</div>
<div v-else-if="!huddle.isEnabled">Feature not available</div>
<div v-if="isLoading">Loading...</div>
<div v-else-if="!isEnabled">Feature not available</div>
<div v-else>
<button @click="huddle.track()">Start huddle!</button>
<button @click="track()">Start huddle!</button>
<button
@click="
(e) =>
huddle.requestFeedback({
requestFeedback({
title:
huddle.config.payload?.question ??
config.payload?.question ??
'How do you like the Huddles feature?',
position: {
type: 'POPOVER',
Expand All @@ -187,7 +206,9 @@ See the reference docs for details.

### `useTrack()`

`useTrack()` lets you send custom events to Bucket. Use this whenever a user _uses_ a feature.
`useTrack()` returns a function which lets you send custom events to Bucket. It takes a string argument with the event name and optionally an object with properties to attach the event.

Using `track` returned from `useFeature()` calles this track function with the feature key as the event name.

```vue
<script setup lang="ts">
Expand Down
15 changes: 7 additions & 8 deletions packages/vue-sdk/dev/plain/components/StartHuddleButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,25 @@ import { useFeature } from "../../../src";

import Section from "./Section.vue";

const huddle = useFeature("huddle");
const { isLoading, isEnabled, config, requestFeedback, track } =
useFeature("huddle");
</script>
<template>
<Section title="Huddle">
<div style="display: flex; gap: 10px; flex-wrap: wrap">
<div>Huddle enabled: {{ huddle.isEnabled }}</div>
<div v-if="huddle.isLoading">Loading...</div>
<div>Huddle enabled: {{ isEnabled }}</div>
<div v-if="isLoading">Loading...</div>
<div v-else style="display: flex; gap: 10px; flex-wrap: wrap">
<div>
<button @click="huddle.track()">
{{
huddle.config.payload?.buttonTitle ?? "Start Huddle (track event)"
}}
<button @click="track()">
{{ config?.payload?.buttonTitle ?? "Start Huddle (track event)" }}
</button>
</div>
<div>
<button
@click="
(e) =>
huddle.requestFeedback({
requestFeedback({
title: 'Do you like huddles?',
})
"
Expand Down
31 changes: 14 additions & 17 deletions packages/vue-sdk/src/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,45 @@
import { inject, InjectionKey, onBeforeUnmount, ref } from "vue";
import { computed, inject, InjectionKey, onBeforeUnmount, ref } from "vue";

import { RequestFeedbackData, UnassignedFeedback } from "@bucketco/browser-sdk";

import {
FeatureKey,
Feature,
ProviderContextType,
RequestFeatureFeedbackOptions,
} from "./types";

export const ProviderSymbol: InjectionKey<ProviderContextType> =
Symbol("BucketProvider");

export function useFeature<TKey extends FeatureKey>(key: TKey) {
export function useFeature(key: string): Feature<any> {
const client = useClient();
const ctx = injectSafe();

const track = () => client?.value.track(key);
const requestFeedback = (opts: RequestFeatureFeedbackOptions) =>
client.value.requestFeedback({ ...opts, featureKey: key });

function getFeature() {
const f = client.value.getFeature(key);
return {
isEnabled: f.isEnabled,
config: f.config,
track,
requestFeedback,
key,
isLoading: ctx.isLoading,
};
}
const feature = ref(client.value.getFeature(key));

const feature = ref(getFeature());
updateFeature();

function updateFeature() {
feature.value = getFeature();
feature.value = client.value.getFeature(key);
}

client.value.on("featuresUpdated", updateFeature);
onBeforeUnmount(() => {
client.value.off("featuresUpdated", updateFeature);
});

return feature;
return {
key,
isEnabled: computed(() => feature.value.isEnabled),
config: computed(() => feature.value.config),
track,
requestFeedback,
isLoading: computed(() => ctx.isLoading.value),
};
}

/**
Expand Down
10 changes: 9 additions & 1 deletion packages/vue-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import {
} from "@bucketco/browser-sdk";

import BucketProvider from "./BucketProvider.vue";
import { BucketProps } from "./types";
import {
BucketProps,
EmptyFeatureRemoteConfig,
Feature,
FeatureType,
} from "./types";

export {
useClient,
Expand All @@ -29,6 +34,9 @@ export { BucketProvider };
export type {
CheckEvent,
CompanyContext,
EmptyFeatureRemoteConfig,
Feature,
FeatureType,
RawFeatures,
TrackEvent,
UserContext,
Expand Down
2 changes: 1 addition & 1 deletion packages/vue-sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface Feature<
key: string;
isEnabled: Ref<boolean>;
isLoading: Ref<boolean>;
config: ({ key: string } & TConfig) | EmptyFeatureRemoteConfig;
config: Ref<({ key: string } & TConfig) | EmptyFeatureRemoteConfig>;
track(): Promise<Response | undefined> | undefined;
requestFeedback: (opts: RequestFeatureFeedbackOptions) => void;
}
Expand Down