Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion examples/guide-example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ import "@knocklabs/react/dist/index.css";
import { useEffect, useState } from "react";
import { Link, Route, Routes } from "react-router";

interface ChangelogCardContent {
headline: string;
title: string;
body: string;
}

const ChangelogCard = () => {
const { guide, step } = useGuide({ type: "changelog-card" });
const { guide, step } = useGuide<ChangelogCardContent>({ type: "changelog-card" });

useEffect(() => {
if (step) step.markAsSeen();
Expand Down
11 changes: 9 additions & 2 deletions packages/client/src/clients/guide/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
mockDefaultGroup,
} from "./helpers";
import {
Any,
ConstructorOpts,
DebugState,
GetGuidesQueryParams,
Expand Down Expand Up @@ -446,7 +447,10 @@ export class KnockGuideClient {
// Store selector
//

selectGuides(state: StoreState, filters: SelectFilterParams = {}) {
selectGuides<C = Any>(
state: StoreState,
filters: SelectFilterParams = {},
): KnockGuide<C>[] {
if (Object.keys(state.guides).length === 0) {
return [];
}
Expand All @@ -465,7 +469,10 @@ export class KnockGuideClient {
return [...result.values()];
}

selectGuide(state: StoreState, filters: SelectFilterParams = {}) {
selectGuide<C = Any>(
state: StoreState,
filters: SelectFilterParams = {},
): KnockGuide<C> | undefined {
if (Object.keys(state.guides).length === 0) {
return undefined;
}
Expand Down
21 changes: 12 additions & 9 deletions packages/client/src/clients/guide/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { GenericData } from "@knocklabs/types";
// Fetch guides API
//

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Any = any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if unknown is better than any here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was on the fence about that but decided to keep it as any for this PR. I understand unknown is considered a "safer" choice, but I'm afraid it would end up adding a lot of boilerplate/annoyance in practice..

Besides, it was initially typed as any so switching to unknown here would technically be a breaking change? I'm definitely open to the idea, but I suspect it's better handled as a separate version if we do decide to make that change.


export interface StepMessageState {
id: string;
seen_at: string | null;
Expand All @@ -13,29 +16,28 @@ export interface StepMessageState {
link_clicked_at: string | null;
}

export interface GuideStepData {
export interface GuideStepData<TContent = Any> {
ref: string;
schema_key: string;
schema_semver: string;
schema_variant_key: string;
message: StepMessageState;
// eslint-disable-next-line
content: any;
content: TContent;
}

interface GuideActivationLocationRuleData {
directive: "allow" | "block";
pathname: string;
}

export interface GuideData {
export interface GuideData<TContent = Any> {
__typename: "Guide";
channel_id: string;
id: string;
key: string;
type: string;
semver: string;
steps: GuideStepData[];
steps: GuideStepData<TContent>[];
activation_location_rules: GuideActivationLocationRuleData[];
bypass_global_group_limit: boolean;
inserted_at: string;
Expand Down Expand Up @@ -148,7 +150,8 @@ export type GuideSocketEvent =
// Guide client
//

export interface KnockGuideStep extends GuideStepData {
export interface KnockGuideStep<TContent = Any>
extends GuideStepData<TContent> {
markAsSeen: () => void;
markAsInteracted: (params?: { metadata?: GenericData }) => void;
markAsArchived: () => void;
Expand All @@ -159,10 +162,10 @@ interface KnockGuideActivationLocationRule
pattern: URLPattern;
}

export interface KnockGuide extends GuideData {
steps: KnockGuideStep[];
export interface KnockGuide<TContent = Any> extends GuideData<TContent> {
steps: KnockGuideStep<TContent>[];
activation_location_rules: KnockGuideActivationLocationRule[];
getStep: () => KnockGuideStep | undefined;
getStep: () => KnockGuideStep<TContent> | undefined;
}

type QueryKey = string;
Expand Down
15 changes: 10 additions & 5 deletions packages/react-core/src/modules/guide/hooks/useGuide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ import { useStore } from "@tanstack/react-store";

import { UseGuideContextReturn, useGuideContext } from "./useGuideContext";

interface UseGuideReturn extends UseGuideContextReturn {
guide: KnockGuide | undefined;
step: KnockGuideStep | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Any = any;

interface UseGuideReturn<C = Any> extends UseGuideContextReturn {
guide: KnockGuide<C> | undefined;
step: KnockGuideStep<C> | undefined;
}

export const useGuide = (filters: KnockGuideFilterParams): UseGuideReturn => {
export const useGuide = <C = Any>(
filters: KnockGuideFilterParams,
): UseGuideReturn<C> => {
const context = useGuideContext();

if (!filters.key && !filters.type) {
Expand All @@ -24,7 +29,7 @@ export const useGuide = (filters: KnockGuideFilterParams): UseGuideReturn => {
const { client, colorMode } = context;

const guide = useStore(client.store, (state) =>
client.selectGuide(state, filters),
client.selectGuide<C>(state, filters),
);

const step = guide && guide.getStep();
Expand Down
13 changes: 8 additions & 5 deletions packages/react-core/src/modules/guide/hooks/useGuides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ import { useStore } from "@tanstack/react-store";

import { UseGuideContextReturn, useGuideContext } from "./useGuideContext";

interface UseGuidesReturn extends UseGuideContextReturn {
guides: KnockGuide[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Any = any;

interface UseGuidesReturn<C = Any> extends UseGuideContextReturn {
guides: KnockGuide<C>[];
}

export const useGuides = (
export const useGuides = <C = Any>(
filters: Pick<KnockGuideFilterParams, "type">,
): UseGuidesReturn => {
): UseGuidesReturn<C> => {
const context = useGuideContext();
const { client, colorMode } = context;

const guides = useStore(client.store, (state) =>
client.selectGuides(state, filters),
client.selectGuides<C>(state, filters),
);

return { client, colorMode, guides };
Expand Down
Loading