diff --git a/apps/frontend/src/components/launches/calendar.context.tsx b/apps/frontend/src/components/launches/calendar.context.tsx index 8c81c99e..fb5bea36 100644 --- a/apps/frontend/src/components/launches/calendar.context.tsx +++ b/apps/frontend/src/components/launches/calendar.context.tsx @@ -26,6 +26,8 @@ import { expandPostsList, expandPosts } from '@gitroom/helpers/utils/posts.list. extend(isoWeek); extend(weekOfYear); +export type ListStateFilter = 'all' | 'scheduled' | 'draft' | 'published'; + export const CalendarContext = createContext({ startDate: newDayjs().startOf('isoWeek').format('YYYY-MM-DD'), endDate: newDayjs().endOf('isoWeek').format('YYYY-MM-DD'), @@ -78,6 +80,10 @@ export const CalendarContext = createContext({ setListPage: (page: number) => { /** empty **/ }, + listState: 'all' as ListStateFilter, + setListState: (state: ListStateFilter) => { + /** empty **/ + }, }); export interface Integrations { @@ -144,6 +150,11 @@ export const CalendarWeekProvider: FC<{ // List view state const [listPage, setListPage] = useState(0); + const [listState, setListStateRaw] = useState('all'); + const setListState = useCallback((next: ListStateFilter) => { + setListStateRaw(next); + setListPage(0); + }, []); // Initialize with current date range based on URL params or defaults const initStartDate = searchParams.get('startDate'); @@ -190,8 +201,9 @@ export const CalendarWeekProvider: FC<{ page: listPage.toString(), limit: '100', customer: filters?.customer?.toString() || '', + state: listState, }).toString(); - }, [listPage, filters.customer]); + }, [listPage, filters.customer, listState]); const loadListData = useCallback(async () => { const response = await fetch(`/posts/list?${listParams}`); @@ -341,6 +353,8 @@ export const CalendarWeekProvider: FC<{ listPage, listTotalPages, setListPage, + listState, + setListState, }} > {children} diff --git a/apps/frontend/src/components/launches/calendar.tsx b/apps/frontend/src/components/launches/calendar.tsx index 1cb3f40b..75c60a90 100644 --- a/apps/frontend/src/components/launches/calendar.tsx +++ b/apps/frontend/src/components/launches/calendar.tsx @@ -493,7 +493,15 @@ export const MonthView = () => { export const ListView = () => { const t = useT(); const user = useUser(); - const { integrations, loading, listPosts } = useCalendar(); + const { integrations, loading, listPosts, listState } = useCalendar(); + const emptyMessage = + listState === 'scheduled' + ? t('no_upcoming_posts', 'No upcoming posts scheduled') + : listState === 'draft' + ? t('no_draft_posts', 'No draft posts') + : listState === 'published' + ? t('no_published_posts', 'No published posts') + : t('no_posts', 'No posts'); // Use shared post actions hook const { editPost, deletePost, copyDebugJson, openStatistics, openMissingRelease } = usePostActions(); @@ -522,9 +530,7 @@ export const ListView = () => { if (listPosts.length === 0) { return (
-
- {t('no_upcoming_posts', 'No upcoming posts scheduled')} -
+
{emptyMessage}
); } diff --git a/apps/frontend/src/components/launches/filters.tsx b/apps/frontend/src/components/launches/filters.tsx index c6b534c8..5fd690f6 100644 --- a/apps/frontend/src/components/launches/filters.tsx +++ b/apps/frontend/src/components/launches/filters.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useCalendar } from '@gitroom/frontend/components/launches/calendar.context'; +import { useCalendar, ListStateFilter } from '@gitroom/frontend/components/launches/calendar.context'; import clsx from 'clsx'; import dayjs from 'dayjs'; import { useCallback } from 'react'; @@ -259,6 +259,21 @@ export const Filters = () => { const isListView = calendar.display === 'list'; + const setListStateFilter = useCallback( + (next: ListStateFilter) => () => { + if (calendar.listState === next) return; + calendar.setListState(next); + }, + [calendar] + ); + + const listStateOptions: { value: ListStateFilter; label: string }[] = [ + { value: 'all', label: t('all', 'All') }, + { value: 'scheduled', label: t('scheduled', 'Scheduled') }, + { value: 'draft', label: t('draft', 'Draft') }, + { value: 'published', label: t('published', 'Published') }, + ]; + const previousPage = useCallback(() => { if (calendar.listPage > 0) { calendar.setListPage(calendar.listPage - 1); @@ -393,6 +408,21 @@ export const Filters = () => { +
+ {listStateOptions.map((option) => ( +
+ {option.label} +
+ ))} +
)} diff --git a/apps/frontend/src/components/new-launch/providers/tiktok/tiktok.provider.tsx b/apps/frontend/src/components/new-launch/providers/tiktok/tiktok.provider.tsx index 7ed473af..edd5e5e4 100644 --- a/apps/frontend/src/components/new-launch/providers/tiktok/tiktok.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/tiktok/tiktok.provider.tsx @@ -29,12 +29,29 @@ const TikTokSettings: FC<{ return value?.[0]?.image?.some((p) => (p?.path?.indexOf?.('mp4') ?? -1) === -1); }, [value]); + const hasMedia = (value?.[0]?.image?.length ?? 0) > 0; + const isVideo = hasMedia && !isTitle; + const disclose = watch('disclose'); const brand_organic_toggle = watch('brand_organic_toggle'); const brand_content_toggle = watch('brand_content_toggle'); const content_posting_method = watch('content_posting_method'); const isUploadMode = content_posting_method === 'UPLOAD'; + const tiktokRestrictionNotice = useMemo(() => { + if (!hasMedia || !isVideo) return null; + if (!isUploadMode) { + return t( + 'tiktok_restriction_direct_video', + 'TikTok restriction: For direct post with video, your post content is used as the title. A separate title field is not available.' + ); + } + return t( + 'tiktok_restriction_upload_video', + 'TikTok restriction: For upload-only video, TikTok does not accept a title or message. The content will default to "#Postiz" and you can edit it inside the TikTok app before publishing.' + ); + }, [hasMedia, isUploadMode, isVideo, t]); + const privacyLevel = [ { value: 'PUBLIC_TO_EVERYONE', @@ -83,6 +100,25 @@ const TikTokSettings: FC<{ return (
{/**/} + {tiktokRestrictionNotice && ( +
+
+ + + +
+
{tiktokRestrictionNotice}
+
+ )} {isTitle && }