Skip to content
Closed
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ yarn-error.log*
next-env.d.ts

tests/reports
playwright-report
playwright-report
.env*.local
53 changes: 53 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"@clerk/clerk-sdk-node": "^5.1.6",
"@types/node-fetch": "^2.6.12",
"@vercel/flags": "^3.1.1",
"next": "15.1.6",
"node-fetch": "^3.3.2",
"react": "^19.0.0",
Expand Down
43 changes: 43 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions src/app/api/flags/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NextResponse } from "next/server";

export async function GET() {
const isEnabled = unstable_flag("WeatherWidgetEnabled", false);
return NextResponse.json({ isEnabled });
}

function unstable_flag(flagName: string, defaultValue: boolean): boolean {
// Simulate a feature flag check. In a real-world scenario, this might query a database or an external service.
const featureFlags: Record<string, boolean> = {
WeatherWidgetEnabled: true, // Example flag
};

if (!(flagName in featureFlags)) {
console.warn(`Feature flag "${flagName}" not found. Using default value.`);
}

return featureFlags[flagName] ?? defaultValue;
}
10 changes: 7 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import WeatherWidget from "./weather-widget";
import TaskList from "./task-list";
import Header from "./header"; // Import the Header component
import Footer from "./footer"; // Import the Footer component
import Header from "./header";
import Footer from "./footer";
import { useState } from "react";

export default function RootLayout({
Expand All @@ -13,6 +13,10 @@ export default function RootLayout({
}) {
const [darkMode, setDarkMode] = useState(false);

// Access the Vercel flag
const showWeatherWidget =
process.env.NEXT_PUBLIC_SHOW_WEATHER_WIDGET === "true";

const toggleDarkMode = () => {
setDarkMode(!darkMode);
};
Expand All @@ -29,7 +33,7 @@ export default function RootLayout({
<Header toggleDarkMode={toggleDarkMode} darkMode={darkMode} />
<main>
{children}
<WeatherWidget />
{showWeatherWidget && <WeatherWidget />}
<TaskList />
</main>
<Footer />
Expand Down
76 changes: 12 additions & 64 deletions src/app/weather-widget.tsx
Original file line number Diff line number Diff line change
@@ -1,76 +1,24 @@
import { useState, useEffect } from "react";
export default async function WeatherWidget() {
const res = await fetch("/api/flags");
const { isEnabled } = await res.json();

export default function WeatherWidget() {
const [weather, setWeather] = useState<WeatherData | null>(null);
const [location, setLocation] = useState<{
latitude: number | null;
longitude: number | null;
}>({
latitude: null,
longitude: null,
});
const [error, setError] = useState("");

useEffect(() => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
setLocation({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
});
},
() => {
setError("Unable to retrieve your location.");
}
);
} else {
setError("Geolocation is not supported by your browser.");
}
}, []);

useEffect(() => {
if (location.latitude && location.longitude) {
fetchWeather(location.latitude, location.longitude);
}
}, [location]);

interface WeatherData {
temperature: number;
windspeed: number;
if (!isEnabled) {
return null; // Do not render the widget if the flag is disabled
}

const fetchWeather = async (
latitude: number,
longitude: number
): Promise<void> => {
try {
const response = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true`
);
if (!response.ok) throw new Error("Failed to fetch weather data");
const data: { current_weather: WeatherData } = await response.json();
setWeather(data.current_weather);
} catch (err) {
console.error(err);
setError("Unable to fetch weather data.");
}
};
const weatherData = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=40.7128&longitude=-74.0060&current_weather=true`
).then((res) => res.json());

const { temperature, windspeed } = weatherData.current_weather;

return (
<div
style={{ border: "1px solid #ccc", padding: "10px", borderRadius: "8px" }}
>
<h3>Current Weather</h3>
{error && <p style={{ color: "red" }}>{error}</p>}
{weather ? (
<div>
<p>Temperature: {weather.temperature}°C</p>
<p>Wind Speed: {weather.windspeed} km/h</p>
</div>
) : (
<p>Loading...</p>
)}
<p>Temperature: {temperature}°C</p>
<p>Wind Speed: {windspeed} km/h</p>
</div>
);
}
26 changes: 26 additions & 0 deletions src/flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import "@vercel/flags/next";

// Define your flags here
type Flag = {
key: string;
defaultValue: boolean;
description: string;
};

export const flags = [
{
key: "WeatherWidgetEnabled",
defaultValue: process.env.WEATHER_WIDGET_ENABLED === "true",
description: "Enable the weather widget on the dashboard.",
},
] as const satisfies readonly Flag[];

type FlagKey = (typeof flags)[number]["key"];

// Pre-compile the flags for client-side usage
const vercelFlags = flags.reduce((flagObj, { key, defaultValue }) => {
flagObj[key] = async () => defaultValue;
return flagObj;
}, {} as Record<FlagKey, () => Promise<boolean>>);

export const getFlag = (key: FlagKey) => vercelFlags[key]!();