Skip to content

Commit 8b643f6

Browse files
ovflowdAndrulkocanerakdas
authored
feat: download snippet generation (#7351)
* feat: download snippet generation * feat: changed approach * chore: simplify and reduce bundle size * chore: reduce bundle * chore: minor code fixes * chore: more code review * feat: introduced skeleton and animation * chore: synchronous shiki * chore: prevent internal errors * chore: attempt to handle null situations * fix: attempt to fix vercel env * fix: erroneous env usage for fetch * chore: fix select styles * chore: updated allowlist for env vars * feat: streamline detectos (only run query once) * fix: avatar rendering and avatar group margins * fix: pass ref * chore: optimized release provider memo * chore: next-image mock * Update crowdin.yml (#7353) * chore: improved loading state * Apply suggestions from code review Co-authored-by: Caner Akdas <[email protected]> Signed-off-by: Claudio W <[email protected]> * chore: correct fixes * fix: unit tests * chore: final code review * fix: server component test * fix: rsc still risky for storybook --------- Signed-off-by: Claudio W <[email protected]> Co-authored-by: Andriy Poznakhovskyy <[email protected]> Co-authored-by: Caner Akdas <[email protected]>
1 parent e56edcc commit 8b643f6

File tree

85 files changed

+5170
-2394
lines changed

Some content is hidden

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

85 files changed

+5170
-2394
lines changed

.github/workflows/translations-pr-lint.yml

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ on:
1515
- '!apps/site/pages/en/**/*.mdx'
1616
- 'packages/i18n/locales/*.json'
1717
- '!packages/i18n/locales/en.json'
18+
- 'apps/site/snippets/**/*.bash'
19+
- '!apps/site/snippets/en/**/*.bash'
1820

1921
# Cancel any runs on the same branch
2022
concurrency:

apps/site/.storybook/main.ts

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const config: StorybookConfig = {
4545
resolve: {
4646
...config.resolve,
4747
alias: {
48+
'next/image': join(mocksFolder, './next-image.mjs'),
4849
'next-intl/navigation': join(mocksFolder, './next-intl.mjs'),
4950
'@/client-context': join(mocksFolder, './client-context.mjs'),
5051
'@': join(__dirname, '../'),

apps/site/app/[locale]/feed/[feed]/route.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ import provideWebsiteFeeds from '@/next-data/providers/websiteFeeds';
44
import { siteConfig } from '@/next.json.mjs';
55
import { defaultLocale } from '@/next.locales.mjs';
66

7-
// We only support fetching these pages from the /en/ locale code
8-
const locale = defaultLocale.code;
9-
10-
type StaticParams = { params: Promise<{ feed: string; locale: string }> };
7+
type DynamicStaticPaths = { locale: string; feed: string };
8+
type StaticParams = { params: Promise<DynamicStaticPaths> };
119

1210
// This is the Route Handler for the `GET` method which handles the request
1311
// for the Node.js Website Blog Feeds (RSS)
@@ -20,15 +18,18 @@ export const GET = async (_: Request, props: StaticParams) => {
2018

2119
return new NextResponse(websiteFeed, {
2220
headers: { 'Content-Type': 'application/xml' },
23-
status: websiteFeed ? 200 : 404,
21+
status: websiteFeed !== undefined ? 200 : 404,
2422
});
2523
};
2624

2725
// This function generates the static paths that come from the dynamic segments
2826
// `[locale]/feeds/[feed]` and returns an array of all available static paths
2927
// This is used for ISR static validation and generation
3028
export const generateStaticParams = async () =>
31-
siteConfig.rssFeeds.map(feed => ({ feed: feed.file, locale }));
29+
siteConfig.rssFeeds.map(feed => ({
30+
locale: defaultLocale.code,
31+
feed: feed.file,
32+
}));
3233

3334
// Enforces that only the paths from `generateStaticParams` are allowed, giving 404 on the contrary
3435
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams

apps/site/app/[locale]/next-data/blog-data/[category]/[page]/route.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import {
44
} from '@/next-data/providers/blogData';
55
import { defaultLocale } from '@/next.locales.mjs';
66

7-
type StaticParams = {
8-
params: Promise<{ locale: string; category: string; page: string }>;
9-
};
7+
type DynamicStaticPaths = { locale: string; category: string; page: string };
8+
type StaticParams = { params: Promise<DynamicStaticPaths> };
109

1110
// This is the Route Handler for the `GET` method which handles the request
1211
// for providing Blog Posts for Blog Categories and Pagination Metadata
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import provideDownloadSnippets from '@/next-data/providers/downloadSnippets';
2+
import { defaultLocale } from '@/next.locales.mjs';
3+
4+
type DynamicStaticPaths = { locale: string };
5+
type StaticParams = { params: Promise<DynamicStaticPaths> };
6+
7+
// This is the Route Handler for the `GET` method which handles the request
8+
// for generating JSON data for Download Snippets
9+
// @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers
10+
export const GET = async (_: Request, props: StaticParams) => {
11+
const params = await props.params;
12+
13+
// Retrieve all available Download snippets for a given locale if available
14+
const snippets = provideDownloadSnippets(params.locale);
15+
16+
// We append always the default/fallback snippets when a result is found
17+
return Response.json(snippets, {
18+
status: snippets !== undefined ? 200 : 404,
19+
});
20+
};
21+
22+
// This function generates the static paths that come from the dynamic segments
23+
// `[locale]/next-data/download-snippets/` this will return a default value as we don't want to
24+
// statically generate this route as it is compute-expensive.
25+
// Hence we generate a fake route just to satisfy Next.js requirements.
26+
export const generateStaticParams = async () => [
27+
{ locale: defaultLocale.code },
28+
];
29+
30+
// Enforces that this route is cached and static as much as possible
31+
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
32+
export const dynamic = 'force-static';
33+
34+
// Ensures that this endpoint is invalidated and re-executed every X minutes
35+
// so that when new deployments happen, the data is refreshed
36+
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate
37+
export const revalidate = 300;

apps/site/app/[locale]/next-data/og/[category]/[title]/route.tsx

+4-7
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,10 @@ const CATEGORY_TO_THEME_COLOUR_MAP = {
1313
vulnerability: tailwindConfig.theme.colors.warning['600'],
1414
};
1515

16-
type StaticParams = {
17-
params: Promise<{
18-
locale: string;
19-
category: keyof typeof CATEGORY_TO_THEME_COLOUR_MAP;
20-
title: string;
21-
}>;
22-
};
16+
type Category = keyof typeof CATEGORY_TO_THEME_COLOUR_MAP;
17+
18+
type DynamicStaticPaths = { locale: string; category: Category; title: string };
19+
type StaticParams = { params: Promise<DynamicStaticPaths> };
2320

2421
// This is the Route Handler for the `GET` method which handles the request
2522
// for generating OpenGraph images for Blog Posts and Pages

apps/site/components/Blog/BlogHeader/index.tsx

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use client';
2-
31
import { RssIcon } from '@heroicons/react/24/solid';
42
import { useTranslations } from 'next-intl';
53
import type { FC } from 'react';
@@ -9,15 +7,13 @@ import { siteConfig } from '@/next.json.mjs';
97

108
import styles from './index.module.css';
119

12-
type BlogHeaderProps = {
13-
category: string;
14-
};
10+
type BlogHeaderProps = { category: string };
1511

1612
const BlogHeader: FC<BlogHeaderProps> = ({ category }) => {
1713
const t = useTranslations();
18-
const currentFile =
19-
siteConfig.rssFeeds.find(item => item.category === category)?.file ??
20-
'blog.xml';
14+
15+
const feed = siteConfig.rssFeeds.find(item => item.category === category);
16+
const currentFile = feed ? feed.file : 'blog.xml';
2117

2218
return (
2319
<header className={styles.blogHeader}>

apps/site/components/Common/AvatarGroup/Avatar/index.module.css

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@
1010
justify-center
1111
rounded-full
1212
border-2
13-
border-white
13+
border-transparent
1414
bg-neutral-100
1515
object-cover
1616
text-xs
1717
text-neutral-800
18-
dark:border-neutral-950
1918
dark:bg-neutral-900
2019
dark:text-neutral-300;
2120
}

apps/site/components/Common/AvatarGroup/Avatar/index.stories.tsx

-8
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,4 @@ export const NoSquare: Story = {
2020
},
2121
};
2222

23-
export const FallBack: Story = {
24-
args: {
25-
image: 'https://avatars.githubusercontent.com/u/',
26-
nickname: 'John Doe',
27-
fallback: 'JD',
28-
},
29-
};
30-
3123
export default { component: Avatar } as Meta;

apps/site/components/Common/AvatarGroup/Avatar/index.tsx

+27-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import * as RadixAvatar from '@radix-ui/react-avatar';
21
import classNames from 'classnames';
3-
import type { ComponentPropsWithoutRef, ElementRef } from 'react';
2+
import Image from 'next/image';
3+
import type { HTMLAttributes } from 'react';
44
import { forwardRef } from 'react';
55

66
import Link from '@/components/Link';
@@ -16,37 +16,45 @@ export type AvatarProps = {
1616
url?: string;
1717
};
1818

19+
// @TODO: We temporarily removed the Avatar Radix UI primitive, since it was causing flashing
20+
// during initial load and not being able to render nicely when images are already cached.
21+
// @see https://github.com/radix-ui/primitives/pull/3008
1922
const Avatar = forwardRef<
20-
ElementRef<typeof RadixAvatar.Root>,
21-
ComponentPropsWithoutRef<typeof RadixAvatar.Root> & AvatarProps
23+
HTMLSpanElement,
24+
HTMLAttributes<HTMLSpanElement> & AvatarProps
2225
>(({ image, nickname, name, fallback, url, size = 'small', ...props }, ref) => {
2326
const Wrapper = url ? Link : 'div';
2427

2528
return (
26-
<RadixAvatar.Root
29+
<span
2730
{...props}
28-
className={classNames(styles.avatar, styles[size], props.className)}
2931
ref={ref}
32+
className={classNames(styles.avatar, styles[size], props.className)}
3033
>
3134
<Wrapper
3235
href={url || undefined}
3336
target={url ? '_blank' : undefined}
3437
className={styles.wrapper}
3538
>
36-
<RadixAvatar.Image
37-
loading="lazy"
38-
src={image}
39-
alt={name || nickname}
40-
className={styles.item}
41-
/>
42-
<RadixAvatar.Fallback
43-
delayMs={500}
44-
className={classNames(styles.item, styles[size])}
45-
>
46-
{fallback}
47-
</RadixAvatar.Fallback>
39+
{image && (
40+
<Image
41+
width={40}
42+
height={40}
43+
loading="lazy"
44+
decoding="async"
45+
src={image}
46+
alt={name || nickname}
47+
className={styles.item}
48+
/>
49+
)}
50+
51+
{!image && (
52+
<span className={classNames(styles.item, styles[size])}>
53+
{fallback}
54+
</span>
55+
)}
4856
</Wrapper>
49-
</RadixAvatar.Root>
57+
</span>
5058
);
5159
});
5260

apps/site/components/Common/AvatarGroup/Overlay/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const AvatarOverlay: FC<AvatarOverlayProps> = ({
2525
fallback={fallback}
2626
size="medium"
2727
/>
28+
2829
<div className={styles.user}>
2930
{name && <div className={styles.name}>{name}</div>}
3031
{nickname && <div className={styles.nickname}>{nickname}</div>}

apps/site/components/Common/AvatarGroup/index.module.css

+12-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@
22
@apply flex
33
flex-wrap
44
items-center;
5+
6+
&:not(.expandable) {
7+
> span {
8+
@apply ml-0;
9+
}
10+
}
511
}
612

713
.small {
8-
@apply xs:-space-x-2
9-
space-x-0.5;
14+
> span {
15+
@apply -ml-2;
16+
}
1017
}
1118

1219
.medium {
13-
@apply -space-x-2.5;
20+
> span {
21+
@apply -ml-2.5;
22+
}
1423
}

apps/site/components/Common/AvatarGroup/index.stories.tsx

+1-7
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,8 @@ const names = [
2323
'araujogui',
2424
];
2525

26-
const unknownAvatar = {
27-
image: 'https://avatars.githubusercontent.com/u/',
28-
nickname: 'unknown-avatar',
29-
fallback: 'UA',
30-
};
31-
3226
const defaultProps = {
33-
avatars: [unknownAvatar, ...getAuthorWithId(names, true)],
27+
avatars: getAuthorWithId(names, true),
3428
};
3529

3630
export const Default: Story = {

apps/site/components/Common/AvatarGroup/index.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ const AvatarGroup: FC<AvatarGroupProps> = ({
3535
);
3636

3737
return (
38-
<div className={classNames(styles.avatarGroup, styles[size])}>
38+
<div
39+
className={classNames(styles.avatarGroup, styles[size], {
40+
[styles.expandable]: avatars.length > limit,
41+
})}
42+
>
3943
{renderAvatars.map(({ ...avatar }) => (
4044
<Fragment key={avatar.nickname}>
4145
<Tooltip
@@ -54,6 +58,7 @@ const AvatarGroup: FC<AvatarGroupProps> = ({
5458
</Tooltip>
5559
</Fragment>
5660
))}
61+
5762
{avatars.length > limit && (
5863
<span
5964
onClick={isExpandable ? () => setShowMore(prev => !prev) : undefined}

apps/site/components/Common/ProgressionSidebar/index.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
'use client';
2+
13
import { useTranslations } from 'next-intl';
24
import type { ComponentProps, FC } from 'react';
35
import { useRef } from 'react';

apps/site/components/Common/Search/index.tsx

+2-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { OramaSearchBox, OramaSearchButton } from '@orama/react-components';
44
import { useTranslations, useLocale } from 'next-intl';
55
import { useTheme } from 'next-themes';
6-
import { type FC } from 'react';
6+
import type { FC } from 'react';
77

88
import { useRouter } from '@/navigation.mjs';
99
import {
@@ -132,9 +132,7 @@ const SearchButton: FC = () => {
132132
const fullURLObject = new URL(event.detail.result.path, BASE_URL);
133133

134134
// result.path already contains LOCALE. Locale is set to undefined here so router does not add it once again.
135-
router.push(fullURLObject.href, {
136-
locale: undefined,
137-
});
135+
router.push(fullURLObject.href, { locale: undefined });
138136
}}
139137
/>
140138
</>

0 commit comments

Comments
 (0)