Files
reflector/www/app/(app)/transcripts/new/page.tsx
Mathieu Virbel 03561453c5 feat: Monadical SSO as replacement of Fief (#393)
* sso: first pass for integrating SSO

still have issue on refreshing
maybe customize the login page, or completely avoid it
make 100% to understand how session server/client are working
need to test with different configuration option (features flags and
requireLogin)

* sso: correctly handle refresh token, with pro-active refresh

Going on interceptors make extra calls to reflector when 401.
We need then to circle back with NextJS backend to update the jwt,
session, then retry the failed request.

I prefered to go pro-active, and ensure the session AND jwt are always
up to date.

A minute before the expiration, we'll try to refresh it. useEffect() of
NextJS cannot be asynchronous, so we cannot wait for the token to be
refreshed.

Every 20s, a minute before the expiration (so 3x in total max) we'll try
to renew. When the accessToken is renewed, the session is updated, and
dispatching up to the client, which updates the useApi().

Therefore, no component will left without a incorrect token.

* fixes: issue with missing key on react-select-search because the default value is undefined

* sso: fixes login/logout button, and avoid seeing the login with authentik page when clicking

* sso: ensure /transcripts/new is not behind protected page, and feature flags page are honored

* sso: fixes user sub->id

* fixes: remove old layout not used

* fixes: set default NEXT_PUBLIC_SITE_URL as localhost

* fixes: removing fief again due to merge with main

* sso: ensure session is always ready before doing any action

* sso: add migration from fief to jwt in server, only from transcripts list

* fixes: user tests

* fixes: compilation issues
2024-09-03 19:27:15 +02:00

177 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import React, { 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 { useRouter } from "next/navigation";
import useCreateTranscript from "../createTranscript";
import SelectSearch from "react-select-search";
import { supportedLanguages } from "../../../supportedLanguages";
import { useSession } from "next-auth/react";
import { featureEnabled } from "../../../domainContext";
import { Button, Text } from "@chakra-ui/react";
import { signIn } from "next-auth/react";
import { Spinner } from "@chakra-ui/react";
const TranscriptCreate = () => {
const router = useRouter();
const { status } = useSession();
const sessionReady = status !== "loading";
const isAuthenticated = status === "authenticated";
const requireLogin = featureEnabled("requireLogin");
const [name, setName] = useState<string>("");
const nameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setName(event.target.value);
};
const [targetLanguage, setTargetLanguage] = useState<string>("NOTRANSLATION");
const onLanguageChange = (newval) => {
(!newval || typeof newval === "string") && setTargetLanguage(newval);
};
const createTranscript = useCreateTranscript();
const [loadingRecord, setLoadingRecord] = useState(false);
const [loadingUpload, setLoadingUpload] = useState(false);
const getTargetLanguage = () => {
if (targetLanguage === "NOTRANSLATION") return;
return targetLanguage;
};
const send = () => {
if (loadingRecord || createTranscript.loading || permissionDenied) return;
setLoadingRecord(true);
createTranscript.create({ name, target_language: getTargetLanguage() });
};
const uploadFile = () => {
if (loadingUpload || createTranscript.loading || permissionDenied) return;
setLoadingUpload(true);
createTranscript.create({ name, target_language: getTargetLanguage() });
};
useEffect(() => {
let action = "record";
if (loadingUpload) action = "upload";
createTranscript.transcript &&
router.push(`/transcripts/${createTranscript.transcript.id}/${action}`);
}, [createTranscript.transcript]);
useEffect(() => {
if (createTranscript.error) setLoadingRecord(false);
}, [createTranscript.error]);
const { loading, permissionOk, permissionDenied, requestPermission } =
useAudioDevice();
return (
<div className="grid grid-rows-layout-topbar gap-2 lg:gap-4 max-h-full overflow-y-scroll">
<div className="lg:grid lg:grid-cols-2 lg:grid-rows-1 lg:gap-4 lg:h-full h-auto flex flex-col">
<section className="flex flex-col w-full lg:h-full items-center justify-evenly p-4 md:px-6 md:py-8">
<div className="flex flex-col max-w-xl items-center justify-center">
<h1 className="text-2xl font-bold mb-2">Welcome to Reflector</h1>
<p>
Reflector is a transcription and summarization pipeline that
transforms audio into knowledge.
<span className="hidden md:block">
The output is meeting minutes and topic summaries enabling
topic-specific analyses stored in your systems of record. This
is accomplished on your infrastructure without 3rd parties
keeping your data private, secure, and organized.
</span>
</p>
<About buttonText="Learn more" />
<p className="mt-6">
In order to use Reflector, we kindly request permission to access
your microphone during meetings and events.
</p>
{featureEnabled("privacy") && (
<Privacy buttonText="Privacy policy" />
)}
</div>
</section>
<section className="flex flex-col justify-center items-center w-full h-full">
{!sessionReady ? (
<Spinner />
) : requireLogin && !isAuthenticated ? (
<button
className="mt-4 bg-blue-400 hover:bg-blue-500 focus-visible:bg-blue-500 text-white font-bold py-2 px-4 rounded"
onClick={() => signIn("authentik")}
>
Log in
</button>
) : (
<div className="rounded-xl md:bg-blue-200 md:w-96 p-4 lg:p-6 flex flex-col mb-4 md:mb-10">
<h2 className="text-2xl font-bold mt-2 mb-2">Try Reflector</h2>
<label className="mb-3">
<p>Recording name</p>
<div className="select-search-container">
<input
className="select-search-input"
type="text"
onChange={nameChange}
placeholder="Optional"
/>
</div>
</label>
<label className="mb-3">
<p>Do you want to enable live translation?</p>
<SelectSearch
search
options={supportedLanguages}
value={targetLanguage}
onChange={onLanguageChange}
placeholder="Choose your language"
/>
</label>
{loading ? (
<p className="">Checking permissions...</p>
) : permissionOk ? (
<p className=""> Microphone permission granted </p>
) : permissionDenied ? (
<p className="">
Permission to use your microphone was denied, please change
the permission setting in your browser and refresh this page.
</p>
) : (
<Button
colorScheme="blue"
onClick={requestPermission}
disabled={permissionDenied}
>
Request Microphone Permission
</Button>
)}
<Button
colorScheme="blue"
onClick={send}
isDisabled={!permissionOk || loadingRecord || loadingUpload}
mt={2}
>
{loadingRecord ? "Loading..." : "Record Meeting"}
</Button>
<Text align="center" m="2">
OR
</Text>
<Button
colorScheme="blue"
onClick={uploadFile}
isDisabled={loadingRecord || loadingUpload}
>
{loadingUpload ? "Loading..." : "Upload File"}
</Button>
</div>
)}
</section>
</div>
</div>
);
};
export default TranscriptCreate;