Skip to content

Commit

Permalink
feat: add preview mode (#533)
Browse files Browse the repository at this point in the history
* feat: add preview component

* fix: display in copyright text

* fix: test
  • Loading branch information
spaenleh authored Dec 11, 2024
1 parent 392cdc7 commit 7c401c1
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 13 deletions.
5 changes: 3 additions & 2 deletions cypress/e2e/player/island.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import {
ITEM_CHATBOX_BUTTON_ID,
ITEM_MAP_BUTTON_ID,
NAVIGATION_ISLAND_ID,
NAVIGATION_ISLAND_CLASSNAME,
buildDocumentId,
buildTreeItemClass,
} from '../../../src/config/selectors';
Expand Down Expand Up @@ -118,6 +118,7 @@ describe('Island', () => {
});
});

// test is flaky
it('Shows only one island when folder contains shortcut', () => {
const items = getFolderWithShortcutFixture();
const parent = items[0];
Expand All @@ -128,7 +129,7 @@ describe('Island', () => {
'contain',
getDocumentExtra(documentTarget.extra as DocumentItemExtra).content,
);
cy.get(`#${NAVIGATION_ISLAND_ID}`)
cy.get(`.${NAVIGATION_ISLAND_CLASSNAME}`)
.should('be.visible')
.and('have.length', 1);
});
Expand Down
2 changes: 1 addition & 1 deletion src/config/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export const FORBIDDEN_CONTENT_CONTAINER_ID = 'forbiddenContentContainer';

export const USER_SWITCH_SIGN_IN_BUTTON_ID = 'userSwitchSignInButton';

export const NAVIGATION_ISLAND_ID = 'navigationIsland';
export const NAVIGATION_ISLAND_CLASSNAME = 'navigationIsland';
export const ITEM_CHATBOX_BUTTON_ID = 'itemChatboxButton';
export const ITEM_MAP_BUTTON_ID = 'itemMapButton';
export const ITEM_PINNED_BUTTON_ID = 'itemPinnedButton';
Expand Down
14 changes: 14 additions & 0 deletions src/modules/landing/footer/CopyrightText.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Meta, StoryObj } from '@storybook/react';

import { CopyrightText } from './CopyrightText';

const meta = {
component: CopyrightText,
} satisfies Meta<typeof CopyrightText>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default = {
args: {},
} satisfies Story;
31 changes: 31 additions & 0 deletions src/modules/landing/footer/CopyrightText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useState } from 'react';

import { Typography } from '@mui/material';

import { usePreviewMode } from '~landing/preview/PreviewModeContext';

export function CopyrightText() {
const [clickCounter, setClickCounter] = useState(0);
const { togglePreview, isEnabled: isPreviewEnabled } = usePreviewMode();

const handleClick = () => {
if (clickCounter === 2) {
togglePreview();
// reset counter
setClickCounter(0);
} else {
setClickCounter((s) => s + 1);
}
};
return (
<Typography
textAlign="center"
variant="note"
onClick={handleClick}
sx={{ userSelect: 'none' }}
>
&copy; Graasp 2014 - {new Date().getFullYear()}
{isPreviewEnabled ? ' (preview)' : ''}
</Typography>
);
}
5 changes: 2 additions & 3 deletions src/modules/landing/footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import LanguageSwitch from '@/components/ui/LanguageSwitch';
import { NS } from '@/config/constants';
import { OnChangeLangProp } from '@/types';

import { CopyrightText } from './CopyrightText';
import { FooterSection } from './FooterSection';
import {
FacebookIcon,
Expand Down Expand Up @@ -191,9 +192,7 @@ export function Footer({ onChangeLang }: Readonly<FooterProps>): JSX.Element {
alignItems="center"
justifyContent="center"
>
<Typography textAlign="center" variant="note">
&copy; Graasp 2014 - {new Date().getFullYear()}
</Typography>
<CopyrightText />
</Stack>
</Stack>
</Stack>
Expand Down
74 changes: 74 additions & 0 deletions src/modules/landing/preview/PreviewModeContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
ReactNode,
createContext,
useContext,
useEffect,
useMemo,
useState,
} from 'react';

const PREVIEW_STORAGE_KEY = 'graasp-preview';

type PreviewContextType = {
togglePreview: () => void;
isEnabled: boolean;
};
const PreviewContext = createContext<PreviewContextType>({
isEnabled: false,
togglePreview: () => console.error('no Preview context present'),
});

const isPreviewEnabled = () => {
const isPresent = localStorage.getItem(PREVIEW_STORAGE_KEY) != null;
return isPresent;
};

export function PreviewContextProvider({
children,
}: Readonly<{ children: ReactNode }>) {
const [isEnabled, setIsEnabled] = useState(isPreviewEnabled());

useEffect(() => {
function listenForStorageChanges(event: StorageEvent) {
if (event.key === PREVIEW_STORAGE_KEY) {
// sync the local state
setIsEnabled(isPreviewEnabled());
}
// discard the event otherwise
}
window.addEventListener('storage', listenForStorageChanges);
return () => window.removeEventListener('storage', listenForStorageChanges);
}, []);

const value = useMemo(
() => ({
togglePreview: () => {
if (isPreviewEnabled()) {
window.localStorage.removeItem(PREVIEW_STORAGE_KEY);
setIsEnabled(false);
} else {
window.localStorage.setItem(PREVIEW_STORAGE_KEY, 'enabled');
setIsEnabled(true);
}
},
isEnabled,
}),
[isEnabled],
);

return (
<PreviewContext.Provider value={value}>{children}</PreviewContext.Provider>
);
}

export function usePreviewMode() {
return useContext<PreviewContextType>(PreviewContext);
}

export function Preview({ children }: { children: ReactNode }) {
const { isEnabled } = usePreviewMode();
if (isEnabled) {
return children;
}
return null;
}
4 changes: 2 additions & 2 deletions src/modules/player/navigationIsland/NavigationIsland.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box, Stack } from '@mui/material';

import { NAVIGATION_ISLAND_ID } from '@/config/selectors';
import { NAVIGATION_ISLAND_CLASSNAME } from '@/config/selectors';

import useChatButton from './ChatButton';
import useGeolocationButton from './GeolocationButton';
Expand All @@ -26,7 +26,7 @@ const NavigationIslandBox = (): JSX.Element | null => {

return (
<Box
id={NAVIGATION_ISLAND_ID}
className={NAVIGATION_ISLAND_CLASSNAME}
// set some background and shadow
bgcolor="white"
boxShadow="0px 3px 6px 0px rgba(0, 0, 0, 0.25)"
Expand Down
6 changes: 5 additions & 1 deletion src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { AuthContextType } from '@/AuthContext';
import { NotFoundComponent } from '@/components/NotFoundComponent';
import { ReactQueryDevtools } from '@/config/queryClient';

import { PreviewContextProvider } from '~landing/preview/PreviewModeContext';

export const Route = createRootRouteWithContext<{ auth: AuthContextType }>()({
component: RootComponent,
notFoundComponent: NotFoundComponent,
Expand All @@ -32,7 +34,9 @@ function RootComponent() {
return (
<Stack id="__root">
<ScrollRestoration />
<Outlet />
<PreviewContextProvider>
<Outlet />
</PreviewContextProvider>
{import.meta.env.MODE !== 'test' && <ReactQueryDevtools />}
<Suspense>
<TanStackRouterDevtools />
Expand Down
10 changes: 8 additions & 2 deletions src/routes/_landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { OnChangeLangProp } from '@/types';

import { Footer } from '~landing/footer/Footer';
import { RightHeader } from '~landing/header/RightHeader';
import { usePreviewMode } from '~landing/preview/PreviewModeContext';

export const Route = createFileRoute('/_landing')({
component: RouteComponent,
Expand All @@ -30,7 +31,7 @@ function RouteComponent() {
const { isMobile } = useMobileView();
const { fill: primary } = useButtonColor('primary');
const { mutate } = mutations.useEditCurrentMember();

const { isEnabled: isPreviewEnabled } = usePreviewMode();
const onChangeLang: OnChangeLangProp = (lang: string) => {
if (isAuthenticated) {
mutate({ extra: { lang } });
Expand Down Expand Up @@ -76,7 +77,12 @@ function RouteComponent() {
<GraaspLogo height={44} sx={{ fill: primary! }} />
{!isMobile && (
<Typography fontWeight="bold" variant="h2" color="primary">
Graasp
Graasp{' '}
{isPreviewEnabled ? (
<Typography variant="note">preview</Typography>
) : (
''
)}
</Typography>
)}
</Stack>
Expand Down
10 changes: 8 additions & 2 deletions src/routes/_landing/features.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { BlendedLearningSection } from '~landing/features/BlendedLearningSection
import { GraaspFeaturesSection } from '~landing/features/GraaspFeaturesSection';
import { PlatformOverviewSection } from '~landing/features/PlatformOverviewSection';
import { TitleSection } from '~landing/features/TitleSection';
import { PricingPlansSection } from '~landing/features/pricing/PricingPlansSection';
import { NewsLetter } from '~landing/home/NewsLetter';
import { Preview } from '~landing/preview/PreviewModeContext';

export const Route = createFileRoute('/_landing/features')({
component: RouteComponent,
Expand All @@ -17,8 +19,12 @@ function RouteComponent() {
<PlatformOverviewSection />
<BlendedLearningSection />
<GraaspFeaturesSection />
{/* <PricingPlansSection /> */}
{/* <BlogSection /> */}
<Preview>
<PricingPlansSection />
</Preview>
{/* <Preview>
<BlogSection />
</Preview> */}
<NewsLetter />
</>
);
Expand Down

0 comments on commit 7c401c1

Please sign in to comment.