mirror of
https://github.com/tjsga/tj-sga-website-react.git
synced 2025-04-21 12:00:17 -04:00
add officer order, some UI components
This commit is contained in:
parent
0339c587a2
commit
4e93c4a948
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import ArticleRow from './ArticleRow';
|
import ArticleRow from './ArticleRow';
|
||||||
import sanity from '../sanity';
|
import sanity from '../sanity';
|
||||||
import '../css/article.css';
|
import '../css/article.css';
|
||||||
|
import BlueButton from './BlueButton';
|
||||||
|
|
||||||
export default function ArticleList() {
|
export default function ArticleList() {
|
||||||
let [articles, setArticles] = React.useState<SGA.ArticleDocument[]>([]);
|
let [articles, setArticles] = React.useState<SGA.ArticleDocument[]>([]);
|
||||||
|
@ -40,27 +41,30 @@ export default function ArticleList() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
let bottomComponent: any;
|
||||||
<div>
|
if (reachedEnd) {
|
||||||
{articles.map((article) => {
|
bottomComponent = <div>No more articles to show</div>;
|
||||||
return <ArticleRow key={article._id} article={article} />;
|
} else {
|
||||||
})}
|
bottomComponent = (
|
||||||
|
<BlueButton
|
||||||
<div className='text-center'>
|
|
||||||
{!reachedEnd ? (
|
|
||||||
<button
|
|
||||||
className='blue-button'
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
let lastArticle = articles[articles.length - 1];
|
let { publish_date, title } = articles[articles.length - 1];
|
||||||
addArticles(lastArticle.publish_date, lastArticle.title);
|
addArticles(publish_date, title);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Load more articles
|
Load more articles
|
||||||
</button>
|
</BlueButton>
|
||||||
) : (
|
);
|
||||||
<div>No more articles to show</div>
|
}
|
||||||
)}
|
|
||||||
</div>
|
const articleList = articles.map((article) => (
|
||||||
|
<ArticleRow key={article._id} article={article} />
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{articleList}
|
||||||
|
<div className='text-center'>{bottomComponent}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import imageUrl from '../imageUrl';
|
import imageUrl from '../lib/imageUrl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import '../css/article.css';
|
import '../css/article.css';
|
||||||
|
|
||||||
|
|
23
src/components/BlockContentWithExternalLinks.tsx
Normal file
23
src/components/BlockContentWithExternalLinks.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import BlockContent from '@sanity/block-content-to-react';
|
||||||
|
|
||||||
|
export default function BlockContentWithExternalLinks({
|
||||||
|
blocks,
|
||||||
|
}: {
|
||||||
|
blocks: any[];
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={(ref) => {
|
||||||
|
// When this element loads, convert all the links to have target="_blank."
|
||||||
|
// This ensures that the links open in a new tab
|
||||||
|
if (ref) {
|
||||||
|
ref.querySelectorAll('a').forEach((link) => {
|
||||||
|
link.target = '_blank';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BlockContent blocks={blocks} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
15
src/components/BlueButton.tsx
Normal file
15
src/components/BlueButton.tsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { MouseEventHandler } from 'react';
|
||||||
|
|
||||||
|
export default function BlueButton({
|
||||||
|
onClick,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
onClick?: MouseEventHandler;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<button onClick={onClick} className='blue-button'>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
23
src/components/BlueButtonLink.tsx
Normal file
23
src/components/BlueButtonLink.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
export default function BlueButtonLink({
|
||||||
|
href,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
href: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
if (!href.startsWith('http')) {
|
||||||
|
return (
|
||||||
|
<Link to={href} className='blue-button'>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<a href={href} className='blue-button'>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
3
src/components/Centered.tsx
Normal file
3
src/components/Centered.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default function Centered({ children }: { children: React.ReactNode }) {
|
||||||
|
return <div style={{ textAlign: 'center' }}>{children}</div>;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import BlockContent from '@sanity/block-content-to-react';
|
import BlockContent from '@sanity/block-content-to-react';
|
||||||
import imageUrl from '../imageUrl';
|
import imageUrl from '../lib/imageUrl';
|
||||||
import '../css/initiative.css';
|
import '../css/initiative.css';
|
||||||
|
|
||||||
export default function InitiativeColumn({ name, thumbnail, content }) {
|
export default function InitiativeColumn({ name, thumbnail, content }) {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import imageUrl from '../lib/imageUrl';
|
||||||
import imageUrl from '../imageUrl';
|
|
||||||
import BlockContent from '@sanity/block-content-to-react';
|
import BlockContent from '@sanity/block-content-to-react';
|
||||||
import '../css/article.css';
|
import '../css/article.css';
|
||||||
|
|
||||||
|
|
15
src/components/LocalLinkClickable.tsx
Normal file
15
src/components/LocalLinkClickable.tsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
export default function LocalLinkClickable({
|
||||||
|
to,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
to: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Link to={to} className='clickable-link'>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import imageUrl from '../imageUrl';
|
import imageUrl from '../lib/imageUrl';
|
||||||
import '../css/article.css';
|
import '../css/article.css';
|
||||||
|
|
||||||
export default function MemberRow({ member }: { member: SGA.MemberDocument }) {
|
export default function MemberRow({ member }: { member: SGA.MemberDocument }) {
|
||||||
|
|
9
src/components/ParagraphHeader.tsx
Normal file
9
src/components/ParagraphHeader.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export default function ParagraphHeader({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<h2 style={{ marginTop: '4rem', marginBottom: '1.5rem' }}>{children}</h2>
|
||||||
|
);
|
||||||
|
}
|
7
src/components/PrimaryHeader.tsx
Normal file
7
src/components/PrimaryHeader.tsx
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default function PrimaryHeader({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return <h1 className='my-4'>{children}</h1>;
|
||||||
|
}
|
8
src/hooks/useCommittee.tsx
Normal file
8
src/hooks/useCommittee.tsx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import useQuery from './useQuery';
|
||||||
|
|
||||||
|
export default function useCommittee(committee: string) {
|
||||||
|
return useQuery<SGA.MemberDocument[]>(
|
||||||
|
`*[_type == 'member' && committee == $committee]`,
|
||||||
|
{ committee }
|
||||||
|
);
|
||||||
|
}
|
12
src/hooks/useNewsArticle.ts
Normal file
12
src/hooks/useNewsArticle.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import sanity from '../sanity';
|
||||||
|
|
||||||
|
export default function useNewsArticle(articleId: string) {
|
||||||
|
let [article, setArticle] = useState<SGA.ArticleDocument>(null!);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sanity.fetch('*[_id == $articleId] [0]', { articleId }).then(setArticle);
|
||||||
|
}, [articleId]);
|
||||||
|
|
||||||
|
return article;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { default as ImageUrlBuilder } from '@sanity/image-url';
|
import { default as ImageUrlBuilder } from '@sanity/image-url';
|
||||||
import sanity from './sanity';
|
import sanity from '../sanity';
|
||||||
|
|
||||||
const builder = ImageUrlBuilder(sanity);
|
const builder = ImageUrlBuilder(sanity);
|
||||||
|
|
24
src/lib/sortCommittee.ts
Normal file
24
src/lib/sortCommittee.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
export default function sortCommittee(
|
||||||
|
members: SGA.MemberDocument[],
|
||||||
|
roleOrder: string[]
|
||||||
|
) {
|
||||||
|
const roleIndexes = {};
|
||||||
|
for (const { _id, role } of members) {
|
||||||
|
roleIndexes[_id] = roleOrder.findIndex((role_) => role_ === role);
|
||||||
|
}
|
||||||
|
|
||||||
|
return members.sort((a, b) => {
|
||||||
|
let roleDifference = roleIndexes[a._id] - roleIndexes[b._id];
|
||||||
|
if (roleDifference !== 0) {
|
||||||
|
return roleDifference;
|
||||||
|
} else {
|
||||||
|
if (a.name < b.name) {
|
||||||
|
return -1;
|
||||||
|
} else if (a.name > b.name) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
|
import Centered from '../components/Centered';
|
||||||
|
import PrimaryHeader from '../components/PrimaryHeader';
|
||||||
|
|
||||||
export default function NotFoundPage() {
|
export default function NotFoundPage() {
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<Centered>
|
||||||
<h1>404: Not Found</h1>
|
<PrimaryHeader>404: Not Found</PrimaryHeader>
|
||||||
<p>This page wasn't found...</p>
|
<p>This page wasn't found...</p>
|
||||||
</div>
|
</Centered>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,18 +4,18 @@ import MemberRow from '../components/MemberRow';
|
||||||
import useQuery from '../hooks/useQuery';
|
import useQuery from '../hooks/useQuery';
|
||||||
|
|
||||||
export default function ClassCouncil() {
|
export default function ClassCouncil() {
|
||||||
let members = useQuery<SGA.MemberDocument[]>(
|
let members =
|
||||||
|
useQuery<SGA.MemberDocument[]>(
|
||||||
`*[_type == 'member' && role == 'class'] | order (year desc)`
|
`*[_type == 'member' && role == 'class'] | order (year desc)`
|
||||||
);
|
) ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Hero heading='Class Council' />
|
<Hero heading='Class Council' />
|
||||||
<main>
|
<main>
|
||||||
{members &&
|
{members.map((member) => (
|
||||||
members.map((member) => {
|
<MemberRow key={member._id} member={member}></MemberRow>
|
||||||
return <MemberRow key={member._id} member={member}></MemberRow>;
|
))}
|
||||||
})}
|
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,9 +4,10 @@ import MemberRow from '../components/MemberRow';
|
||||||
import useQuery from '../hooks/useQuery';
|
import useQuery from '../hooks/useQuery';
|
||||||
|
|
||||||
export default function Committee() {
|
export default function Committee() {
|
||||||
let excomm = useQuery<SGA.MemberDocument[]>(
|
let excomm =
|
||||||
|
useQuery<SGA.MemberDocument[]>(
|
||||||
`*[_type == 'member' && committee == 'excomm'] | order (role, year desc)`
|
`*[_type == 'member' && committee == 'excomm'] | order (role, year desc)`
|
||||||
);
|
) ?? [];
|
||||||
// year desc because seniority 8)
|
// year desc because seniority 8)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -16,12 +17,9 @@ export default function Committee() {
|
||||||
imageURL='/images/who-we-are/excomm.png'
|
imageURL='/images/who-we-are/excomm.png'
|
||||||
/>
|
/>
|
||||||
<main>
|
<main>
|
||||||
<div>
|
{excomm.map((member) => (
|
||||||
{excomm &&
|
<MemberRow key={member._id} member={member} />
|
||||||
excomm.map((member) => {
|
))}
|
||||||
return <MemberRow key={member._id} member={member} />;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,19 +4,17 @@ import InitiativeRow from '../components/InitiativeRow';
|
||||||
import useQuery from '../hooks/useQuery';
|
import useQuery from '../hooks/useQuery';
|
||||||
|
|
||||||
export default function Initiatives() {
|
export default function Initiatives() {
|
||||||
let initiatives = useQuery<SGA.InitiativeDocument[]>(
|
let initiatives =
|
||||||
'*[_type == "initiative"]'
|
useQuery<SGA.InitiativeDocument[]>('*[_type == "initiative"]') ?? [];
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Hero heading='Initiatives' />
|
<Hero heading='Initiatives' />
|
||||||
<main>
|
<main>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
{initiatives &&
|
{initiatives.map((initiative) => (
|
||||||
initiatives.map((initiative) => {
|
<InitiativeRow initiative={initiative} />
|
||||||
return <InitiativeRow initiative={initiative} />;
|
))}
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,25 +1,28 @@
|
||||||
import { SanityDocument } from '@sanity/client';
|
import { SanityDocument } from '@sanity/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import BlueButtonLink from '../components/BlueButtonLink';
|
||||||
import GetInvolvedRow from '../components/GetInvolvedRow';
|
import GetInvolvedRow from '../components/GetInvolvedRow';
|
||||||
import Hero from '../components/Hero';
|
import Hero from '../components/Hero';
|
||||||
|
import ParagraphHeader from '../components/ParagraphHeader';
|
||||||
import '../css/get-involved.css';
|
import '../css/get-involved.css';
|
||||||
import sanity from '../sanity';
|
import sanity from '../sanity';
|
||||||
|
|
||||||
export default function GetInvolved() {
|
export default function GetInvolved() {
|
||||||
let [ways, setWays] = React.useState<
|
let [getInvolved, setGetInvolved] = React.useState<
|
||||||
SanityDocument<SGA.GetInvolvedDocument> | undefined
|
SanityDocument<SGA.GetInvolvedDocument> | undefined
|
||||||
>();
|
>();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
sanity.getDocument<SGA.GetInvolvedDocument>('get_involved').then(setWays);
|
sanity
|
||||||
|
.getDocument<SGA.GetInvolvedDocument>('get_involved')
|
||||||
|
.then(setGetInvolved);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Hero heading='Get Involved' />
|
<Hero heading='Get Involved' />
|
||||||
<main className='text-center'>
|
<main className='text-center'>
|
||||||
<h2 className='my-2'>SGA Calendar</h2>
|
<ParagraphHeader>SGA Calendar</ParagraphHeader>
|
||||||
<iframe
|
<iframe
|
||||||
src='https://calendar.google.com/calendar/u/0/embed?src=mbftfg4hu7i8ueqrgcb5o7hc6k@group.calendar.google.com&ctz=America/New_York'
|
src='https://calendar.google.com/calendar/u/0/embed?src=mbftfg4hu7i8ueqrgcb5o7hc6k@group.calendar.google.com&ctz=America/New_York'
|
||||||
title='SGA Calendar'
|
title='SGA Calendar'
|
||||||
|
@ -32,19 +35,17 @@ export default function GetInvolved() {
|
||||||
idea or concern or get to know your representatives, reach out to us
|
idea or concern or get to know your representatives, reach out to us
|
||||||
at <b>sga@tjhsst.edu</b>!
|
at <b>sga@tjhsst.edu</b>!
|
||||||
</p>
|
</p>
|
||||||
{ways ? (
|
<ParagraphHeader>
|
||||||
<>
|
|
||||||
<h2 style={{ marginTop: '4rem', marginBottom: '1.5rem' }}>
|
|
||||||
Here are some ways to connect with SGA:
|
Here are some ways to connect with SGA:
|
||||||
</h2>
|
</ParagraphHeader>
|
||||||
{ways.ways.map((way) => (
|
|
||||||
|
{getInvolved
|
||||||
|
? getInvolved.ways.map((way) => (
|
||||||
<GetInvolvedRow way={way} key={way._id} />
|
<GetInvolvedRow way={way} key={way._id} />
|
||||||
))}
|
))
|
||||||
</>
|
: null}
|
||||||
) : null}
|
|
||||||
<Link className='blue-button' to='/feedback'>
|
<BlueButtonLink href='/feedback'>Give Feedback</BlueButtonLink>
|
||||||
Give Feedback
|
|
||||||
</Link>
|
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,6 +2,35 @@ import React from 'react';
|
||||||
import Hero from '../components/Hero';
|
import Hero from '../components/Hero';
|
||||||
import useMission from '../hooks/useMission';
|
import useMission from '../hooks/useMission';
|
||||||
import '../css/mission.css';
|
import '../css/mission.css';
|
||||||
|
import BlueButtonLink from '../components/BlueButtonLink';
|
||||||
|
import Centered from '../components/Centered';
|
||||||
|
|
||||||
|
function MissionQuote({ text, author }) {
|
||||||
|
return (
|
||||||
|
<div className='mission-quote'>
|
||||||
|
<span className='mission-quote-text'>“{text}”</span>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<span className='mission-quote-author'>— {author}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MissionParagraph({ title, body }: { title: string; body: string }) {
|
||||||
|
return (
|
||||||
|
<div className='d-flex'>
|
||||||
|
<div className='flex-1 p-2'>
|
||||||
|
<span className='mission-header'>{title}</span>
|
||||||
|
</div>
|
||||||
|
<div className='flex-2 p-2'>
|
||||||
|
<p className='mission-para'>{body}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousLeadershipLink =
|
||||||
|
'https://docs.google.com/spreadsheets/d/1a3RYdqrDi1IPG9BKWQ2xhoX3YCPQKUl_FsRLvIVEMPg/edit?usp=drive_open&ouid=0';
|
||||||
|
|
||||||
export default function Mission() {
|
export default function Mission() {
|
||||||
let mission = useMission();
|
let mission = useMission();
|
||||||
|
@ -11,38 +40,19 @@ export default function Mission() {
|
||||||
<Hero heading='Mission and History' />
|
<Hero heading='Mission and History' />
|
||||||
{mission ? (
|
{mission ? (
|
||||||
<main>
|
<main>
|
||||||
<div className='mission-quote'>
|
<MissionQuote
|
||||||
<span className='mission-quote-text'>“{mission.quote_text}”</span>
|
author={mission.quote_author}
|
||||||
<br />
|
text={mission.quote_text}
|
||||||
<br />
|
/>
|
||||||
<span className='mission-quote-author'>
|
|
||||||
— {mission.quote_author}
|
<MissionParagraph title='Vision' body={mission.vision} />
|
||||||
</span>
|
<MissionParagraph title='Mission' body={mission.mission} />
|
||||||
</div>
|
|
||||||
<div className='d-flex'>
|
<Centered>
|
||||||
<div className='flex-1 p-2'>
|
<BlueButtonLink href={previousLeadershipLink}>
|
||||||
<span className='mission-header'>Vision</span>
|
|
||||||
</div>
|
|
||||||
<div className='flex-2 p-2'>
|
|
||||||
<p className='mission-para'>{mission.vision}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='d-flex'>
|
|
||||||
<div className='flex-1 p-2'>
|
|
||||||
<span className='mission-header'>Mission</span>
|
|
||||||
</div>
|
|
||||||
<div className='flex-2 p-2'>
|
|
||||||
<p className='mission-para'>{mission.mission}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='text-center'>
|
|
||||||
<a
|
|
||||||
href='https://docs.google.com/spreadsheets/d/1a3RYdqrDi1IPG9BKWQ2xhoX3YCPQKUl_FsRLvIVEMPg/edit?usp=drive_open&ouid=0'
|
|
||||||
className='blue-button'
|
|
||||||
>
|
|
||||||
Previous Leadership
|
Previous Leadership
|
||||||
</a>
|
</BlueButtonLink>
|
||||||
</div>
|
</Centered>
|
||||||
</main>
|
</main>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,62 +1,43 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link, useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import Hero from '../components/Hero';
|
import Hero from '../components/Hero';
|
||||||
import imageUrl from '../imageUrl';
|
import imageUrl from '../lib/imageUrl';
|
||||||
import BlockContent from '@sanity/block-content-to-react';
|
|
||||||
import sanity from '../sanity';
|
|
||||||
import '../css/article.css';
|
import '../css/article.css';
|
||||||
|
import useNewsArticle from '../hooks/useNewsArticle';
|
||||||
|
import LocalLinkClickable from '../components/LocalLinkClickable';
|
||||||
|
import BlockContentWithExternalLinks from '../components/BlockContentWithExternalLinks';
|
||||||
|
import PrimaryHeader from '../components/PrimaryHeader';
|
||||||
|
|
||||||
export default function NewsArticle() {
|
export default function NewsArticle() {
|
||||||
let { articleId } = useParams<{ articleId: string }>();
|
let { articleId } = useParams<{ articleId: string }>();
|
||||||
let [article, setArticle] = React.useState<SGA.ArticleDocument>(null!);
|
let article = useNewsArticle(articleId);
|
||||||
|
|
||||||
React.useEffect(() => {
|
let thumbnailUrl = '/images/hero.png';
|
||||||
sanity.fetch('*[_id == $articleId] [0]', { articleId }).then(setArticle);
|
|
||||||
}, [articleId]);
|
|
||||||
|
|
||||||
let thumbUrl: string;
|
|
||||||
if (article?.thumbnail) {
|
if (article?.thumbnail) {
|
||||||
thumbUrl = imageUrl(article.thumbnail).url();
|
thumbnailUrl = imageUrl(article.thumbnail).url();
|
||||||
} else {
|
|
||||||
thumbUrl = '/images/hero.png';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const goToAllNewsArticles = (
|
||||||
|
<LocalLinkClickable to='/news'>Go to all news articles</LocalLinkClickable>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Hero heading='News' imageURL={thumbUrl || undefined} />
|
<Hero heading='News' imageURL={thumbnailUrl || undefined} />
|
||||||
<main>
|
<main>
|
||||||
{article ? (
|
{article ? (
|
||||||
<div style={{ maxWidth: '640px', margin: '2rem auto' }}>
|
<div style={{ maxWidth: '640px', margin: '2rem auto' }}>
|
||||||
<Link to='/news' className='clickable-link'>
|
{goToAllNewsArticles}
|
||||||
Go to all news articles
|
<PrimaryHeader>{article.title}</PrimaryHeader>
|
||||||
</Link>
|
|
||||||
<h1>{article.title}</h1>
|
|
||||||
<i>
|
<i>
|
||||||
Posted {article.publish_date} by {article.author || 'No author'}
|
Posted {article.publish_date} by {article.author || 'No author'}
|
||||||
</i>
|
</i>
|
||||||
<br />
|
<br />
|
||||||
{/* Wrap the BlockContent in a div because it expands to <></> */}
|
<div id='article-content' className='article-paragraphs'>
|
||||||
<div
|
<BlockContentWithExternalLinks blocks={article.content} />
|
||||||
id='article-content'
|
|
||||||
className='article-paragraphs'
|
|
||||||
ref={(ref) => {
|
|
||||||
/*
|
|
||||||
When this element loads, convert all the links to have target="_blank."
|
|
||||||
This ensures that the links open in a new tab
|
|
||||||
*/
|
|
||||||
if (ref) {
|
|
||||||
ref.querySelectorAll('a').forEach((link) => {
|
|
||||||
link.target = '_blank';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<BlockContent blocks={article.content} />
|
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<Link to='/news' className='clickable-link'>
|
{goToAllNewsArticles}
|
||||||
Go to all news articles
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -1,27 +1,37 @@
|
||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import Hero from '../components/Hero';
|
import Hero from '../components/Hero';
|
||||||
import MemberRow from '../components/MemberRow';
|
import MemberRow from '../components/MemberRow';
|
||||||
import useQuery from '../hooks/useQuery';
|
import sortCommittee from '../lib/sortCommittee';
|
||||||
|
import sanity from '../sanity';
|
||||||
|
|
||||||
|
const officerOrder = [
|
||||||
|
'SGA President',
|
||||||
|
'SGA Vice-President',
|
||||||
|
'SGA Treasurer',
|
||||||
|
'SGA Secretary',
|
||||||
|
];
|
||||||
|
|
||||||
export default function Officers() {
|
export default function Officers() {
|
||||||
let officers = useQuery<SGA.MemberDocument[]>(
|
const [officers, setOfficers] = useState<SGA.MemberDocument[]>();
|
||||||
`*[_type == 'member' && committee == 'officer']`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!officers) {
|
useEffect(() => {
|
||||||
return null;
|
sanity
|
||||||
}
|
.fetch("*[_type == 'member' && committee == $committee]", {
|
||||||
|
committee: 'officer',
|
||||||
|
})
|
||||||
|
.then(setOfficers);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const officersSorted = sortCommittee(officers ?? [], officerOrder);
|
||||||
|
const officerList = officersSorted.map((officer) => (
|
||||||
|
<MemberRow member={officer} />
|
||||||
|
));
|
||||||
|
console.log(officersSorted);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Hero heading='Officers' imageURL='/images/who-we-are/officers.jpg' />
|
<Hero heading='Officers' imageURL='/images/who-we-are/officers.jpg' />
|
||||||
<main>
|
<main>{officerList}</main>
|
||||||
{officers
|
|
||||||
? officers.map((officer) => {
|
|
||||||
return <MemberRow member={officer} />;
|
|
||||||
})
|
|
||||||
: null}
|
|
||||||
</main>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user