Mobile overlay structure
This commit is contained in:
parent
2e92324a74
commit
00960af1f7
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"projects": {
|
||||
"default": "oxen-io",
|
||||
"staging": "oxen-io",
|
||||
"production": "oxen-io"
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
import classNames from 'classnames';
|
||||
import React, { ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useClickAway } from 'react-use';
|
||||
// import ExitSVG from '../assets/svgs/exit-primary.svg';
|
||||
import { UI } from '../constants';
|
||||
import { collapseSearchOverlay } from '../state/navigation';
|
||||
import { IState } from '../state/reducers';
|
||||
|
||||
interface Props {
|
||||
modalId: string;
|
||||
isOpen: boolean;
|
||||
children: ReactNode;
|
||||
isMobileFullscreen?: boolean;
|
||||
className?: string;
|
||||
close?: () => void;
|
||||
}
|
||||
|
||||
export function Modal(props: Props) {
|
||||
const { modalId, isOpen, close, className, children } = props;
|
||||
const { searchOverlayExpanded, openedModal } = useSelector(
|
||||
(state: IState) => state.navigation,
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [shouldRender, setShouldRender] = useState(false);
|
||||
|
||||
const ref = useRef(null);
|
||||
useClickAway(ref, close);
|
||||
|
||||
useEffect(() => {
|
||||
// If modal is open, close search overlay
|
||||
if (isOpen && searchOverlayExpanded) {
|
||||
dispatch(collapseSearchOverlay());
|
||||
}
|
||||
|
||||
// Refuse to open if another modal is currently open
|
||||
if (modalId !== openedModal) {
|
||||
console.log(
|
||||
`Cannot open modal ${modalId}, ${openedModal} is already open.`,
|
||||
);
|
||||
|
||||
setShouldRender(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!isOpen || !shouldRender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={{
|
||||
zIndex: UI.Z_INDEX_MODAL_OVERLAY,
|
||||
paddingLeft: `${UI.PAGE_CONTAINED_PADDING_VW}vw`,
|
||||
paddingRight: `${UI.PAGE_CONTAINED_PADDING_VW}vw`,
|
||||
}}
|
||||
className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-25"
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
minWidth: '200px',
|
||||
maxWidth: '100%',
|
||||
minHeight: '150px',
|
||||
maxHeight: '80vh',
|
||||
}}
|
||||
className={classNames(
|
||||
'relative border-2 border-gray px-6 pb-4 pt-12 bg-white',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="absolute inset-0 flex justify-end pt-3 pr-3">
|
||||
{/* <ExitSVG onClick={close} className="h-8 cursor-pointer" /> */}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { ScreenContext } from '../../contexts/screen';
|
||||
import { IPost } from '../../types/blog';
|
||||
import { IPost } from '../../types/cms';
|
||||
import { Contained } from '../Contained';
|
||||
import { ArticleSectionContent } from './sections/ArticleSectionContent';
|
||||
import { ArticleSectionTitle } from './sections/ArticleSectionTitle';
|
||||
|
@ -23,7 +23,7 @@ function ArticleMobile(props: IPost) {
|
|||
return (
|
||||
<article>
|
||||
<Contained>
|
||||
<div className="flex flex-col items-center space-y-6 mt-12 mb-6">
|
||||
<div className="flex flex-col items-center mt-12 mb-6 space-y-6">
|
||||
<ArticleSectionTitle title={title} />
|
||||
<ArticleWidgetAuthor author={author} publishedDate={publishedDate} />
|
||||
<ArticleSubtitleSection subtitle={subtitle} />
|
||||
|
@ -39,7 +39,7 @@ function ArticleDesktop(props: IPost) {
|
|||
|
||||
return (
|
||||
<article>
|
||||
<div className="flex flex-col items-center space-y-4 mt-20 mb-10">
|
||||
<div className="flex flex-col items-center mt-20 mb-10 space-y-4">
|
||||
<ArticleSectionTitle title={title} />
|
||||
<ArticleWidgetAuthor author={author} publishedDate={publishedDate} />
|
||||
<ArticleSubtitleSection subtitle={subtitle} />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { ScreenContext } from '../../../contexts/screen';
|
||||
import { IPost } from '../../../types/blog';
|
||||
import { IPost } from '../../../types/cms';
|
||||
import { Contained } from '../../Contained';
|
||||
import { ArticleBody } from '../ArticleBody';
|
||||
import { ArticleSectionFeatureImage } from './ArticleSectionFeatureImage';
|
||||
|
@ -27,7 +27,7 @@ const MobileContent = (post: IPost) => {
|
|||
|
||||
const DesktopContent = (post: IPost) => {
|
||||
return (
|
||||
<div className="flex flex-col space-y-10 items-center">
|
||||
<div className="flex flex-col items-center space-y-10">
|
||||
<ArticleSectionFeatureImage featureImage={post.featureImage} />
|
||||
<div className="my-10">
|
||||
<ArticleBody body={post.body} />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IFigureImage } from '../../../types/blog';
|
||||
import { IFigureImage } from '../../../types/cms';
|
||||
|
||||
interface Props {
|
||||
featureImage: IFigureImage;
|
||||
|
@ -6,10 +6,10 @@ interface Props {
|
|||
|
||||
export function ArticleSectionFeatureImage({ featureImage }: Props) {
|
||||
return (
|
||||
<div className="pb-4 w-full desktop:pb-0">
|
||||
<div className="w-full pb-4 desktop:pb-0">
|
||||
<div
|
||||
style={{ paddingBottom: '40%' }}
|
||||
className="relative w-full h-0 mb-4 bg-gray-300 rounded-md overflow-hidden"
|
||||
className="relative w-full h-0 mb-4 overflow-hidden bg-gray-300 rounded-md"
|
||||
>
|
||||
<div className="absolute inset-0">
|
||||
<img
|
||||
|
@ -22,7 +22,7 @@ export function ArticleSectionFeatureImage({ featureImage }: Props) {
|
|||
</div>
|
||||
|
||||
{featureImage?.description && (
|
||||
<div className="w-8/12 italic text-sm">{featureImage.description}</div>
|
||||
<div className="w-8/12 text-sm italic">{featureImage.description}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { IAuthor } from '../../../types/blog';
|
||||
import { IAuthor } from '../../../types/cms';
|
||||
import { Avatar } from '../../Avatar';
|
||||
|
||||
interface Props {
|
||||
|
@ -13,7 +13,7 @@ export function ArticleWidgetAuthor({ author, publishedDate }: Props) {
|
|||
<Avatar size={10} imageSrc={author?.avatar.imageUrl} />
|
||||
|
||||
<div className="flex flex-col leading-tight">
|
||||
<span className="font-roboto tracking-wider text-sm font-bold">
|
||||
<span className="text-sm font-bold tracking-wider font-roboto">
|
||||
By: {author?.name}
|
||||
</span>
|
||||
<span>{publishedDate}</span>
|
||||
|
|
|
@ -2,7 +2,7 @@ import classNames from 'classnames';
|
|||
import router from 'next/dist/client/router';
|
||||
import { SyntheticEvent } from 'react';
|
||||
import { useMeasure } from 'react-use';
|
||||
import { IPost } from '../../types/blog';
|
||||
import { IPost } from '../../types/cms';
|
||||
import { generateURL } from '../../utils/routing';
|
||||
import { titleCase } from '../../utils/text';
|
||||
import { OutlineBlock } from '../OutlineBlock';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Link from 'next/link';
|
||||
import React, { useContext } from 'react';
|
||||
import { ScreenContext } from '../../contexts/screen';
|
||||
import { IPost } from '../../types/blog';
|
||||
import { IPost } from '../../types/cms';
|
||||
import { generateURL } from '../../utils/routing';
|
||||
|
||||
export function ArticleCardRow(post: IPost) {
|
||||
|
@ -15,7 +15,7 @@ export function ArticleCardRow(post: IPost) {
|
|||
lineHeight: '1.33em',
|
||||
height: '4em',
|
||||
}}
|
||||
className="text-base overflow-hidden"
|
||||
className="overflow-hidden text-base"
|
||||
>
|
||||
{post.subtitle}
|
||||
</p>
|
||||
|
@ -27,13 +27,13 @@ export function ArticleCardRow(post: IPost) {
|
|||
width: isMobile ? '33%' : '10rem',
|
||||
height: isMobile ? '66%' : '6rem',
|
||||
}}
|
||||
className="relative rounded-lg bg-primary bg-opacity-10 overflow-hidden"
|
||||
className="relative overflow-hidden rounded-lg bg-primary bg-opacity-10"
|
||||
>
|
||||
{post?.featureImage?.imageUrl && (
|
||||
<img
|
||||
src={post.featureImage.imageUrl}
|
||||
alt={post.featureImage.description}
|
||||
className="w-full h-full rounded-lg object-cover"
|
||||
className="object-cover w-full h-full rounded-lg"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -42,7 +42,7 @@ export function ArticleCardRow(post: IPost) {
|
|||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
<div className="flex flex-col w-full space-y-4 mb-6">
|
||||
<div className="flex flex-col w-full mb-6 space-y-4">
|
||||
<div className="flex w-full space-x-6">
|
||||
<ArticlePreviewImage />
|
||||
<div className="w-2/3">
|
||||
|
@ -62,7 +62,7 @@ export function ArticleCardRow(post: IPost) {
|
|||
className="flex flex-col flex-grow"
|
||||
>
|
||||
<Link href={href} as={as}>
|
||||
<a className="font-roboto text-xl text-primary">{post.title}</a>
|
||||
<a className="text-xl font-roboto text-primary">{post.title}</a>
|
||||
</Link>
|
||||
|
||||
<ArticlePreviewContent />
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import React from 'react';
|
||||
// import OxenLogo from '../../assets/svgs/brand.svg';
|
||||
// import EmailLogoSVG from '../../assets/svgs/hot.svg';
|
||||
import { ModalInstance } from '../../state/navigation';
|
||||
import { Button } from '../Button';
|
||||
import { Modal } from '../Modal';
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
close?: () => void;
|
||||
}
|
||||
|
||||
export function LoginModal(props: Props) {
|
||||
return (
|
||||
<Modal modalId={ModalInstance.LOGIN} {...props}>
|
||||
<div className="">
|
||||
<div className="flex flex-col items-center space-y-6 mb-32">
|
||||
{/* <OxenLogo className="fill-current h-6" /> */}
|
||||
|
||||
<h1 className="font-roboto text-3xl mb-2">Hello!</h1>
|
||||
|
||||
<Button
|
||||
type="outline"
|
||||
color="secondary"
|
||||
// prefix={<EmailLogoSVG className="h-6 w-8" />}
|
||||
suffix={<div className="w-6"></div>}
|
||||
onClick={() => alert('sdf')}
|
||||
>
|
||||
Continue with email
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
By proceeding, you agree to our{' '}
|
||||
<a href="#" className="underline font-semibold">
|
||||
Terms of Use
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -1,15 +1,22 @@
|
|||
import classNames from 'classnames';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import OxenLogoSVG from '../../assets/svgs/brand.svg';
|
||||
import TriangleSVG from '../../assets/svgs/triangle.svg';
|
||||
import { UI } from '../../constants';
|
||||
import { toggleMobileMenu } from '../../state/navigation';
|
||||
import { collapseSideMenu, expandSideMenu } from '../../state/navigation';
|
||||
import { IState } from '../../state/reducers';
|
||||
|
||||
export function MobileHeader() {
|
||||
const { sideMenuExpanded: expanded } = useSelector(
|
||||
(state: IState) => state.navigation,
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const toggleSideMenu = () =>
|
||||
dispatch(expanded ? collapseSideMenu() : expandSideMenu());
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
|
@ -30,8 +37,11 @@ export function MobileHeader() {
|
|||
</div>
|
||||
|
||||
<TriangleSVG
|
||||
onClick={() => dispatch(toggleMobileMenu())}
|
||||
className="h-12 transform rotate-90 outline-none"
|
||||
onClick={() => toggleSideMenu()}
|
||||
className={classNames(
|
||||
'h-5 transform outline-none duration-300',
|
||||
expanded ? '-rotate-60' : 'rotate-180',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -34,7 +34,11 @@ export function SideMenuActiveIndicator() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-start w-0 h-0 transform -rotate-90">
|
||||
<div
|
||||
className={classNames(
|
||||
'flex items-center justify-start w-0 h-0 duration-300 transform -rotate-90',
|
||||
)}
|
||||
>
|
||||
<span className="whitespace-no-wrap">
|
||||
{NAVIGATION.SIDE_MENU_ITEMS[active].label}
|
||||
</span>
|
||||
|
|
|
@ -16,13 +16,13 @@ export function SideMenuDefault() {
|
|||
width: '50vw',
|
||||
minWidth: '375px',
|
||||
}}
|
||||
className="relative flex text-primary"
|
||||
className="relative flex text-primary bg-alt"
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: `calc(100vh - ${UI.HEADER_HEIGHT_PX}px`,
|
||||
}}
|
||||
className="w-full overflow-y-auto duration-300 children:last:border-b-0"
|
||||
className="w-full overflow-y-auto"
|
||||
>
|
||||
<SideMenuInner />
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useDispatch, useSelector } from 'react-redux';
|
|||
import { NAVIGATION } from '../../constants';
|
||||
import { setSideMenuActive, SideMenuItem } from '../../state/navigation';
|
||||
import { IState } from '../../state/reducers';
|
||||
import { SideMenuRow } from './SideMenuRowProps';
|
||||
import { SideMenuRow } from './SideMenuRow';
|
||||
|
||||
export function SideMenuInner() {
|
||||
const { sideMenuActive: active } = useSelector(
|
||||
|
@ -15,7 +15,7 @@ export function SideMenuInner() {
|
|||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-y-auto duration-300 children:last:border-b-0">
|
||||
<div className="flex flex-col h-full duration-300 mobile:children:last:border-b-0">
|
||||
{Object.entries(NAVIGATION.SIDE_MENU_ITEMS).map(([key, item]) => (
|
||||
<SideMenuRow
|
||||
item={item}
|
||||
|
|
|
@ -1,9 +1,26 @@
|
|||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { UI } from '../../constants';
|
||||
import { SideMenuActiveIndicator } from './SideMenuActiveIndicator';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import FacebookSVG from '../../assets/svgs/socials/facebook.svg';
|
||||
import InstargamSVG from '../../assets/svgs/socials/instagram.svg';
|
||||
import TwitterSVG from '../../assets/svgs/socials/twitter.svg';
|
||||
import { NAVIGATION, UI } from '../../constants';
|
||||
import { IState } from '../../state/reducers';
|
||||
import { Contained } from '../Contained';
|
||||
import { SideMenuInner } from './SideMenuInner';
|
||||
|
||||
export function SideMenuMobile() {
|
||||
const { sideMenuExpanded: expanded } = useSelector(
|
||||
(state: IState) => state.navigation,
|
||||
);
|
||||
|
||||
console.log(
|
||||
'SideMenuMobile ➡️ NAVIGATION.MENU_ITEMS:',
|
||||
NAVIGATION.MENU_ITEMS,
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
|
@ -11,13 +28,38 @@ export function SideMenuMobile() {
|
|||
top: `${UI.HEADER_HEIGHT_PX}px`,
|
||||
zIndex: 30000,
|
||||
}}
|
||||
className="fixed inset-0 flex bg-alt"
|
||||
className={classNames(
|
||||
'fixed inset-0 flex duration-300 transform border-t bg-alt border-primary',
|
||||
expanded ? '-translate-x-full' : 'translate-x-0',
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col w-full">
|
||||
<SideMenuInner />
|
||||
</div>
|
||||
<div className="flex flex-col w-full space-y-4">
|
||||
<div className="flex flex-col flex-grow">
|
||||
<SideMenuInner />
|
||||
</div>
|
||||
|
||||
<SideMenuActiveIndicator />
|
||||
<Contained>
|
||||
<div className="flex flex-col w-full space-y-4">
|
||||
<div className="flex justify-between pt-8 pb-2 text-sm font-medium uppercase font-prompt">
|
||||
{_.chunk(NAVIGATION.MENU_ITEMS, 3).map(group => (
|
||||
<div key={uuid()} className="flex flex-col space-y-2">
|
||||
{group.map(item => (
|
||||
<a key={item.label} href={item.href}>
|
||||
{item.label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-3">
|
||||
<FacebookSVG className="h-10" />
|
||||
<TwitterSVG className="h-10" />
|
||||
<InstargamSVG className="h-10" />
|
||||
</div>
|
||||
</div>
|
||||
</Contained>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import classNames from 'classnames';
|
|||
import React, { useContext } from 'react';
|
||||
import TriangleOutlinedSVG from '../../assets/svgs/triangle-outlined.svg';
|
||||
import TriangleSVG from '../../assets/svgs/triangle.svg';
|
||||
import { UI } from '../../constants';
|
||||
import { ScreenContext } from '../../contexts/screen';
|
||||
import { ISideMenuItem } from './SideMenu';
|
||||
|
||||
|
@ -18,17 +19,29 @@ export function SideMenuRow({ item, isActive, onClick }: SideMenuRowProps) {
|
|||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
style={{
|
||||
maxHeight: '5rem',
|
||||
padding:
|
||||
isMobile || isTablet
|
||||
? `0 ${UI.PAGE_CONTAINED_PADDING_VW}vw`
|
||||
: 'unset',
|
||||
}}
|
||||
className={classNames(
|
||||
'flex flex-1 space-x-6 justify-between items-center cursor-pointer border-b border-black px-4 py-4 hover:bg-secondary duration-300',
|
||||
isHuge ? 'text-3xl' : isDesktop ? 'text-xl' : '',
|
||||
'flex flex-1 space-x-6 justify-between text-primary items-center cursor-pointer border-b border-black py-4 hover:bg-secondary duration-300',
|
||||
isHuge ? 'text-3xl' : isDesktop ? 'text-xl' : 'text-xl',
|
||||
isActive ? 'bg-secondary' : 'bg-transparent',
|
||||
)}
|
||||
>
|
||||
<span className="whitespace-no-wrap">{item.label}</span>
|
||||
{!isCollapsible && isActive ? (
|
||||
<TriangleSVG className="h-4" />
|
||||
) : (
|
||||
<TriangleOutlinedSVG className="h-4" />
|
||||
<span className="pl-6 whitespace-no-wrap">{item.label}</span>
|
||||
|
||||
{!isMobile && !isTablet && (
|
||||
<>
|
||||
{!isCollapsible && isActive ? (
|
||||
<TriangleSVG className="h-4 pr-6" />
|
||||
) : (
|
||||
<TriangleOutlinedSVG className="h-4 pr-6" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// import FIREBASE from './firebase';
|
||||
import METADATA from './metadata';
|
||||
import NAVIGATION from './navigation';
|
||||
import SEARCH from './search';
|
||||
|
|
|
@ -32,9 +32,6 @@
|
|||
"contentful": "^8.1.7",
|
||||
"dotenv": "^8.2.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.3.1",
|
||||
"firebase": "^8.2.1",
|
||||
"firebase-admin": "^9.4.2",
|
||||
"firebase-functions": "^3.13.0",
|
||||
"firestore-pagination-hook": "^1.0.0",
|
||||
"global": "^4.4.0",
|
||||
"groq": "^1.149.16",
|
||||
|
@ -53,7 +50,6 @@
|
|||
"react-dom": "^17.0.0",
|
||||
"react-paginate": "^6.5.0",
|
||||
"react-redux": "^7.2.1",
|
||||
"react-redux-firebase": "^3.9.0",
|
||||
"react-use": "^15.3.4",
|
||||
"redux": "^4.0.5",
|
||||
"redux-firestore": "^0.14.0",
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'firebase/auth';
|
||||
import 'firebase/firestore'; // <- needed if using firestore
|
||||
import type { AppProps } from 'next/app';
|
||||
import Head from 'next/head';
|
||||
import React, { useEffect } from 'react';
|
||||
|
@ -10,16 +8,16 @@ import '../assets/style.scss';
|
|||
import Layout from '../components/layout';
|
||||
import { METADATA } from '../constants';
|
||||
import ScreenProvider from '../contexts/screen';
|
||||
import { collapseSearchOverlay } from '../state/navigation';
|
||||
import { collapseSideMenu } from '../state/navigation';
|
||||
import { rootReducer } from '../state/reducers';
|
||||
|
||||
const store = createStore(rootReducer);
|
||||
|
||||
function App({ Component, pageProps }: AppProps) {
|
||||
// Close search overlay on page changed
|
||||
// Close side menu on page changed
|
||||
const location = useLocation();
|
||||
useEffect(() => {
|
||||
store.dispatch(collapseSearchOverlay());
|
||||
store.dispatch(collapseSideMenu());
|
||||
}, [location.pathname, location.search]);
|
||||
|
||||
return (
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
import Head from 'next/head';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Article } from '../../components/article/Article';
|
||||
import { BlogApi } from '../../services/blog';
|
||||
import { IPost } from '../../types/blog';
|
||||
import { CmsApi } from '../../services/cms';
|
||||
import { IPost } from '../../types/cms';
|
||||
import { generateTitle } from '../../utils/metadata';
|
||||
|
||||
export async function getServerSideProps({ params }) {
|
||||
console.log('Sulg', params);
|
||||
const api = new BlogApi();
|
||||
const api = new CmsApi();
|
||||
const post = await api.fetchBlogBySlug(String(params.slug) ?? '');
|
||||
|
||||
console.log('index ➡️ post:', post);
|
||||
|
|
|
@ -3,11 +3,11 @@ import Head from 'next/head';
|
|||
import React from 'react';
|
||||
import { ArticleCard } from '../../components/cards/ArticleCard';
|
||||
import { CardGrid } from '../../components/cards/CardGrid';
|
||||
import { BlogApi } from '../../services/blog';
|
||||
import { CmsApi } from '../../services/cms';
|
||||
import { generateTitle } from '../../utils/metadata';
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const api = new BlogApi();
|
||||
const api = new CmsApi();
|
||||
const posts = await api.fetchBlogEntries();
|
||||
|
||||
console.log('index ➡️ posts:', posts);
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
import { InferGetServerSidePropsType } from 'next';
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
import { SideMenu } from '../components/navigation/SideMenu';
|
||||
import { HomeLanding } from '../components/pages/home/HomeLanding';
|
||||
import { METADATA } from '../constants';
|
||||
import { CmsApi } from '../services/cms';
|
||||
import { SideMenuItem } from '../state/navigation';
|
||||
|
||||
// interface Props {
|
||||
// posts: Array<ISanityArticle>;
|
||||
// AuthUserInfo: any;
|
||||
// }
|
||||
export async function getServerSideProps(context) {
|
||||
const api = new CmsApi();
|
||||
const page = await api.fetchPageById(SideMenuItem.WHO_ARE_WE);
|
||||
|
||||
// const Index: NextPage<Props> = () => {
|
||||
const Index = () => {
|
||||
console.log('index ➡️ page:', page);
|
||||
return { props: page };
|
||||
}
|
||||
|
||||
const Index = (
|
||||
page: InferGetServerSidePropsType<typeof getServerSideProps>,
|
||||
) => {
|
||||
// const cards = posts
|
||||
// ? posts.slice(0, 4).map(post => <ArticleCard key={post.id} {...post} />)
|
||||
// : [];
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { ContentfulClientApi, createClient } from 'contentful';
|
||||
import moment from 'moment';
|
||||
import { IAuthor, IFigureImage, IPost } from '../types/blog';
|
||||
import { SideMenuItem } from '../state/navigation';
|
||||
import { IAuthor, IFigureImage, IPost, ISplitPage } from '../types/cms';
|
||||
|
||||
export class BlogApi {
|
||||
export class CmsApi {
|
||||
client: ContentfulClientApi;
|
||||
|
||||
constructor() {
|
||||
|
@ -36,6 +37,16 @@ export class BlogApi {
|
|||
});
|
||||
}
|
||||
|
||||
public async fetchPageById(id: SideMenuItem): Promise<ISplitPage> {
|
||||
return this.client.getEntry(id).then(entry => {
|
||||
if (entry) {
|
||||
console.log('cms ➡️ entry:', entry);
|
||||
// return this.convertPage(entry);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public async fetchBlogBySlug(slug: string): Promise<IPost> {
|
||||
return this.client
|
||||
.getEntries({
|
|
@ -1,7 +1,3 @@
|
|||
export enum ModalInstance {
|
||||
LOGIN = 'LOGIN',
|
||||
}
|
||||
|
||||
export enum SideMenuItem {
|
||||
WHO_ARE_WE = 'WHO_ARE_WE',
|
||||
MISSION = 'MISSION',
|
||||
|
@ -16,9 +12,6 @@ export enum SideMenuItem {
|
|||
export interface INavigation {
|
||||
sideMenuExpanded: boolean;
|
||||
sideMenuActive: SideMenuItem;
|
||||
mobileMenuExpanded: boolean;
|
||||
searchOverlayExpanded: boolean;
|
||||
openedModal: ModalInstance | null;
|
||||
}
|
||||
|
||||
export const initialNavigationState: INavigation = {
|
||||
|
@ -26,22 +19,12 @@ export const initialNavigationState: INavigation = {
|
|||
// On desktop it's always open (if it fits).
|
||||
sideMenuExpanded: false,
|
||||
sideMenuActive: SideMenuItem.WHO_ARE_WE,
|
||||
mobileMenuExpanded: false,
|
||||
searchOverlayExpanded: false,
|
||||
openedModal: null,
|
||||
};
|
||||
|
||||
export enum NavigationActions {
|
||||
EXPAND_SIDE_MENU = 'EXPAND_SIDE_MENU',
|
||||
SET_SIDE_MENU_ACTIVE = 'SET_SIDE_MENU_ACTIVE',
|
||||
COLLAPSE_SIDE_MENU = 'COLLAPSE_SIDE_MENU',
|
||||
OPEN_MOBILE_MENU = 'OPEN_MOBILE_MENU',
|
||||
CLOSE_MOBILE_MENU = 'CLOSE_MOBILE_MENU',
|
||||
TOGGLE_MOBILE_MENU = 'TOGGLE_MOBILE_MENU',
|
||||
EXPAND_SEARCH_OVERLAY = 'EXPAND_SEARCH_OVERLAY',
|
||||
COLLAPSE_SEARCH_OVERLAY = 'COLLAPSE_SEARCH_OVERLAY',
|
||||
TOGGLE_SEARCH_OVERLAY = 'TOGGLE_SEARCH_OVERLAY',
|
||||
SET_MODAL_IS_OPEN = 'SET_MODAL_IS_OPEN',
|
||||
}
|
||||
|
||||
// ////////////////////////////// //
|
||||
|
@ -59,32 +42,3 @@ export const setSideMenuActive = (active: SideMenuItem) => ({
|
|||
export const collapseSideMenu = () => ({
|
||||
type: NavigationActions.COLLAPSE_SIDE_MENU,
|
||||
});
|
||||
|
||||
export const openMobileMenu = () => ({
|
||||
type: NavigationActions.OPEN_MOBILE_MENU,
|
||||
});
|
||||
|
||||
export const closeMobileMenu = () => ({
|
||||
type: NavigationActions.CLOSE_MOBILE_MENU,
|
||||
});
|
||||
|
||||
export const toggleMobileMenu = () => ({
|
||||
type: NavigationActions.TOGGLE_MOBILE_MENU,
|
||||
});
|
||||
|
||||
export const expandSearchOverlay = () => ({
|
||||
type: NavigationActions.EXPAND_SEARCH_OVERLAY,
|
||||
});
|
||||
|
||||
export const collapseSearchOverlay = () => ({
|
||||
type: NavigationActions.COLLAPSE_SEARCH_OVERLAY,
|
||||
});
|
||||
|
||||
export const toggleSearchOverlay = () => ({
|
||||
type: NavigationActions.TOGGLE_SEARCH_OVERLAY,
|
||||
});
|
||||
|
||||
export const setCurrentOpenModal = (isOpen: boolean) => ({
|
||||
type: NavigationActions.SET_MODAL_IS_OPEN,
|
||||
payload: isOpen,
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IPost } from '../../types/blog';
|
||||
import { IPost } from '../../types/cms';
|
||||
|
||||
export const initialArticleState: IPost | Record<string, unknown> = {};
|
||||
|
||||
|
|
|
@ -1,28 +1,15 @@
|
|||
import 'firebase/auth';
|
||||
import 'firebase/firestore'; // <- needed if using firestore
|
||||
import { firebaseReducer } from 'react-redux-firebase';
|
||||
import { combineReducers } from 'redux';
|
||||
import { firestoreReducer } from 'redux-firestore'; // <- needed if using firestore
|
||||
import { IFirestore } from '../../constants/firebase';
|
||||
import { IPost } from '../../types/blog';
|
||||
import { IPost } from '../../types/cms';
|
||||
import { INavigation } from '../navigation';
|
||||
import { ISearch } from '../search';
|
||||
import { articleReducer } from './article';
|
||||
import { navigationReducer } from './navigation';
|
||||
import { searchReducer } from './search';
|
||||
|
||||
export interface IState {
|
||||
navigation: INavigation;
|
||||
search: ISearch;
|
||||
article: IPost;
|
||||
firestore: IFirestore;
|
||||
}
|
||||
|
||||
export const rootReducer = combineReducers({
|
||||
navigation: navigationReducer,
|
||||
search: searchReducer,
|
||||
article: articleReducer,
|
||||
|
||||
firebase: firebaseReducer,
|
||||
firestore: firestoreReducer, // <- needed if using firestore
|
||||
});
|
||||
|
|
|
@ -23,27 +23,6 @@ export const navigationReducer = (
|
|||
case NavigationActions.COLLAPSE_SIDE_MENU: {
|
||||
return { ...state, sideMenuExpanded: false };
|
||||
}
|
||||
case NavigationActions.OPEN_MOBILE_MENU: {
|
||||
return { ...state, mobileMenuExpanded: true };
|
||||
}
|
||||
case NavigationActions.CLOSE_MOBILE_MENU: {
|
||||
return { ...state, mobileMenuExpanded: false };
|
||||
}
|
||||
case NavigationActions.TOGGLE_MOBILE_MENU: {
|
||||
return { ...state, mobileMenuExpanded: !state.mobileMenuExpanded };
|
||||
}
|
||||
case NavigationActions.EXPAND_SEARCH_OVERLAY: {
|
||||
return { ...state, searchOverlayExpanded: true };
|
||||
}
|
||||
case NavigationActions.COLLAPSE_SEARCH_OVERLAY: {
|
||||
return { ...state, searchOverlayExpanded: false };
|
||||
}
|
||||
case NavigationActions.TOGGLE_SEARCH_OVERLAY: {
|
||||
return { ...state, searchOverlayExpanded: !state.searchOverlayExpanded };
|
||||
}
|
||||
case NavigationActions.SET_MODAL_IS_OPEN: {
|
||||
return { ...state, modalIsOpen: action.payload };
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IPost } from '../types/blog';
|
||||
import { IPost } from '../types/cms';
|
||||
|
||||
export interface ISearch {
|
||||
searchQuery: string;
|
||||
|
|
|
@ -34,3 +34,11 @@ export type BodyDocument = {
|
|||
nodeType: 'document';
|
||||
content: any;
|
||||
};
|
||||
|
||||
export interface ISplitPage {
|
||||
id: ISplitPage;
|
||||
label: string;
|
||||
title: string;
|
||||
body: Document;
|
||||
hero?: IFigureImage;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
export enum UserData {
|
||||
RECENT_SEARCHES = 'recentSearches',
|
||||
REFERRED_FROM = 'referredFrom',
|
||||
USER_SESSIONS = 'userSessions',
|
||||
USER_DEVICE = 'userDevice',
|
||||
}
|
||||
|
||||
export interface IUserSession {
|
||||
device: 'mobile' | 'tablet' | 'desktop';
|
||||
sessionStartTimestamp: number;
|
||||
sessionEndTimestamp: number;
|
||||
}
|
||||
|
||||
export interface IRecentSearch {
|
||||
query: string;
|
||||
timestamp: number;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { ArticleCard } from '../components/cards/ArticleCard';
|
||||
import { IPost } from '../types/blog';
|
||||
import { IPost } from '../types/cms';
|
||||
|
||||
export function postsToCards(posts: Array<IPost>) {
|
||||
const cards = posts
|
||||
|
|
Loading…
Reference in New Issue