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

Session technology foundation site baseline #60

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
a10f22e
chore: disable broken logger wrapper
Aerilym Oct 7, 2024
c70d462
feat: create typography component for ui library
Aerilym Oct 7, 2024
744c176
feat: create navlink variants
Aerilym Oct 7, 2024
6787616
chore: add new icons
Aerilym Oct 7, 2024
0a8294b
fix: add document name to type in sanity typer
Aerilym Oct 7, 2024
9bb2e1c
feat: create sanity schemas for links, tiles, authors, and posts
Aerilym Oct 7, 2024
919872a
feat: create disable draft mode button
Aerilym Oct 7, 2024
efdcdef
fix: add link resolver to sanity button
Aerilym Oct 7, 2024
026014e
feat: create sanity tiles component
Aerilym Oct 7, 2024
1b36543
feat: create scroll button
Aerilym Oct 7, 2024
835d8ab
feat: add ui library typography to sanity portable text
Aerilym Oct 7, 2024
de7d992
fix: rework sanity preview draft mode
Aerilym Oct 7, 2024
13d0e78
feat: add advanced nextjs cache mechanics to sanity fetcher
Aerilym Oct 7, 2024
2ea840e
chore: create sanity utility libraries
Aerilym Oct 7, 2024
9eb30e9
feat: create sanity schema query functions
Aerilym Oct 7, 2024
1fdc241
feat: add tag and path revalidation logic to next.js revalidation end…
Aerilym Oct 7, 2024
2cc9a57
fix: refactor link validator to use a decoded path segment
Aerilym Oct 7, 2024
cd49a49
chore: add tailwind to sanity cms lib
Aerilym Oct 7, 2024
beb751a
feat: create boilerplate nextjs app for foundation website
Aerilym Oct 7, 2024
b554261
feat: create localization handler and global provider
Aerilym Oct 7, 2024
170ff7e
feat: create foundation sanity config
Aerilym Oct 7, 2024
172b4e0
feat: create foundation sanity studio
Aerilym Oct 7, 2024
0534a7e
feat: create foundation site sanity endpoints
Aerilym Oct 7, 2024
fd00dff
feat: populate foundation site metadata
Aerilym Oct 7, 2024
bc90e94
feat: create foundation site components and page generators
Aerilym Oct 7, 2024
e31f3cf
feat: create foundation site blog
Aerilym Oct 7, 2024
1646496
chore: add foundation site dummy env var to gh action
Aerilym Oct 7, 2024
a5d0336
chore: use github secrets in build action for mock values
Aerilym Oct 7, 2024
05feced
fix: add black underline to read more text
Aerilym Oct 7, 2024
bc9a82b
fix: center align blogs without headings
Aerilym Oct 7, 2024
352f5fd
fix: filter leading or tailing slashed in cms slugs
Aerilym Oct 8, 2024
2309ab7
fix: add link check for root page
Aerilym Oct 8, 2024
672fd6d
fix: add AA WCAG compliant link styling
Aerilym Oct 8, 2024
9bad718
fix: add hover to read more
Aerilym Oct 8, 2024
3d3921a
fix: localize not found metadata
Aerilym Oct 8, 2024
45ed7b7
chore: fix space in comment
Aerilym Oct 9, 2024
c4962a7
feat: make scroll button sticky and scroll to each tile as target
Aerilym Oct 9, 2024
78bc2ff
chore: reword docstring
Aerilym Oct 9, 2024
698ef4b
chore: comment refactor
Aerilym Oct 9, 2024
6912837
chore: fix tailwind naming issue
Aerilym Oct 9, 2024
285b018
fix: rebuild favicon
Aerilym Oct 9, 2024
68cf973
fix: tile text selection on hover
Aerilym Oct 9, 2024
b801904
fix: container margin
Aerilym Oct 9, 2024
71bd3f5
fix: footer matching content width
Aerilym Oct 9, 2024
f1492e2
fix: author query
Aerilym Oct 9, 2024
c9a511d
feat: add figures and figure captions as options for content images
Aerilym Oct 10, 2024
fc79f5b
fix: render figure parent
Aerilym Oct 10, 2024
83bfd89
chore: remove border
Aerilym Oct 10, 2024
cfa24f1
fix: 404 page button gap
Aerilym Oct 10, 2024
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
8 changes: 8 additions & 0 deletions .github/workflows/builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,13 @@ jobs:
run: |
grep '^export ' ./apps/staking/scripts/mock-build-env.sh | sed 's/export //' >> $GITHUB_ENV
- name: Set environment variables from github secrets
run: |
echo "NEXT_PUBLIC_SANITY_DATASET=${{ secrets.NEXT_PUBLIC_SANITY_DATASET }}" >> $GITHUB_ENV
echo "NEXT_PUBLIC_SANITY_PROJECT_ID=${{ secrets.NEXT_PUBLIC_SANITY_PROJECT_ID }}" >> $GITHUB_ENV
echo "SANITY_API_READ_TOKEN=${{ secrets.SANITY_API_READ_TOKEN }}" >> $GITHUB_ENV
echo "SANITY_REVALIDATE_SECRET=${{ secrets.SANITY_REVALIDATE_SECRET }}" >> $GITHUB_ENV
echo "NEXT_PUBLIC_SANITY_API_VERSION=${{ secrets.NEXT_PUBLIC_SANITY_API_VERSION }}" >> $GITHUB_ENV
- name: Run builds
run: pnpm build
6 changes: 6 additions & 0 deletions apps/foundation/.env.local.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
NEXT_PUBLIC_ENV_FLAG= pick from dev, qa, stg, prd
NEXT_PUBLIC_SANITY_DATASET=
NEXT_PUBLIC_SANITY_PROJECT_ID=
SANITY_API_READ_TOKEN=
SANITY_REVALIDATE_SECRET=
yougotwill marked this conversation as resolved.
Show resolved Hide resolved
NEXT_PUBLIC_SANITY_API_VERSION="2024-09-30"
9 changes: 9 additions & 0 deletions apps/foundation/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** @type {import("eslint").Linter.Config} */
module.exports = {
root: true,
extends: ['@session/eslint-config/next.js'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: true,
},
};
13 changes: 13 additions & 0 deletions apps/foundation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Session Technology Foundation Website

The Session Technology Foundation Website is a [Next.js](https://nextjs.org/) app.

## Getting Started

You can follow the generic instructions in the root [README.md](../../README.md#getting-started) to get started.

## Development

Running the app requires several environment variables to be set. See the [.env.local.template](.env.local.template)
file for a list
of required variables.
10 changes: 10 additions & 0 deletions apps/foundation/app/(Sanity)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { ReactNode } from 'react';
import '@session/ui/styles';

export default function SanityLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
8 changes: 8 additions & 0 deletions apps/foundation/app/(Sanity)/studio/[[...tool]]/Studio.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use client';

import SanityStudio from '@session/sanity-cms/components/SanityStudio';
import { sanityConfig } from '@/lib/sanity/sanity.config';

export default function Studio() {
return <SanityStudio config={sanityConfig} />;
}
7 changes: 7 additions & 0 deletions apps/foundation/app/(Sanity)/studio/[[...tool]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Loading } from '@session/ui/components/loading';
import { SanityStudioSSRPage } from '@session/sanity-cms/components/SanityStudioSSRPage';
import Studio from '@/app/(Sanity)/studio/[[...tool]]/Studio';

export default function StudioPage() {
return <SanityStudioSSRPage sanityStudio={<Studio />} suspenseFallback={<Loading />} />;
}
13 changes: 13 additions & 0 deletions apps/foundation/app/(Site)/[slug]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { ReactNode } from 'react';
import { Footer } from '@/components/Footer';
import { getInitialSiteDataForSSR } from '@/lib/sanity/sanity-server';

export default async function UniversalPageLayout({ children }: { children: ReactNode }) {
const { settings } = await getInitialSiteDataForSSR();
return (
<>
{children}
<Footer className="max-w-screen-md" {...settings} />
</>
);
}
64 changes: 64 additions & 0 deletions apps/foundation/app/(Site)/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { getPageBySlug } from '@session/sanity-cms/queries/getPage';
import { client } from '@/lib/sanity/sanity.client';
import { getPagesSlugs } from '@session/sanity-cms/queries/getPages';
import { notFound } from 'next/navigation';
import { getLandingPageSlug } from '@/lib/sanity/sanity-server';
import PortableText from '@/components/PortableText';
import logger from '@/lib/logger';
import { NEXTJS_EXPLICIT_IGNORED_ROUTES, NEXTJS_IGNORED_PATTERNS } from '@/lib/constants';

/**
* Force static rendering and cache the data of a layout or page by forcing `cookies()`, `headers()`
* and `useSearchParams()` to return empty values.
* @see {@link https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic}
*/
export const dynamic = 'force-static';
/**
* Dynamic segments not included in generateStaticParams are generated on demand.
* @see {@link https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams}
*/
export const dynamicParams = true;

export async function generateStaticParams() {
const pages = await getPagesSlugs({ client });
const slugs = new Set(pages.map((page) => page.slug.current));

const landingPageSlug = await getLandingPageSlug();
if (landingPageSlug) {
slugs.delete(landingPageSlug);
} else {
console.warn('No landing page set in settings to statically generate');
}

const pagesToGenerate = Array.from(slugs);
logger.info(`Generating static params for ${pagesToGenerate.length} pages`);
logger.info(pagesToGenerate);
return pagesToGenerate;
}

type PageProps = {
params: { slug?: string };
};

export default async function UniversalPage({ params }: PageProps) {
const slug = params.slug;
if (!slug) return notFound();

if (
NEXTJS_EXPLICIT_IGNORED_ROUTES.includes(slug) ||
NEXTJS_IGNORED_PATTERNS.some((pattern) => slug.includes(pattern))
) {
return;
}

logger.info(`Generating page for slug ${slug}`);

const page = await getPageBySlug({
client,
slug,
});

if (!page) return notFound();

return <PortableText body={page.body} className="max-w-screen-md" wrapperComponent="main" />;
yougotwill marked this conversation as resolved.
Show resolved Hide resolved
}
67 changes: 67 additions & 0 deletions apps/foundation/app/(Site)/blog/[slug]/BlogPost.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import PortableText from '@/components/PortableText';
import { getLocale, getTranslations } from 'next-intl/server';
import { Button } from '@session/ui/ui/button';
import { cn } from '@session/ui/lib/utils';
import { getLangDir } from 'rtl-detect';
import Link from 'next/link';
import { ButtonDataTestId } from '@/testing/data-test-ids';
import { SANITY_SCHEMA_URL } from '@/lib/constants';
import type { FormattedPostType } from '@session/sanity-cms/queries/getPost';
import { notFound } from 'next/navigation';
import logger from '@/lib/logger';
import PostInfoBlock from '@/app/(Site)/blog/[slug]/PostInfoBlock';
import HeadingOutline from '@/app/(Site)/blog/[slug]/HeadingOutline';

export type PostProps = {
post: FormattedPostType;
};

export default async function BlogPost({ post }: PostProps) {
const blogDictionary = await getTranslations('blog');
const locale = await getLocale();
const direction = getLangDir(locale);

const body = post.body;

if (!body) {
logger.error(`No body found for post: ${post.slug}`);
return notFound();
}

const allH2s = body.filter((block) => block._type === 'block' && block.style === 'h2');

const headings: Array<string> = allH2s
.map((block) =>
'children' in block && Array.isArray(block.children) ? block.children[0].text : null
yougotwill marked this conversation as resolved.
Show resolved Hide resolved
)
.filter(Boolean);

return (
<article className="mx-auto mb-32 mt-4 flex max-w-screen-xl flex-col">
<Link href={SANITY_SCHEMA_URL.POST} prefetch>
<Button
data-testid={ButtonDataTestId.Back_To_Blog}
className={cn('text-session-text-black-secondary my-2 gap-2 fill-current px-1')}
size="sm"
rounded="md"
variant="ghost"
>
<span className={cn(direction === 'rtl' && 'rotate-180')}>←</span>
{blogDictionary('backToBlog')}
</Button>
</Link>
<PostInfoBlock
className="w-full"
postInfo={post}
renderWithPriority
mobileImagePosition="below"
/>
<div className="mt-6 flex flex-row justify-center gap-12 md:mt-12">
<PortableText body={body} className="max-w-screen-md" wrapperComponent="section" />
{headings.length ? (
<HeadingOutline headings={headings} title={blogDictionary('inThisArticle')} />
) : null}
</div>
</article>
);
}
44 changes: 44 additions & 0 deletions apps/foundation/app/(Site)/blog/[slug]/HeadingOutline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';

import Typography from '@session/ui/components/Typography';
import { cn } from '@session/ui/lib/utils';
import { navlinkVariants } from '@session/ui/components/NavLink';

function scrollToHeading(text: string) {
document.querySelectorAll('h2').forEach((heading) => {
if (text && heading.textContent && heading.textContent === text) {
heading.scrollIntoView({
behavior: 'smooth',
});
}
});
}

type HeadingOutlineProps = {
headings: Array<string>;
title: string;
};

export default function HeadingOutline({ title, headings }: HeadingOutlineProps) {
return (
<nav className="wrap sticky top-12 mt-7 hidden h-max w-max max-w-[25vw] lg:block">
<Typography variant="h2" className="mb-3">
{title}
</Typography>
<ul className="text-session-text-black-secondary flex flex-col gap-2">
{headings.map((heading) => (
<li key={`scroll-to-${heading}`}>
<button
onClick={() => {
scrollToHeading(heading);
}}
className={cn(navlinkVariants({ active: false }), 'w-full text-wrap text-start')}
>
{heading}
</button>
</li>
))}
</ul>
</nav>
);
}
79 changes: 79 additions & 0 deletions apps/foundation/app/(Site)/blog/[slug]/PostInfoBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { SanityImage } from '@session/sanity-cms/components/SanityImage';
import { client } from '@/lib/sanity/sanity.client';
import Typography from '@session/ui/components/Typography';
import { getLocale } from 'next-intl/server';
import { cn } from '@session/ui/lib/utils';
import { safeTry } from '@session/util-js/try';
import logger from '@/lib/logger';
import type { FormattedPostType } from '@session/sanity-cms/queries/getPost';
import type { ReactNode } from 'react';

const getLocalizedPosedDate = async (date: Date) => {
const locale = await getLocale();
return new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'long', day: 'numeric' }).format(
date
);
};
export type PostBlockProps = {
postInfo: Pick<FormattedPostType, 'title' | 'summary' | 'featuredImage' | 'author' | 'date'>;
renderWithPriority?: boolean;
mobileImagePosition?: 'above' | 'below';
columnAlways?: boolean;
className?: string;
children?: ReactNode;
};

export default async function PostInfoBlock({
postInfo,
renderWithPriority,
mobileImagePosition = 'above',
columnAlways,
className,
children,
}: PostBlockProps) {
const { title, summary, featuredImage, author, date } = postInfo;

let localizedPublishedAt = null;
if (date) {
const [err, res] = await safeTry(getLocalizedPosedDate(date));
if (err) {
logger.error(err);
localizedPublishedAt = date.toLocaleDateString();
} else {
localizedPublishedAt = res;
}
}
yougotwill marked this conversation as resolved.
Show resolved Hide resolved

return (
<div
className={cn(
'flex w-full items-center gap-8',
columnAlways ? 'flex-col' : 'md:grid md:grid-cols-2',
mobileImagePosition === 'below' ? 'flex-col-reverse' : 'flex-col',
className
)}
>
<div className="aspect-video w-full overflow-hidden rounded-lg">
<SanityImage
className="h-full"
client={client}
value={featuredImage}
cover
renderWithPriority={renderWithPriority}
/>
</div>
<div className="flex w-full flex-col gap-2">
<Typography variant={columnAlways ? 'h2' : 'h1'} className="w-full">
{title}
</Typography>
<span className="text-session-text-black-secondary inline-flex w-full gap-1">
{date ? <time dateTime={date.toISOString()}>{localizedPublishedAt}</time> : null}
{date ? '/' : null}
{author?.name ? <address>{author.name}</address> : null}
</span>
{summary ? <p className="w-full">{summary}</p> : null}
{children}
</div>
</div>
);
}
Loading
Loading