Skip to content

Commit

Permalink
feat: Detect and upgrade users with lissing metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
codeincontext committed Aug 28, 2024
1 parent 1a80404 commit c158f3d
Show file tree
Hide file tree
Showing 13 changed files with 401 additions and 158 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"posttest",
"pptxgen",
"pptxgenjs",
"Preloadable",
"PSED",
"PSHE",
"psql",
Expand Down
7 changes: 6 additions & 1 deletion apps/nextjs/scripts/local-dev.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ const routesToPreBuild = [
"/aila",
];

const headers = {
"x-dev-preload": "true",
};

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const preBuildRoutes = async (
Expand All @@ -29,7 +33,7 @@ const preBuildRoutes = async (
return limit(() => {
if (typeof route === "string") {
return axios
.get(`http://localhost:2525${route}`)
.get(`http://localhost:2525${route}`, { headers })
.then(() => console.log(`Pre-built route: ${route}`))
.catch((error) => {
console.log(`Error pre-building route: ${route}`, error.message);
Expand All @@ -39,6 +43,7 @@ const preBuildRoutes = async (
return axios({
method: route.method,
url: `http://localhost:2525${route.url}`,
headers,
})
.then(() =>
console.log(
Expand Down
7 changes: 5 additions & 2 deletions apps/nextjs/src/app/aila/[id]/share/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { User, clerkClient } from "@clerk/nextjs/server";
import { getSessionModerations } from "@oakai/aila/src/features/moderation/getSessionModerations";
import { demoUsers } from "@oakai/core";
import { isToxic } from "@oakai/core/src/utils/ailaModeration/helpers";
import { PersistedModerationBase } from "@oakai/core/src/utils/ailaModeration/moderationSchema";
import { type Metadata } from "next";
Expand All @@ -25,8 +26,10 @@ export async function generateMetadata({
}

function userCanShare(user: User) {
const isDemoUser = Boolean(user.publicMetadata.isDemoUser ?? "true");

if (!demoUsers.isDemoStatusSet(user)) {
return false;
}
const isDemoUser = Boolean(user.publicMetadata.labs.isDemoUser ?? "true");
if (!isDemoUser) {
return true;
}
Expand Down
144 changes: 31 additions & 113 deletions apps/nextjs/src/app/onboarding/onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,128 +1,46 @@
"use client";

import { useState } from "react";
import { useEffect, useRef } from "react";

import { useUser } from "@clerk/nextjs";
import logger from "@oakai/logger/browser";
import {
OakBox,
OakFlex,
OakHeading,
OakLink,
OakP,
OakPrimaryButton,
OakSpan,
} from "@oaknational/oak-components";
import Link from "next/link";
import { useReloadSession } from "hooks/useReloadSession";

import Button from "@/components/Button";
import CheckBox from "@/components/CheckBox";
import SignUpSignInLayout from "@/components/SignUpSignInLayout";
import TermsContent from "@/components/TermsContent";
import { AcceptTermsForm } from "@/components/Onboarding/AcceptTermsForm";
import { LegacyUpgradeNotice } from "@/components/Onboarding/LegacyUpgradeNotice";
import { trpc } from "@/utils/trpc";

export const OnBoarding = () => {
const [dropDownOpen, setDropDownOpen] = useState(true);
const [termsAcceptedLocal, setTermsAcceptedLocal] = useState(false);
const [privacyAcceptedLocal, setPrivacyAcceptedLocal] = useState(false);
const acceptTerms = trpc.auth.acceptTerms.useMutation();

const handleAcceptTermsOfUse = async () => {
try {
const response = await acceptTerms.mutateAsync({
termsOfUse: new Date(),
privacyPolicy: privacyAcceptedLocal ? new Date() : false,
});

if (!response?.acceptedTermsOfUse) {
throw new Error("Could not accept terms of use");
const { user } = useUser();
const reloadSession = useReloadSession();
const setDemoStatus = trpc.auth.setDemoStatus.useMutation();

const userHasAlreadyAcceptedTerms =
user?.publicMetadata?.["labs"]?.["isOnboarded"];

// Edge case: Legacy users have already accepted terms but don't have a demo status
const isHandlingLegacyCase = useRef(false);
useEffect(() => {
async function handleDemoStatusSet() {
if (userHasAlreadyAcceptedTerms && !isHandlingLegacyCase.current) {
isHandlingLegacyCase.current = true;
logger.debug("User has already accepted terms");
await setDemoStatus.mutateAsync();
logger.debug("Demo status set successfully");
await reloadSession();
logger.debug("Session token refreshed successfully. Redirecting");
window.location.href = "/";
}

logger.debug("Terms of use accepted successfully.");
window.location.href = "/";
} catch (error) {
logger.error(error, "An error occurred while accepting terms of use");
}
};

return (
<SignUpSignInLayout loaded={true}>
<OakBox
$mh="auto"
$borderRadius="border-radius-m"
$background="white"
$pa="inner-padding-xl2"
$maxWidth={"all-spacing-22"}
>
<OakHeading $font="heading-6" tag="h1">
This product is experimental and uses AI
</OakHeading>
<OakBox $mt="space-between-s">
<OakP>
We have worked to ensure that our tools are as high quality and as
safe as possible but we cannot guarantee accuracy. Please use with
caution.
</OakP>
</OakBox>
handleDemoStatusSet();
}, [userHasAlreadyAcceptedTerms, setDemoStatus, reloadSession]);

<OakBox $pt="inner-padding-m" $mv="space-between-s">
<CheckBox
label="Accept"
setValue={setPrivacyAcceptedLocal}
size="base"
>
<OakSpan>
Keep me updated with latest Oak AI experiments, resources and
other helpful content by email. You can unsubscribe at any time.
See our{" "}
<OakLink element={Link} href="/legal/privacy">
privacy policy
</OakLink>
.
</OakSpan>
</CheckBox>
</OakBox>
if (userHasAlreadyAcceptedTerms) {
return <LegacyUpgradeNotice />;
}

{termsAcceptedLocal ? (
<OakFlex $flexDirection="column" $gap="all-spacing-7">
<p>
Terms accepted, if the page does not reload please refresh and
navigate to home.
</p>
</OakFlex>
) : (
<OakFlex
$flexDirection="row"
$justifyContent="between"
$gap="all-spacing-4"
$width="100%"
$alignItems="center"
$mt="space-between-l"
>
<Button
variant="text-link"
onClick={() => setDropDownOpen(!dropDownOpen)}
icon={dropDownOpen ? "chevron-down" : "chevron-up"}
>
See terms
</Button>
<OakPrimaryButton
onClick={() => {
handleAcceptTermsOfUse();
setTermsAcceptedLocal(true);
}}
>
I understand
</OakPrimaryButton>
</OakFlex>
)}
{!dropDownOpen && (
<OakBox>
<TermsContent />
</OakBox>
)}
</OakBox>
</SignUpSignInLayout>
);
// For the typical new user, show the accept terms form
return <AcceptTermsForm />;
};

export default OnBoarding;
2 changes: 1 addition & 1 deletion apps/nextjs/src/app/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import OnBoarding from "./onboarding";

export default function OnBoardingPage() {
export default async function OnBoardingPage() {
return <OnBoarding />;
}
138 changes: 138 additions & 0 deletions apps/nextjs/src/components/Onboarding/AcceptTermsForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
"use client";

import { useState } from "react";

import { useUser } from "@clerk/nextjs";
import logger from "@oakai/logger/browser";
import {
OakBox,
OakFlex,
OakHeading,
OakLink,
OakP,
OakPrimaryButton,
OakSpan,
} from "@oaknational/oak-components";
import { useReloadSession } from "hooks/useReloadSession";
import Link from "next/link";

import Button from "@/components/Button";
import CheckBox from "@/components/CheckBox";
import SignUpSignInLayout from "@/components/SignUpSignInLayout";
import TermsContent from "@/components/TermsContent";
import { trpc } from "@/utils/trpc";

export const AcceptTermsForm = ({}) => {
const [dropDownOpen, setDropDownOpen] = useState(true);
const { isLoaded } = useUser();
const reloadSession = useReloadSession();

const [termsAcceptedLocal, setTermsAcceptedLocal] = useState(false);
const [privacyAcceptedLocal, setPrivacyAcceptedLocal] = useState(false);
const setDemoStatus = trpc.auth.setDemoStatus.useMutation();
const acceptTerms = trpc.auth.acceptTerms.useMutation({});

const handleAcceptTermsOfUse = async () => {
try {
await setDemoStatus.mutateAsync();
logger.debug("Demo status set successfully");

const response = await acceptTerms.mutateAsync({
termsOfUse: new Date(),
privacyPolicy: privacyAcceptedLocal ? new Date() : false,
});

if (!response?.acceptedTermsOfUse) {
throw new Error("Could not accept terms of use");
}
logger.debug("Terms of use accepted successfully.");

await reloadSession();
logger.debug("Session token refreshed successfully. Redirecting");

window.location.href = "/";
} catch (error) {
logger.error(error, "An error occurred while accepting terms of use");
}
};

return (
<SignUpSignInLayout loaded={isLoaded}>
<OakBox
$mh="auto"
$borderRadius="border-radius-m"
$background="white"
$pa="inner-padding-xl2"
$maxWidth={"all-spacing-22"}
>
<OakHeading $font="heading-6" tag="h1">
This product is experimental and uses AI
</OakHeading>
<OakBox $mt="space-between-s">
<OakP>
We have worked to ensure that our tools are as high quality and as
safe as possible but we cannot guarantee accuracy. Please use with
caution.
</OakP>
</OakBox>

<OakBox $pt="inner-padding-m" $mv="space-between-s">
<CheckBox
label="Accept"
setValue={setPrivacyAcceptedLocal}
size="base"
>
<OakSpan>
Keep me updated with latest Oak AI experiments, resources and
other helpful content by email. You can unsubscribe at any time.
See our{" "}
<OakLink element={Link} href="/legal/privacy">
privacy policy
</OakLink>
.
</OakSpan>
</CheckBox>
</OakBox>

{termsAcceptedLocal ? (
<OakFlex $flexDirection="column" $gap="all-spacing-7">
<p>
Terms accepted, if the page does not reload please refresh and
navigate to home.
</p>
</OakFlex>
) : (
<OakFlex
$flexDirection="row"
$justifyContent="between"
$gap="all-spacing-4"
$width="100%"
$alignItems="center"
$mt="space-between-l"
>
<Button
variant="text-link"
onClick={() => setDropDownOpen(!dropDownOpen)}
icon={dropDownOpen ? "chevron-down" : "chevron-up"}
>
See terms
</Button>
<OakPrimaryButton
onClick={() => {
handleAcceptTermsOfUse();
setTermsAcceptedLocal(true);
}}
>
I understand
</OakPrimaryButton>
</OakFlex>
)}
{!dropDownOpen && (
<OakBox>
<TermsContent />
</OakBox>
)}
</OakBox>
</SignUpSignInLayout>
);
};
28 changes: 28 additions & 0 deletions apps/nextjs/src/components/Onboarding/LegacyUpgradeNotice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";

import { OakBox, OakFlex, OakHeading } from "@oaknational/oak-components";

import LoadingWheel from "@/components/LoadingWheel";
import SignUpSignInLayout from "@/components/SignUpSignInLayout";

export const LegacyUpgradeNotice = ({}) => {
return (
<SignUpSignInLayout loaded>
<OakBox
$mh="auto"
$ml="space-between-l"
$borderRadius="border-radius-m"
$background="white"
$pa="inner-padding-xl2"
$maxWidth={"all-spacing-22"}
>
<OakHeading $font="heading-6" tag="h1">
Preparing your account
</OakHeading>
<OakFlex $mt="space-between-s" $width={"100%"} $justifyContent="center">
<LoadingWheel />
</OakFlex>
</OakBox>
</SignUpSignInLayout>
);
};
Loading

0 comments on commit c158f3d

Please sign in to comment.