Skip to content

Commit fe76cc2

Browse files
authored
Merge pull request #4 from code-star/feature/add_toots
Feature/add toots
2 parents 87c08e0 + 82ad6b3 commit fe76cc2

File tree

9 files changed

+651
-47
lines changed

9 files changed

+651
-47
lines changed

components/EmbeddedSocialMedia/EmbeddedSocialMedia.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
1-
import Link from "next/link";
21
import { FC } from "react";
2+
import { IToots } from "../../lib/mastodon/getToots";
33
import { ITweets } from "../../lib/twitter/getTweets";
44
import { IPlaylistItem } from "../../lib/youtube-playlist/youtube-playlist.types";
5-
import PlaylistItemCard from "../PlaylistItemCard/PlaylistItemCard";
6-
import styles from "./EmbeddedSocialMedia.module.scss";
7-
import TweetCard from "../TweetCard/TweetCard";
85
import Playlist from "../Playlist/Playlist";
6+
import TootCard from "../TootCard/TootCard";
7+
import styles from "./EmbeddedSocialMedia.module.scss";
98

109
interface EmbeddedSocialMediaProps {
10+
toots: IToots | null;
1111
tweets: ITweets | null;
1212
playlist: IPlaylistItem[];
1313
}
1414

1515
const EmbeddedSocialMedia: FC<EmbeddedSocialMediaProps> = ({
16-
tweets,
16+
toots,
1717
playlist,
1818
}) => {
1919
return (
2020
<div className={styles["embedded-social-media"]}>
21-
<TweetCard tweets={tweets} />
21+
<TootCard toots={toots} />
22+
{/* <TweetCard tweets={tweets} /> */}
2223
<Playlist playlist={playlist} viewport="sm" />
2324
</div>
2425
);

components/Footer/Footer.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { FC } from "react";
22
import styles from "./Footer.module.scss";
33
import Image from "next/image";
4+
import mastodonSvg from "../../public/mastodon_logo.svg";
45
import twitterSvg from "../../public/twitter_logo.svg";
56
import githubSvg from "../../public/github_logo.svg";
67
import mediumSvg from "../../public/medium_logo.svg";
@@ -31,6 +32,14 @@ const Footer: FC = () => {
3132
</ul>
3233

3334
<p className="social-links">
35+
<a href="https://mastodon.social/@codestar" aria-label="Mastodon">
36+
<Image
37+
src={mastodonSvg}
38+
alt="Codestar Mastodon"
39+
width={32}
40+
height={32}
41+
/>
42+
</a>
3443
<a href="https://twitter.com/codestar_nl" aria-label="Twitter">
3544
<Image
3645
src={twitterSvg}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
@import "../constants";
2+
3+
.toot-card {
4+
display: none;
5+
border: 1px solid $highlight-card-bg;
6+
border-radius: $card-border-radius;
7+
padding: 1rem;
8+
9+
> a {
10+
display: block;
11+
text-align: right;
12+
font-weight: bold;
13+
}
14+
15+
p:first-child {
16+
align-items: center;
17+
color: $underline-text;
18+
display: flex;
19+
gap: 10px;
20+
}
21+
22+
div > a {
23+
display: block;
24+
margin-bottom: 0.5rem;
25+
}
26+
27+
@media (min-width: $main-width) {
28+
display: block;
29+
width: $aside-width;
30+
}
31+
}
32+
33+
.toot-card--badges {
34+
display: flex;
35+
gap: 0.7rem;
36+
cursor: default;
37+
}

components/TootCard/TootCard.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import Image from "next/image";
2+
import { FC } from "react";
3+
import { formatDate } from "../../lib/formatDate";
4+
import { IToots } from "../../lib/mastodon/getToots";
5+
import mastodonSvg from "../../public/mastodon_logo.svg";
6+
import styles from "./TootCard.module.scss";
7+
8+
interface ITootCardProps {
9+
toots: IToots | null;
10+
}
11+
12+
const TootCard: FC<ITootCardProps> = ({ toots }) => {
13+
if (!toots) {
14+
return <></>;
15+
}
16+
const { author, data } = toots;
17+
return (
18+
<section className={styles["toot-card"]}>
19+
<a href={author.url}>@{author.username}@mastodon.social</a>
20+
{data.map(
21+
({
22+
id,
23+
text,
24+
created_at,
25+
url,
26+
reblogs_count,
27+
replies_count,
28+
favourites_count,
29+
}) => (
30+
<div key={id}>
31+
<p>
32+
<Image
33+
src={mastodonSvg}
34+
alt="Codestar Mastodon"
35+
width={24}
36+
height={24}
37+
/>
38+
<span>{formatDate(created_at)}</span>
39+
</p>
40+
<a href={url}>{text}</a>
41+
<div className={styles["toot-card--badges"]}>
42+
<span title="replies">↩️ {replies_count}</span>{" "}
43+
<span title="reblogs">🔁 {reblogs_count}</span>{" "}
44+
<span title="favourites">{favourites_count}</span>
45+
</div>
46+
</div>
47+
)
48+
)}
49+
</section>
50+
);
51+
};
52+
53+
export default TootCard;

lib/mastodon/getToots.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { convert } from "html-to-text";
2+
3+
interface IMastodonAccountResponse {
4+
id: string;
5+
username: string;
6+
acct: string;
7+
display_name: string;
8+
created_at: string; // 'yyyy-mm-ddT00:00:00.000Z',
9+
note: string; // html
10+
url: string;
11+
avatar: string;
12+
avatar_static: string;
13+
header: string;
14+
header_static: string;
15+
followers_count: number;
16+
following_count: number;
17+
statuses_count: number;
18+
last_status_at: string; // yyyy-mm-dd
19+
}
20+
21+
interface ITootResponse {
22+
id: string;
23+
created_at: string; // 'yyyy-mm-ddT00:00:00.000Z',
24+
url: string;
25+
replies_count: number;
26+
reblogs_count: number;
27+
favourites_count: number;
28+
content: string; // html
29+
account: IMastodonAccountResponse;
30+
}
31+
32+
type AccountsStatusesResponse = ITootResponse[];
33+
34+
interface IToot {
35+
id: string;
36+
text: string;
37+
created_at: string;
38+
url: string;
39+
reblogs_count: number;
40+
replies_count: number;
41+
favourites_count: number;
42+
}
43+
44+
export interface IToots {
45+
data: IToot[];
46+
author: { username: string; url: string };
47+
}
48+
49+
const MASTODON_API_ACCOUNTS_URL = "https://mastodon.social/api/v1/accounts/";
50+
51+
const getTootsById = async (
52+
MASTODON_ACCESS_TOKEN: string,
53+
MASTODON_ID: string
54+
): Promise<AccountsStatusesResponse | undefined> => {
55+
try {
56+
const response = await fetch(
57+
`${MASTODON_API_ACCOUNTS_URL}${MASTODON_ID}/statuses`,
58+
{
59+
headers: {
60+
Authorization: `Bearer ${MASTODON_ACCESS_TOKEN}`,
61+
},
62+
}
63+
);
64+
if (response.ok) {
65+
const toots: AccountsStatusesResponse = await response.json();
66+
return toots;
67+
} else {
68+
console.log("getTootsById not ok");
69+
return;
70+
}
71+
} catch (err) {
72+
return;
73+
}
74+
};
75+
76+
const mapTootResponseToToot = ({
77+
id,
78+
created_at,
79+
content,
80+
url,
81+
reblogs_count,
82+
replies_count,
83+
favourites_count,
84+
}: ITootResponse): IToot => ({
85+
id,
86+
created_at,
87+
url,
88+
text: convert(content, {
89+
wordwrap: 130,
90+
ignoreHref: true,
91+
}),
92+
replies_count,
93+
reblogs_count,
94+
favourites_count,
95+
});
96+
97+
export const getToots = async (): Promise<IToots | null> => {
98+
const { MASTODON_ACCESS_TOKEN, MASTODON_ID } = process.env;
99+
const count = 2;
100+
101+
if (!MASTODON_ACCESS_TOKEN || !MASTODON_ID) {
102+
console.log("getToots envars not set");
103+
return null;
104+
}
105+
106+
try {
107+
const toots = await getTootsById(MASTODON_ACCESS_TOKEN, MASTODON_ID);
108+
109+
if (!toots || toots.length <= 0) {
110+
throw Error(`no toots found for ${MASTODON_ID}`);
111+
}
112+
return {
113+
data: toots.slice(0, count).map(mapTootResponseToToot),
114+
author: {
115+
username: toots[0].account.username,
116+
url: toots[0].account.url,
117+
},
118+
};
119+
} catch (err) {
120+
console.log("error: " + err);
121+
return null;
122+
}
123+
};

0 commit comments

Comments
 (0)