diff --git a/www/app/(auth)/userInfo.tsx b/www/app/(auth)/userInfo.tsx index ca7a2858..f4840f3f 100644 --- a/www/app/(auth)/userInfo.tsx +++ b/www/app/(auth)/userInfo.tsx @@ -1,13 +1,9 @@ "use client"; -import { - useFiefIsAuthenticated, - useFiefUserinfo, -} from "@fief/fief/nextjs/react"; +import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react"; import Link from "next/link"; export default function UserInfo() { const isAuthenticated = useFiefIsAuthenticated(); - const userinfo = useFiefUserinfo(); return !isAuthenticated ? ( diff --git a/www/app/[domain]/api/current-user.ts b/www/app/[domain]/api/current-user.ts new file mode 100644 index 00000000..20771551 --- /dev/null +++ b/www/app/[domain]/api/current-user.ts @@ -0,0 +1,10 @@ +import { NextApiRequest } from "next"; +import { fief, getFiefAuth } from "../../lib/fief"; +// type FiefNextApiHandler = (req: NextApiRequest & AuthenticateRequestResult, res: NextApiResponse) => unknown | Promise; + +export default (req: any, res) => { + const domain = req.url; + console.log("user", req.url, getFiefAuth("localhost").currentUser()); + return getFiefAuth("localhost").currentUser()(req, res); +}; +// export default fief.currentUser() diff --git a/www/app/browse/page.tsx b/www/app/[domain]/browse/page.tsx similarity index 94% rename from www/app/browse/page.tsx rename to www/app/[domain]/browse/page.tsx index 29a0089b..49dffb81 100644 --- a/www/app/browse/page.tsx +++ b/www/app/[domain]/browse/page.tsx @@ -1,17 +1,19 @@ "use client"; import React, { useState, useEffect } from "react"; -import getApi from "../lib/getApi"; +import getApi from "../../lib/getApi"; import { PageGetTranscript, GetTranscript, GetTranscriptFromJSON, -} from "../api"; -import { Title } from "../lib/textComponents"; +} from "../../api"; +import { Title } from "../../lib/textComponents"; import Pagination from "./pagination"; import Link from "next/link"; import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faGear } from "@fortawesome/free-solid-svg-icons"; +import { featureEnabled } from "../domainContext"; +import router from "next/router"; export default function TranscriptBrowser() { const api = getApi(); @@ -19,6 +21,7 @@ export default function TranscriptBrowser() { const [page, setPage] = useState(1); const [isLoading, setIsLoading] = useState(false); const isAuthenticated = useFiefIsAuthenticated(); + const browseEnabled = featureEnabled("browse"); useEffect(() => { if (!isAuthenticated) return; diff --git a/www/app/browse/pagination.tsx b/www/app/[domain]/browse/pagination.tsx similarity index 100% rename from www/app/browse/pagination.tsx rename to www/app/[domain]/browse/pagination.tsx diff --git a/www/app/[domain]/domainContext.tsx b/www/app/[domain]/domainContext.tsx new file mode 100644 index 00000000..0b89c6e7 --- /dev/null +++ b/www/app/[domain]/domainContext.tsx @@ -0,0 +1,49 @@ +"use client"; +import { createContext, useContext, useEffect, useState } from "react"; + +type DomainContextType = { + features: { + requireLogin: boolean; + privacy: boolean; + browse: boolean; + }; + apiUrl: string | null; +}; + +export const DomainContext = createContext({ + features: { + requireLogin: false, + privacy: true, + browse: false, + }, + apiUrl: null, +}); + +export const DomainContextProvider = ({ config, children }) => { + const [context, setContext] = useState(); + + useEffect(() => { + if (!config) return; + setContext({ + features: { + requireLogin: !!config["features"]["requireLogin"], + privacy: !!config["features"]["privacy"], + browse: !!config["features"]["browse"], + }, + apiUrl: config["api_url"], + }); + }, [config]); + + if (!context) return; + + return ( + {children} + ); +}; + +export const featureEnabled = ( + featureName: "requireLogin" | "privacy" | "browse", +) => { + const context = useContext(DomainContext); + return context.features[featureName] as boolean | undefined; +}; diff --git a/www/app/[domain]/layout.tsx b/www/app/[domain]/layout.tsx new file mode 100644 index 00000000..980a931f --- /dev/null +++ b/www/app/[domain]/layout.tsx @@ -0,0 +1,162 @@ +import "../styles/globals.scss"; +import { Poppins } from "next/font/google"; +import { Metadata } from "next"; +import FiefWrapper from "../(auth)/fiefWrapper"; +import UserInfo from "../(auth)/userInfo"; +import { ErrorProvider } from "../(errors)/errorContext"; +import ErrorMessage from "../(errors)/errorMessage"; +import Image from "next/image"; +import Link from "next/link"; +import About from "../(aboutAndPrivacy)/about"; +import Privacy from "../(aboutAndPrivacy)/privacy"; +import { get } from "@vercel/edge-config"; +import { DomainContextProvider } from "./domainContext"; + +const poppins = Poppins({ subsets: ["latin"], weight: ["200", "400", "600"] }); + +export const metadata: Metadata = { + title: { + template: "%s – Reflector", + default: "Reflector - AI-Powered Meeting Transcriptions by Monadical", + }, + description: + "Reflector is an AI-powered tool that transcribes your meetings with unparalleled accuracy, divides content by topics, and provides insightful summaries. Maximize your productivity with Reflector, brought to you by Monadical. Capture the signal, not the noise", + applicationName: "Reflector", + referrer: "origin-when-cross-origin", + keywords: ["Reflector", "Monadical", "AI", "Meetings", "Transcription"], + authors: [{ name: "Monadical Team", url: "https://monadical.com/team.html" }], + formatDetection: { + email: false, + address: false, + telephone: false, + }, + + openGraph: { + title: "Reflector", + description: + "Reflector is an AI-powered tool that transcribes your meetings with unparalleled accuracy, divides content by topics, and provides insightful summaries. Maximize your productivity with Reflector, brought to you by Monadical. Capture the signal, not the noise.", + type: "website", + }, + + twitter: { + card: "summary_large_image", + title: "Reflector", + description: + "Reflector is an AI-powered tool that transcribes your meetings with unparalleled accuracy, divides content by topics, and provides insightful summaries. Maximize your productivity with Reflector, brought to you by Monadical. Capture the signal, not the noise.", + images: ["/r-icon.png"], + }, + + icons: { + icon: "/r-icon.png", + shortcut: "/r-icon.png", + apple: "/r-icon.png", + }, + viewport: { + width: "device-width", + initialScale: 1, + maximumScale: 1, + }, + + robots: { index: false, follow: false, noarchive: true, noimageindex: true }, +}; + +type LayoutProps = { + params: { + domain: string; + }; + children: any; +}; + +export default async function RootLayout({ children, params }: LayoutProps) { + const config = await get(params.domain); + // console.log(config); + const requireLogin = config ? config["features"]["requireLogin"] : false; + // console.log(requireLogin); + const privacy = config ? config["features"]["privacy"] : true; + + const browse = config ? config["features"]["browse"] : true; + + return ( + + + + + + +
+
+ {/* Logo on the left */} + + Reflector +
+

+ Reflector +

+

+ Capture the signal, not the noise +

+
+ +
+ {/* Text link on the right */} + + Create + + {browse ? ( + <> +  ·  + + Browse + + + ) : ( + <> + )} +  ·  + + {privacy ? ( + <> +  ·  + + + ) : ( + <> + )} + {requireLogin ? ( + <> +  ·  + + + ) : ( + <> + )} +
+
+ + {children} +
+
+
+
+ + + ); +} diff --git a/www/app/page.tsx b/www/app/[domain]/page.tsx similarity index 100% rename from www/app/page.tsx rename to www/app/[domain]/page.tsx diff --git a/www/app/transcripts/[transcriptId]/page.tsx b/www/app/[domain]/transcripts/[transcriptId]/page.tsx similarity index 94% rename from www/app/transcripts/[transcriptId]/page.tsx rename to www/app/[domain]/transcripts/[transcriptId]/page.tsx index ea911c5e..4c35f242 100644 --- a/www/app/transcripts/[transcriptId]/page.tsx +++ b/www/app/[domain]/transcripts/[transcriptId]/page.tsx @@ -1,6 +1,6 @@ "use client"; import Modal from "../modal"; -import getApi from "../../lib/getApi"; +import getApi from "../../../lib/getApi"; import useTranscript from "../useTranscript"; import useTopics from "../useTopics"; import useWaveform from "../useWaveform"; @@ -8,13 +8,13 @@ import { TopicList } from "../topicList"; import Recorder from "../recorder"; import { Topic } from "../webSocketTypes"; import React, { useEffect, useState } from "react"; -import "../../styles/button.css"; +import "../../../styles/button.css"; import FinalSummary from "../finalSummary"; import ShareLink from "../shareLink"; import QRCode from "react-qr-code"; import TranscriptTitle from "../transcriptTitle"; -import { featRequireLogin } from "../../../app/lib/utils"; import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react"; +import { featureEnabled } from "../../domainContext"; type TranscriptDetails = { params: { @@ -32,7 +32,7 @@ export default function TranscriptDetails(details: TranscriptDetails) { const useActiveTopic = useState(null); useEffect(() => { - if (featRequireLogin() && !isAuthenticated) return; + if (featureEnabled("requireLogin") && !isAuthenticated) return; setTranscriptId(details.params.transcriptId); }, [api]); diff --git a/www/app/transcripts/[transcriptId]/record/page.tsx b/www/app/[domain]/transcripts/[transcriptId]/record/page.tsx similarity index 96% rename from www/app/transcripts/[transcriptId]/record/page.tsx rename to www/app/[domain]/transcripts/[transcriptId]/record/page.tsx index 4a67d624..8e31327c 100644 --- a/www/app/transcripts/[transcriptId]/record/page.tsx +++ b/www/app/[domain]/transcripts/[transcriptId]/record/page.tsx @@ -6,14 +6,14 @@ import useWebRTC from "../../useWebRTC"; import useTranscript from "../../useTranscript"; import { useWebSockets } from "../../useWebSockets"; import useAudioDevice from "../../useAudioDevice"; -import "../../../styles/button.css"; +import "../../../../styles/button.css"; import { Topic } from "../../webSocketTypes"; -import getApi from "../../../lib/getApi"; +import getApi from "../../../../lib/getApi"; import LiveTrancription from "../../liveTranscription"; import DisconnectedIndicator from "../../disconnectedIndicator"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faGear } from "@fortawesome/free-solid-svg-icons"; -import { lockWakeState, releaseWakeState } from "../../../lib/wakeLock"; +import { lockWakeState, releaseWakeState } from "../../../../lib/wakeLock"; type TranscriptDetails = { params: { diff --git a/www/app/transcripts/audioInputsDropdown.tsx b/www/app/[domain]/transcripts/audioInputsDropdown.tsx similarity index 100% rename from www/app/transcripts/audioInputsDropdown.tsx rename to www/app/[domain]/transcripts/audioInputsDropdown.tsx diff --git a/www/app/transcripts/createTranscript.ts b/www/app/[domain]/transcripts/createTranscript.ts similarity index 86% rename from www/app/transcripts/createTranscript.ts rename to www/app/[domain]/transcripts/createTranscript.ts index 53697765..aaecdc32 100644 --- a/www/app/transcripts/createTranscript.ts +++ b/www/app/[domain]/transcripts/createTranscript.ts @@ -1,8 +1,11 @@ import { useEffect, useState } from "react"; -import { DefaultApi, V1TranscriptsCreateRequest } from "../api/apis/DefaultApi"; -import { GetTranscript } from "../api"; -import { useError } from "../(errors)/errorContext"; -import getApi from "../lib/getApi"; +import { + DefaultApi, + V1TranscriptsCreateRequest, +} from "../../api/apis/DefaultApi"; +import { GetTranscript } from "../../api"; +import { useError } from "../../(errors)/errorContext"; +import getApi from "../../lib/getApi"; type CreateTranscript = { response: GetTranscript | null; diff --git a/www/app/transcripts/disconnectedIndicator.tsx b/www/app/[domain]/transcripts/disconnectedIndicator.tsx similarity index 100% rename from www/app/transcripts/disconnectedIndicator.tsx rename to www/app/[domain]/transcripts/disconnectedIndicator.tsx diff --git a/www/app/transcripts/finalSummary.tsx b/www/app/[domain]/transcripts/finalSummary.tsx similarity index 98% rename from www/app/transcripts/finalSummary.tsx rename to www/app/[domain]/transcripts/finalSummary.tsx index 747a20be..a2ebebbc 100644 --- a/www/app/transcripts/finalSummary.tsx +++ b/www/app/[domain]/transcripts/finalSummary.tsx @@ -2,7 +2,7 @@ import { useRef, useState } from "react"; import React from "react"; import ReactDom from "react-dom"; import Markdown from "react-markdown"; -import "../styles/markdown.css"; +import "../../styles/markdown.css"; type FinalSummaryProps = { summary: string; diff --git a/www/app/transcripts/liveTranscription.tsx b/www/app/[domain]/transcripts/liveTranscription.tsx similarity index 100% rename from www/app/transcripts/liveTranscription.tsx rename to www/app/[domain]/transcripts/liveTranscription.tsx diff --git a/www/app/transcripts/modal.tsx b/www/app/[domain]/transcripts/modal.tsx similarity index 100% rename from www/app/transcripts/modal.tsx rename to www/app/[domain]/transcripts/modal.tsx diff --git a/www/app/transcripts/new/page.tsx b/www/app/[domain]/transcripts/new/page.tsx similarity index 90% rename from www/app/transcripts/new/page.tsx rename to www/app/[domain]/transcripts/new/page.tsx index 949458fd..b2d19a5a 100644 --- a/www/app/transcripts/new/page.tsx +++ b/www/app/[domain]/transcripts/new/page.tsx @@ -1,22 +1,22 @@ "use client"; -import React, { useEffect, useState } from "react"; +import React, { useContext, useEffect, useState } from "react"; import useAudioDevice from "../useAudioDevice"; import "react-select-search/style.css"; -import "../../styles/button.css"; -import "../../styles/form.scss"; -import About from "../../(aboutAndPrivacy)/about"; -import Privacy from "../../(aboutAndPrivacy)/privacy"; +import "../../../styles/button.css"; +import "../../../styles/form.scss"; +import About from "../../../(aboutAndPrivacy)/about"; +import Privacy from "../../../(aboutAndPrivacy)/privacy"; import { useRouter } from "next/navigation"; import useCreateTranscript from "../createTranscript"; import SelectSearch from "react-select-search"; -import { supportedLatinLanguages } from "../../supportedLanguages"; -import { featRequireLogin, featPrivacy } from "../../lib/utils"; +import { supportedLatinLanguages } from "../../../supportedLanguages"; import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react"; +import { featureEnabled } from "../../domainContext"; const TranscriptCreate = () => { const router = useRouter(); const isAuthenticated = useFiefIsAuthenticated(); - const requireLogin = featRequireLogin(); + const requireLogin = featureEnabled("requireLogin"); const [name, setName] = useState(); const nameChange = (event: React.ChangeEvent) => { @@ -74,7 +74,9 @@ const TranscriptCreate = () => { In order to use Reflector, we kindly request permission to access your microphone during meetings and events.

- {featPrivacy() && } + {featureEnabled("privacy") && ( + + )}
diff --git a/www/app/transcripts/recorder.tsx b/www/app/[domain]/transcripts/recorder.tsx similarity index 97% rename from www/app/transcripts/recorder.tsx rename to www/app/[domain]/transcripts/recorder.tsx index d9ded651..401b6c9e 100644 --- a/www/app/transcripts/recorder.tsx +++ b/www/app/[domain]/transcripts/recorder.tsx @@ -1,20 +1,20 @@ import React, { useRef, useEffect, useState } from "react"; import WaveSurfer from "wavesurfer.js"; -import RecordPlugin from "../lib/custom-plugins/record"; -import CustomRegionsPlugin from "../lib/custom-plugins/regions"; +import RecordPlugin from "../../lib/custom-plugins/record"; +import CustomRegionsPlugin from "../../lib/custom-plugins/regions"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faMicrophone } from "@fortawesome/free-solid-svg-icons"; import { faDownload } from "@fortawesome/free-solid-svg-icons"; -import { formatTime } from "../lib/time"; +import { formatTime } from "../../lib/time"; import { Topic } from "./webSocketTypes"; -import { AudioWaveform } from "../api"; +import { AudioWaveform } from "../../api"; import AudioInputsDropdown from "./audioInputsDropdown"; import { Option } from "react-dropdown"; -import { useError } from "../(errors)/errorContext"; -import { waveSurferStyles } from "../styles/recorder"; +import { useError } from "../../(errors)/errorContext"; +import { waveSurferStyles } from "../../styles/recorder"; type RecorderProps = { setStream?: React.Dispatch>; diff --git a/www/app/transcripts/scrollToBottom.tsx b/www/app/[domain]/transcripts/scrollToBottom.tsx similarity index 100% rename from www/app/transcripts/scrollToBottom.tsx rename to www/app/[domain]/transcripts/scrollToBottom.tsx diff --git a/www/app/transcripts/shareLink.tsx b/www/app/[domain]/transcripts/shareLink.tsx similarity index 93% rename from www/app/transcripts/shareLink.tsx rename to www/app/[domain]/transcripts/shareLink.tsx index 99e5ac9a..49163a5b 100644 --- a/www/app/transcripts/shareLink.tsx +++ b/www/app/[domain]/transcripts/shareLink.tsx @@ -1,5 +1,5 @@ import React, { useState, useRef, useEffect, use } from "react"; -import { featPrivacy } from "../lib/utils"; +import { featureEnabled } from "../domainContext"; const ShareLink = () => { const [isCopied, setIsCopied] = useState(false); @@ -23,12 +23,14 @@ const ShareLink = () => { } }; + const privacyEnabled = featureEnabled("privacy"); + return (
- {featPrivacy() ? ( + {privacyEnabled ? (

You can share this link with others. Anyone with the link will have access to the page, including the full audio recording, for the next 7 diff --git a/www/app/transcripts/topicList.tsx b/www/app/[domain]/transcripts/topicList.tsx similarity index 98% rename from www/app/transcripts/topicList.tsx rename to www/app/[domain]/transcripts/topicList.tsx index 21990642..e5de09c8 100644 --- a/www/app/transcripts/topicList.tsx +++ b/www/app/[domain]/transcripts/topicList.tsx @@ -4,7 +4,7 @@ import { faChevronRight, faChevronDown, } from "@fortawesome/free-solid-svg-icons"; -import { formatTime } from "../lib/time"; +import { formatTime } from "../../lib/time"; import ScrollToBottom from "./scrollToBottom"; import { Topic } from "./webSocketTypes"; diff --git a/www/app/transcripts/transcriptTitle.tsx b/www/app/[domain]/transcripts/transcriptTitle.tsx similarity index 100% rename from www/app/transcripts/transcriptTitle.tsx rename to www/app/[domain]/transcripts/transcriptTitle.tsx diff --git a/www/app/transcripts/useAudioDevice.ts b/www/app/[domain]/transcripts/useAudioDevice.ts similarity index 100% rename from www/app/transcripts/useAudioDevice.ts rename to www/app/[domain]/transcripts/useAudioDevice.ts diff --git a/www/app/transcripts/useMp3.ts b/www/app/[domain]/transcripts/useMp3.ts similarity index 90% rename from www/app/transcripts/useMp3.ts rename to www/app/[domain]/transcripts/useMp3.ts index 98c35658..8bccf903 100644 --- a/www/app/transcripts/useMp3.ts +++ b/www/app/[domain]/transcripts/useMp3.ts @@ -2,9 +2,9 @@ import { useEffect, useState } from "react"; import { DefaultApi, V1TranscriptGetAudioMp3Request, -} from "../api/apis/DefaultApi"; -import {} from "../api"; -import { useError } from "../(errors)/errorContext"; +} from "../../api/apis/DefaultApi"; +import {} from "../../api"; +import { useError } from "../../(errors)/errorContext"; type Mp3Response = { url: string | null; diff --git a/www/app/transcripts/useTopics.ts b/www/app/[domain]/transcripts/useTopics.ts similarity index 89% rename from www/app/transcripts/useTopics.ts rename to www/app/[domain]/transcripts/useTopics.ts index b653bf50..58aebcec 100644 --- a/www/app/transcripts/useTopics.ts +++ b/www/app/[domain]/transcripts/useTopics.ts @@ -2,9 +2,8 @@ import { useEffect, useState } from "react"; import { DefaultApi, V1TranscriptGetTopicsRequest, -} from "../api/apis/DefaultApi"; -import { TranscriptTopic } from "../api"; -import { useError } from "../(errors)/errorContext"; +} from "../../api/apis/DefaultApi"; +import { useError } from "../../(errors)/errorContext"; import { Topic } from "./webSocketTypes"; type TranscriptTopics = { diff --git a/www/app/transcripts/useTranscript.ts b/www/app/[domain]/transcripts/useTranscript.ts similarity index 85% rename from www/app/transcripts/useTranscript.ts rename to www/app/[domain]/transcripts/useTranscript.ts index 2523557d..81c9202f 100644 --- a/www/app/transcripts/useTranscript.ts +++ b/www/app/[domain]/transcripts/useTranscript.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; -import { DefaultApi, V1TranscriptGetRequest } from "../api/apis/DefaultApi"; -import { GetTranscript } from "../api"; -import { useError } from "../(errors)/errorContext"; +import { DefaultApi, V1TranscriptGetRequest } from "../../api/apis/DefaultApi"; +import { GetTranscript } from "../../api"; +import { useError } from "../../(errors)/errorContext"; type Transcript = { response: GetTranscript | null; diff --git a/www/app/transcripts/useWaveform.ts b/www/app/[domain]/transcripts/useWaveform.ts similarity index 89% rename from www/app/transcripts/useWaveform.ts rename to www/app/[domain]/transcripts/useWaveform.ts index d44b60e7..2cc22349 100644 --- a/www/app/transcripts/useWaveform.ts +++ b/www/app/[domain]/transcripts/useWaveform.ts @@ -2,9 +2,9 @@ import { useEffect, useState } from "react"; import { DefaultApi, V1TranscriptGetAudioWaveformRequest, -} from "../api/apis/DefaultApi"; -import { AudioWaveform } from "../api"; -import { useError } from "../(errors)/errorContext"; +} from "../../api/apis/DefaultApi"; +import { AudioWaveform } from "../../api"; +import { useError } from "../../(errors)/errorContext"; type AudioWaveFormResponse = { waveform: AudioWaveform | null; diff --git a/www/app/transcripts/useWebRTC.ts b/www/app/[domain]/transcripts/useWebRTC.ts similarity index 94% rename from www/app/transcripts/useWebRTC.ts rename to www/app/[domain]/transcripts/useWebRTC.ts index 3f5b72f5..07468547 100644 --- a/www/app/transcripts/useWebRTC.ts +++ b/www/app/[domain]/transcripts/useWebRTC.ts @@ -3,8 +3,8 @@ import Peer from "simple-peer"; import { DefaultApi, V1TranscriptRecordWebrtcRequest, -} from "../api/apis/DefaultApi"; -import { useError } from "../(errors)/errorContext"; +} from "../../api/apis/DefaultApi"; +import { useError } from "../../(errors)/errorContext"; const useWebRTC = ( stream: MediaStream | null, diff --git a/www/app/transcripts/useWebSockets.ts b/www/app/[domain]/transcripts/useWebSockets.ts similarity index 99% rename from www/app/transcripts/useWebSockets.ts rename to www/app/[domain]/transcripts/useWebSockets.ts index 3f2ba6bf..6bd7bf48 100644 --- a/www/app/transcripts/useWebSockets.ts +++ b/www/app/[domain]/transcripts/useWebSockets.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { Topic, FinalSummary, Status } from "./webSocketTypes"; -import { useError } from "../(errors)/errorContext"; +import { useError } from "../../(errors)/errorContext"; import { useRouter } from "next/navigation"; type UseWebSockets = { diff --git a/www/app/transcripts/webSocketTypes.ts b/www/app/[domain]/transcripts/webSocketTypes.ts similarity index 100% rename from www/app/transcripts/webSocketTypes.ts rename to www/app/[domain]/transcripts/webSocketTypes.ts diff --git a/www/app/layout.tsx b/www/app/layout.tsx deleted file mode 100644 index 87dfc104..00000000 --- a/www/app/layout.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import "./styles/globals.scss"; -import { Poppins } from "next/font/google"; -import { Metadata } from "next"; -import FiefWrapper from "./(auth)/fiefWrapper"; -import UserInfo from "./(auth)/userInfo"; -import { ErrorProvider } from "./(errors)/errorContext"; -import ErrorMessage from "./(errors)/errorMessage"; -import Image from "next/image"; -import Link from "next/link"; -import About from "./(aboutAndPrivacy)/about"; -import Privacy from "./(aboutAndPrivacy)/privacy"; -import { featPrivacy, featRequireLogin } from "./lib/utils"; - -const poppins = Poppins({ subsets: ["latin"], weight: ["200", "400", "600"] }); - -export const metadata: Metadata = { - title: { - template: "%s – Reflector", - default: "Reflector - AI-Powered Meeting Transcriptions by Monadical", - }, - description: - "Reflector is an AI-powered tool that transcribes your meetings with unparalleled accuracy, divides content by topics, and provides insightful summaries. Maximize your productivity with Reflector, brought to you by Monadical. Capture the signal, not the noise", - applicationName: "Reflector", - referrer: "origin-when-cross-origin", - keywords: ["Reflector", "Monadical", "AI", "Meetings", "Transcription"], - authors: [{ name: "Monadical Team", url: "https://monadical.com/team.html" }], - formatDetection: { - email: false, - address: false, - telephone: false, - }, - - openGraph: { - title: "Reflector", - description: - "Reflector is an AI-powered tool that transcribes your meetings with unparalleled accuracy, divides content by topics, and provides insightful summaries. Maximize your productivity with Reflector, brought to you by Monadical. Capture the signal, not the noise.", - type: "website", - }, - - twitter: { - card: "summary_large_image", - title: "Reflector", - description: - "Reflector is an AI-powered tool that transcribes your meetings with unparalleled accuracy, divides content by topics, and provides insightful summaries. Maximize your productivity with Reflector, brought to you by Monadical. Capture the signal, not the noise.", - images: ["/r-icon.png"], - }, - - icons: { - icon: "/r-icon.png", - shortcut: "/r-icon.png", - apple: "/r-icon.png", - }, - viewport: { - width: "device-width", - initialScale: 1, - maximumScale: 1, - }, - - robots: { index: false, follow: false, noarchive: true, noimageindex: true }, -}; - -export default function RootLayout({ children }) { - const requireLogin = featRequireLogin(); - return ( - - - - - -

-
- {/* Logo on the left */} - - Reflector -
-

- Reflector -

-

- Capture the signal, not the noise -

-
- -
- {/* Text link on the right */} - - Create - -  ·  - - Browse - -  ·  - - {featPrivacy() ? ( - <> -  ·  - - - ) : ( - <> - )} - {requireLogin ? ( - <> -  ·  - - - ) : ( - <> - )} -
-
- - {children} -
- - - - - ); -} diff --git a/www/app/lib/fief.ts b/www/app/lib/fief.ts index d778aad7..2ea118b6 100644 --- a/www/app/lib/fief.ts +++ b/www/app/lib/fief.ts @@ -1,6 +1,8 @@ -"use client"; import { Fief, FiefUserInfo } from "@fief/fief"; import { FiefAuth, IUserInfoCache } from "@fief/fief/nextjs"; +import { get } from "@vercel/edge-config"; +import { NextRequest, NextResponse } from "next/server"; +import { useError } from "../(errors)/errorContext"; export const SESSION_COOKIE_NAME = "reflector-auth"; @@ -38,13 +40,54 @@ class MemoryUserInfoCache implements IUserInfoCache { } } -export const fiefAuth = new FiefAuth({ - client: fiefClient, - sessionCookieName: SESSION_COOKIE_NAME, - redirectURI: - process.env.NEXT_PUBLIC_AUTH_CALLBACK_URL || - "http://localhost:3000/auth-callback", - logoutRedirectURI: - process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000", - userInfoCache: new MemoryUserInfoCache(), -}); +const FIEF_AUTHS = {} as { [domain: string]: FiefAuth }; + +export const getFiefAuth = async (url: URL) => { + if (FIEF_AUTHS[url.hostname]) { + return FIEF_AUTHS[url.hostname]; + } else { + const config = url && (await get(url.hostname)); + if (config) { + FIEF_AUTHS[url.hostname] = new FiefAuth({ + client: fiefClient, + sessionCookieName: SESSION_COOKIE_NAME, + redirectURI: config["auth_callback_url"], + logoutRedirectURI: url.origin, + userInfoCache: new MemoryUserInfoCache(), + }); + return FIEF_AUTHS[url.hostname]; + } else { + throw new Error("Fief intanciation failed"); + } + } +}; + +export const getFiefAuthMiddleware = async (url) => { + const protectedPaths = [ + { + matcher: "/:domain/transcripts", + parameters: {}, + }, + { + matcher: "/:domain/transcripts/:path*", + parameters: {}, + }, + { + matcher: "/:domain/browse", + parameters: {}, + }, + { + matcher: "/transcripts", + parameters: {}, + }, + { + matcher: "/transcripts/:path*", + parameters: {}, + }, + { + matcher: "/browse", + parameters: {}, + }, + ]; + return (await getFiefAuth(url))?.middleware(protectedPaths); +}; diff --git a/www/app/lib/getApi.ts b/www/app/lib/getApi.ts index 198bfd59..8d52ec20 100644 --- a/www/app/lib/getApi.ts +++ b/www/app/lib/getApi.ts @@ -2,12 +2,16 @@ import { Configuration } from "../api/runtime"; import { DefaultApi } from "../api/apis/DefaultApi"; import { useFiefAccessTokenInfo } from "@fief/fief/nextjs/react"; +import { useContext } from "react"; +import { DomainContext } from "../[domain]/domainContext"; export default function getApi(): DefaultApi { const accessTokenInfo = useFiefAccessTokenInfo(); + const api_url = useContext(DomainContext).apiUrl; + if (!api_url) throw new Error("no API URL"); const apiConfiguration = new Configuration({ - basePath: process.env.NEXT_PUBLIC_API_URL, + basePath: api_url, accessToken: accessTokenInfo ? "Bearer " + accessTokenInfo.access_token : undefined, diff --git a/www/app/lib/utils.ts b/www/app/lib/utils.ts index 5719d4cf..db775f07 100644 --- a/www/app/lib/utils.ts +++ b/www/app/lib/utils.ts @@ -1,15 +1,3 @@ export function isDevelopment() { return process.env.NEXT_PUBLIC_ENV === "development"; } - -export function featPrivacy() { - return process.env.NEXT_PUBLIC_FEAT_PRIVACY === "1"; -} - -export function featBrowse() { - return process.env.NEXT_PUBLIC_FEAT_BROWSE === "1"; -} - -export function featRequireLogin() { - return process.env.NEXT_PUBLIC_FEAT_LOGIN_REQUIRED === "1"; -} diff --git a/www/middleware.ts b/www/middleware.ts index 1b5b64f2..7b593ac4 100644 --- a/www/middleware.ts +++ b/www/middleware.ts @@ -1,22 +1,50 @@ -import type { NextRequest } from "next/server"; +import { NextResponse, NextRequest } from "next/server"; +import { get } from "@vercel/edge-config"; -import { fiefAuth } from "./app/lib/fief"; +import { FiefAuth, IUserInfoCache } from "@fief/fief/nextjs"; +import { getFiefAuth, getFiefAuthMiddleware } from "./app/lib/fief"; -let protectedPath: any = []; -if (process.env.NEXT_PUBLIC_FEAT_LOGIN_REQUIRED === "1") { - protectedPath = [ - { - matcher: "/transcripts/((?!new).*)", - parameters: {}, - }, - { - matcher: "/browse", - parameters: {}, - }, - ]; -} - -const authMiddleware = fiefAuth.middleware(protectedPath); export async function middleware(request: NextRequest) { - return authMiddleware(request); + const domain = request.nextUrl.hostname; + const config = await get(domain); + + if (!config) return NextResponse.error(); + + // Feature-flag protedted paths + if ( + !config["features"]["browse"] && + request.nextUrl.pathname.startsWith("/browse") + ) { + return NextResponse.redirect(request.nextUrl.origin); + } + + if (config["features"]["requireLogin"]) { + const fiefMiddleware = await getFiefAuthMiddleware(request.nextUrl); + const fiefResponse = fiefMiddleware(request); + if ( + request.nextUrl.pathname == "/" || + request.nextUrl.pathname.startsWith("/transcripts") || + request.nextUrl.pathname.startsWith("/browse") + ) { + // return fiefAuthMiddleware(domain, config['auth_callback_url'])(request, {rewrite: request.nextUrl.origin + "/" + domain + request.nextUrl.pathname}) + const response = NextResponse.rewrite( + request.nextUrl.origin + "/" + domain + request.nextUrl.pathname, + ); + // response = (await fiefResponse).headers + return response; + } + return fiefResponse; + } + + if ( + request.nextUrl.pathname == "/" || + request.nextUrl.pathname.startsWith("/transcripts") || + request.nextUrl.pathname.startsWith("/browse") + ) { + return NextResponse.rewrite( + request.nextUrl.origin + "/" + domain + request.nextUrl.pathname, + ); + } + + return NextResponse.next(); } diff --git a/www/package.json b/www/package.json index 91de6ef7..edbc0790 100644 --- a/www/package.json +++ b/www/package.json @@ -16,6 +16,7 @@ "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.2.0", "@sentry/nextjs": "^7.64.0", + "@vercel/edge-config": "^0.4.1", "autoprefixer": "10.4.14", "axios": "^1.4.0", "fontawesome": "^5.6.3", diff --git a/www/pages/api/current-user.ts b/www/pages/api/current-user.ts index ccb53362..8cda77a2 100644 --- a/www/pages/api/current-user.ts +++ b/www/pages/api/current-user.ts @@ -1,3 +1,13 @@ -import { fiefAuth } from "../../app/lib/fief"; +import { get } from "@vercel/edge-config"; +import { getFiefAuth } from "../../app/lib/fief"; +import { NextApiRequest, NextApiResponse } from "next"; -export default fiefAuth.currentUser(); +export default async (req: NextApiRequest, res: NextApiResponse) => { + const fromUrl = req.headers["referer"] && new URL(req.headers["referer"]); + const fief = fromUrl && (await getFiefAuth(fromUrl)); + if (fief) { + return fief.currentUser()(req as any, res as any); + } else { + return res.status(200).json({ userinfo: null, access_token_info: null }); + } +}; diff --git a/www/yarn.lock b/www/yarn.lock index d3d39205..a67822be 100644 --- a/www/yarn.lock +++ b/www/yarn.lock @@ -496,6 +496,18 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@vercel/edge-config-fs@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@vercel/edge-config-fs/-/edge-config-fs-0.1.0.tgz#cda8327f611418c1d1cbb28c6bed02d782be928b" + integrity sha512-NRIBwfcS0bUoUbRWlNGetqjvLSwgYH/BqKqDN7vK1g32p7dN96k0712COgaz6VFizAm9b0g6IG6hR6+hc0KCPg== + +"@vercel/edge-config@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@vercel/edge-config/-/edge-config-0.4.1.tgz#c247011dcd05ce6a6b4d17139215c8f684d83cb3" + integrity sha512-4Mc3H7lE+x4RrL17nY8CWeEorvJHbkNbQTy9p8H1tO7y11WeKj5xeZSr07wNgfWInKXDUwj5FZ3qd/jIzjPxug== + dependencies: + "@vercel/edge-config-fs" "0.1.0" + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"