feature-flag-core
Version:
🏁 Turn features on and off like a pro – no magic spells required.🎩✨
187 lines (186 loc) • 7.79 kB
JavaScript
import React, { useEffect, useState } from "react";
// write a logic to remove an exisiting port from the URL
const removeCurrentPortAndSetNewPort = (url, port) => {
const baseUrl = new URL(url); // Create a new URL object
baseUrl.port = ''; // Clear the existing port (if any)
baseUrl.port = port.toString(); // Set the new port
return baseUrl.toString();
};
// const API_BASE = `${window.location.origin}:${port}/`; // Dynamic port
// const API_URL = `${API_BASE}flags`;
export const FeatureFlagManager = ({ port = 3231, customUrl }) => {
const [flags, setFlags] = useState({});
const [loading, setLoading] = useState(true);
const [newFlagName, setNewFlagName] = useState("");
const [newFlagValue, setNewFlagValue] = useState(true);
let API_BASE;
if (customUrl) {
API_BASE = customUrl.endsWith("/") ? customUrl : `${customUrl}/`;
}
else {
API_BASE = removeCurrentPortAndSetNewPort(window.location.origin, port);
}
// const API_BASE = removeCurrentPortAndSetNewPort(window.location.origin, port);
const API_URL = `${API_BASE}flags`;
// // write a logic to remove an exisiting port from the URL
// const removeCurrentPortAndSetNewPort = (url: string, port: number) => {
// const baseUrl = new URL(url); // Create a new URL object
// baseUrl.port = ''; // Clear the existing port (if any)
// baseUrl.port = port.toString(); // Set the new port
// return baseUrl.toString();
// };
// // const API_BASE = `${window.location.origin}:${port}/`; // Dynamic port
// // const API_URL = `${API_BASE}flags`;
// const API_BASE = removeCurrentPortAndSetNewPort(window.location.origin, port);
// const API_URL = `${API_BASE}flags`;
useEffect(() => {
if (port) {
// If a port is passed, set it to localStorage
localStorage.setItem("featureFlagPort", port.toString());
}
fetchFlags();
}, []);
const fetchFlags = async () => {
setLoading(true);
try {
const res = await fetch(API_URL);
const data = await res.json();
setFlags(data);
}
catch (err) {
console.error("Failed to fetch flags:", err);
}
finally {
setLoading(false);
}
};
const handleToggle = async (key, currentValue) => {
try {
await fetch(API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key, value: !currentValue }),
});
setFlags((prev) => ({ ...prev, [key]: !currentValue }));
}
catch (err) {
console.error("Failed to update flag:", err);
}
};
const handleDelete = async (key) => {
const confirmed = window.confirm(`Are you sure you want to delete "${key}"?`);
if (!confirmed)
return;
try {
await fetch(`${API_URL}/${key}`, { method: "DELETE" });
setFlags((prev) => {
const updated = { ...prev };
delete updated[key];
return updated;
});
}
catch (err) {
console.error("Failed to delete flag:", err);
}
};
const handleAdd = async () => {
const trimmedKey = newFlagName.trim();
if (!trimmedKey)
return alert("Flag name cannot be empty");
if (flags.hasOwnProperty(trimmedKey))
return alert("Flag already exists");
try {
await fetch(API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: trimmedKey, value: newFlagValue }),
});
setFlags((prev) => ({ ...prev, [trimmedKey]: newFlagValue }));
setNewFlagName("");
setNewFlagValue(true);
}
catch (err) {
console.error("Failed to add flag:", err);
}
};
if (loading)
return React.createElement("div", { style: styles.loading }, "Loading feature flags...");
return (React.createElement("div", { style: styles.container },
React.createElement("h2", { style: styles.heading }, "\uD83C\uDF1F Feature Flag Manager"),
React.createElement("a", { href: "https://github.com/cinfinit", style: { margin: "0px", marginBottom: "0.5rem", color: 'cyan' } }, "@ cinfinit"),
React.createElement("table", { style: styles.table },
React.createElement("thead", null,
React.createElement("tr", null,
React.createElement("th", { style: styles.th }, "Flag Name"),
React.createElement("th", { style: styles.th }, "Enabled"),
React.createElement("th", { style: styles.th }, "Actions"))),
React.createElement("tbody", null,
Object.entries(flags).map(([key, value]) => (React.createElement("tr", { key: key },
React.createElement("td", { style: styles.td }, key),
React.createElement("td", { style: styles.td },
React.createElement("label", { className: "switch" },
React.createElement("input", { type: "checkbox", checked: value, onChange: () => handleToggle(key, value) }),
React.createElement("span", { className: "slider" }))),
React.createElement("td", { style: styles.td },
React.createElement("button", { style: styles.button, onClick: () => handleDelete(key) }, "\u274C Delete"))))),
React.createElement("tr", null,
React.createElement("td", { style: styles.td },
React.createElement("input", { type: "text", placeholder: "New flag name", value: newFlagName, onChange: (e) => setNewFlagName(e.target.value), style: styles.input })),
React.createElement("td", { style: styles.td },
React.createElement("label", { className: "switch" },
React.createElement("input", { type: "checkbox", checked: newFlagValue, onChange: () => handleToggle(newFlagName, newFlagValue) }),
React.createElement("span", { className: "slider" }))),
React.createElement("td", { style: styles.td },
React.createElement("button", { style: styles.button, onClick: handleAdd }, "\u254B Add")))))));
};
// Dark theme styles
const styles = {
container: {
maxWidth: 700,
margin: "2rem auto",
backgroundColor: "#2C3E50",
padding: "2rem",
borderRadius: "8px",
color: "#ECF0F1",
fontFamily: "Arial, sans-serif",
},
heading: {
textAlign: "center",
marginBottom: "0.5rem",
},
loading: {
color: "#ECF0F1",
textAlign: "center",
marginTop: "2rem",
},
table: {
width: "100%",
borderCollapse: "collapse",
},
th: {
textAlign: "center",
padding: "12px 8px",
borderBottom: "2px solid #3C4A5A",
color: "#ECF0F1",
},
td: {
padding: "10px 8px",
borderBottom: "1px solid #3C4A5A",
},
input: {
width: "100%",
padding: "6px 8px",
backgroundColor: "#34495E",
border: "1px solid #3C4A5A",
borderRadius: "4px",
color: "#ECF0F1",
},
button: {
background: "transparent",
border: "1px solid #ECF0F1",
color: "#ECF0F1",
padding: "4px 10px",
borderRadius: "4px",
cursor: "pointer",
},
};