Skip to content

Commit 13294ee

Browse files
committed
feat(sdk): added react sdk and created setup guide
1 parent 59ab831 commit 13294ee

19 files changed

Lines changed: 687 additions & 122 deletions

File tree

apps/web/app/(main)/projects/[project]/environments/page-client.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { CreateEnvironmentModal } from "@/components/environment/create-environm
66
import { DeleteEnvironmentModal } from "@/components/environment/delete-environment-modal";
77
import { EnvironmentDetails } from "@/components/environment/environment-details";
88
import { EnvironmentTabs } from "@/components/environment/environment-tabs";
9+
import { SetupGuide } from "@/components/environment/setup-guide";
910
import { EnvironmentPageSkeleton } from "@/components/environment/skeleton";
1011
import {
1112
useCreateEnvironment,
@@ -169,6 +170,12 @@ export default function PageClient() {
169170
)}
170171
</div>
171172

173+
{selectedEnv && (
174+
<div className="mt-6 flex flex-col space-y-4 overflow-hidden rounded-lg border border-gray-200 bg-white px-6 py-6 shadow-sm">
175+
<SetupGuide environment={selectedEnv} />
176+
</div>
177+
)}
178+
172179
<CreateEnvironmentModal
173180
isOpen={isCreateModalOpen}
174181
isSubmitting={isSubmitting}

apps/web/app/providers.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"use client";
22

3+
import { FlagixProvider } from "@flagix/react";
34
import { Toaster } from "@flagix/ui/components/sonner";
45
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5-
import { FlagixProvider } from "@/providers/flagix";
66

77
const queryClient = new QueryClient({
88
defaultOptions: {

apps/web/components/dashboard/project-list.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import { useFlag } from "@flagix/react";
34
import { Button } from "@flagix/ui/components/button";
45
import { Input } from "@flagix/ui/components/input";
56
import { Skeleton } from "@flagix/ui/components/skeleton";
@@ -14,7 +15,6 @@ import { Plus, Search } from "lucide-react";
1415
import { useState } from "react";
1516
import { ProjectSection } from "@/components/dashboard/project-section";
1617
import { CreateProjectModal } from "@/components/project/project-create-modal";
17-
import { useFlag } from "@/hooks/use-flag";
1818
import { api } from "@/lib/api";
1919
import { QUERY_KEYS } from "@/lib/queries/keys";
2020
import type { Project } from "@/types/project";
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
"use client";
2+
3+
import { Button } from "@flagix/ui/components/button";
4+
import { Check, Copy, Terminal } from "lucide-react";
5+
import { useState } from "react";
6+
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
7+
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
8+
import { SNIPPETS } from "@/components/environment/setup-snippets";
9+
import { cn } from "@/lib/utils";
10+
import type { FullEnvironment } from "@/types/environment";
11+
12+
type Framework = "nextjs" | "react" | "js";
13+
14+
const frameworkNames: Record<Framework, string> = {
15+
js: "JavaScript",
16+
react: "React",
17+
nextjs: "Next.js",
18+
};
19+
20+
export function SetupGuide({ environment }: { environment: FullEnvironment }) {
21+
const [selectedFramework, setSelectedFramework] =
22+
useState<Framework>("nextjs");
23+
const [copiedSection, setCopiedSection] = useState<string | null>(null);
24+
25+
const handleCopy = (code: string, section: string) => {
26+
navigator.clipboard.writeText(code);
27+
setCopiedSection(section);
28+
setTimeout(() => setCopiedSection(null), 2000);
29+
};
30+
31+
const withDirective = (code: string) => {
32+
return selectedFramework === "nextjs" ? `"use client";\n\n${code}` : code;
33+
};
34+
35+
const getProviderTitle = () => {
36+
if (selectedFramework === "nextjs") {
37+
return "2. Configure the Provider (app/providers.tsx)";
38+
}
39+
40+
return "2. Configure the Provider (main.tsx)";
41+
};
42+
43+
const CodeBlock = ({
44+
code,
45+
section,
46+
title,
47+
language = "typescript",
48+
}: {
49+
code: string;
50+
section: string;
51+
title: string;
52+
language?: string;
53+
}) => {
54+
const isCopied = copiedSection === section;
55+
56+
return (
57+
<div className="space-y-3">
58+
<div className="flex items-center justify-between">
59+
<div className="flex items-center space-x-2">
60+
{section === "install" && (
61+
<Terminal className="h-4 w-4 text-gray-400" />
62+
)}
63+
<p className="font-medium text-gray-700 text-sm">{title}</p>
64+
</div>
65+
<Button
66+
className={cn(
67+
"h-7 rounded px-2 text-xs transition-all",
68+
isCopied
69+
? "bg-emerald-50 text-emerald-600"
70+
: "bg-gray-100 text-gray-600"
71+
)}
72+
onClick={() => handleCopy(code, section)}
73+
variant="ghost"
74+
>
75+
{isCopied ? (
76+
<>
77+
<Check className="mr-1 h-3 w-3" /> Copied
78+
</>
79+
) : (
80+
<>
81+
<Copy className="mr-1 h-3 w-3" /> Copy
82+
</>
83+
)}
84+
</Button>
85+
</div>
86+
87+
<div className="relative overflow-hidden rounded-lg border border-gray-200 text-sm shadow-sm">
88+
<SyntaxHighlighter
89+
customStyle={{
90+
margin: 0,
91+
padding: "1.25rem",
92+
fontSize: "0.85rem",
93+
lineHeight: "1.6",
94+
backgroundColor: "#0d1117",
95+
}}
96+
language={language}
97+
style={vscDarkPlus}
98+
>
99+
{code}
100+
</SyntaxHighlighter>
101+
</div>
102+
</div>
103+
);
104+
};
105+
106+
return (
107+
<div className="w-full">
108+
<div className="mb-6 flex flex-col space-y-1">
109+
<h2 className="font-semibold text-gray-900 text-lg">
110+
Quick Setup Guide
111+
</h2>
112+
<p className="text-gray-500 text-sm">
113+
Integrate Flagix into your {frameworkNames[selectedFramework]}{" "}
114+
application.
115+
</p>
116+
</div>
117+
118+
<div className="flex items-center space-x-6 border-gray-100 border-b">
119+
{(Object.keys(frameworkNames) as Framework[]).map((fw) => {
120+
const isActive = selectedFramework === fw;
121+
return (
122+
<Button
123+
className={cn(
124+
"relative px-1 py-3 font-medium text-sm transition-colors",
125+
isActive
126+
? "text-emerald-600"
127+
: "text-gray-400 hover:text-gray-600"
128+
)}
129+
key={fw}
130+
onClick={() => setSelectedFramework(fw)}
131+
>
132+
{frameworkNames[fw]}
133+
{isActive && (
134+
<div className="absolute right-0 bottom-0 left-0 h-0.5 bg-emerald-600" />
135+
)}
136+
</Button>
137+
);
138+
})}
139+
</div>
140+
141+
<div className="mt-8 space-y-10">
142+
<CodeBlock
143+
code={SNIPPETS.install(selectedFramework)}
144+
language="bash"
145+
section="install"
146+
title="1. Install the SDK"
147+
/>
148+
149+
{selectedFramework === "js" ? (
150+
<CodeBlock
151+
code={SNIPPETS.vanilla(environment.apiKey)}
152+
section="vanilla"
153+
title="2. Initialize & Use"
154+
/>
155+
) : (
156+
<>
157+
<CodeBlock
158+
code={withDirective(SNIPPETS.provider(environment.apiKey))}
159+
section="provider"
160+
title={getProviderTitle()}
161+
/>
162+
<CodeBlock
163+
code={withDirective(SNIPPETS.usage)}
164+
section="usage"
165+
title="3. Use in your Components"
166+
/>
167+
</>
168+
)}
169+
</div>
170+
</div>
171+
);
172+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
export const SNIPPETS = {
2+
install: (framework: string) =>
3+
framework === "js"
4+
? "npm install @flagix/js-sdk"
5+
: "npm install @flagix/react",
6+
7+
provider: (apiKey: string) => `import { FlagixProvider } from "@flagix/react";
8+
9+
const options = {
10+
apiKey: "${apiKey}",
11+
apiBaseUrl: "http://localhost:5000",
12+
initialContext: {
13+
userId: "user_123",
14+
platform: "web",
15+
},
16+
};
17+
18+
export default function Providers({ children }: { children: React.ReactNode }) {
19+
return (
20+
<FlagixProvider options={options}>
21+
{children}
22+
</FlagixProvider>
23+
);
24+
}`,
25+
26+
usage: `import { useFlag, useFlagixActions } from "@flagix/react";
27+
28+
export default function MyComponent() {
29+
const isFeatureEnabled = useFlag<boolean>("my-feature-flag");
30+
const { track } = useFlagixActions();
31+
32+
if (isFeatureEnabled) {
33+
return (
34+
<button onClick={() => track("button_click")}>
35+
New Feature
36+
</button>
37+
);
38+
}
39+
40+
return <div>Old Feature</div>;
41+
}`,
42+
43+
vanilla: (apiKey: string) => `import { Flagix } from "@flagix/js-sdk";
44+
45+
// Initialize the SDK
46+
await Flagix.initialize({
47+
apiKey: "${apiKey}",
48+
apiBaseUrl: "http://localhost:5000",
49+
initialContext: { userId: "user_123" }
50+
});
51+
52+
// Evaluate a flag
53+
const isEnabled = Flagix.evaluate("my-feature-flag");
54+
55+
// Listen for real-time updates
56+
Flagix.onFlagUpdate((key) => {
57+
if (key === "my-feature-flag") {
58+
const updatedValue = Flagix.evaluate(key);
59+
console.log("Flag updated to:", updatedValue);
60+
}
61+
});
62+
63+
// Track events
64+
Flagix.track("conversion_event", { source: "web" });`,
65+
};

apps/web/hooks/use-flag.ts

Lines changed: 0 additions & 52 deletions
This file was deleted.

apps/web/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
"@dnd-kit/core": "^6.3.1",
1515
"@dnd-kit/sortable": "^10.0.0",
1616
"@dnd-kit/utilities": "^3.2.2",
17+
"@flagix/react": "workspace:*",
1718
"@flagix/ui": "workspace:*",
18-
"@flagix/js-sdk": "workspace:*",
1919
"@tailwindcss/postcss": "^4.1.16",
2020
"@tanstack/react-query": "^5.90.7",
2121
"axios": "^1.12.2",
@@ -30,6 +30,7 @@
3030
"react": "^19.1.0",
3131
"react-dom": "^19.1.0",
3232
"react-icons": "^5.5.0",
33+
"react-syntax-highlighter": "^16.1.0",
3334
"recharts": "^3.5.1",
3435
"tailwind-merge": "^3.3.1",
3536
"tailwindcss": "^4.1.16"
@@ -39,6 +40,7 @@
3940
"@types/node": "^22.15.3",
4041
"@types/react": "19.1.0",
4142
"@types/react-dom": "19.1.1",
43+
"@types/react-syntax-highlighter": "^15.5.13",
4244
"cors": "^2.8.5",
4345
"eslint": "^9.34.0",
4446
"tw-animate-css": "^1.4.0",

0 commit comments

Comments
 (0)