diff --git a/packages/beta-switch/.gitignore b/packages/beta-switch/.gitignore
new file mode 100644
index 000000000..b67fef920
--- /dev/null
+++ b/packages/beta-switch/.gitignore
@@ -0,0 +1,3 @@
+/dist/
+/node_modules/
+/tsconfig.tsbuildinfo
diff --git a/packages/beta-switch/README.md b/packages/beta-switch/README.md
new file mode 100644
index 000000000..d4b0a342e
--- /dev/null
+++ b/packages/beta-switch/README.md
@@ -0,0 +1,8 @@
+# beta-switch
+For switching between two sites running in the same domain
+
+
+## Scripts
+
+- `yarn run build`: Builds the project
+- `yarn run dev`: Builds the project & watches files for changes, triggering a rebuild
diff --git a/packages/beta-switch/package.json b/packages/beta-switch/package.json
new file mode 100644
index 000000000..651426d5a
--- /dev/null
+++ b/packages/beta-switch/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@thunderstore/beta-switch",
+ "version": "0.1.0",
+ "description": "Small thing for switching between two projects running in the same domain",
+ "repository": "https://github.com/thunderstore-io/thunderstore-ui/tree/master/packages/beta-switch",
+ "main": "dist/thunderstore-beta-switch.cjs.js",
+ "module": "dist/thunderstore-beta-switch.esm.js",
+ "types": "dist/thunderstore-beta-switch.cjs.d.ts",
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch"
+ }
+}
diff --git a/packages/beta-switch/src/index.ts b/packages/beta-switch/src/index.ts
new file mode 100644
index 000000000..b56f5e6bd
--- /dev/null
+++ b/packages/beta-switch/src/index.ts
@@ -0,0 +1,137 @@
+const enabledPages = ["/communities", "c/[^/]+/?$"];
+
+const legacy = {
+ protocol: "http://",
+ hostname: "thunderstore.temp",
+ port: "",
+};
+
+const beta = {
+ protocol: "http://",
+ hostname: "new.thunderstore.temp",
+ port: "",
+};
+
+function setCookie(key: string, value: string) {
+ const date = new Date();
+ date.setFullYear(date.getFullYear() + 1);
+ document.cookie = `${key}=${value}; expires=${date.toUTCString()}; path=/`;
+}
+
+function checkBetaRedirect() {
+ let betaIsAvailable = false;
+ enabledPages.forEach((regPath) => {
+ const regExecuted = new RegExp(regPath).exec(window.location.pathname);
+ const found = regExecuted !== null && regExecuted.length < 2;
+ if (!betaIsAvailable && found) {
+ betaIsAvailable = true;
+ }
+ }, betaIsAvailable);
+
+ const desireCookie = new RegExp("betaDesire=([^;]+);").exec(
+ document.cookie[-1] === ";" ? document.cookie : document.cookie + ";"
+ )?.[1];
+ console.log("betaDesire:", desireCookie);
+
+ if (!desireCookie) {
+ setCookie("betaDesire", "legacy");
+ }
+ let userDesiredProject = legacy;
+ if (desireCookie === "beta") {
+ userDesiredProject = beta;
+ }
+
+ // const wouldBeRedirect = `${userDesiredProject.protocol}${userDesiredProject.hostname}${userDesiredProject.port !== "" ? ":" : ""}${userDesiredProject.port}${window.location.pathname}${window.location.search}`;
+ // console.log("would be redirect", wouldBeRedirect);
+ // const legacyRedirect = `${legacy.protocol}${legacy.hostname}${legacy.port !== "" ? ":" : ""}${legacy.port}${window.location.pathname}${window.location.search}`;
+ // console.log("legacy redirect", legacyRedirect);
+
+ // const currentUrl = `${window.location.protocol}//${window.location.hostname}${window.location.port !== "" ? ":" : ""}${window.location.port}`;
+ // const desiredUrl = `${userDesiredProject.protocol}${userDesiredProject.hostname}${userDesiredProject.port !== "" ? ":" : ""}${userDesiredProject.port}`;
+
+ // console.log("currentUrl", currentUrl);
+ // console.log("desiredUrl", desiredUrl);
+ if (betaIsAvailable) {
+ if (
+ `${window.location.protocol}//${window.location.hostname}${
+ window.location.port !== "" ? ":" : ""
+ }${window.location.port}` !==
+ `${userDesiredProject.protocol}${userDesiredProject.hostname}${
+ userDesiredProject.port !== "" ? ":" : ""
+ }${userDesiredProject.port}`
+ ) {
+ console.log("redirecting desired");
+ window.location.replace(
+ `${userDesiredProject.protocol}${userDesiredProject.hostname}${
+ userDesiredProject.port !== "" ? ":" : ""
+ }${userDesiredProject.port}${window.location.pathname}${
+ window.location.search
+ }`
+ );
+ }
+ // "else" user is on desired and available page
+ } else {
+ console.log("redirecting to legacy");
+ window.location.replace(
+ `${legacy.protocol}${legacy.hostname}${legacy.port !== "" ? ":" : ""}${
+ legacy.port
+ }${window.location.pathname}${window.location.search}`
+ );
+ }
+ // console.log("not redirecting");
+}
+
+async function insertSwitchBar() {
+ const bar = document.createElement("div");
+ bar.setAttribute(
+ "style",
+ "position:relative;top:0;width:100%;display: flex;padding: 6px 8px;justify-content: center;align-items: center;gap: 8px;align-self: stretch;background:#3498DB;font-family: Lato;font-size: 13px;font-style: normal;font-weight: 700;color: #FFF;"
+ );
+
+ bar.innerHTML =
+ '';
+
+ const text = document.createElement("p");
+ text.setAttribute("style", "line-height: 140%");
+ text.innerHTML =
+ "There’s a new beta version of this page available. Want to take it for a spin?";
+
+ const switchToBetaButton = document.createElement("button");
+ switchToBetaButton.setAttribute(
+ "style",
+ "display:flex;padding:6px 8px;justify-content:center;align-items:center;gap:12px;border-radius:6px;border:1px solid #FFF;background:transparent;"
+ );
+ switchToBetaButton.onclick = () => {
+ setCookie("betaDesire", "beta");
+ checkBetaRedirect();
+ };
+ switchToBetaButton.innerHTML = "Switch to beta";
+
+ const ignoreBarButton = document.createElement("button");
+ ignoreBarButton.setAttribute(
+ "style",
+ "color:#FFF;position:absolute;right:16px;height:16px;width:16px;background:transparent;fill:#FFF;"
+ );
+ ignoreBarButton.onclick = () => {
+ setCookie("hideBetaBar", "hide");
+ };
+ ignoreBarButton.innerHTML =
+ '';
+
+ bar.appendChild(text);
+ bar.appendChild(switchToBetaButton);
+ bar.appendChild(ignoreBarButton);
+ await new Promise((r) => setTimeout(r, 1000));
+ document.body.insertBefore(bar, document.body.firstChild);
+}
+
+const hideBetaBarCookie = new RegExp("hideBetaBar=([^;]+);").exec(
+ document.cookie[-1] === ";" ? document.cookie : document.cookie + ";"
+)?.[1];
+console.log("hideBetaBar:", hideBetaBarCookie);
+if (hideBetaBarCookie !== "hide") {
+ insertSwitchBar();
+}
+console.log("inserted switch bar");
+checkBetaRedirect();
+console.log("checked beta");
diff --git a/packages/beta-switch/tsconfig.json b/packages/beta-switch/tsconfig.json
new file mode 100644
index 000000000..51e494b37
--- /dev/null
+++ b/packages/beta-switch/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "node",
+ "removeComments": true,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "strictFunctionTypes": true,
+ "noImplicitThis": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "resolveJsonModule": true,
+ "forceConsistentCasingInFileNames": true,
+ "composite": true,
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "jsx": "react-jsx"
+ },
+ "include": ["./src/**/*.tsx", "./src/**/*.ts"],
+ "exclude": ["node_modules"]
+}