Skip to content

Commit dd83f0b

Browse files
committed
feat: finish home page and rewrite site in nextjs 13
1 parent 7534f93 commit dd83f0b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2875
-10632
lines changed

app/(home)/DownloadButtonClient.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use client';
2+
import Button from "../common/uiLibrary/button";
3+
import {useEffect, useState} from "react";
4+
import getDownloadLink from "../../utils/platform";
5+
import {GITHUB_RELEASES_URL} from "../../utils/urlContants";
6+
import {BiSolidDownload} from "react-icons/bi";
7+
8+
9+
10+
const DownloadButtonClient = () => {
11+
const [downloadLink, setDownloadLink] = useState(GITHUB_RELEASES_URL);
12+
13+
useEffect(() => {
14+
getDownloadLink().then((link) => setDownloadLink(link));
15+
}, []);
16+
17+
return (
18+
<div className={'flex flex-wrap justify-center mt-8 gap-4'}>
19+
<Button filled={true} text={'Download'} linkTo={downloadLink} iconRight={BiSolidDownload} />
20+
</div>
21+
)
22+
}
23+
24+
export default DownloadButtonClient;

app/(home)/LandingButtonsServer.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Button from "../common/uiLibrary/button";
2+
import {DISCORD_INVITE_URL, GITHUB_URL, PATREON_URL} from "../../utils/urlContants";
3+
4+
5+
6+
const LandingButtonsServer = () => {
7+
return (
8+
<div className={'flex flex-wrap justify-center mt-8 gap-4'}>
9+
<Button filled={false} text={'Github'} linkTo={GITHUB_URL}/>
10+
<Button filled={false} text={'Discord'} linkTo={DISCORD_INVITE_URL}/>
11+
<Button filled={false} text={'Patreon'} linkTo={PATREON_URL}/>
12+
</div>
13+
)
14+
}
15+
16+
export default LandingButtonsServer;

app/(home)/contactInformation.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {DISCORD_INVITE_URL} from "../../utils/urlContants";
2+
import {BsDiscord} from "react-icons/bs";
3+
import {MdEmail} from "react-icons/md";
4+
import React from "react";
5+
6+
const ContactInformation = () => {
7+
return (
8+
<div
9+
style={{boxShadow: 'inset 0 10px 10px -10px rgba(0, 0, 0, 0.5)'}}
10+
className="w-full p-4 text-center sm:p-8 bg-gray-800 border-gray-700">
11+
<h5 className="mb-2 text-3xl font-bold text-white">Let&apos;s Chat!</h5>
12+
<p className="mb-5 text-base sm:text-lg text-gray-400">
13+
We&apos;d love to hear from you! The quickest way to reach us is by joining our Discord server and dropping
14+
us a message. But, if you&apos;d rather, feel free to shoot us an email.
15+
</p>
16+
<div className="items-center justify-center space-y-4 sm:flex sm:space-y-0 sm:space-x-4">
17+
<a href={DISCORD_INVITE_URL}
18+
className="w-full sm:w-auto focus:ring-4 focus:outline-none text-white rounded-lg inline-flex items-center justify-center px-4 py-2.5 bg-gray-700 hover:bg-gray-600 focus:ring-gray-700">
19+
<BsDiscord className="mr-3 w-7 h-7"/>
20+
<div className="text-left">
21+
<div className="mb-1 text-xs">Join our</div>
22+
<div className="-mt-1 font-sans text-sm font-semibold">Discord Server</div>
23+
</div>
24+
</a>
25+
<a href="mailto:[email protected]"
26+
className="w-full sm:w-auto focus:ring-4 focus:outline-none text-white rounded-lg inline-flex items-center justify-center px-4 py-2.5 bg-gray-700 hover:bg-gray-600 focus:ring-gray-700">
27+
<MdEmail className="mr-3 w-7 h-7"/>
28+
<div className="text-left">
29+
<div className="mb-1 text-xs">Email us at</div>
30+
<div className="-mt-1 font-sans text-sm font-semibold">[email protected]</div>
31+
</div>
32+
</a>
33+
</div>
34+
</div>
35+
);
36+
}
37+
38+
export default ContactInformation;

app/(home)/featuresList.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import {IconType} from "react-icons";
3+
4+
export type FeatureData = {
5+
title: string;
6+
description: string;
7+
icon: IconType,
8+
imageUrl: string;
9+
}
10+
11+
interface FeaturesListProps{
12+
features: FeatureData[];
13+
}
14+
15+
type FeatureCardProps = {
16+
feature: FeatureData;
17+
}
18+
19+
const FeatureCard = ({ feature }: FeatureCardProps) => {
20+
const { title, description, icon: Icon } = feature;
21+
22+
return (
23+
<div className="flex flex-col space-y-2 p-4 bg-gray-900 rounded-xl shadow-md max-w-sm my-2 h-full">
24+
<div className="flex space-x-2 items-start mb-4">
25+
<div className="mt-1">
26+
<Icon size={24} color="#10b981" />
27+
</div>
28+
<div>
29+
<h2 className="font-semibold text-lg text-gray-200">{title}</h2>
30+
<p className="text-sm text-gray-300 mt-2">{description}</p>
31+
</div>
32+
</div>
33+
</div>
34+
);
35+
}
36+
37+
38+
const FeaturesList = ({ features }: FeaturesListProps) => {
39+
return (
40+
<div className="grid xl:grid-cols-4 lg:grid-cols-2 sm:grid-cols-1 gap-2 items-stretch justify-center">
41+
{features.map((feature, index) => (
42+
<FeatureCard key={index} feature={feature} />
43+
))}
44+
</div>
45+
);
46+
}
47+
48+
export default FeaturesList;
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import RandomThirdTextClient from "./randomThirdTextClient";
2+
13
interface LandingTextProps {
24
mainText: string
35
secondaryText: string
4-
lastText: string
56
}
67

78
const LandingText = (props: LandingTextProps) => {
8-
const {mainText, secondaryText, lastText} = props;
9+
const {mainText, secondaryText} = props;
910

1011
return (
1112
<div className={'max-w-3xl mx-auto text-center'}>
@@ -15,10 +16,8 @@ const LandingText = (props: LandingTextProps) => {
1516
<p className="mt-4 sm:leading-relaxed sm:text-xl">
1617
{secondaryText}
1718
</p>
18-
<p className={'mx-auto mt-4 font-extralight'}>
19-
{lastText}
20-
</p>
19+
<RandomThirdTextClient />
2120
</div>)
2221
}
2322

24-
export default LandingText;
23+
export default LandingText;

app/(home)/latestNews.tsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import {BlogPost} from "../../types/blogPost";
2+
import classNames from "classnames";
3+
import Container from "../common/uiLibrary/container";
4+
import {toAgoTime} from "../../utils/timeUtils";
5+
import PageSectionTitle from "../common/uiLibrary/pageSectionTitle";
6+
7+
interface NewsCardProps {
8+
post: BlogPost,
9+
isMain?: boolean,
10+
className?: string
11+
}
12+
13+
interface LatestNewsProps {
14+
posts: BlogPost[]
15+
}
16+
17+
const NewsCard = (props: NewsCardProps) => {
18+
const {title, summary, socials_image, date_created, slug} = props.post;
19+
const {className: classes} = props;
20+
const isMain = props.isMain ?? false;
21+
22+
const style = {
23+
backgroundImage: `url(${socials_image})`,
24+
backgroundRepeat: 'no-repeat',
25+
backgroundSize: 'cover',
26+
backgroundBlendMode: 'multiply',
27+
backgroundPosition: 'center',
28+
}
29+
30+
const truncateSummary = (summary: string, isMain: boolean) => {
31+
if (isMain) {
32+
return summary;
33+
}
34+
35+
return summary.slice(0, 175) + '...';
36+
}
37+
38+
return (
39+
<a href={`/blog/${slug}`} className={classNames('relative overflow-hidden', classes)}>
40+
<div className="absolute inset-0 rounded-lg shadow-lg bg-gray-400" style={style}></div>
41+
<div className="relative p-3 h-full flex flex-col justify-between">
42+
<div>
43+
<div
44+
className={
45+
classNames('text-2xl text-white leading-tight border-b hover:border-dashed hover:border-gray-500',
46+
{'lg:text-5xl': isMain})}>{title}</div>
47+
<div className="text-normal text-gray-300">
48+
<span className=" pb-1">{toAgoTime(date_created)}</span>
49+
</div>
50+
</div>
51+
<p className="">{truncateSummary(summary, isMain)}</p>
52+
</div>
53+
</a>
54+
)
55+
}
56+
57+
const LatestNews = (props: LatestNewsProps) => {
58+
const {posts} = props;
59+
60+
return (
61+
<>
62+
<PageSectionTitle>Latest News</PageSectionTitle>
63+
<Container>
64+
<div className="flex flex-col sm:flex-col md:flex-col lg:flex-row gap-5">
65+
<div className="w-full lg:w-1/2 flex flex-col">
66+
<NewsCard post={posts[0]} isMain className="h-full" />
67+
</div>
68+
<div className="w-full lg:w-1/2 flex flex-col gap-4">
69+
{posts.slice(1).map((post, index) => (
70+
<NewsCard key={index} post={post} className="h-auto flex-grow" />
71+
))}
72+
</div>
73+
</div>
74+
</Container>
75+
</>
76+
);
77+
}
78+
79+
export default LatestNews;

app/(home)/page.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import LandingText from "./landingText";
2+
import {BlogPost} from "../../types/blogPost";
3+
import {BlogPostsResponse} from "../../types/blogPostsResponse";
4+
import FetchOfType from "../../utils/fetchOfType";
5+
import PageSection from "../common/uiLibrary/pageSection";
6+
import LandingButtonsServer from "./LandingButtonsServer";
7+
import DownloadButtonClient from "./DownloadButtonClient";
8+
import LatestNews from "./latestNews";
9+
import ContactInformation from "./contactInformation";
10+
import FeaturesList, {FeatureData} from "./featuresList";
11+
import React from "react";
12+
import {RiGamepadLine, RiRefreshLine, RiRocket2Line, RiTeamLine} from "react-icons/ri";
13+
14+
const mainText = "Welcome to Unitystation!";
15+
const secondaryText = "Free and open-source remake of the cult classic Space Station 13, made in Unity Engine.";
16+
17+
const features: FeatureData[] = [
18+
{
19+
title: "Player-driven gameplay",
20+
description: "Every round plays out differently. Almost anything on the station can be picked up, examined or vandalized",
21+
icon: RiGamepadLine,
22+
imageUrl: "https://unitystationfile.b-cdn.net/CommunityStuff/1/chicken.png"
23+
},
24+
{
25+
title: "Slapstick Simulation",
26+
description: "Fly a shuttle into the station, breaching the hull and venting everyone into space.",
27+
icon: RiRocket2Line,
28+
imageUrl: "https://unitystationfile.b-cdn.net/CommunityStuff/1/production.gif"
29+
},
30+
{
31+
title: "Dozens of jobs",
32+
description: "Want to play a cook? A janitor? Law enforcement? Or maybe the captain? Even lawyers have a place on the outpost.",
33+
icon: RiTeamLine,
34+
imageUrl: "https://unitystationfile.b-cdn.net/WeeklyBlogUpdates/8/60.png"
35+
},
36+
{
37+
title: "Finally Remade",
38+
description: "No more dealing with BYOND to play your favorite spessman game.",
39+
icon: RiRefreshLine,
40+
imageUrl: "https://unitystationfile.b-cdn.net/WeeklyBlogUpdates/1/25.png"
41+
}
42+
]
43+
44+
const fetchLatestBlogPost = async (): Promise<BlogPost[]> => {
45+
const revalidateConfig = {next: {revalidate: 60}};
46+
const resPage1 = await FetchOfType<BlogPostsResponse>('https://changelog.unitystation.org/posts/', revalidateConfig);
47+
const resPage2 = await FetchOfType<BlogPostsResponse>('https://changelog.unitystation.org/posts/?page=2', revalidateConfig);
48+
return resPage1.results.concat(resPage2.results);
49+
}
50+
51+
52+
const HomePage: () => Promise<JSX.Element> = async () => {
53+
54+
const latestBlogPosts: BlogPost[] = await fetchLatestBlogPost();
55+
56+
return (
57+
<>
58+
<PageSection className="gap-16">
59+
<div>
60+
<LandingText mainText={mainText} secondaryText={secondaryText}/>
61+
<DownloadButtonClient/>
62+
<LandingButtonsServer/>
63+
</div>
64+
<FeaturesList features={features}/>
65+
</PageSection>
66+
<PageSection verticalCenter={false}>
67+
<LatestNews posts={latestBlogPosts}/>
68+
</PageSection>
69+
<ContactInformation/>
70+
</>
71+
)
72+
}
73+
74+
export default HomePage;

app/(home)/randomThirdTextClient.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use client';
2+
3+
4+
import {useEffect, useState} from "react";
5+
6+
const texts = [
7+
'Join our Discord server to get involved!',
8+
'Yeah, we have some bugs but at least they are funny!',
9+
'We are not dead yet!',
10+
'Surviving the curse one PR at a time!',
11+
'I\'m not a web developer, please send help!',
12+
'Thank you patreons for keeping the lights on!',
13+
'Check our Github if you want to contribute!',
14+
'Devlog might release any time now!',
15+
'[object Object]',
16+
'Join us every Saturday on our community playtest! (More info on Discord)',
17+
]
18+
19+
const RandomThirdTextClient = () => {
20+
21+
const [randomText, setRandomText] = useState(texts[0]);
22+
23+
useEffect(() => {
24+
const interval = setInterval(() => {
25+
setRandomText(texts[Math.floor(Math.random() * texts.length)]);
26+
}, 5000);
27+
return () => clearInterval(interval);
28+
})
29+
30+
return (
31+
<p className={'mx-auto mt-4 font-extralight'}>
32+
{randomText}
33+
</p>
34+
)
35+
}
36+
37+
export default RandomThirdTextClient;

0 commit comments

Comments
 (0)