diff --git a/.gitignore b/.gitignore index 73d6529..0f21eb6 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ yarn-error.log* next-env.d.ts tests/reports -playwright-report \ No newline at end of file +playwright-report +.env*.local diff --git a/package-lock.json b/package-lock.json index b362e32..b028db2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,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", @@ -157,6 +158,15 @@ "node": ">=18.17.0" } }, + "node_modules/@edge-runtime/cookies": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@edge-runtime/cookies/-/cookies-5.0.2.tgz", + "integrity": "sha512-Sd8LcWpZk/SWEeKGE8LT6gMm5MGfX/wm+GPnh1eBEtCpya3vYqn37wYknwAHw92ONoyyREl1hJwxV/Qx2DWNOg==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/@emnapi/runtime": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", @@ -1350,6 +1360,40 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@vercel/flags": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vercel/flags/-/flags-3.1.1.tgz", + "integrity": "sha512-JM0xXRCHvN5wiSr9C2u6PSbXMnqN/0IXyNEOob6UYWB70vBWVLUKM96wgSuj6orRzRTowItmja2RTYyRvVhuQg==", + "license": "MIT", + "dependencies": { + "@edge-runtime/cookies": "^5.0.2", + "jose": "^5.2.1" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0", + "@sveltejs/kit": "*", + "next": "*", + "react": "*", + "react-dom": "*" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "next": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -3948,6 +3992,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", diff --git a/package.json b/package.json index 81e46aa..a0c505c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef9bdad..66cd0d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@types/node-fetch': specifier: ^2.6.12 version: 2.6.12 + '@vercel/flags': + specifier: ^3.1.1 + version: 3.1.1(next@15.1.6(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next: specifier: 15.1.6 version: 15.1.6(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -106,6 +109,10 @@ packages: resolution: {integrity: sha512-suMvTWPeUYwu55Ai46siOHpAr36SUx7YfqqLmY96oQvJJFdLVUljockc9V5ZmAzV5gO1CoiXOG3ZGMiJWlCLQQ==} engines: {node: '>=18.17.0'} + '@edge-runtime/cookies@5.0.2': + resolution: {integrity: sha512-Sd8LcWpZk/SWEeKGE8LT6gMm5MGfX/wm+GPnh1eBEtCpya3vYqn37wYknwAHw92ONoyyREl1hJwxV/Qx2DWNOg==} + engines: {node: '>=16'} + '@emnapi/runtime@1.3.1': resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} @@ -455,6 +462,26 @@ packages: resolution: {integrity: sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vercel/flags@3.1.1': + resolution: {integrity: sha512-JM0xXRCHvN5wiSr9C2u6PSbXMnqN/0IXyNEOob6UYWB70vBWVLUKM96wgSuj6orRzRTowItmja2RTYyRvVhuQg==} + peerDependencies: + '@opentelemetry/api': ^1.7.0 + '@sveltejs/kit': '*' + next: '*' + react: '*' + react-dom: '*' + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@sveltejs/kit': + optional: true + next: + optional: true + react: + optional: true + react-dom: + optional: true + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1188,6 +1215,9 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + jose@5.10.0: + resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} + js-cookie@3.0.5: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} @@ -1910,6 +1940,8 @@ snapshots: dependencies: csstype: 3.1.3 + '@edge-runtime/cookies@5.0.2': {} + '@emnapi/runtime@1.3.1': dependencies: tslib: 2.8.1 @@ -2236,6 +2268,15 @@ snapshots: '@typescript-eslint/types': 8.24.0 eslint-visitor-keys: 4.2.0 + '@vercel/flags@3.1.1(next@15.1.6(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@edge-runtime/cookies': 5.0.2 + jose: 5.10.0 + optionalDependencies: + next: 15.1.6(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: acorn: 8.14.0 @@ -3155,6 +3196,8 @@ snapshots: jiti@1.21.7: {} + jose@5.10.0: {} + js-cookie@3.0.5: {} js-tokens@4.0.0: {} diff --git a/src/app/api/flags/route.ts b/src/app/api/flags/route.ts new file mode 100644 index 0000000..1b17438 --- /dev/null +++ b/src/app/api/flags/route.ts @@ -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 = { + WeatherWidgetEnabled: true, // Example flag + }; + + if (!(flagName in featureFlags)) { + console.warn(`Feature flag "${flagName}" not found. Using default value.`); + } + + return featureFlags[flagName] ?? defaultValue; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3267ce1..472572d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -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({ @@ -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); }; @@ -29,7 +33,7 @@ export default function RootLayout({
{children} - + {showWeatherWidget && }