commit
4224f88569
|
@ -148,7 +148,7 @@ body {
|
|||
&:hover {
|
||||
filter: brightness(0.9);
|
||||
&:not(.active) {
|
||||
color: theme('colors.secondary')
|
||||
color: theme('colors.secondary');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,3 +169,21 @@ body {
|
|||
ul li > p {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.accordion {
|
||||
background-color: theme('colors.secondary');
|
||||
color: theme('colors.primary');
|
||||
cursor: pointer;
|
||||
padding: 0.5em;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.accordion:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
.accordion-content {
|
||||
transition: max-height 0.2s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import React, { useState, useRef } from 'react';
|
||||
import { RichBody } from '../components/RichBody';
|
||||
import classNames from 'classnames';
|
||||
import TriangleSVG from '../assets/svgs/triangle.svg';
|
||||
|
||||
export function Accordion(props) {
|
||||
const { question, answer } = props;
|
||||
const [isActive, setActiveState] = useState(false);
|
||||
const [height, setHeight] = useState('0px');
|
||||
|
||||
const content = useRef(null);
|
||||
function toggleAccordion() {
|
||||
setActiveState(!isActive);
|
||||
setHeight(isActive ? '0px' : `${content.current.scrollHeight}px`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-1 border border-current rounded-sm">
|
||||
<button
|
||||
className={
|
||||
'flex accordion text-xl tablet:text-2xl w-full text-left bg-secondary px-3 justify-between items-center'
|
||||
}
|
||||
onClick={toggleAccordion}
|
||||
>
|
||||
<div style={{ maxWidth: '95%' }}> {question}</div>
|
||||
<TriangleSVG
|
||||
className={classNames(
|
||||
'h-3 fill-current text-primary transform outline-none cursor-pointer duration-200',
|
||||
isActive ? 'rotate-90' : '',
|
||||
)}
|
||||
/>{' '}
|
||||
</button>
|
||||
<div
|
||||
ref={content}
|
||||
style={{
|
||||
maxHeight: height,
|
||||
}}
|
||||
className={classNames('accordion-content')}
|
||||
>
|
||||
<div className="w-full px-4 pt-4 text-lg text-left ease-in-out tablet:text-xl">
|
||||
<RichBody body={answer} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -13,6 +13,9 @@ const METADATA = {
|
|||
DESCRIPTION: "View Oxen's Blog Updates Here",
|
||||
URL: 'https://oxen.io/blog',
|
||||
},
|
||||
FAQ: {
|
||||
DESCRIPTION: 'View Some Frequently Asked Questions here',
|
||||
},
|
||||
};
|
||||
|
||||
export default METADATA;
|
||||
|
|
|
@ -62,6 +62,11 @@ const SIDE_MENU_ITEMS = {
|
|||
label: "Oxen's 2021 roadmap",
|
||||
href: '/roadmap',
|
||||
},
|
||||
[SideMenuItem.FAQ]: {
|
||||
id: 9,
|
||||
label: 'Frequently Asked Questions',
|
||||
href: '/faq',
|
||||
},
|
||||
} as { [name: string]: ISideMenuItem };
|
||||
|
||||
const MENU_ITEMS: IMenuItem[] = [
|
||||
|
|
|
@ -4,14 +4,14 @@ import React, { useEffect } from 'react';
|
|||
import { useDispatch } from 'react-redux';
|
||||
import { Contained } from '../components/Contained';
|
||||
import { RichBody } from '../components/RichBody';
|
||||
import { NAVIGATION, METADATA } from '../constants';
|
||||
import { NAVIGATION } from '../constants';
|
||||
import { CmsApi, unslugify } from '../services/cms';
|
||||
import { PageType, setPageType, SideMenuItem } from '../state/navigation';
|
||||
import { ISplitPage } from '../types/cms';
|
||||
import { generateTitle, generateURL } from '../utils/metadata';
|
||||
|
||||
interface IPath {
|
||||
params: { page: string; isRoadmap?: boolean };
|
||||
params: { page: string };
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
|
@ -31,17 +31,6 @@ export async function getStaticPaths() {
|
|||
export async function getStaticProps({ params }) {
|
||||
const href = params?.page ?? '';
|
||||
const id = unslugify(String(href));
|
||||
// Roadmap page is special 👁️👄👁️
|
||||
if (SideMenuItem[id] == [SideMenuItem.ROADMAP]) {
|
||||
return {
|
||||
props: {
|
||||
page: null,
|
||||
isRoadmap: true,
|
||||
href: `/${href}`, // the '/' is removed from the href from getStaticPaths(), so let's add it back here
|
||||
},
|
||||
revalidate: 60,
|
||||
};
|
||||
}
|
||||
|
||||
const cms = new CmsApi();
|
||||
const page = await cms.fetchPageById(SideMenuItem[id]);
|
||||
|
@ -52,39 +41,20 @@ export async function getStaticProps({ params }) {
|
|||
return {
|
||||
props: {
|
||||
page,
|
||||
isRoadmap: false,
|
||||
href: `/${href}`,
|
||||
},
|
||||
revalidate: 60,
|
||||
};
|
||||
}
|
||||
|
||||
function Page({
|
||||
page,
|
||||
isRoadmap,
|
||||
href,
|
||||
}: {
|
||||
page: ISplitPage | null;
|
||||
isRoadmap?: boolean;
|
||||
href: string;
|
||||
}) {
|
||||
function Page({ page, href }: { page: ISplitPage | null; href: string }) {
|
||||
const dispatch = useDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(setPageType(PageType.NORMAL));
|
||||
}, []);
|
||||
const pageTitle = generateTitle(
|
||||
isRoadmap
|
||||
? NAVIGATION.SIDE_MENU_ITEMS[SideMenuItem.ROADMAP].label
|
||||
: page?.label,
|
||||
);
|
||||
|
||||
const pageDescription = isRoadmap
|
||||
? METADATA.ROADMAP.DESCRIPTION
|
||||
: page?.title;
|
||||
|
||||
const pageURL = generateURL(
|
||||
isRoadmap ? NAVIGATION.SIDE_MENU_ITEMS[SideMenuItem.ROADMAP].href : href,
|
||||
);
|
||||
const pageTitle = generateTitle(page?.label);
|
||||
const pageDescription = page?.title;
|
||||
const pageURL = generateURL(href);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
import { GetServerSideProps } from 'next';
|
||||
import { NAVIGATION, METADATA, CMS } from '../constants';
|
||||
import { SideMenuItem } from '../state/navigation';
|
||||
import { generateTitle, generateURL } from '../utils/metadata';
|
||||
import { CmsApi } from '../services/cms';
|
||||
import { IFAQItem } from '../types/cms';
|
||||
import { Accordion } from '../components/Accordion';
|
||||
import { Contained } from '../components/Contained';
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async context => {
|
||||
const cms = new CmsApi();
|
||||
|
||||
// Fetch posts even when tag, for related etc
|
||||
// Pagination only occurs when tag isnt defined.
|
||||
// If tag is defined, pagination is for tag results
|
||||
const { faqItems, total } = await cms.fetchFAQItems();
|
||||
|
||||
return {
|
||||
props: {
|
||||
faqItems,
|
||||
total,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface Props {
|
||||
faqItems: IFAQItem[];
|
||||
total: number;
|
||||
}
|
||||
function FAQ(props: Props) {
|
||||
const { faqItems } = props;
|
||||
|
||||
const pageTitle = generateTitle(
|
||||
NAVIGATION.SIDE_MENU_ITEMS[SideMenuItem.FAQ].label,
|
||||
);
|
||||
const pageURL = generateURL(
|
||||
NAVIGATION.SIDE_MENU_ITEMS[SideMenuItem.FAQ].href,
|
||||
);
|
||||
const imagePathLocal = 'img/what_is_oxen.png';
|
||||
const imageURL = `${METADATA.OXEN_HOST_URL}/${imagePathLocal}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{pageTitle}</title>
|
||||
<meta name="description" content={METADATA.FAQ.DESCRIPTION}></meta>
|
||||
<meta property="og:title" content={pageTitle} key="ogtitle" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content={METADATA.FAQ.DESCRIPTION}
|
||||
key="ogdesc"
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content={imageURL} key="ogimage" />
|
||||
<meta property="og:url" content={pageURL} />
|
||||
|
||||
<link rel="canonical" href={pageURL}></link>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={pageTitle} />
|
||||
<meta name="twitter:description" content={METADATA.FAQ.DESCRIPTION} />
|
||||
<meta name="twitter:image" content={imageURL} />
|
||||
</Head>
|
||||
|
||||
<div className="bg-alt">
|
||||
<div className="relative flex items-center justify-center w-full h-full pt-3 bg-gradient-to-bl from-hyper to-blush">
|
||||
<img
|
||||
style={{ maxHeight: '33vh' }}
|
||||
src={imagePathLocal}
|
||||
className="object-contain w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Contained>
|
||||
<div className="flex flex-col pb-6 mx-4">
|
||||
<div>
|
||||
<h1 className="mt-6 mb-3 text-3xl font-medium text-left tablet:text-4xl font-prompt text-primary">
|
||||
FAQ
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{faqItems.map(faqItem => (
|
||||
<div key={faqItem.id}>
|
||||
<Accordion
|
||||
question={faqItem.question}
|
||||
answer={faqItem.answer}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Contained>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default FAQ;
|
|
@ -39,7 +39,7 @@ function Roadmap() {
|
|||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={pageURL} />
|
||||
|
||||
<meta property="og:image" content={imageURL} key="ogimage" />
|
||||
<link rel="canonical" href={pageURL}></link>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={pageTitle} />
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 115 KiB |
|
@ -9,13 +9,24 @@ import TelegramSVG from '../assets/svgs/socials/brand-telegram.svg';
|
|||
import { Button } from '../components/Button';
|
||||
import { CMS } from '../constants';
|
||||
import { SideMenuItem, TPages } from '../state/navigation';
|
||||
import { IAuthor, IFigureImage, IPost, ISplitPage } from '../types/cms';
|
||||
import {
|
||||
IAuthor,
|
||||
IFigureImage,
|
||||
IPost,
|
||||
ISplitPage,
|
||||
IFAQItem,
|
||||
} from '../types/cms';
|
||||
|
||||
interface IFetchBlogEntriesReturn {
|
||||
posts: Array<IPost>;
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface IFetchFAQItemsReturn {
|
||||
faqItems: Array<IFAQItem>;
|
||||
total: number;
|
||||
}
|
||||
|
||||
// Turns CMS IDs into slugs
|
||||
export const slugify = (id: string) => id?.replace(/_/g, '-').toLowerCase();
|
||||
export const unslugify = (slug: string) =>
|
||||
|
@ -155,6 +166,20 @@ export class CmsApi {
|
|||
});
|
||||
}
|
||||
|
||||
public async fetchFAQItems(): Promise<IFetchFAQItemsReturn> {
|
||||
const entries = await this.client.getEntries({
|
||||
content_type: 'faq_item', // only fetch faq items
|
||||
order: 'fields.id',
|
||||
});
|
||||
|
||||
if (entries && entries.items && entries.items.length > 0) {
|
||||
const faqItems = entries.items.map(entry => this.convertFAQ(entry));
|
||||
return { faqItems, total: entries.total };
|
||||
}
|
||||
|
||||
return { faqItems: [], total: 0 } as IFetchFAQItemsReturn;
|
||||
}
|
||||
|
||||
public convertImage = (rawImage): IFigureImage =>
|
||||
rawImage
|
||||
? {
|
||||
|
@ -211,6 +236,17 @@ export class CmsApi {
|
|||
hero: this.convertImage(rawHero),
|
||||
};
|
||||
};
|
||||
|
||||
public convertFAQ = (rawData): IFAQItem => {
|
||||
const rawFAQ = rawData.fields;
|
||||
const { question, answer, id } = rawFAQ;
|
||||
|
||||
return {
|
||||
id: id ?? null,
|
||||
question: question ?? null,
|
||||
answer: answer ?? null,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const extractShortcodeGeneralButton = (shortcode: string) => {
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
'use strict';
|
||||
exports.__esModule = true;
|
||||
exports.collapseMobileHeader = exports.expandMobileHeaderMenu = exports.setSplitPagesContent = exports.collapseSideMenu = exports.expandSideMenu = exports.setPostTitle = exports.setPageType = exports.setHeaderCollapsed = exports.NavigationActions = exports.initialNavigationState = exports.PageType = exports.SideMenuItem = void 0;
|
||||
var SideMenuItem;
|
||||
(function (SideMenuItem) {
|
||||
SideMenuItem['BUILD'] = 'BUILD';
|
||||
SideMenuItem['BUY_OXEN'] = 'BUY_OXEN';
|
||||
SideMenuItem['WHO_ARE_WE'] = 'WHO_ARE_WE';
|
||||
SideMenuItem['STAKE'] = 'STAKE';
|
||||
SideMenuItem['USES'] = 'USES';
|
||||
SideMenuItem['SESSION_LOKINET'] = 'SESSION_LOKINET';
|
||||
SideMenuItem['GET_INVOLVED'] = 'GET_INVOLVED';
|
||||
SideMenuItem['ROADMAP'] = 'ROADMAP';
|
||||
})((SideMenuItem = exports.SideMenuItem || (exports.SideMenuItem = {})));
|
||||
var PageType;
|
||||
(function (PageType) {
|
||||
PageType['NORMAL'] = 'NORMAL';
|
||||
PageType['BLOG'] = 'BLOG';
|
||||
PageType['POST'] = 'POST';
|
||||
})((PageType = exports.PageType || (exports.PageType = {})));
|
||||
exports.initialNavigationState = {
|
||||
// Side menu expanded only toggles for mobile.
|
||||
// On desktop it's always open (if it fits).
|
||||
pageType: PageType.NORMAL,
|
||||
postTitle: undefined,
|
||||
headerCollapsed: true,
|
||||
sideMenuExpanded: false,
|
||||
headerMobileMenuExpanded: false,
|
||||
};
|
||||
var NavigationActions;
|
||||
(function (NavigationActions) {
|
||||
NavigationActions['SET_HEADER_COLLAPSED'] = 'SET_HEADER_COLLAPSED';
|
||||
NavigationActions['SET_PAGE_TYPE'] = 'SET_PAGE_TYPE';
|
||||
NavigationActions['SET_POST_TITLE'] = 'SET_POST_TITLE';
|
||||
NavigationActions['EXPAND_SIDE_MENU'] = 'EXPAND_SIDE_MENU';
|
||||
NavigationActions['COLLAPSE_SIDE_MENU'] = 'COLLAPSE_SIDE_MENU';
|
||||
NavigationActions['SET_SPLIT_PAGES_CONTENT'] = 'SET_SPLIT_PAGES_CONTENT';
|
||||
NavigationActions['EXPAND_MOBILE_HEADER_MENU'] = 'EXPAND_MOBILE_HEADER_MENU';
|
||||
NavigationActions['COLLAPSE_MOBILE_HEADER_MENU'] =
|
||||
'COLLAPSE_MOBILE_HEADER_MENU';
|
||||
})(
|
||||
(NavigationActions =
|
||||
exports.NavigationActions || (exports.NavigationActions = {})),
|
||||
);
|
||||
// ////////////////////////////// //
|
||||
// Action Creators //
|
||||
// ////////////////////////////// //
|
||||
var setHeaderCollapsed = function (collapsed) {
|
||||
return {
|
||||
type: NavigationActions.SET_HEADER_COLLAPSED,
|
||||
payload: collapsed,
|
||||
};
|
||||
};
|
||||
exports.setHeaderCollapsed = setHeaderCollapsed;
|
||||
var setPageType = function (type) {
|
||||
return {
|
||||
type: NavigationActions.SET_PAGE_TYPE,
|
||||
payload: type,
|
||||
};
|
||||
};
|
||||
exports.setPageType = setPageType;
|
||||
// For sidebar title
|
||||
var setPostTitle = function (title) {
|
||||
return {
|
||||
type: NavigationActions.SET_POST_TITLE,
|
||||
payload: title,
|
||||
};
|
||||
};
|
||||
exports.setPostTitle = setPostTitle;
|
||||
var expandSideMenu = function () {
|
||||
return {
|
||||
type: NavigationActions.EXPAND_SIDE_MENU,
|
||||
};
|
||||
};
|
||||
exports.expandSideMenu = expandSideMenu;
|
||||
var collapseSideMenu = function () {
|
||||
return {
|
||||
type: NavigationActions.COLLAPSE_SIDE_MENU,
|
||||
};
|
||||
};
|
||||
exports.collapseSideMenu = collapseSideMenu;
|
||||
var setSplitPagesContent = function (pages) {
|
||||
return {
|
||||
type: NavigationActions.SET_SPLIT_PAGES_CONTENT,
|
||||
payload: pages,
|
||||
};
|
||||
};
|
||||
exports.setSplitPagesContent = setSplitPagesContent;
|
||||
var expandMobileHeaderMenu = function () {
|
||||
return {
|
||||
type: NavigationActions.EXPAND_MOBILE_HEADER_MENU,
|
||||
};
|
||||
};
|
||||
exports.expandMobileHeaderMenu = expandMobileHeaderMenu;
|
||||
var collapseMobileHeader = function () {
|
||||
return {
|
||||
type: NavigationActions.COLLAPSE_MOBILE_HEADER_MENU,
|
||||
};
|
||||
};
|
||||
exports.collapseMobileHeader = collapseMobileHeader;
|
|
@ -10,6 +10,7 @@ export enum SideMenuItem {
|
|||
GET_INVOLVED = 'GET_INVOLVED',
|
||||
ROADMAP = 'ROADMAP',
|
||||
AIRDROP = 'AIRDROP',
|
||||
FAQ = 'FAQ',
|
||||
}
|
||||
|
||||
export type TPages = {
|
||||
|
|
|
@ -44,3 +44,9 @@ export interface ISplitPage {
|
|||
body: Document;
|
||||
hero?: IFigureImage;
|
||||
}
|
||||
|
||||
export interface IFAQItem {
|
||||
id: number;
|
||||
question: string;
|
||||
answer: Document;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue