Skip to content
Open
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
26 changes: 7 additions & 19 deletions app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { ComponentProps, useEffect, useMemo } from "react";
import { useLocation } from "react-router-dom";
import React, {ComponentProps, useMemo} from "react";
import {useLocation} from "react-router-dom";
import Providers from "@/components/Providers";
import { isAddress } from "viem";
import { ConnectButton } from "@rainbow-me/rainbowkit";
Expand All @@ -8,12 +8,16 @@ import SwapWidget from "@ensofinance/shortcuts-widget";
import logoUrl from "./logo_black_white.png";

import "@rainbow-me/rainbowkit/styles.css";
import useProjectInfo from "@/hooks/useProjectInfo";
// import "./App.css";

const EnsoApiKey = import.meta.env.VITE_ENSO_API_KEY;

function App() {
const location = useLocation();

const projectInfo = useProjectInfo()

const props = useMemo(() => {
const searchParams = new URLSearchParams(window.location.search);
const tokenInParam = searchParams.get("tokenIn");
Expand All @@ -33,21 +37,6 @@ function App() {
return props;
}, [location]);

useEffect(() => {
// Set the title of the page from the environment variable
if (import.meta.env.VITE_APP_TITLE) {
document.title = `ENSO | ${import.meta.env.VITE_APP_TITLE}`;
}

// Set the favicon of the page from the environment variable
if (import.meta.env.VITE_APP_LOGO_URL) {
const favicon = document.querySelector("link[rel='icon']");
if (favicon instanceof HTMLLinkElement) {
favicon.href = import.meta.env.VITE_APP_LOGO_URL;
}
}
}, []);

return (
<Providers>
<div
Expand All @@ -61,11 +50,10 @@ function App() {
padding: "5px",
}}
>
<img src={logoUrl} alt={"Enso"} style={{ height: "50px" }} />
<img src={projectInfo?.logo || logoUrl} alt={"Enso"} style={{ height: "50px" }} />

<ConnectButton />
</div>

<div
style={{
display: "flex",
Expand Down
95 changes: 95 additions & 0 deletions app/src/hooks/useProjectInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { useEffect, useState } from "react";
import { FirebaseProjectI } from "@/types/project";

/**
* Updates the document title and favicon dynamically based on project data.
* @param title - The title of the project (used in the page title).
* @param logo - The URL of the project's favicon.
*/
const applyProjectInfoInDOM = (title?: string, logo?: string) => {
if (title) {
document.title = `ENSO | ${title}`;
}
if (logo) {
let favicon = document.querySelector<HTMLLinkElement>("link[rel='icon']");

// If favicon does not exist, create a new one
if (!favicon) {
favicon = document.createElement("link");
favicon.rel = "icon";
document.head.appendChild(favicon);
}
favicon.href = logo;
}
};

/**
* Fetches project data from Firebase using the project name extracted from the URL.
* @param slug - The project name derived from the current hostname.
*/
const getProjectData = async (slug: string): Promise<FirebaseProjectI> => {
try {
const resp = await fetch(`https://us-central1-enso-95b84.cloudfunctions.net/getProject`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ subdomain: slug }),
});

// Check if the response is valid
if (!resp.ok) {
throw new Error(`Failed to fetch project data: ${resp.status} ${resp.statusText}`);
}

return await resp.json();

} catch (error) {
console.error("Error fetching project data:", error);
throw error;
}
};

const useProjectInfo = () => {
const hostname = window.location.hostname; // e.g., "sub.widget.denys-heraymov.website"
const parts = hostname.split('.');
const subdomain = parts[0] + "." + parts[1]; // "sub.widget"
const storageKey = `projectInfo_${subdomain}`;

const [projectInfo, setProjectInfo] = useState<FirebaseProjectI | null>(null);

useEffect(() => {
if (subdomain) {
// Try to retrieve cached project info from localStorage
const cachedData = localStorage.getItem(storageKey);
if (cachedData) {
try {
const parsedData: FirebaseProjectI = JSON.parse(cachedData);
setProjectInfo(parsedData);
applyProjectInfoInDOM(parsedData.name, parsedData.logo);
return; // Data found, skip fetching
} catch (error) {
console.error("Error parsing project info from localStorage:", error);
// If error occurs during parsing, clear the corrupted data
localStorage.removeItem(storageKey);
}
}

// If not in localStorage, fetch data from the API
getProjectData(subdomain).then((data: FirebaseProjectI) => {
if (data?.name || data?.logo) {
setProjectInfo(data);
applyProjectInfoInDOM(data.name, data.logo);
// Save fetched data to localStorage for later use
localStorage.setItem(storageKey, JSON.stringify(data));
}
}).catch((error) => {
console.error("Error in fetching project data:", error);
});
}
}, [subdomain]); // Re-run if subdomain changes

return projectInfo; // Return project info to be used in components
};

export default useProjectInfo;
17 changes: 15 additions & 2 deletions app/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import {BrowserRouter, Route, Routes} from "react-router-dom";
import "./index.css";
import App from "./App";

createRoot(document.getElementById("root")!).render(
<StrictMode>
<BrowserRouter>
<App />
<Routes>
<Route
path="/"
element={<App />}
/>
<Route
path="/:slug"
element={<App />}
/>
<Route
path="*"
element={<div>Empty page</div>}
/>
</Routes>
</BrowserRouter>
</StrictMode>,
);
12 changes: 12 additions & 0 deletions app/src/types/project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface FirebaseProjectI {
id: string
name: string
projectName: string
subdomain: string
userId: string
category: string
variant: string
twitter?: string
logo?: string
banner?: string
}
6 changes: 3 additions & 3 deletions app/vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"trailingSlash": false,
"rewrites": [
{
"source": "/widget/:path*",
"destination": "/:path*"
"source": "/(.*)",
"destination": "/index.html"
}
]
}
}
14 changes: 10 additions & 4 deletions app/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@ import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";

// https://vite.dev/config/
export default defineConfig(({ mode }) => {
// @ts-expect-error env is accessible
const env = loadEnv(mode, process.cwd(), "");

return {
base: env.VITE_BASE_PATH,
base: "/",
plugins: [react(), tsconfigPaths()],
resolve: {
alias: {
"@": "/src",
},
},
server: {
fs: {
strict: false,
},
},
build: {
outDir: "dist",
assetsDir: "assets",
},
};
});
9 changes: 9 additions & 0 deletions vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"trailingSlash": false,
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}