{"id":28164960,"url":"https://github.com/sougata-github/next-play","last_synced_at":"2025-08-10T11:15:11.593Z","repository":{"id":277681525,"uuid":"933208694","full_name":"sougata-github/next-play","owner":"sougata-github","description":"A modern video-sharing platform built using Next.js.","archived":false,"fork":false,"pushed_at":"2025-05-31T14:27:37.000Z","size":391,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-01T02:40:53.290Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sougata-github.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-02-15T12:19:01.000Z","updated_at":"2025-05-31T14:27:41.000Z","dependencies_parsed_at":"2025-02-15T12:24:37.973Z","dependency_job_id":"6f5772ba-b9e5-467f-a561-863f32dc7edd","html_url":"https://github.com/sougata-github/next-play","commit_stats":null,"previous_names":["sougata-github/next-play"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sougata-github/next-play","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sougata-github%2Fnext-play","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sougata-github%2Fnext-play/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sougata-github%2Fnext-play/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sougata-github%2Fnext-play/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sougata-github","download_url":"https://codeload.github.com/sougata-github/next-play/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sougata-github%2Fnext-play/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269713884,"owners_count":24463244,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-10T02:00:08.965Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-15T12:10:31.149Z","updated_at":"2025-08-10T11:15:11.584Z","avatar_url":"https://github.com/sougata-github.png","language":"TypeScript","readme":"## Create a tRPC client for Client Components\n\nThe trpc/client.tsx is the entrypoint when consuming your tRPC API from client components. In here, import the type definition of your tRPC router and create typesafe hooks using createTRPCReact.\n\n## Create a tRPC caller for Server Components\n\nTo prefetch queries from server components, we use a tRPC caller. The `@trpc/react-query/rsc` module exports a thin wrapper around `createCaller` that integrates with your React Query client.\n\n## Hybrid data fetching pattern using both RSC \u0026 Client Components\n\nHere we leverage the speed of `server component` to prefetch data and let the `client component` not have to fetch anything initially. It will have the data ready\n\nServer Component (prefetching data)\n\n```typescript\nimport { ErrorBoundary } from \"react-error-boundary\";\nimport { HydrateClient, trpc } from \"@/trpc/server\";\nimport { Suspense } from \"react\";\n\nimport { PageClient } from \"./client\";\n\nexport default async function Home() {\n  void trpc.hello.prefetch({ text: \"Next.js\" });\n\n  return (\n    \u003cHydrateClient\u003e\n      \u003cSuspense fallback={\u003cp\u003eLoading...\u003c/p\u003e}\u003e\n        \u003cErrorBoundary fallback={\u003cp\u003eError...\u003c/p\u003e}\u003e\n          \u003cPageClient /\u003e\n        \u003c/ErrorBoundary\u003e\n      \u003c/Suspense\u003e\n    \u003c/HydrateClient\u003e\n  );\n}\n```\n\nClient Component (with ready prefetched data)\n\n```typescript\n\"use client\";\n\nimport { trpc } from \"@/trpc/client\";\n\nexport const PageClient = () =\u003e {\n  const [data] = trpc.hello.useSuspenseQuery({\n    text: \"Next.js\",\n  });\n\n  return \u003cdiv\u003e{data.greeting}\u003c/div\u003e;\n};\n```\n\nSo in this approach, the data is fetched once on the server and hydrated into the Client Component.\n\n- Fetch and serialize data on the server.\n\n- Send the serialized data to the client.\n\n- On the client → `\u003cHydrateClient\u003e` deserializes and restores the data into React Query's cache.\n\nHydrating the data means restoring `pre-fetched` or `serialized data` back into its original format on the client-side so it can be used efficiently.\n\n- Serialization is the process of converting complex data structures (like objects, arrays, dates, or custom classes) into a format that can be easily stored, transferred, or processed—typically into a string (JSON, binary, etc.).\n\n- Since we are prefetching data in page.tsx, Next.js will consider it static, so we must declare `export const dynamic = \"force-dynamic\"`.\n\n- Hydrate Client in the same component/page where you are prefetching data.\n\n- When using `prefetchInfinite` in page.tsx, use `useSuspenseInfiniteQuery` in corresponding client component.\n\nAdvantages:\n\n- trpc.hello.prefetch() ensures that the query result is cached and hydrated into the Client Component.\n\n- When the Client Component mounts and calls `useSuspenseQuery`, it instantly gets the preloaded data from the cache instead of making another request.\n\n- No unnecessary loading state in the Client Component (which would be the case if we use useQuery and isLoading in Client Component)\n\n## tRPC Configuration\n\n- Enable transformer on tRPC✅\n- Add auth to tRPC context✅\n- Add protectedProcedure✅\n- Add rate limiting (using upstash)✅\n\n## Video Categories\n\n- Create categories schema✅\n- Push changes to db✅\n- Seed categories✅\n- Organise tRPC routers✅\n- Prefetch categories✅\n- Create categories component✅\n\n## Stuido layout\n\n- Create studio route group✅\n- Create studio layout✅\n- Protect studio routes✅\n\n## Studio Videos\n\n- Create videos schema✅\n- Push db changes✅\n- Create studio procedures✅\n- Add video record creation✅\n\n## Infinite Loading\n\n- Add Suspense and error boundaries✅\n- Create reusable InfiniteScroll component✅\n- Demonstrate infinite scroll✅\n\n## Mux Integration\n\n- Create Responsive Dialog✅\n- Create a free Mux account✅\n- Get a 15-second video✅\n- Create upload modal✅\n\nWhen we upload a video, it takes time to process, hence webhooks and since webhooks are anonymous, we need a way to preserve which user has uploaded, hence `userId` (metadata) to `passthrough`.\n\nWhen we create a video, we also generate an upload url using Mux. We send this back to the Uploader and use it as an endpoint. After we upload the video, the webhook with `video.asset.created` is triggered and we update the video with the `upload url` (muxUploadId) record in our db with the status and asset id received from mux payload.\n\n## Mux Webhooks\n\n- Update video schema✅\n- Push db changes✅\n- Handle `video.asset.ready` event✅\n  - assign thumbnail\n  - assign preview\n- Handle `video.asset.errored` event✅\n  - update status\n- Handle `video.asset.deleted` event✅\n  - delete from db\n- Handle `video.asset.track.ready` event✅\n  - update trackId and trackStatus\n\n## Video Form\n\n- Add Skeleton to videos-section✅\n- Create video form page✅\n- Create video player✅\n- Add ability to update video information✅\n  - Title, Description, Category, Visibility✅\n\n## Video Thumbnails\n\n- Integrate UploadThing✅\n- Add thumbnail upload functionality✅\n- Add thumbnail restore functionality✅\n- Refactor thumbnail fields in the schema✅\n  - Proper UploadThing cleanup✅\n\nUnified approach to uploading files. Every file should be on `uploadthing`.\n\nwhen creating/restoring a video:\n\nget mux asset url (temp thumbnail) -\u003e upload to uploadthing using utapi -\u003e get url from uploadthing -\u003e update db.\n\noptimisation for future: load the entire form element until video is ready. use video.preparing for that. Show an overlay on top of the form.\n\n## AI background jobs\n\nWhy background jobs?\n\n- Avoid timeout from long-running tasks\n  - problematic with AI integrations\n- Ensure retries in case of failure\n\n- Integrate Upstash workflow✅\n- Trigger a background job✅\n- Add AI using vercel SDK✅\n- Add background jobs✅\n\n  - Generate title✅\n  - Generate description✅\n  - Generate thumbnail✅\n\n## AI Thumbnails\n\n- Create thumbnail prompt modal✅\n- Create thumbail generation workflow✅\n- Add Skeleton to form-section loading state✅\n\n## Video Page\n\n- Create video \"getOne\" procedure✅\n  - inner-join \"user\" (author information)\n- Prefetching process✅\n- Video section✅\n- Comments section (Placeholder)✅\n- Suggestions section (Placeholder)✅\n\n## Video Views\n\n- Create video views schema✅\n- Combine video views for \"getOne\" videos procedure✅\n- Create video views creation procedure✅\n- Trigger video view creation on video play✅\n\n## Video reactions\n\n- Create video reactions schema✅\n- Combine video reactions for \"getOne\" videos procedure✅\n- Create video reactions like \u0026 dislike procedure✅\n- Connect VideoReactions component with new API✅\n\n## Subscriptions\n\n- Create subscriptions schema✅\n- Combine subscriptions for \"getOne\" videos procedure✅\n- Create subscriptions procedures✅\n- Create SubscriptionButton component with new API✅\n\n## Comments\n\n- Create comments schema✅\n- Create comments procedures✅\n- Create comments section✅\n\n## Comments Infinite Loading\n\n- Modify comments \"getMany\" procedure✅\n- Change prefect() to prefecthInfinte()✅\n- Change suspense() to useSuspenseInfiniteQuery()✅\n- Add InfiniteLoading component✅\n\n## Comment Reactions\n\n- Add \"commentReactions\" schema✅\n- Create comment reactions UI✅\n- Combine \"commentReactions\" with comments \"getMany\" procedure✅\n\n## Comment Replies\n\n- Extend comment schema by adding \"parentId\" foreign key✅\n- Create UI for replies✅\n- Modify comments \"getMany\" procedure by combining parentId✅\n- Create variants for \"CommentItem\" component✅\n- Create variants for \"CommentForm\" component✅\n- Create CommentReplies component✅\n\n## Suggestions\n\n- Create suggestions procedure✅\n- Prefetch suggestions✅\n- Create VideoRowCard and VideoGrid components✅\n- Connect Suggestions section with new API✅\n\n## Search Page\n\n- Add manual video re-validation✅\n  - in case webhook fail\n  - in case webhooks fire out of order\n- Add proper categoryId query to suggestions✅\n- Create search procedure✅\n- Prefetch search page✅\n- Connect search section to API✅\n\n## Home Feed\n\n- Create videos procedures✅\n- Add Home page✅\n- Add Trending page✅\n- Add Subscriptions page✅\n\n## Playlists\n\n- Create playlists procedures✅\n- Create History \u0026 Liked videos page✅\n\n## Custom Playlists\n\n- Create custom playlists schema✅\n- Create custom playlists procedures✅\n- Create playlists page✅\n\n## Populate Playlists\n\n- Create PlaylistAddModal component✅\n- Create \"getManyForVideo\" playlist procedure✅\n- Create add and remove procedures for playlists✅\n\n## Individual Playlists\n\n- Create \"getVideos\" procedure to load custom playlist's videos✅\n- Build custom playlist page✅\n- Add ability to delete a playlist✅\n- Add ability to remove a video from a playlist✅\n\n## User Page\n\n- Add \"bannerUrl\" and \"bannerKey\" to user schema✅\n- Create \"users.getOne\" procedure✅\n- Modify \"videos.getMany\" procedure to accept userId prop✅\n- Create userId page✅\n\n## Banner Upload\n\n- Implement \"bannerUploader\" endpoint in uploadthing/core.ts✅\n- Create BannerUploadModal✅\n\n## Subscriptions List\n\n- Create subscriptions \"getMany\" procedure✅\n- Load recent subscriptions in sidebar✅\n- Create all subscriptions page✅\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsougata-github%2Fnext-play","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsougata-github%2Fnext-play","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsougata-github%2Fnext-play/lists"}