From 7790651c630eaa07aef661fa7c4043f4d30c80c0 Mon Sep 17 00:00:00 2001 From: Wes Copeland Date: Sun, 19 Jan 2025 08:22:48 -0500 Subject: [PATCH] feat(news): add a basic 'category' field (#3073) --- app/Community/Enums/NewsCategory.php | 69 +++++++++++++++++++ app/Data/NewsData.php | 3 + app/Filament/Resources/NewsResource.php | 30 +++++++- app/Models/News.php | 3 + .../2025_01_18_000000_update_news_table.php | 23 +++++++ lang/en_US.json | 6 ++ .../FrontPageNews/FrontPageNews.test.tsx | 15 ++++ .../+root/FrontPageNews/NewsCard.tsx | 17 +++-- .../NewsCategoryLabel.test.tsx | 21 ++++++ .../NewsCategoryLabel/NewsCategoryLabel.tsx | 17 +++++ .../FrontPageNews/NewsCategoryLabel/index.ts | 1 + resources/js/test/factories/createNews.ts | 1 + resources/js/types/generated.d.ts | 9 +++ 13 files changed, 205 insertions(+), 10 deletions(-) create mode 100644 app/Community/Enums/NewsCategory.php create mode 100644 database/migrations/2025_01_18_000000_update_news_table.php create mode 100644 resources/js/features/home/components/+root/FrontPageNews/NewsCategoryLabel/NewsCategoryLabel.test.tsx create mode 100644 resources/js/features/home/components/+root/FrontPageNews/NewsCategoryLabel/NewsCategoryLabel.tsx create mode 100644 resources/js/features/home/components/+root/FrontPageNews/NewsCategoryLabel/index.ts diff --git a/app/Community/Enums/NewsCategory.php b/app/Community/Enums/NewsCategory.php new file mode 100644 index 0000000000..504ccd28c4 --- /dev/null +++ b/app/Community/Enums/NewsCategory.php @@ -0,0 +1,69 @@ +user), link: $news->link, imageAssetPath: $news->image_asset_path, + category: $news->category, publishAt: $news->publish_at, unpublishAt: $news->unpublish_at, pinnedAt: $news->pinned_at, diff --git a/app/Filament/Resources/NewsResource.php b/app/Filament/Resources/NewsResource.php index db13be518d..3a87f1f64a 100644 --- a/app/Filament/Resources/NewsResource.php +++ b/app/Filament/Resources/NewsResource.php @@ -4,6 +4,7 @@ namespace App\Filament\Resources; +use App\Community\Enums\NewsCategory; use App\Filament\Extensions\Resources\Resource; use App\Filament\Resources\NewsResource\Pages; use App\Models\News; @@ -51,10 +52,37 @@ public static function form(Form $form): Form ->required() ->activeUrl(), + Forms\Components\Select::make('category') + ->label('Category') + ->options([ + NewsCategory::AchievementSet->value => 'Featured Set', + NewsCategory::Community->value => 'Community', + NewsCategory::Events->value => 'Events', + NewsCategory::Guide->value => 'Guide', + NewsCategory::Media->value => 'Media', + NewsCategory::Technical->value => 'Technical', + ]) + ->helperText(function ($state) { + // Show an example based on selected category. + $example = match ($state) { + NewsCategory::AchievementSet->value => 'Example: "New set: Pokémon XD: Gale of Darkness"', + NewsCategory::Community->value => 'Example: "Come Celebrate 1 MILLION Users!"', + NewsCategory::Events->value => 'Example: "RetroAchievemas 2024 Event"', + NewsCategory::Guide->value => 'Example: "Achievement Guide: Final Fantasy VII"', + NewsCategory::Media->value => 'Example: "authorblues and Skybilz at AGDQ 2025"', + NewsCategory::Technical->value => 'Example: "Upcoming Hardcore Restriction"', + default => 'Optional.', + }; + + return $example; + }) + ->live() + ->placeholder('No category') + ->nullable(), + Forms\Components\Toggle::make('pinned_at') ->label('Pinned') ->helperText('If enabled, this will be sorted to the top of the news until unpinned.') - ->columnSpanFull() ->disabled(fn (?News $record) => !$record || !$user->can('pin', $record)) ->dehydrated() ->afterStateHydrated(function (?News $record, $component) { diff --git a/app/Models/News.php b/app/Models/News.php index c486c311d4..c9acbb0992 100644 --- a/app/Models/News.php +++ b/app/Models/News.php @@ -6,6 +6,7 @@ use App\Community\Concerns\HasAuthor; use App\Community\Contracts\HasComments; +use App\Community\Enums\NewsCategory; use App\Support\Database\Eloquent\BaseModel; use Carbon\Carbon; use Database\Factories\NewsFactory; @@ -40,12 +41,14 @@ class News extends BaseModel implements HasComments, HasMedia 'user_id', 'link', 'image_asset_path', + 'category', 'publish_at', 'unpublish_at', 'pinned_at', ]; protected $casts = [ + 'category' => NewsCategory::class, 'publish_at' => 'datetime', 'unpublish_at' => 'datetime', 'pinned_at' => 'datetime', diff --git a/database/migrations/2025_01_18_000000_update_news_table.php b/database/migrations/2025_01_18_000000_update_news_table.php new file mode 100644 index 0000000000..6ff44d5284 --- /dev/null +++ b/database/migrations/2025_01_18_000000_update_news_table.php @@ -0,0 +1,23 @@ +string('category', 50)->nullable()->after('image_asset_path'); + }); + } + + public function down(): void + { + Schema::table('news', function (Blueprint $table) { + $table->dropColumn('category'); + }); + } +}; diff --git a/lang/en_US.json b/lang/en_US.json index 7fcce6300c..5f35a8f9cd 100644 --- a/lang/en_US.json +++ b/lang/en_US.json @@ -632,6 +632,12 @@ "Beaten by same players": "Beaten by same players", "Mastered by same players": "Mastered by same players", "<1>{{achievementTitle}} from <2>{{gameTitle}}": "<1>{{achievementTitle}} from <2>{{gameTitle}}", + "news-category.achievement-set": "Featured Set", + "news-category.community": "Community", + "news-category.events": "Events", + "news-category.guide": "Guide", + "news-category.media": "Media", + "news-category.technical": "Technical", "Don't ask for links to copyrighted ROMs. Don't share links to copyrighted ROMs.": "Don't ask for links to copyrighted ROMs. Don't share links to copyrighted ROMs.", "Start new topic": "Start new topic", "enter your new topic's title...": "enter your new topic's title..." diff --git a/resources/js/features/home/components/+root/FrontPageNews/FrontPageNews.test.tsx b/resources/js/features/home/components/+root/FrontPageNews/FrontPageNews.test.tsx index 9ed31877a1..1f91817f4b 100644 --- a/resources/js/features/home/components/+root/FrontPageNews/FrontPageNews.test.tsx +++ b/resources/js/features/home/components/+root/FrontPageNews/FrontPageNews.test.tsx @@ -275,4 +275,19 @@ describe('Component: FrontPageNews', () => { // ASSERT expect(screen.queryByText('new')).not.toBeInTheDocument(); }); + + it('given a news post has a category, displays the category', () => { + // ARRANGE + const recentNews = [createNews({ title: 'Foo', category: 'achievement-set' })]; + + render(, { + pageProps: { + recentNews, + ziggy: createZiggyProps({ device: 'desktop' }), + }, + }); + + // ASSERT + expect(screen.getByText(/featured set/i)).toBeVisible(); + }); }); diff --git a/resources/js/features/home/components/+root/FrontPageNews/NewsCard.tsx b/resources/js/features/home/components/+root/FrontPageNews/NewsCard.tsx index dfcdfe32c4..c9aa7939b2 100644 --- a/resources/js/features/home/components/+root/FrontPageNews/NewsCard.tsx +++ b/resources/js/features/home/components/+root/FrontPageNews/NewsCard.tsx @@ -15,11 +15,12 @@ import { usePageProps } from '@/common/hooks/usePageProps'; import { cn } from '@/common/utils/cn'; import { formatDate } from '@/common/utils/l10n/formatDate'; +import { NewsCategoryLabel } from './NewsCategoryLabel'; + interface NewsCardProps { news: App.Data.News; className?: string; - tagLabel?: string; } export const NewsCard: FC = ({ news, className }) => { @@ -54,14 +55,6 @@ export const NewsCard: FC = ({ news, className }) => { ) : null} - - {/* {tagLabel ? ( -
-
- {tagLabel} -
-
- ) : null} */}
@@ -96,6 +89,12 @@ export const NewsCard: FC = ({ news, className }) => { {'·'} {t('by {{authorDisplayName}}', { authorDisplayName: news?.user.displayName })} + + {news.category ? ( + <> + {' · '} + + ) : null}

diff --git a/resources/js/features/home/components/+root/FrontPageNews/NewsCategoryLabel/NewsCategoryLabel.test.tsx b/resources/js/features/home/components/+root/FrontPageNews/NewsCategoryLabel/NewsCategoryLabel.test.tsx new file mode 100644 index 0000000000..2f7f554405 --- /dev/null +++ b/resources/js/features/home/components/+root/FrontPageNews/NewsCategoryLabel/NewsCategoryLabel.test.tsx @@ -0,0 +1,21 @@ +import { render, screen } from '@/test'; + +import { NewsCategoryLabel } from './NewsCategoryLabel'; + +describe('Component: NewsCategoryLabel', () => { + it('renders without crashing', () => { + // ARRANGE + const { container } = render(); + + // ASSERT + expect(container).toBeTruthy(); + }); + + it('displays the label', () => { + // ARRANGE + render(); + + // ASSERT + expect(screen.getByText(/events/i)).toBeVisible(); + }); +}); diff --git a/resources/js/features/home/components/+root/FrontPageNews/NewsCategoryLabel/NewsCategoryLabel.tsx b/resources/js/features/home/components/+root/FrontPageNews/NewsCategoryLabel/NewsCategoryLabel.tsx new file mode 100644 index 0000000000..bdca3105e2 --- /dev/null +++ b/resources/js/features/home/components/+root/FrontPageNews/NewsCategoryLabel/NewsCategoryLabel.tsx @@ -0,0 +1,17 @@ +import type { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +interface NewsCategoryLabelProps { + category: App.Community.Enums.NewsCategory; +} + +export const NewsCategoryLabel: FC = ({ category }) => { + const { t } = useTranslation(); + + return ( + + {/* eslint-disable-next-line @typescript-eslint/no-explicit-any -- this is intentional */} + {t(`news-category.${category}` as any)} + + ); +}; diff --git a/resources/js/features/home/components/+root/FrontPageNews/NewsCategoryLabel/index.ts b/resources/js/features/home/components/+root/FrontPageNews/NewsCategoryLabel/index.ts new file mode 100644 index 0000000000..e3af93b0be --- /dev/null +++ b/resources/js/features/home/components/+root/FrontPageNews/NewsCategoryLabel/index.ts @@ -0,0 +1 @@ +export * from './NewsCategoryLabel'; diff --git a/resources/js/test/factories/createNews.ts b/resources/js/test/factories/createNews.ts index 80f9a234b7..f0f0d19ccb 100644 --- a/resources/js/test/factories/createNews.ts +++ b/resources/js/test/factories/createNews.ts @@ -7,6 +7,7 @@ export const createNews = createFactory((faker) => { createdAt: faker.date.recent().toISOString(), id: faker.number.int({ min: 1, max: 10000 }), imageAssetPath: faker.internet.url(), + category: null, lead: faker.word.words(24), link: faker.internet.url(), pinnedAt: null, diff --git a/resources/js/types/generated.d.ts b/resources/js/types/generated.d.ts index 0fc4029bd7..162ecaacd8 100644 --- a/resources/js/types/generated.d.ts +++ b/resources/js/types/generated.d.ts @@ -134,6 +134,14 @@ declare namespace App.Community.Data { } declare namespace App.Community.Enums { export type ArticleType = 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12; + export type NewsCategory = + | 'achievement-set' + | 'community' + | 'events' + | 'guide' + | 'media' + | 'site-release-notes' + | 'technical'; export type AwardType = 1 | 2 | 3 | 6 | 7 | 8 | 9; export type ClaimSetType = 0 | 1; export type ClaimStatus = 0 | 1 | 2 | 3; @@ -216,6 +224,7 @@ declare namespace App.Data { user: App.Data.User; link: string | null; imageAssetPath: string | null; + category: App.Community.Enums.NewsCategory | null; publishAt: string | null; unpublishAt: string | null; pinnedAt: string | null;