Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AI expires value to local storage. #391

Merged
merged 1 commit into from
Dec 5, 2024
Merged
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
14 changes: 12 additions & 2 deletions components/Header/Super.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import { NavResponsiveOnly } from "@/components/Nav/Nav.styled";
import { NorthwesternWordmark } from "@/components/Shared/SVG/Northwestern";
import React from "react";
import { UserContext } from "@/context/user-context";
import { defaultAIState } from "@/hooks/useGenerativeAISearchToggle";
import useLocalStorage from "@/hooks/useLocalStorage";
import { useRouter } from "next/router";

const nav = [
{
Expand All @@ -33,9 +35,12 @@ const nav = [
];

export default function HeaderSuper() {
const router = useRouter();
const { query } = router;

const [isLoaded, setIsLoaded] = React.useState(false);
const [isExpanded, setIsExpanded] = React.useState(false);
const [ai, setAI] = useLocalStorage("ai", "false");
const [ai, setAI] = useLocalStorage("ai", defaultAIState);

React.useEffect(() => {
setIsLoaded(true);
Expand All @@ -45,7 +50,12 @@ export default function HeaderSuper() {
const handleMenu = () => setIsExpanded(!isExpanded);

const handleLogout = () => {
if (ai === "true") setAI("false");
// reset AI state and remove query param
setAI(defaultAIState);
delete query?.ai;
router.push(router.pathname, { query });

// logout
window.location.href = `${DCAPI_ENDPOINT}/auth/logout`;
};

Expand Down
18 changes: 14 additions & 4 deletions components/Search/GenerativeAIToggle.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ describe("GenerativeAIToggle", () => {

await user.click(checkbox);
expect(checkbox).toHaveAttribute("data-state", "checked");
expect(localStorage.getItem("ai")).toEqual(JSON.stringify("true"));

const ai = JSON.parse(String(localStorage.getItem("ai")));
expect(ai?.enabled).toEqual("true");
expect(typeof ai?.expires).toEqual("number");
expect(ai?.expires).toBeGreaterThan(Date.now());
});

it("renders the generative AI tooltip", () => {
Expand Down Expand Up @@ -99,7 +103,10 @@ describe("GenerativeAIToggle", () => {
...defaultSearchState,
};

localStorage.setItem("ai", JSON.stringify("true"));
localStorage.setItem(
"ai",
JSON.stringify({ enabled: "true", expires: 9733324925021 }),
);

mockRouter.setCurrentUrl("/search");
render(
Expand All @@ -117,7 +124,7 @@ describe("GenerativeAIToggle", () => {

mockRouter.setCurrentUrl("/");

localStorage.setItem("ai", JSON.stringify("false"));
localStorage.setItem("ai", JSON.stringify({ enabled: "false" }));

render(
withUserProvider(
Expand All @@ -127,6 +134,9 @@ describe("GenerativeAIToggle", () => {

await user.click(screen.getByRole("checkbox"));

expect(localStorage.getItem("ai")).toEqual(JSON.stringify("true"));
const ai = JSON.parse(String(localStorage.getItem("ai")));
expect(ai?.enabled).toEqual("true");
expect(typeof ai?.expires).toEqual("number");
expect(ai?.expires).toBeGreaterThan(Date.now());
});
});
3 changes: 2 additions & 1 deletion components/Search/GenerativeAIToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ export default function GenerativeAIToggle() {
<SharedAlertDialog
isOpen={dialog.isOpen}
cancel={{ label: "Cancel", onClick: closeDialog }}
action={{ label: "Login", onClick: handleLogin }}
action={{ label: "Sign in", onClick: handleLogin }}
title="Sign in to Digital Collections"
>
{AI_LOGIN_ALERT}
</SharedAlertDialog>
Expand Down
5 changes: 4 additions & 1 deletion components/Search/Search.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ describe("Search component", () => {
});

it("renders generative AI placeholder text when AI search is active", () => {
localStorage.setItem("ai", JSON.stringify("true"));
localStorage.setItem(
"ai",
JSON.stringify({ enabled: "true", expires: 9733324925021 }),
);

render(withUserProvider(<Search isSearchActive={mockIsSearchActive} />));

Expand Down
11 changes: 8 additions & 3 deletions components/Shared/AlertDialog.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const AlertDialogOverlay = styled(AlertDialog.Overlay, {

const AlertDialogContent = styled(AlertDialog.Content, {
backgroundColor: "white",
borderRadius: 6,
borderRadius: "6px",
boxShadow:
"hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px",
position: "fixed",
Expand All @@ -29,8 +29,9 @@ const AlertDialogContent = styled(AlertDialog.Content, {
width: "90vw",
maxWidth: "500px",
maxHeight: "85vh",
padding: 25,
padding: "$gr4",
zIndex: "2",
fontSize: "$gr3",

"&:focus": { outline: "none" },
});
Expand All @@ -46,7 +47,11 @@ const AlertDialogTitle = styled(AlertDialog.Title, {

const AlertDialogButtonRow = styled("div", {
display: "flex",
justifyContent: "flex-end",
justifyContent: "space-between",

"> button": {
margin: 0,
},

"& > *:not(:last-child)": {
marginRight: "$gr3",
Expand Down
4 changes: 2 additions & 2 deletions components/Shared/AlertDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ export default function SharedAlertDialog({
<AlertDialog.Description>{children}</AlertDialog.Description>
<AlertDialogButtonRow>
{cancel && (
<Button isText onClick={cancel?.onClick}>
<Button onClick={cancel?.onClick} isLowercase>
{cancelLabel}
</Button>
)}

<AlertDialog.Action asChild>
<Button isPrimary onClick={action.onClick}>
<Button isPrimary onClick={action.onClick} isLowercase>
{action.label}
</Button>
</AlertDialog.Action>
Expand Down
19 changes: 14 additions & 5 deletions hooks/useGenerativeAISearchToggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,26 @@ import { UserContext } from "@/context/user-context";
import useLocalStorage from "@/hooks/useLocalStorage";
import { useRouter } from "next/router";

const defaultModalState = {
export const defaultAIState = {
enabled: "false",
expires: undefined,
};

export const defaultModalState = {
isOpen: false,
title: "Use Generative AI",
};

export default function useGenerativeAISearchToggle() {
const router = useRouter();

const [ai, setAI] = useLocalStorage("ai", "false");
const [ai, setAI] = useLocalStorage("ai", defaultAIState);
const { user } = React.useContext(UserContext);

const [dialog, setDialog] = useState(defaultModalState);

const isAIPreference = ai === "true";
const expires = Date.now() + 1000 * 60 * 60;
const isAIPreference = ai.enabled === "true";
const isChecked = isAIPreference && user?.isLoggedIn;

const loginUrl = `${DCAPI_ENDPOINT}/auth/login?goto=${goToLocation()}`;
Expand All @@ -36,7 +42,7 @@ export default function useGenerativeAISearchToggle() {
if (router.isReady) {
const { query } = router;
if (query.ai === "true") {
setAI("true");
setAI({ enabled: "true", expires });
}
}
}, [router.asPath]);
Expand All @@ -61,7 +67,10 @@ export default function useGenerativeAISearchToggle() {
if (!user?.isLoggedIn) {
setDialog({ ...dialog, isOpen: checked });
} else {
setAI(checked ? "true" : "false");
setAI({
enabled: checked ? "true" : "false",
expires: checked ? expires : undefined,
});
}
}

Expand Down
2 changes: 1 addition & 1 deletion hooks/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from "react";

function useLocalStorage(key: string, initialValue: string) {
function useLocalStorage(key: string, initialValue: any) {
// Get the initial value from localStorage or use the provided initialValue
const [storedValue, setStoredValue] = useState(() => {
if (typeof window !== "undefined") {
Expand Down
8 changes: 6 additions & 2 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import React from "react";
import { SearchProvider } from "@/context/search-context";
import { User } from "@/types/context/user";
import { UserProvider } from "@/context/user-context";
import { defaultAIState } from "@/hooks/useGenerativeAISearchToggle";
import { defaultOpenGraphData } from "@/lib/open-graph";
import { getUser } from "@/lib/user-helpers";
import globalStyles from "@/styles/global";
Expand All @@ -37,8 +38,8 @@ function MyApp({ Component, pageProps }: MyAppProps) {
const [mounted, setMounted] = React.useState(false);
const [user, setUser] = React.useState<User>();

const [ai] = useLocalStorage("ai", "false");
const isUsingAI = ai === "true";
const [ai, setAI] = useLocalStorage("ai", defaultAIState);
const isUsingAI = ai?.enabled === "true";

React.useEffect(() => {
async function getData() {
Expand All @@ -47,6 +48,9 @@ function MyApp({ Component, pageProps }: MyAppProps) {
setMounted(true);
}
getData();

// Check if AI is enabled and if it has expired
if (ai?.expires && ai.expires < Date.now()) setAI(defaultAIState);
}, []);

React.useEffect(() => {
Expand Down
Loading